From d365f1595c6db45569e143cd7fbc590846601e01 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 12 Jun 2026 13:36:21 +0300 Subject: dt-bindings: serial: snps-dw-apb-uart: Document Baikal-T1 UART Baikal-T1 is equipped with two UART interfaces based on the DW APB UART v3.14c. The interface is almost fully compatible with the generic IP-core except the platform-specific peculiarity connected with two small FIFO and slow uncacheable memory access, which causes the system not keeping up with a heavy traffic (>460800 baud rate). In that case the incoming data buffer must be reduced to more effectively split up the transactions+data handling procedure. The reduction is implemented on the platform-specific basis. Thus the vendor-specific compatible string has been introduced. Let's add it to the DT-schema too. Signed-off-by: Serge Semin --- Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml b/Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml index 6efe43089a74d..79f791dad6cc4 100644 --- a/Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml +++ b/Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml @@ -42,6 +42,9 @@ allOf: properties: compatible: oneOf: + - items: + - const: baikal,bt1-uart + - const: snps,dw-apb-uart - items: - const: renesas,r9a06g032-uart - const: renesas,rzn1-uart -- cgit v1.2.3 From 1cecaab18ade1e1e232acd28ea90f0b7f83ab131 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Tue, 30 Jul 2024 20:54:54 +0300 Subject: serial: 8250_dma: Fix Rx overruns for slow coherent memory IOs If the system has slow coherent memory IOs, then the greater-than-DMA-buf Rx transfers may cause the UART Rx FIFO overrun errors if the DMA-buffer copying to the serial flip string buffer was too slow to keep up with the incoming traffic. Let's decrease the buffer size to speed up the copying and thus the DMA-based Rx procedure re-charging. Signed-off-by: Serge Semin rx_size) - dma->rx_size = PAGE_SIZE; + dma->rx_size = SZ_512; dma->rx_buf = dma_alloc_coherent(dma->rxchan->device->dev, dma->rx_size, &dma->rx_addr, GFP_KERNEL); -- cgit v1.2.3 From eb39db6c27941f2a31e2c589d0ccc4737eb91335 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 4 Oct 2024 19:35:22 +0300 Subject: serial: 8250_dw: Introduce Rx DMA-buffer size override plat-data It's quite possible to have the DMA-coherent memory allocation call to return a non-cacheable memory chunk. In that case the memory IOs to/from that chunk will be very-very slow. If plus to that the DW UART device has been synthesized with a small FIFO, the memory copy procedure (performed via tty_insert_flip_string()) might be not keeping up with a fast incoming traffic. It will eventually cause the Rx FIFO overrun error depending on the device FIFO size, Rx DMA buffer size and under the pressure of the high baud rate and heavy incoming traffic. For instance, our platform is DMA-noncoherent, so the dma_alloc_coherent() method returns a memory from the noncacheable space. Page size is 16KB. thus the Rx DMA-buffer will be of 16KB size. memcpy() procedure from noncacheable memory shows ~30MB/s (byte-by-byte copy is of just ~6MB/s). Thus the entire buffer will be copied in about ~500usec. DW APB UART has been synthesized with a FIFO if 16 bytes size. It is fully filled in in about 300usec for the 460800 baud rate. Thus any heavy incoming traffic exceeding 16KB will surely cause the FIFO overrun in that case since the copying procedure will take twice greater time than the FIFO being filled in. Let's fix that on the platform-specific basis by introducing the Rx DMA-buffer size override parameter. It can be utilized on the platforms affected by that problem. Signed-off-by: Serge Semin --- drivers/tty/serial/8250/8250_dw.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c index 94beadb4024df..1038f4bb0f24e 100644 --- a/drivers/tty/serial/8250/8250_dw.c +++ b/drivers/tty/serial/8250/8250_dw.c @@ -75,6 +75,7 @@ struct dw8250_platform_data { u8 usr_reg; u32 cpr_value; + size_t rx_size; unsigned int quirks; }; @@ -647,6 +648,9 @@ static void dw8250_quirks(struct uart_port *p, struct dw8250_data *data) unsigned int quirks = data->pdata->quirks; u32 cpr_value = data->pdata->cpr_value; + if (data->pdata && data->pdata->rx_size) + data->data.dma.rx_size = data->pdata->rx_size; + if (quirks & DW_UART_QUIRK_CPR_VALUE) data->data.cpr_value = cpr_value; -- cgit v1.2.3 From ababede80a49f5f664d505109903bcab68d4dfdb Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 4 Oct 2024 20:09:46 +0300 Subject: serial: 8250_dw: Add compatible string for Baikal-T1 UART The only available MIPS P5600 Warrior-based SoC Baikal-T1 is equipped with two UARTs based on the Synopsys DW UART IP-core. The controller has been synthesized with the 16-bytes FIFO and DMA-handshaking interface connected to a DW DMAC controller. Let's add the respective compatible string to the generic DW 8250 UART driver. Note in order for the DMA-based communications to work correctly the Rx DMA-buffer size must be reduced to 1KB. It's required because the memcpy-ing from the noncacheable 16KB Rx DMA-buffer doesn't keep up with the high-speed incoming traffic. Reducing the buffer size solves the problem at least for up to 1.5Mbps rate. Signed-off-by: Serge Semin --- drivers/tty/serial/8250/8250_dw.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c index 1038f4bb0f24e..22d809ebe75ff 100644 --- a/drivers/tty/serial/8250/8250_dw.c +++ b/drivers/tty/serial/8250/8250_dw.c @@ -940,6 +940,11 @@ static const struct dw8250_platform_data dw8250_dw_apb = { .usr_reg = DW_UART_USR, }; +static const struct dw8250_platform_data dw8250_baikal_bt1_data = { + .usr_reg = DW_UART_USR, + .rx_size = SZ_1K, +}; + static const struct dw8250_platform_data dw8250_octeon_3860_data = { .usr_reg = OCTEON_UART_USR, .quirks = DW_UART_QUIRK_OCTEON, @@ -968,6 +973,7 @@ static const struct dw8250_platform_data dw8250_intc10ee = { static const struct of_device_id dw8250_of_match[] = { { .compatible = "snps,dw-apb-uart", .data = &dw8250_dw_apb }, + { .compatible = "baikal,bt1-uart", .data = &dw8250_baikal_bt1_data }, { .compatible = "cavium,octeon-3860-uart", .data = &dw8250_octeon_3860_data }, { .compatible = "marvell,armada-38x-uart", .data = &dw8250_armada_38x_data }, { .compatible = "renesas,rzn1-uart", .data = &dw8250_renesas_rzn1_data }, -- cgit v1.2.3 From 2762165facd5225a1b62b4b2f2dc5a4e004f0fff Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 12 Jun 2026 13:48:42 +0300 Subject: serial: 8250_early: Add earlycon support for Baikal-T1 UART Declare an OF early console for Baikal-T1 UARTs so that the early console device can be specified via the "stdout-path" property in device-tree. Signed-off-by: Serge Semin --- drivers/tty/serial/8250/8250_early.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/tty/serial/8250/8250_early.c b/drivers/tty/serial/8250/8250_early.c index dc0371857ecbe..f8e7c5fa27ad5 100644 --- a/drivers/tty/serial/8250/8250_early.c +++ b/drivers/tty/serial/8250/8250_early.c @@ -174,6 +174,7 @@ EARLYCON_DECLARE(uart8250, early_serial8250_setup); EARLYCON_DECLARE(uart, early_serial8250_setup); OF_EARLYCON_DECLARE(ns16550, "ns16550", early_serial8250_setup); OF_EARLYCON_DECLARE(ns16550a, "ns16550a", early_serial8250_setup); +OF_EARLYCON_DECLARE(uart, "baikal,bt1-uart", early_serial8250_setup); OF_EARLYCON_DECLARE(uart, "nvidia,tegra20-uart", early_serial8250_setup); OF_EARLYCON_DECLARE(uart, "snps,dw-apb-uart", early_serial8250_setup); -- cgit v1.2.3 From 91b28a955b00257407a1b6a2620472e2563a6e51 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 5 Aug 2026 23:32:23 +0300 Subject: Revert "spi: dt-bindings: snps,dw-abp-ssi: Remove unused bindings" This reverts commit 297318a1c26dabb5a2d8540fdf436c22094eb2d7. The commit da0a672268b3 ("spi: dw: Remove not-going-to-be-supported code for Baikal SoC") incorrectly stated that the Baikal platform wasn't supported. It has and will be maintained by the original maintainer. Get back the Baikal-T1 SPI controller bindings then by reverting the commit removing it from the DW APB SSI DT-bindings schema. Signed-off-by: Serge Semin --- .../devicetree/bindings/spi/snps,dw-apb-ssi.yaml | 31 +++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml b/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml index 8ebebcebca160..81838577cf9cd 100644 --- a/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml +++ b/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml @@ -22,6 +22,21 @@ allOf: properties: reg: minItems: 2 + - if: + properties: + compatible: + contains: + enum: + - baikal,bt1-sys-ssi + then: + properties: + mux-controls: + maxItems: 1 + required: + - mux-controls + else: + required: + - interrupts - if: properties: compatible: @@ -60,6 +75,10 @@ properties: const: intel,mountevans-imc-ssi - description: AMD Pensando Elba SoC SPI Controller const: amd,pensando-elba-spi + - description: Baikal-T1 SPI Controller + const: baikal,bt1-ssi + - description: Baikal-T1 System Boot SPI Controller + const: baikal,bt1-sys-ssi - description: Canaan Kendryte K210 SoS SPI Controller const: canaan,k210-spi - description: Renesas RZ/N1 SPI Controller @@ -151,7 +170,6 @@ required: - "#address-cells" - "#size-cells" - clocks - - interrupts examples: - | @@ -172,4 +190,15 @@ examples: rx-sample-delay-ns = <7>; }; }; + - | + spi@1f040100 { + compatible = "baikal,bt1-sys-ssi"; + reg = <0x1f040100 0x900>, + <0x1c000000 0x1000000>; + #address-cells = <1>; + #size-cells = <0>; + mux-controls = <&boot_mux>; + clocks = <&ccu_sys>; + clock-names = "ssi_clk"; + }; ... -- cgit v1.2.3 From 46594dcb4e968ee4ad03c6e067ea95a206a53316 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 5 Aug 2026 23:49:24 +0300 Subject: Revert "i2c: designware: Remove not-going-to-be-supported code for Baikal SoC" This reverts commit d70f60ad964dc773bfcf73d52099472228629cac. The Baikal-T1 platform has been and will be supported by the original maintainer. So will the Baikal-T1 I2C code. Get it back by reverting the commit removing it from the DW I2C driver. Signed-off-by: Serge Semin --- drivers/i2c/busses/Kconfig | 1 + drivers/i2c/busses/i2c-designware-core.h | 1 + drivers/i2c/busses/i2c-designware-platdrv.c | 68 +++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+) diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 7cb6b9b864a74..72fc592e1b015 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -580,6 +580,7 @@ if I2C_DESIGNWARE_CORE config I2C_DESIGNWARE_PLATFORM tristate "Synopsys DesignWare Platform driver" depends on (ACPI && COMMON_CLK) || !ACPI + select MFD_SYSCON if MIPS_BAIKAL_T1 default I2C_DESIGNWARE_CORE help If you say yes to this option, support will be included for the diff --git a/drivers/i2c/busses/i2c-designware-core.h b/drivers/i2c/busses/i2c-designware-core.h index 9d8d104cc3911..5c7a35f37a1a1 100644 --- a/drivers/i2c/busses/i2c-designware-core.h +++ b/drivers/i2c/busses/i2c-designware-core.h @@ -328,6 +328,7 @@ struct dw_i2c_dev { #define ARBITRATION_SEMAPHORE BIT(2) #define ACCESS_POLLING BIT(3) +#define MODEL_BAIKAL_BT1 BIT(9) #define MODEL_AMD_NAVI_GPU BIT(10) #define MODEL_WANGXUN_SP BIT(11) #define MODEL_MASK GENMASK(11, 8) diff --git a/drivers/i2c/busses/i2c-designware-platdrv.c b/drivers/i2c/busses/i2c-designware-platdrv.c index 426ffec06e221..b0ece736b9d62 100644 --- a/drivers/i2c/busses/i2c-designware-platdrv.c +++ b/drivers/i2c/busses/i2c-designware-platdrv.c @@ -37,6 +37,70 @@ static u32 i2c_dw_get_clk_rate_khz(struct dw_i2c_dev *dev) return clk_get_rate(dev->clk) / HZ_PER_KHZ; } +#ifdef CONFIG_OF +#define BT1_I2C_CTL 0x100 +#define BT1_I2C_CTL_ADDR_MASK GENMASK(7, 0) +#define BT1_I2C_CTL_WR BIT(8) +#define BT1_I2C_CTL_GO BIT(31) +#define BT1_I2C_DI 0x104 +#define BT1_I2C_DO 0x108 + +static int bt1_i2c_read(void *context, unsigned int reg, unsigned int *val) +{ + struct dw_i2c_dev *dev = context; + int ret; + + /* + * Note these methods shouldn't ever fail because the system controller + * registers are memory mapped. We check the return value just in case. + */ + ret = regmap_write(dev->sysmap, BT1_I2C_CTL, + BT1_I2C_CTL_GO | (reg & BT1_I2C_CTL_ADDR_MASK)); + if (ret) + return ret; + + return regmap_read(dev->sysmap, BT1_I2C_DO, val); +} + +static int bt1_i2c_write(void *context, unsigned int reg, unsigned int val) +{ + struct dw_i2c_dev *dev = context; + int ret; + + ret = regmap_write(dev->sysmap, BT1_I2C_DI, val); + if (ret) + return ret; + + return regmap_write(dev->sysmap, BT1_I2C_CTL, + BT1_I2C_CTL_GO | BT1_I2C_CTL_WR | (reg & BT1_I2C_CTL_ADDR_MASK)); +} + +static const struct regmap_config bt1_i2c_cfg = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .fast_io = true, + .reg_read = bt1_i2c_read, + .reg_write = bt1_i2c_write, + .max_register = DW_IC_COMP_TYPE, +}; + +static int bt1_i2c_request_regs(struct dw_i2c_dev *dev) +{ + dev->sysmap = syscon_node_to_regmap(dev->dev->of_node->parent); + if (IS_ERR(dev->sysmap)) + return PTR_ERR(dev->sysmap); + + dev->map = devm_regmap_init(dev->dev, NULL, dev, &bt1_i2c_cfg); + return PTR_ERR_OR_ZERO(dev->map); +} +#else +static int bt1_i2c_request_regs(struct dw_i2c_dev *dev) +{ + return -ENODEV; +} +#endif + static int dw_i2c_get_parent_regmap(struct dw_i2c_dev *dev) { dev->map = dev_get_regmap(dev->dev->parent, NULL); @@ -63,6 +127,9 @@ static int dw_i2c_plat_request_regs(struct dw_i2c_dev *dev) return dw_i2c_get_parent_regmap(dev); switch (dev->flags & MODEL_MASK) { + case MODEL_BAIKAL_BT1: + ret = bt1_i2c_request_regs(dev); + break; case MODEL_WANGXUN_SP: ret = dw_i2c_get_parent_regmap(dev); break; @@ -252,6 +319,7 @@ static void dw_i2c_plat_remove(struct platform_device *pdev) } static const struct of_device_id dw_i2c_of_match[] = { + { .compatible = "baikal,bt1-sys-i2c", .data = (void *)MODEL_BAIKAL_BT1 }, { .compatible = "mobileye,eyeq6lplus-i2c" }, { .compatible = "mscc,ocelot-i2c" }, { .compatible = "snps,designware-i2c" }, -- cgit v1.2.3 From 0946dcd95ffc09dd6c68d64d091a28b857d1fa77 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 6 Aug 2026 00:17:04 +0300 Subject: i2c: designware: Use device_is_compatible() instead of MODEL_BAIKAL_BT1 It's what should have been originally done since the flag is assigned based on the device compatible name and used in a single place at the device probe stage. So replace the MODEL-flag with using the device_is_compatible() method to detect the controller and initialize the CSRs regmap. As a nice side-effect it will ease the maintenance in case of new flags being added in upstream. No missing overlaps would be possible. Signed-off-by: Serge Semin --- drivers/i2c/busses/i2c-designware-core.h | 1 - drivers/i2c/busses/i2c-designware-platdrv.c | 7 +++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/i2c/busses/i2c-designware-core.h b/drivers/i2c/busses/i2c-designware-core.h index 5c7a35f37a1a1..9d8d104cc3911 100644 --- a/drivers/i2c/busses/i2c-designware-core.h +++ b/drivers/i2c/busses/i2c-designware-core.h @@ -328,7 +328,6 @@ struct dw_i2c_dev { #define ARBITRATION_SEMAPHORE BIT(2) #define ACCESS_POLLING BIT(3) -#define MODEL_BAIKAL_BT1 BIT(9) #define MODEL_AMD_NAVI_GPU BIT(10) #define MODEL_WANGXUN_SP BIT(11) #define MODEL_MASK GENMASK(11, 8) diff --git a/drivers/i2c/busses/i2c-designware-platdrv.c b/drivers/i2c/busses/i2c-designware-platdrv.c index b0ece736b9d62..23ce3b7faace0 100644 --- a/drivers/i2c/busses/i2c-designware-platdrv.c +++ b/drivers/i2c/busses/i2c-designware-platdrv.c @@ -125,11 +125,10 @@ static int dw_i2c_plat_request_regs(struct dw_i2c_dev *dev) if (device_is_compatible(dev->dev, "intel,xe-i2c")) return dw_i2c_get_parent_regmap(dev); + else if (device_is_compatible(dev->dev, "baikal,bt1-sys-i2c")) + return bt1_i2c_request_regs(dev); switch (dev->flags & MODEL_MASK) { - case MODEL_BAIKAL_BT1: - ret = bt1_i2c_request_regs(dev); - break; case MODEL_WANGXUN_SP: ret = dw_i2c_get_parent_regmap(dev); break; @@ -319,7 +318,7 @@ static void dw_i2c_plat_remove(struct platform_device *pdev) } static const struct of_device_id dw_i2c_of_match[] = { - { .compatible = "baikal,bt1-sys-i2c", .data = (void *)MODEL_BAIKAL_BT1 }, + { .compatible = "baikal,bt1-sys-i2c" }, { .compatible = "mobileye,eyeq6lplus-i2c" }, { .compatible = "mscc,ocelot-i2c" }, { .compatible = "snps,designware-i2c" }, -- cgit v1.2.3 From 437481d79bf21eb9e08d588f6d3bb02e05d07cb4 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 20 Nov 2023 18:11:41 +0300 Subject: dmaengine: dw-edma: Enable local abort interrupts It turns out that in order to have the local abort/stop/error IRQ being raised it needs to be enabled in the DMA_WRITE_LINKED_LIST_ERR_EN/DMA_READ_LINKED_LIST_ERR_EN registers. Currently only the remote (MSI) IRQ is enabled. Fix this by activating both local and remote IRQs for the remote eDMA device setup (accessible over the PCIe bus) and only the local IRQs if the DW eDMA is locally available (accessible over the CPU application side). Fixes: d6b03171f9fc ("dmaengine: dw-edma: Add support for chip-specific flags") Suggested-by: Sergey Nazarov Signed-off-by: Serge Semin --- drivers/dma/dw-edma/dw-edma-v0-core.c | 4 +++- drivers/dma/dw-edma/dw-edma-v0-regs.h | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/dma/dw-edma/dw-edma-v0-core.c b/drivers/dma/dw-edma/dw-edma-v0-core.c index b75fdaffad9a4..2fb6ba4854c14 100644 --- a/drivers/dma/dw-edma/dw-edma-v0-core.c +++ b/drivers/dma/dw-edma/dw-edma-v0-core.c @@ -414,7 +414,9 @@ static void dw_edma_v0_core_start(struct dw_edma_chunk *chunk, bool first) SET_RW_32(dw, chan->dir, int_mask, tmp); /* Linked list error */ tmp = GET_RW_32(dw, chan->dir, linked_list_err_en); - tmp |= FIELD_PREP(EDMA_V0_LINKED_LIST_ERR_MASK, BIT(chan->id)); + tmp |= FIELD_PREP(EDMA_V0_LL_LOCAL_ABORT_INT_MASK, BIT(chan->id)); + if (!(dw->chip->flags & DW_EDMA_CHIP_LOCAL)) + tmp |= FIELD_PREP(EDMA_V0_LL_REMOTE_ABORT_INT_MASK, BIT(chan->id)); SET_RW_32(dw, chan->dir, linked_list_err_en, tmp); /* Channel control */ SET_CH_32(dw, chan->dir, chan->id, ch_control1, diff --git a/drivers/dma/dw-edma/dw-edma-v0-regs.h b/drivers/dma/dw-edma/dw-edma-v0-regs.h index e175f7b20480e..ffc544afc5e39 100644 --- a/drivers/dma/dw-edma/dw-edma-v0-regs.h +++ b/drivers/dma/dw-edma/dw-edma-v0-regs.h @@ -19,7 +19,8 @@ #define EDMA_V0_READ_CH_COUNT_MASK GENMASK(19, 16) #define EDMA_V0_CH_STATUS_MASK GENMASK(6, 5) #define EDMA_V0_DOORBELL_CH_MASK GENMASK(2, 0) -#define EDMA_V0_LINKED_LIST_ERR_MASK GENMASK(7, 0) +#define EDMA_V0_LL_REMOTE_ABORT_INT_MASK GENMASK(7, 0) +#define EDMA_V0_LL_LOCAL_ABORT_INT_MASK GENMASK(23, 16) #define EDMA_V0_CH_ODD_MSI_DATA_MASK GENMASK(31, 16) #define EDMA_V0_CH_EVEN_MSI_DATA_MASK GENMASK(15, 0) -- cgit v1.2.3 From 4555b854c9c23b1bd0774a82fcb3ae0af0927862 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 19 Dec 2022 00:38:25 +0300 Subject: PCI: dwc: Fix erroneous version type test helper Due to an unfortunate mistake the macro function actually checks the IP-core version instead of the IP-core version type which isn't what originally implied. Fix it by introducing a new helper __dw_pcie_ver_type_cmp() with the same semantic as the __dw_pcie_ver_cmp() counterpart except it refers to the dw_pcie.type field in order to perform the passed comparison operation. Fixes: 0b0a780d52ad ("PCI: dwc: Add macros to compare Synopsys IP core versions") Signed-off-by: Serge Semin Reviewed-by: Manivannan Sadhasivam Reviewed-by: Yoshihiro Shimoda --- drivers/pci/controller/dwc/pcie-designware.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h index ae6389dd9caa5..7fc9904c4313e 100644 --- a/drivers/pci/controller/dwc/pcie-designware.h +++ b/drivers/pci/controller/dwc/pcie-designware.h @@ -40,17 +40,20 @@ #define __dw_pcie_ver_cmp(_pci, _ver, _op) \ ((_pci)->version _op DW_PCIE_VER_ ## _ver) +#define __dw_pcie_ver_type_cmp(_pci, _type, _op) \ + ((_pci)->type _op DW_PCIE_VER_TYPE_ ## _type) + #define dw_pcie_ver_is(_pci, _ver) __dw_pcie_ver_cmp(_pci, _ver, ==) #define dw_pcie_ver_is_ge(_pci, _ver) __dw_pcie_ver_cmp(_pci, _ver, >=) #define dw_pcie_ver_type_is(_pci, _ver, _type) \ (__dw_pcie_ver_cmp(_pci, _ver, ==) && \ - __dw_pcie_ver_cmp(_pci, TYPE_ ## _type, ==)) + __dw_pcie_ver_type_cmp(_pci, _type, ==)) #define dw_pcie_ver_type_is_ge(_pci, _ver, _type) \ (__dw_pcie_ver_cmp(_pci, _ver, ==) && \ - __dw_pcie_ver_cmp(_pci, TYPE_ ## _type, >=)) + __dw_pcie_ver_type_cmp(_pci, _type, >=)) /* DWC PCIe controller capabilities */ #define DW_PCIE_CAP_REQ_RES 0 -- cgit v1.2.3 From 5758caa0ec48cc2a9b3c7a8d64999ba989c5de90 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 12 Jan 2023 22:56:12 +0300 Subject: PCI: bt1: Fix printing false error message The dev_err_probe() method is supposed to be invoked only if any error is happened. It was definitely wrong to call it unconditionally. Due to that the DWC PCIe host initialization error-message is printed all the time the Baikal-T1 PCIe controller is probed even if no error actually happened. Fixes: ba6ed462dcf4 ("PCI: dwc: Add Baikal-T1 PCIe controller support") Signed-off-by: Serge Semin Reviewed-by: Manivannan Sadhasivam --- drivers/pci/controller/dwc/pcie-bt1.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/pci/controller/dwc/pcie-bt1.c b/drivers/pci/controller/dwc/pcie-bt1.c index 1340edc18d128..b2fc7eea4bdfe 100644 --- a/drivers/pci/controller/dwc/pcie-bt1.c +++ b/drivers/pci/controller/dwc/pcie-bt1.c @@ -597,8 +597,10 @@ static int bt1_pcie_add_port(struct bt1_pcie *btpci) dw_pcie_cap_set(&btpci->dw, REQ_RES); ret = dw_pcie_host_init(&btpci->dw.pp); + if (ret) + dev_err_probe(dev, ret, "Failed to initialize DWC PCIe host\n"); - return dev_err_probe(dev, ret, "Failed to initialize DWC PCIe host\n"); + return ret; } static void bt1_pcie_del_port(struct bt1_pcie *btpci) -- cgit v1.2.3 From 1456dfc6a847977825d7d24f37ffadbe1a6ac74b Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 14 Sep 2023 03:54:03 +0300 Subject: PCI: bt1: Fix PWR reset de-assertion error message The reset is called as PHY instead of pwr in the error message. Use the correct name in there corresponding to the reset actually de-asserted. Fixes: ba6ed462dcf4 ("PCI: dwc: Add Baikal-T1 PCIe controller support") Signed-off-by: Serge Semin --- drivers/pci/controller/dwc/pcie-bt1.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pci/controller/dwc/pcie-bt1.c b/drivers/pci/controller/dwc/pcie-bt1.c index b2fc7eea4bdfe..6ef71c1b08cd8 100644 --- a/drivers/pci/controller/dwc/pcie-bt1.c +++ b/drivers/pci/controller/dwc/pcie-bt1.c @@ -420,7 +420,7 @@ static int bt1_pcie_cold_start_bus(struct bt1_pcie *btpci) /* First get out of the Power/Hot reset state */ ret = reset_control_deassert(pci->core_rsts[DW_PCIE_PWR_RST].rstc); if (ret) { - dev_err(dev, "Failed to deassert PHY reset\n"); + dev_err(dev, "Failed to deassert pwr reset\n"); return ret; } -- cgit v1.2.3 From e4475607213869795551fd83a98e7e6ff8be631f Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 14 Sep 2023 03:29:22 +0300 Subject: PCI: bt1: Disable DBI CS2 by default Even though the DBI CS2 flag is disabled by default in hardware it might be left enabled by the system firmware (i.e. BFK3.1 board one). Disable it for sure in order to prevent some of the DW PCIe RC CSRs having unexpected semantic. Fixes: ba6ed462dcf4 ("PCI: dwc: Add Baikal-T1 PCIe controller support") Signed-off-by: Serge Semin --- drivers/pci/controller/dwc/pcie-bt1.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-bt1.c b/drivers/pci/controller/dwc/pcie-bt1.c index 6ef71c1b08cd8..ce16a4c2cc984 100644 --- a/drivers/pci/controller/dwc/pcie-bt1.c +++ b/drivers/pci/controller/dwc/pcie-bt1.c @@ -377,9 +377,9 @@ static void bt1_pcie_full_stop_bus(struct bt1_pcie *btpci, bool init) struct dw_pcie *pci = &btpci->dw; int ret; - /* Disable LTSSM for sure */ + /* Make sure LTSSM and DBI CS2 are disabled by default */ regmap_update_bits(btpci->sys_regs, BT1_CCU_PCIE_GENC, - BT1_CCU_PCIE_LTSSM_EN, 0); + BT1_CCU_PCIE_DBI2_MODE | BT1_CCU_PCIE_LTSSM_EN, 0); /* * Application reset controls are trigger-based so assert the core -- cgit v1.2.3 From c892f3d8f5ddcbd38366a83291d1f2eec0cbaedd Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 14 Sep 2023 04:17:38 +0300 Subject: PCI: bt1: Fix controller re-initialization procedure It turns out the implemented DW PCIe host initialization procedure works well only for the controller being started up from scratch, i.e. right after the system cold start with no bootloader interventions. If the controller has already been started by the system firmware or at least attempted to be started, the system will crash/hang up either at the full-stop method or a bit later during the early TLPs emitted on the PCIe bus. Fix that by introducing the next updates in the full-stop method: 1. perform application reset cycle only while the application clocks are enabled (fixes a hang up on the core resets assertion); 2. perform application reset cycle before and after the core resets de-assertion (fixes a hang up on the core resets de-assertion); 3. add a short delay after the core resets de-assertion so the following application reset would be performed with fully stopped core; and the next updates in the cold-start procedure: 1. enable all the clocks before de-asserting any further reset (required for the PHY part working well); 2. de-assert the pipe reset after the core and PHY resets (comply to the DW PCIe cold reset sequence). 3. drop application reset cycle (it's pointless since the reset is already toggled on the cold-stop procedure). Thus the full-stop and cold-start procedures will get to be quiet universal: working well on the cold stopped controller and on the pre-initialized controller. Fixes: ba6ed462dcf4 ("PCI: dwc: Add Baikal-T1 PCIe controller support") Signed-off-by: Serge Semin --- drivers/pci/controller/dwc/pcie-bt1.c | 97 +++++++++++++++++++++-------------- 1 file changed, 59 insertions(+), 38 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-bt1.c b/drivers/pci/controller/dwc/pcie-bt1.c index ce16a4c2cc984..54e3c52363034 100644 --- a/drivers/pci/controller/dwc/pcie-bt1.c +++ b/drivers/pci/controller/dwc/pcie-bt1.c @@ -134,6 +134,7 @@ #define BT1_PCIE_NUM_CORE_RSTS ARRAY_SIZE(bt1_pcie_core_rsts) /* PCIe bus setup delays and timeouts */ +#define BT1_PCIE_RST_DELAY_US 100 #define BT1_PCIE_RST_DELAY_MS 100 #define BT1_PCIE_RUN_DELAY_US 100 #define BT1_PCIE_REQ_DELAY_US 1 @@ -382,22 +383,49 @@ static void bt1_pcie_full_stop_bus(struct bt1_pcie *btpci, bool init) BT1_CCU_PCIE_DBI2_MODE | BT1_CCU_PCIE_LTSSM_EN, 0); /* - * Application reset controls are trigger-based so assert the core - * resets only. + * In case if the link has already been attempted to be set up + * the PCIe M/S AXI clocks _must_ be enabled for the application + * resets to succeed. */ + ret = clk_bulk_prepare_enable(DW_PCIE_NUM_APP_CLKS, pci->app_clks); + if (ret) + dev_err(dev, "Failed to enable APP clocks\n"); + + ret = reset_control_bulk_reset(DW_PCIE_NUM_APP_RSTS, pci->app_rsts); + if (ret) + dev_err(dev, "Failed to reset APP domains\n"); + + clk_bulk_disable_unprepare(DW_PCIE_NUM_APP_CLKS, pci->app_clks); + + /* The PCIe core resets can be now safely de-asserted */ ret = reset_control_bulk_assert(DW_PCIE_NUM_CORE_RSTS, pci->core_rsts); if (ret) dev_err(dev, "Failed to assert core resets\n"); + /* Wait a bit for the resets to take actions */ + udelay(BT1_PCIE_RST_DELAY_US); + /* * Clocks are disabled by default at least in accordance with the clk - * enable counter value on init stage. + * enable counter value on init stage. So skip disabling them in that + * case. */ - if (!init) { + if (!init) clk_bulk_disable_unprepare(DW_PCIE_NUM_CORE_CLKS, pci->core_clks); - clk_bulk_disable_unprepare(DW_PCIE_NUM_APP_CLKS, pci->app_clks); - } + /* + * The application domains _must_ be reset one more time so further + * initializations would not freeze and be performed from scratch. + */ + ret = clk_bulk_prepare_enable(DW_PCIE_NUM_APP_CLKS, pci->app_clks); + if (ret) + dev_err(dev, "Failed to post-enable APP clocks\n"); + + ret = reset_control_bulk_reset(DW_PCIE_NUM_APP_RSTS, pci->app_rsts); + if (ret) + dev_err(dev, "Failed to post-reset APP domains\n"); + + clk_bulk_disable_unprepare(DW_PCIE_NUM_APP_CLKS, pci->app_clks); /* The peripheral devices are unavailable anyway so reset them too */ gpiod_set_value_cansleep(pci->pe_rst, 1); @@ -430,26 +458,11 @@ static int bt1_pcie_cold_start_bus(struct bt1_pcie *btpci) goto err_assert_pwr_rst; } - /* Wait for the PM-core to stop requesting the PHY reset */ - ret = regmap_read_poll_timeout(btpci->sys_regs, BT1_CCU_PCIE_RSTC, val, - !(val & BT1_CCU_PCIE_REQ_PHY_RST), - BT1_PCIE_REQ_DELAY_US, BT1_PCIE_REQ_TIMEOUT_US); - if (ret) { - dev_err(dev, "Timed out waiting for PM to stop PHY resetting\n"); - goto err_assert_hot_rst; - } - - ret = reset_control_deassert(pci->core_rsts[DW_PCIE_PHY_RST].rstc); - if (ret) { - dev_err(dev, "Failed to deassert PHY reset\n"); - goto err_assert_hot_rst; - } - /* Clocks can be now enabled, but the ref one is crucial at this stage */ ret = clk_bulk_prepare_enable(DW_PCIE_NUM_APP_CLKS, pci->app_clks); if (ret) { dev_err(dev, "Failed to enable app clocks\n"); - goto err_assert_phy_rst; + goto err_assert_hot_rst; } ret = clk_bulk_prepare_enable(DW_PCIE_NUM_CORE_CLKS, pci->core_clks); @@ -458,32 +471,40 @@ static int bt1_pcie_cold_start_bus(struct bt1_pcie *btpci) goto err_disable_app_clk; } - /* Wait for the PM to stop requesting the controller core reset */ + /* Wait for the PM-core to stop requesting the PHY reset */ ret = regmap_read_poll_timeout(btpci->sys_regs, BT1_CCU_PCIE_RSTC, val, - !(val & BT1_CCU_PCIE_REQ_CORE_RST), + !(val & BT1_CCU_PCIE_REQ_PHY_RST), BT1_PCIE_REQ_DELAY_US, BT1_PCIE_REQ_TIMEOUT_US); if (ret) { - dev_err(dev, "Timed out waiting for PM to stop core resetting\n"); + dev_err(dev, "Timed out waiting for PM to stop PHY resetting\n"); goto err_disable_core_clk; } - /* PCS-PIPE interface and controller core can be now activated */ - ret = reset_control_deassert(pci->core_rsts[DW_PCIE_PIPE_RST].rstc); + ret = reset_control_deassert(pci->core_rsts[DW_PCIE_PHY_RST].rstc); if (ret) { - dev_err(dev, "Failed to deassert PIPE reset\n"); + dev_err(dev, "Failed to deassert PHY reset\n"); goto err_disable_core_clk; } + /* Wait for the PM to stop requesting the controller core reset */ + ret = regmap_read_poll_timeout(btpci->sys_regs, BT1_CCU_PCIE_RSTC, val, + !(val & BT1_CCU_PCIE_REQ_CORE_RST), + BT1_PCIE_REQ_DELAY_US, BT1_PCIE_REQ_TIMEOUT_US); + if (ret) { + dev_err(dev, "Timed out waiting for PM to stop core resetting\n"); + goto err_assert_phy_rst; + } + + /* Core and PCS-PIPE interface can be now activated */ ret = reset_control_deassert(pci->core_rsts[DW_PCIE_CORE_RST].rstc); if (ret) { dev_err(dev, "Failed to deassert core reset\n"); - goto err_assert_pipe_rst; + goto err_assert_phy_rst; } - /* It's recommended to reset the core and application logic together */ - ret = reset_control_bulk_reset(DW_PCIE_NUM_APP_RSTS, pci->app_rsts); + ret = reset_control_deassert(pci->core_rsts[DW_PCIE_PIPE_RST].rstc); if (ret) { - dev_err(dev, "Failed to reset app domain\n"); + dev_err(dev, "Failed to deassert PIPE reset\n"); goto err_assert_core_rst; } @@ -491,7 +512,7 @@ static int bt1_pcie_cold_start_bus(struct bt1_pcie *btpci) ret = reset_control_deassert(pci->core_rsts[DW_PCIE_STICKY_RST].rstc); if (ret) { dev_err(dev, "Failed to deassert sticky reset\n"); - goto err_assert_core_rst; + goto err_assert_pipe_rst; } ret = reset_control_deassert(pci->core_rsts[DW_PCIE_NON_STICKY_RST].rstc); @@ -511,11 +532,14 @@ static int bt1_pcie_cold_start_bus(struct bt1_pcie *btpci) err_assert_sticky_rst: reset_control_assert(pci->core_rsts[DW_PCIE_STICKY_RST].rstc); +err_assert_pipe_rst: + reset_control_assert(pci->core_rsts[DW_PCIE_PIPE_RST].rstc); + err_assert_core_rst: reset_control_assert(pci->core_rsts[DW_PCIE_CORE_RST].rstc); -err_assert_pipe_rst: - reset_control_assert(pci->core_rsts[DW_PCIE_PIPE_RST].rstc); +err_assert_phy_rst: + reset_control_assert(pci->core_rsts[DW_PCIE_PHY_RST].rstc); err_disable_core_clk: clk_bulk_disable_unprepare(DW_PCIE_NUM_CORE_CLKS, pci->core_clks); @@ -523,9 +547,6 @@ err_disable_core_clk: err_disable_app_clk: clk_bulk_disable_unprepare(DW_PCIE_NUM_APP_CLKS, pci->app_clks); -err_assert_phy_rst: - reset_control_assert(pci->core_rsts[DW_PCIE_PHY_RST].rstc); - err_assert_hot_rst: reset_control_assert(pci->core_rsts[DW_PCIE_HOT_RST].rstc); -- cgit v1.2.3 From 25585771d6f06c707d8b419e9bba746094204c5a Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 14 Sep 2023 05:13:12 +0300 Subject: PCI: bt1: Don't stop already established link Even though the controller initialization procedure works well in the vast majority of the cases it might still cause the system hang up in case if the controller has already been initialized by the system firmware. Let's prevent that to happen by skipping the controller cold-stop procedure if the link is known to be established. Further cold-start method will just turn to be mainly noop and is required for the clocks enable counters being synchronized with the clocks hardware state. Note this will also speed the controller probe procedure. Fixes: ba6ed462dcf4 ("PCI: dwc: Add Baikal-T1 PCIe controller support") Signed-off-by: Serge Semin --- drivers/pci/controller/dwc/pcie-bt1.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/drivers/pci/controller/dwc/pcie-bt1.c b/drivers/pci/controller/dwc/pcie-bt1.c index 54e3c52363034..3bf549d40d913 100644 --- a/drivers/pci/controller/dwc/pcie-bt1.c +++ b/drivers/pci/controller/dwc/pcie-bt1.c @@ -256,6 +256,18 @@ static void bt1_pcie_write_dbi2(struct dw_pcie *pci, void __iomem *base, u32 reg BT1_CCU_PCIE_DBI2_MODE, 0); } +static int bt1_pcie_link_up(struct dw_pcie *pci) +{ + struct bt1_pcie *btpci = to_bt1_pcie(pci); + u32 val; + + regmap_read(btpci->sys_regs, BT1_CCU_PCIE_PMSC, &val); + if ((val & BT1_CCU_PCIE_SMLH_LINKUP) && (val & BT1_CCU_PCIE_RDLH_LINKUP)) + return true; + + return false; +} + static int bt1_pcie_start_link(struct dw_pcie *pci) { struct bt1_pcie *btpci = to_bt1_pcie(pci); @@ -566,8 +578,14 @@ static int bt1_pcie_host_init(struct dw_pcie_rp *pp) if (ret) return ret; - bt1_pcie_full_stop_bus(btpci, true); + /* Don't stop bus if it has already been set up by firmware */ + if (!bt1_pcie_link_up(pci)) + bt1_pcie_full_stop_bus(btpci, true); + /* + * Perform bus start procedure in any case at least to re-initialize + * the kernel clock handlers. + */ return bt1_pcie_cold_start_bus(btpci); } -- cgit v1.2.3 From 06654f62b2178ec0ea99f469be526333ccb7b3bd Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 14 Sep 2023 04:54:23 +0300 Subject: PCI: bt1: Serialise link-up/speed-change exec sequence At some very rare cases it might turn out that the speed-change is re-enabled before the enabled by default speed-change procedure is finished. In that case the link won't be re-trained for the higher speeds. Fix that by introducing the strict link-up sequence: first wait for the link to be established, then wait for the pre-enabled speed-change procedure being finished, only after that re-enable the speed-change procedure in order to gain the higher bus speeds. Fixes: ba6ed462dcf4 ("PCI: dwc: Add Baikal-T1 PCIe controller support") Signed-off-by: Serge Semin --- drivers/pci/controller/dwc/pcie-bt1.c | 40 +++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-bt1.c b/drivers/pci/controller/dwc/pcie-bt1.c index 3bf549d40d913..7302490a88cb9 100644 --- a/drivers/pci/controller/dwc/pcie-bt1.c +++ b/drivers/pci/controller/dwc/pcie-bt1.c @@ -297,22 +297,44 @@ static int bt1_pcie_start_link(struct dw_pcie *pci) return ret; } + ret = regmap_read_poll_timeout(btpci->sys_regs, BT1_CCU_PCIE_PMSC, val, + BT1_CCU_PCIE_LTSSM_LINKUP(val), + BT1_PCIE_LNK_DELAY_US, BT1_PCIE_LNK_TIMEOUT_US); + if (ret) { + dev_err(pci->dev, "LTSSM failed to get into L0 state\n"); + return ret; + } + + /* + * Wait for the speed change enabled by default and by the DW PCIe core + * driver to be finished. + */ + ret = read_poll_timeout(dw_pcie_readl_dbi, val, !(val & PORT_LOGIC_SPEED_CHANGE), + BT1_PCIE_LNK_DELAY_US, BT1_PCIE_LNK_TIMEOUT_US, false, + pci, PCIE_LINK_WIDTH_SPEED_CONTROL); + if (ret) { + dev_err(pci->dev, "Initial speed change hanged up\n"); + return ret; + } + /* - * Activate direct speed change after the link is established in an - * attempt to reach a higher bus performance (up to Gen.3 - 8.0 GT/s). - * This is required at least to get 8.0 GT/s speed. + * Activate the direct speed change one more time after the link is + * established in order to reach a higher bus performance. This is + * required at least to get 8.0 GT/s speed. */ val = dw_pcie_readl_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL); val |= PORT_LOGIC_SPEED_CHANGE; dw_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, val); - ret = regmap_read_poll_timeout(btpci->sys_regs, BT1_CCU_PCIE_PMSC, val, - BT1_CCU_PCIE_LTSSM_LINKUP(val), - BT1_PCIE_LNK_DELAY_US, BT1_PCIE_LNK_TIMEOUT_US); - if (ret) - dev_err(pci->dev, "LTSSM failed to get into L0 state\n"); + ret = read_poll_timeout(dw_pcie_readl_dbi, val, !(val & PORT_LOGIC_SPEED_CHANGE), + BT1_PCIE_LNK_DELAY_US, BT1_PCIE_LNK_TIMEOUT_US, false, + pci, PCIE_LINK_WIDTH_SPEED_CONTROL); + if (ret) { + dev_err(pci->dev, "Speed change hanged up\n"); + return ret; + } - return ret; + return 0; } static void bt1_pcie_stop_link(struct dw_pcie *pci) -- cgit v1.2.3 From 55814faa9910eae65f06a1a9924c859b442a0c73 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 15 Nov 2023 12:54:33 +0300 Subject: PCI: bt1: Make PCIE_T_PVPERL_MS utilized Since commit 164f66be0c25 ("PCI: Add T_PVPERL macro") there has been a macros defining the PERST# signal stable state duration for the PCIe peripherals to be well reset. Make it utilized in the BT1 PCIe host driver instead of the locally defined one especially seeing they have matching values. Signed-off-by: Serge Semin --- drivers/pci/controller/dwc/pcie-bt1.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-bt1.c b/drivers/pci/controller/dwc/pcie-bt1.c index 7302490a88cb9..e99fd6620b8db 100644 --- a/drivers/pci/controller/dwc/pcie-bt1.c +++ b/drivers/pci/controller/dwc/pcie-bt1.c @@ -23,6 +23,7 @@ #include #include +#include "../../pci.h" #include "pcie-designware.h" /* Baikal-T1 System CCU control registers */ @@ -135,7 +136,6 @@ /* PCIe bus setup delays and timeouts */ #define BT1_PCIE_RST_DELAY_US 100 -#define BT1_PCIE_RST_DELAY_MS 100 #define BT1_PCIE_RUN_DELAY_US 100 #define BT1_PCIE_REQ_DELAY_US 1 #define BT1_PCIE_REQ_TIMEOUT_US 1000 @@ -465,7 +465,7 @@ static void bt1_pcie_full_stop_bus(struct bt1_pcie *btpci, bool init) gpiod_set_value_cansleep(pci->pe_rst, 1); /* Make sure all the resets are settled */ - msleep(BT1_PCIE_RST_DELAY_MS); + msleep(PCIE_T_PVPERL_MS); } /* -- cgit v1.2.3 From 3fa6338e50895377fe753d2bb4cfd176553f749a Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 15 Feb 2023 22:37:57 +0300 Subject: PCI: dwc: Drop duplicated fast-link-mode flag unsetting Most likely by mistake the PORT_LINK_FAST_LINK_MODE flag unsetting was added twice in the commit cff9244432e8 ("PCI: dwc: Ensure FAST_LINK_MODE is cleared"): first it is cleared right after the content of the PCIE_PORT_LINK_CONTROL register is read, second it's cleared in the framework of the link-mode initialization procedure. The later action is redundant. Let's drop it. Signed-off-by: Serge Semin Reviewed-by: Manivannan Sadhasivam Reviewed-by: Yoshihiro Shimoda --- drivers/pci/controller/dwc/pcie-designware.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c index 5741c09dde7f4..89a230d5f830a 100644 --- a/drivers/pci/controller/dwc/pcie-designware.c +++ b/drivers/pci/controller/dwc/pcie-designware.c @@ -906,7 +906,6 @@ static void dw_pcie_link_set_max_link_width(struct dw_pcie *pci, u32 num_lanes) /* Set the number of lanes */ plc = dw_pcie_readl_dbi(pci, PCIE_PORT_LINK_CONTROL); - plc &= ~PORT_LINK_FAST_LINK_MODE; plc &= ~PORT_LINK_MODE_MASK; /* Set link width speed control register */ -- cgit v1.2.3 From 61a7360a7c47bf4c88bf562c67bf17251bb3d1d0 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 2 Feb 2024 18:01:31 +0300 Subject: PCI: dwc: Fix eDMA mapping info string DW PCIe controller can be equipped with the next types of DMA controllers: 1. eDMA controller with viewport-based CSRs access (so called legacy), 2. eDMA controller with unrolled CSRs mapping, 3. HDMA controller compatible with the eDMA unrolled CSRs mapping, 4. HDMA controller with native CSRs mapping. The later three types imply having the DMA-engine CSRs _unrolled_ mapping. Let's fix the info-message printed in the DW PCIe eDMA controller detection procedure to comply with that. Signed-off-by: Serge Semin --- drivers/pci/controller/dwc/pcie-designware.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c index 89a230d5f830a..a61e4dbb99c2c 100644 --- a/drivers/pci/controller/dwc/pcie-designware.c +++ b/drivers/pci/controller/dwc/pcie-designware.c @@ -1213,7 +1213,7 @@ int dw_pcie_edma_detect(struct dw_pcie *pci) } dev_info(pci->dev, "eDMA: unroll %s, %hu wr, %hu rd\n", - pci->edma.mf == EDMA_MF_EDMA_UNROLL ? "T" : "F", + pci->edma.mf != EDMA_MF_EDMA_LEGACY ? "T" : "F", pci->edma.ll_wr_cnt, pci->edma.ll_rd_cnt); return 0; -- cgit v1.2.3 From 0f332861de257545f4afb9942aa9c1c4e51df377 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 19 Dec 2022 01:14:00 +0300 Subject: MAINTAINERS: Add all generic DW PCIe RP/EP DT-schemas Recently the DT-schema of the DW PCIe Root Port and End-point controllers has been refactored by detaching the common bindings into a separate schema. The provided modification must be reflected in the MAINTAINERS list so the patch submitters would be aware of the new files maintainers. Let's do that by adding the maintained files wildcard pattern like snps,dw-pcie*.yaml, which is applicable for all the old DW PCIe DT-schema files and the new one. Signed-off-by: Serge Semin Acked-by: Manivannan Sadhasivam --- MAINTAINERS | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index d1cc0e12fe1f0..7a292680f9877 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20297,8 +20297,7 @@ M: Jingoo Han M: Manivannan Sadhasivam L: linux-pci@vger.kernel.org S: Maintained -F: Documentation/devicetree/bindings/pci/snps,dw-pcie-ep.yaml -F: Documentation/devicetree/bindings/pci/snps,dw-pcie.yaml +F: Documentation/devicetree/bindings/pci/snps,dw-pcie*.yaml F: drivers/pci/controller/dwc/*designware* F: include/linux/pcie-dwc.h -- cgit v1.2.3 From adf368d2c82fda251c542f690002558f38898d8b Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 28 Dec 2023 16:28:02 +0300 Subject: mips, bpf: Simplify cleanup path in bpf_int_jit_compile() Currently the bpf_int_jit_compile() method cleanup part is designed more complicated than it could be. There is a single label to which the code jump in case of error. The code behind that label makes sure that the bpf_int_jit_compile() returns a pointer to the original BPF-program and releases the memory allocated for the image. Then it unconditionally jumps _up_ to the normal part of the bpf_int_jit_compile() method and releases the cloned BPF-program and the JIT-context data. Yes, such design permits to re-use two statements (clone and context freeing), but makes the code much harder to read: non-linear code execution is hard to follow, saving and restoring the 'prog' variable makes hard to remember what is returned by the method at last. Let's fix all of that by converting the bpf_int_jit_compile() function to having a traditional cleanup-on-error path design: named labels indicating the cleanup code to execute, linear cleanup code execution independent from the last normal statements of the function, return pointer to the original BPF-program indicating that the JIT-compilation wasn't successful. Signed-off-by: Serge Semin --- Note 1. The BPF-program clone release procedure will be simplified in the next commit. Note 2. A similar simplification can be applied to the RISCV bpf_int_jit_compile() method. --- arch/mips/net/bpf_jit_comp.c | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/arch/mips/net/bpf_jit_comp.c b/arch/mips/net/bpf_jit_comp.c index e355dfca44008..b228194698eea 100644 --- a/arch/mips/net/bpf_jit_comp.c +++ b/arch/mips/net/bpf_jit_comp.c @@ -949,11 +949,12 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) ctx.descriptors = kcalloc(prog->len + 1, sizeof(*ctx.descriptors), GFP_KERNEL); if (ctx.descriptors == NULL) - goto out_err; + goto err_free_clone; /* First pass discovers used resources */ if (build_body(&ctx) < 0) - goto out_err; + goto err_free_descriptors; + /* * Second pass computes instruction offsets. * If any PC-relative branches are out of range, a sequence of @@ -975,11 +976,11 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) if (tries == 2) set_convert_flag(&ctx, true); if (build_body(&ctx) < 0) - goto out_err; + goto err_free_descriptors; } while (ctx.changes > 0 && --tries > 0); if (WARN_ONCE(ctx.changes > 0, "JIT offsets failed to converge")) - goto out_err; + goto err_free_descriptors; build_epilogue(&ctx, MIPS_R_RA); @@ -992,7 +993,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) * we must fall back to the interpretation */ if (header == NULL) - goto out_err; + goto err_free_descriptors; /* Actual pass to generate final JIT code */ ctx.target = (u32 *)image_ptr; @@ -1004,7 +1005,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) */ build_prologue(&ctx); if (build_body(&ctx) < 0) - goto out_err; + goto err_free_header; build_epilogue(&ctx, MIPS_R_RA); /* Populate line info meta data */ @@ -1013,7 +1014,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) /* Set as read-only exec and flush instruction cache */ if (bpf_jit_binary_lock_ro(header)) - goto out_err; + goto err_free_header; flush_icache_range((unsigned long)header, (unsigned long)&ctx.target[ctx.jit_index]); @@ -1024,16 +1025,21 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) prog->jited = 1; prog->jited_len = image_size; -out: if (tmp_blinded) bpf_jit_prog_release_other(prog, prog == orig_prog ? tmp : orig_prog); kfree(ctx.descriptors); return prog; -out_err: - prog = orig_prog; - if (header) - bpf_jit_binary_free(header); - goto out; +err_free_header: + bpf_jit_binary_free(header); + +err_free_descriptors: + kfree(ctx.descriptors); + +err_free_clone: + if (tmp_blinded) + bpf_jit_prog_release_other(orig_prog, tmp); + + return orig_prog; } -- cgit v1.2.3 From f617b28ba7c3bdd3df02448a0998812bb409dbaf Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 28 Dec 2023 17:04:20 +0300 Subject: mips, bpf: Simplify clone handling code in bpf_int_jit_compile() Currently implemented semantics as follows: 1 save a pointer to the original BPF-program in the local 'orig_prog' variable, 2 call bpf_jit_blind_constants() possibly creating a cloned BPF-program and save a pointer to the clone in the "tmp" local variable, 3. if the program has been really cloned then set the "tmp_blinded" variable to 'true', 4. if no error happened release the original BPF-program if 'tmp_blinded' is 'true'. 5. if error happened release the clone if 'tmp_blinded' is 'true'. The logic is too overcomplicated than it could originally be. First of all 'tmp_blinded' variable is redundant since it's always possible to detect whether the bpf_jit_blind_constants() method returned a blinded BPF-program clone by comparing the 'orig_prog' and 'prog' pointers. Other than that the 'tmp_blinded' variable is unused. So there is no need to know whether the clone has been created in the framework of the constants blinding procedure. Secondly the 'tmp' variable is redundant. There are only two bpf_prog pointers are considered in the bpf_int_jit_compile() method: pointer to the original BPF-program and pointer to the cloned one. 'orig_prog' is initialized at the function head and never changed afterwards. 'prog' can used to save the passed BPF-program clone. If for some reason the bpf_int_jit_compile() hasn't created a clone and hasn't returned an error (I failed to find that option though) then it can be spotted afterwards just by comparing the 'prog' and 'orig_prog' pointers. Thus let's simplify the BPF-program clone handling semantics in bpf_int_jit_compile() by dropping the redundant 'tmp' and 'tmp_blinded' variable and refactoring the respective code to use just two pointers 'orig_prog' and 'prog'. Signed-off-by: Serge Semin --- Note. A similar simplification can be applied to many other bpf_int_jit_compile() methods. --- arch/mips/net/bpf_jit_comp.c | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/arch/mips/net/bpf_jit_comp.c b/arch/mips/net/bpf_jit_comp.c index b228194698eea..7a800f66275c4 100644 --- a/arch/mips/net/bpf_jit_comp.c +++ b/arch/mips/net/bpf_jit_comp.c @@ -911,10 +911,9 @@ bool bpf_jit_needs_zext(void) struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) { - struct bpf_prog *tmp, *orig_prog = prog; struct bpf_binary_header *header = NULL; + struct bpf_prog *orig_prog = prog; struct jit_context ctx; - bool tmp_blinded = false; unsigned int tmp_idx; unsigned int image_size; u8 *image_ptr; @@ -931,13 +930,9 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) * then we must fall back to the interpreter. Otherwise, we save * the new JITed code. */ - tmp = bpf_jit_blind_constants(prog); - if (IS_ERR(tmp)) + prog = bpf_jit_blind_constants(prog); + if (IS_ERR(prog)) return orig_prog; - if (tmp != prog) { - tmp_blinded = true; - prog = tmp; - } memset(&ctx, 0, sizeof(ctx)); ctx.program = prog; @@ -1025,9 +1020,9 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) prog->jited = 1; prog->jited_len = image_size; - if (tmp_blinded) - bpf_jit_prog_release_other(prog, prog == orig_prog ? - tmp : orig_prog); + if (prog != orig_prog) + bpf_jit_prog_release_other(prog, orig_prog); + kfree(ctx.descriptors); return prog; @@ -1038,8 +1033,8 @@ err_free_descriptors: kfree(ctx.descriptors); err_free_clone: - if (tmp_blinded) - bpf_jit_prog_release_other(orig_prog, tmp); + if (prog != orig_prog) + bpf_jit_prog_release_other(orig_prog, prog); return orig_prog; } -- cgit v1.2.3 From a274e533e5b9a817144df2ce93fb1c6cb164af81 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 28 Dec 2023 12:20:46 +0300 Subject: mips, bpf: Convert to dyn-allocating the context data MIPS JIT context data needs to be dynamically allocated in order to be preserved and re-used in case if an JIT-compile extra-pass is required. So convert the bpf_int_jit_compile() method to allocating the jit_context structure instance. As the most suitable way to do that the change implies the jit_context->descriptors pointer being converted to a flexible array field, since the former field has already implemented as dynamically allocated. From that perspective there were no much point in having the context defined on the function stack. Anyway by doing so a single allocation call would be performed thus creating both context structure instance and the descriptors array. Note this is a preparation change to adding the BPF-to-BPF calls support. Signed-off-by: Serge Semin --- arch/mips/net/bpf_jit_comp.c | 73 ++++++++++++++++++++++---------------------- arch/mips/net/bpf_jit_comp.h | 4 +-- 2 files changed, 38 insertions(+), 39 deletions(-) diff --git a/arch/mips/net/bpf_jit_comp.c b/arch/mips/net/bpf_jit_comp.c index 7a800f66275c4..bdd64144f8fd9 100644 --- a/arch/mips/net/bpf_jit_comp.c +++ b/arch/mips/net/bpf_jit_comp.c @@ -913,7 +913,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) { struct bpf_binary_header *header = NULL; struct bpf_prog *orig_prog = prog; - struct jit_context ctx; + struct jit_context *ctx; unsigned int tmp_idx; unsigned int image_size; u8 *image_ptr; @@ -934,21 +934,19 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) if (IS_ERR(prog)) return orig_prog; - memset(&ctx, 0, sizeof(ctx)); - ctx.program = prog; - /* - * Not able to allocate memory for descriptors[], then + * Not able to allocate memory for context and descriptors[], then * we must fall back to the interpreter */ - ctx.descriptors = kcalloc(prog->len + 1, sizeof(*ctx.descriptors), - GFP_KERNEL); - if (ctx.descriptors == NULL) + ctx = kzalloc(struct_size(ctx, descriptors, prog->len + 1), GFP_KERNEL); + if (ctx == NULL) goto err_free_clone; + ctx->program = prog; + /* First pass discovers used resources */ - if (build_body(&ctx) < 0) - goto err_free_descriptors; + if (build_body(ctx) < 0) + goto err_free_context; /* * Second pass computes instruction offsets. @@ -960,27 +958,27 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) * converted, to guarantee offset table convergence within a * fixed number of iterations. */ - ctx.jit_index = 0; - build_prologue(&ctx); - tmp_idx = ctx.jit_index; + ctx->jit_index = 0; + build_prologue(ctx); + tmp_idx = ctx->jit_index; tries = JIT_MAX_ITERATIONS; do { - ctx.jit_index = tmp_idx; - ctx.changes = 0; + ctx->jit_index = tmp_idx; + ctx->changes = 0; if (tries == 2) - set_convert_flag(&ctx, true); - if (build_body(&ctx) < 0) - goto err_free_descriptors; - } while (ctx.changes > 0 && --tries > 0); + set_convert_flag(ctx, true); + if (build_body(ctx) < 0) + goto err_free_context; + } while (ctx->changes > 0 && --tries > 0); - if (WARN_ONCE(ctx.changes > 0, "JIT offsets failed to converge")) - goto err_free_descriptors; + if (WARN_ONCE(ctx->changes > 0, "JIT offsets failed to converge")) + goto err_free_context; - build_epilogue(&ctx, MIPS_R_RA); + build_epilogue(ctx, MIPS_R_RA); /* Now we know the size of the structure to make */ - image_size = sizeof(u32) * ctx.jit_index; + image_size = sizeof(u32) * ctx->jit_index; header = bpf_jit_binary_alloc(image_size, &image_ptr, sizeof(u32), jit_fill_hole); /* @@ -988,49 +986,50 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) * we must fall back to the interpretation */ if (header == NULL) - goto err_free_descriptors; + goto err_free_context; /* Actual pass to generate final JIT code */ - ctx.target = (u32 *)image_ptr; - ctx.jit_index = 0; + ctx->target = (u32 *)image_ptr; + ctx->jit_index = 0; /* * If building the JITed code fails somehow, * we fall back to the interpretation. */ - build_prologue(&ctx); - if (build_body(&ctx) < 0) + build_prologue(ctx); + if (build_body(ctx) < 0) goto err_free_header; - build_epilogue(&ctx, MIPS_R_RA); + build_epilogue(ctx, MIPS_R_RA); /* Populate line info meta data */ - set_convert_flag(&ctx, false); - bpf_prog_fill_jited_linfo(prog, &ctx.descriptors[1]); + set_convert_flag(ctx, false); + bpf_prog_fill_jited_linfo(prog, &ctx->descriptors[1]); /* Set as read-only exec and flush instruction cache */ if (bpf_jit_binary_lock_ro(header)) goto err_free_header; flush_icache_range((unsigned long)header, - (unsigned long)&ctx.target[ctx.jit_index]); + (unsigned long)&ctx->target[ctx->jit_index]); if (bpf_jit_enable > 1) - bpf_jit_dump(prog->len, image_size, 2, ctx.target); + bpf_jit_dump(prog->len, image_size, 2, ctx->target); - prog->bpf_func = (void *)ctx.target; + prog->bpf_func = (void *)ctx->target; prog->jited = 1; prog->jited_len = image_size; if (prog != orig_prog) bpf_jit_prog_release_other(prog, orig_prog); - kfree(ctx.descriptors); + kfree(ctx); + return prog; err_free_header: bpf_jit_binary_free(header); -err_free_descriptors: - kfree(ctx.descriptors); +err_free_context: + kfree(ctx); err_free_clone: if (prog != orig_prog) diff --git a/arch/mips/net/bpf_jit_comp.h b/arch/mips/net/bpf_jit_comp.h index a37fe20818eb9..634f9a9963aed 100644 --- a/arch/mips/net/bpf_jit_comp.h +++ b/arch/mips/net/bpf_jit_comp.h @@ -74,9 +74,7 @@ /* JIT context for an eBPF program */ struct jit_context { struct bpf_prog *program; /* The eBPF program being JITed */ - u32 *descriptors; /* eBPF to JITed CPU insn descriptors */ u32 *target; /* JITed code buffer */ - u32 bpf_index; /* Index of current BPF program insn */ u32 jit_index; /* Index of current JIT target insn */ u32 changes; /* Number of PC-relative branch conv */ u32 accessed; /* Bit mask of read eBPF registers */ @@ -84,6 +82,8 @@ struct jit_context { u32 stack_size; /* Total allocated stack size in bytes */ u32 saved_size; /* Size of callee-saved registers */ u32 stack_used; /* Stack size used for function calls */ + u32 bpf_index; /* Index of current BPF program insn */ + u32 descriptors[]; /* eBPF to JITed CPU insn descriptors */ }; /* Emit the instruction if the JIT memory space has been allocated */ -- cgit v1.2.3 From f6c5bfebcc8ac577140491fb53b16d300b56b236 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 28 Dec 2023 12:34:51 +0300 Subject: mips, bpf: Move image and index fields to jit_context tail Since the descriptors and BPF instruction index fields have just been moved the jit_context tail let's do the same with the jit_context.target and jit_context.jit_index fields. Thus the target (JIT) image and the source (BPF) image related arrays and their indexes would be grouped together making the structure definition being more coherent and the code - a tiny bit more readable. Signed-off-by: Serge Semin --- arch/mips/net/bpf_jit_comp.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/mips/net/bpf_jit_comp.h b/arch/mips/net/bpf_jit_comp.h index 634f9a9963aed..09d3e1facaf22 100644 --- a/arch/mips/net/bpf_jit_comp.h +++ b/arch/mips/net/bpf_jit_comp.h @@ -74,14 +74,14 @@ /* JIT context for an eBPF program */ struct jit_context { struct bpf_prog *program; /* The eBPF program being JITed */ - u32 *target; /* JITed code buffer */ - u32 jit_index; /* Index of current JIT target insn */ u32 changes; /* Number of PC-relative branch conv */ u32 accessed; /* Bit mask of read eBPF registers */ u32 clobbered; /* Bit mask of modified CPU registers */ u32 stack_size; /* Total allocated stack size in bytes */ u32 saved_size; /* Size of callee-saved registers */ u32 stack_used; /* Stack size used for function calls */ + u32 jit_index; /* Index of current JIT target insn */ + u32 *target; /* JITed code buffer */ u32 bpf_index; /* Index of current BPF program insn */ u32 descriptors[]; /* eBPF to JITed CPU insn descriptors */ }; -- cgit v1.2.3 From 004fe96ab716bf30740950fd8bbe24c873977c37 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 28 Dec 2023 13:05:33 +0300 Subject: mips, bpf: Collect total number of JIT-compile passes Currently the total number of JIT-compile passes is not calculated. Meanwhile the bpf_int_jit_compile() method do perform at least three of them: 1 pass: discover used resources, 2+ pass: compute instruction offsets to detect long jump cases, N pass: generate final JIT code. and the bpf_jit_dump() method semantics implies passing the actual number of passes. Let's convert the MIPS BPF JIT compile procedure to calculating the number of JIT-compile passes performed in the framework of the build_body() method and the pass the calculated number to the build_body() function. Note since it's possible to have one more bpf_int_jit_compile() call to finalize the BPF-to-BPF calls compilation, the calculated value is preserved in the context data. Signed-off-by: Serge Semin --- arch/mips/net/bpf_jit_comp.c | 6 +++++- arch/mips/net/bpf_jit_comp.h | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/arch/mips/net/bpf_jit_comp.c b/arch/mips/net/bpf_jit_comp.c index bdd64144f8fd9..4f996940bcac4 100644 --- a/arch/mips/net/bpf_jit_comp.c +++ b/arch/mips/net/bpf_jit_comp.c @@ -881,6 +881,10 @@ static int build_body(struct jit_context *ctx) /* Store the end offset, where the epilogue begins */ ctx->descriptors[prog->len] = ctx->jit_index; + + /* Increase the number of JIT-compile passes for the callee to note */ + ctx->passes++; + return 0; } @@ -1012,7 +1016,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) (unsigned long)&ctx->target[ctx->jit_index]); if (bpf_jit_enable > 1) - bpf_jit_dump(prog->len, image_size, 2, ctx->target); + bpf_jit_dump(prog->len, image_size, ctx->passes, ctx->target); prog->bpf_func = (void *)ctx->target; prog->jited = 1; diff --git a/arch/mips/net/bpf_jit_comp.h b/arch/mips/net/bpf_jit_comp.h index 09d3e1facaf22..48f8d6bd30c23 100644 --- a/arch/mips/net/bpf_jit_comp.h +++ b/arch/mips/net/bpf_jit_comp.h @@ -74,6 +74,7 @@ /* JIT context for an eBPF program */ struct jit_context { struct bpf_prog *program; /* The eBPF program being JITed */ + u32 passes; /* Number of compile passes performed */ u32 changes; /* Number of PC-relative branch conv */ u32 accessed; /* Bit mask of read eBPF registers */ u32 clobbered; /* Bit mask of modified CPU registers */ -- cgit v1.2.3 From 154ac253ba9d330e73f162fe0cbdbe48181c1160 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 28 Dec 2023 22:31:09 +0300 Subject: mips, bpf: Save JIT-binary header in jit_context structure A pointer to the BPF-JIT image header needs to be available on the JIT extra-pass stage, i.e. on the second bpf_int_jit_compile() invocation in order to lock the image memory and flush the instruction cache on top of it. Let's add a pointer to bpf_binary_header structure field to the jit_context structure then. Note this is a final preparation before adding the BPF-to-BPF calls support. Signed-off-by: Serge Semin --- arch/mips/net/bpf_jit_comp.c | 13 ++++++------- arch/mips/net/bpf_jit_comp.h | 1 + 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/arch/mips/net/bpf_jit_comp.c b/arch/mips/net/bpf_jit_comp.c index 4f996940bcac4..81deabf1f158e 100644 --- a/arch/mips/net/bpf_jit_comp.c +++ b/arch/mips/net/bpf_jit_comp.c @@ -915,7 +915,6 @@ bool bpf_jit_needs_zext(void) struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) { - struct bpf_binary_header *header = NULL; struct bpf_prog *orig_prog = prog; struct jit_context *ctx; unsigned int tmp_idx; @@ -983,13 +982,13 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) /* Now we know the size of the structure to make */ image_size = sizeof(u32) * ctx->jit_index; - header = bpf_jit_binary_alloc(image_size, &image_ptr, - sizeof(u32), jit_fill_hole); + ctx->header = bpf_jit_binary_alloc(image_size, &image_ptr, + sizeof(u32), jit_fill_hole); /* * Not able to allocate memory for the structure then * we must fall back to the interpretation */ - if (header == NULL) + if (ctx->header == NULL) goto err_free_context; /* Actual pass to generate final JIT code */ @@ -1010,9 +1009,9 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) bpf_prog_fill_jited_linfo(prog, &ctx->descriptors[1]); /* Set as read-only exec and flush instruction cache */ - if (bpf_jit_binary_lock_ro(header)) + if (bpf_jit_binary_lock_ro(ctx->header)) goto err_free_header; - flush_icache_range((unsigned long)header, + flush_icache_range((unsigned long)ctx->header, (unsigned long)&ctx->target[ctx->jit_index]); if (bpf_jit_enable > 1) @@ -1030,7 +1029,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) return prog; err_free_header: - bpf_jit_binary_free(header); + bpf_jit_binary_free(ctx->header); err_free_context: kfree(ctx); diff --git a/arch/mips/net/bpf_jit_comp.h b/arch/mips/net/bpf_jit_comp.h index 48f8d6bd30c23..7c181c04200dc 100644 --- a/arch/mips/net/bpf_jit_comp.h +++ b/arch/mips/net/bpf_jit_comp.h @@ -74,6 +74,7 @@ /* JIT context for an eBPF program */ struct jit_context { struct bpf_prog *program; /* The eBPF program being JITed */ + struct bpf_binary_header *header; /* The eBPF JITed binary header */ u32 passes; /* Number of compile passes performed */ u32 changes; /* Number of PC-relative branch conv */ u32 accessed; /* Bit mask of read eBPF registers */ -- cgit v1.2.3 From b66d55decccdc456065edc6c29270a5aea101a31 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 29 Dec 2023 21:37:30 +0300 Subject: mips, bpf: Fix instructions with non-fixed func address involved BPF-calls imply using the immediate constants as the functions address arguments. Since the addresses are unknown on the initial BPF JIT-compile passes and in general functions can reside at any CPU address space, the respective instructions must be emitted with the full address width in mind in order to be safely updated afterwards, when the address are available. If this isn't done the final JIT pass (also called 'extra-pass') may end-up with an image different () from what was initially generated. Fix that by introducing the non-optimized versions of the emit_mov_i*() methods and make sure they are called for the BPF_CALL and LDIMM64-pseudo-call BPF instructions. Note this is a required fix before adding the BPF-to-BPF calls support. Signed-off-by: Serge Semin --- arch/mips/net/bpf_jit_comp.c | 9 +++++++++ arch/mips/net/bpf_jit_comp.h | 3 +++ arch/mips/net/bpf_jit_comp32.c | 19 +++++++++++++------ arch/mips/net/bpf_jit_comp64.c | 33 ++++++++++++++++++++++++++++----- 4 files changed, 53 insertions(+), 11 deletions(-) diff --git a/arch/mips/net/bpf_jit_comp.c b/arch/mips/net/bpf_jit_comp.c index 81deabf1f158e..6143c8a649c82 100644 --- a/arch/mips/net/bpf_jit_comp.c +++ b/arch/mips/net/bpf_jit_comp.c @@ -200,6 +200,15 @@ void emit_mov_i(struct jit_context *ctx, u8 dst, s32 imm) clobber_reg(ctx, dst); } +/* dst = imm (register width, no optimizations) */ +void emit_full_mov_i(struct jit_context *ctx, u8 dst, s32 imm) +{ + emit(ctx, lui, dst, (s16)((u32)imm >> 16)); + emit(ctx, ori, dst, dst, (u16)(imm & 0xffff)); + + clobber_reg(ctx, dst); +} + /* dst = src (register width) */ void emit_mov_r(struct jit_context *ctx, u8 dst, u8 src) { diff --git a/arch/mips/net/bpf_jit_comp.h b/arch/mips/net/bpf_jit_comp.h index 7c181c04200dc..36f6c8b706987 100644 --- a/arch/mips/net/bpf_jit_comp.h +++ b/arch/mips/net/bpf_jit_comp.h @@ -161,6 +161,9 @@ int get_offset(const struct jit_context *ctx, int off); /* dst = imm (32-bit) */ void emit_mov_i(struct jit_context *ctx, u8 dst, s32 imm); +/* dst = imm (32-bit, no optimizations) */ +void emit_full_mov_i(struct jit_context *ctx, u8 dst, s32 imm); + /* dst = src (32-bit) */ void emit_mov_r(struct jit_context *ctx, u8 dst, u8 src); diff --git a/arch/mips/net/bpf_jit_comp32.c b/arch/mips/net/bpf_jit_comp32.c index 40a878b672f5d..44e8e86b65ceb 100644 --- a/arch/mips/net/bpf_jit_comp32.c +++ b/arch/mips/net/bpf_jit_comp32.c @@ -1269,14 +1269,15 @@ static int emit_call(struct jit_context *ctx, const struct bpf_insn *insn) if (bpf_jit_get_func_addr(ctx->program, insn, false, &addr, &fixed) < 0) return -1; - if (!fixed) - return -1; /* Push stack arguments */ push_regs(ctx, JIT_STACK_REGS, 0, JIT_RESERVED_STACK); - /* Emit function call */ - emit_mov_i(ctx, MIPS_R_T9, addr); + /* Emit function call (BPF-to-BPF calls require full address dump) */ + if (fixed) + emit_mov_i(ctx, MIPS_R_T9, addr); + else + emit_full_mov_i(ctx, MIPS_R_T9, addr); emit(ctx, jalr, MIPS_R_RA, MIPS_R_T9); emit(ctx, nop); /* Delay slot */ @@ -1648,8 +1649,14 @@ int build_insn(const struct bpf_insn *insn, struct jit_context *ctx) break; /* dst = imm64 */ case BPF_LD | BPF_IMM | BPF_DW: - emit_mov_i(ctx, lo(dst), imm); - emit_mov_i(ctx, hi(dst), insn[1].imm); + /* Function calls need full address dump */ + if (bpf_pseudo_func(insn)) { + emit_full_mov_i(ctx, lo(dst), imm); + emit_full_mov_i(ctx, hi(dst), insn[1].imm); + } else { + emit_mov_i(ctx, lo(dst), imm); + emit_mov_i(ctx, hi(dst), insn[1].imm); + } return 1; /* LDX: dst = *(size *)(src + off) */ case BPF_LDX | BPF_MEM | BPF_W: diff --git a/arch/mips/net/bpf_jit_comp64.c b/arch/mips/net/bpf_jit_comp64.c index fa7e9aa37f498..13226819b5f0b 100644 --- a/arch/mips/net/bpf_jit_comp64.c +++ b/arch/mips/net/bpf_jit_comp64.c @@ -154,6 +154,22 @@ static void emit_mov_i64(struct jit_context *ctx, u8 dst, u64 imm64) clobber_reg(ctx, dst); } +/* dst = imm (64-bit, no optimizations) */ +static void emit_full_mov_i64(struct jit_context *ctx, u8 dst, u64 imm64) +{ + u16 half = imm64 >> 48; + int k; + + emit(ctx, ori, dst, MIPS_R_ZERO, half); + for (k = 1; k < 4; k++) { + half = imm64 >> (48 - 16 * k); + + emit(ctx, dsll_safe, dst, dst, 16); + emit(ctx, ori, dst, dst, half); + } + clobber_reg(ctx, dst); +} + /* ALU immediate operation (64-bit) */ static void emit_alu_i64(struct jit_context *ctx, u8 dst, s32 imm, u8 op) { @@ -447,14 +463,15 @@ static int emit_call(struct jit_context *ctx, const struct bpf_insn *insn) if (bpf_jit_get_func_addr(ctx->program, insn, false, &addr, &fixed) < 0) return -1; - if (!fixed) - return -1; /* Push caller-saved registers on stack */ push_regs(ctx, ctx->clobbered & JIT_CALLER_REGS, 0, 0); - /* Emit function call */ - emit_mov_i64(ctx, tmp, addr & JALR_MASK); + /* Emit function call (BPF-to-BPF calls require full address dump) */ + if (fixed) + emit_mov_i64(ctx, tmp, addr & JALR_MASK); + else + emit_full_mov_i64(ctx, tmp, addr & JALR_MASK); emit(ctx, jalr, MIPS_R_RA, tmp); emit(ctx, nop); /* Delay slot */ @@ -642,6 +659,7 @@ int build_insn(const struct bpf_insn *insn, struct jit_context *ctx) s32 imm = insn->imm; s32 val, rel; u8 alu, jmp; + u64 imm64; switch (code) { /* ALU operations */ @@ -818,7 +836,12 @@ int build_insn(const struct bpf_insn *insn, struct jit_context *ctx) break; /* dst = imm64 */ case BPF_LD | BPF_IMM | BPF_DW: - emit_mov_i64(ctx, dst, (u32)imm | ((u64)insn[1].imm << 32)); + /* Function calls need full address dump */ + imm64 = ((u64)insn[1].imm << 32) | (u32)imm; + if (bpf_pseudo_func(insn)) + emit_full_mov_i64(ctx, dst, imm64); + else + emit_mov_i64(ctx, dst, imm64); return 1; /* LDX: dst = *(size *)(src + off) */ case BPF_LDX | BPF_MEM | BPF_W: -- cgit v1.2.3 From 617c701fb81465363119a5e97c8ec7ac943d25c7 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 29 Dec 2023 22:00:15 +0300 Subject: mips, bpf: Add BPF-functions call support BPF local functions based on the BPF_CALL and BPF_IMM-mode instructions require extra-pass being performed on the image in order to have the calling function addresses applied. It's done by calling the bpf_int_jit_compile() twice: first to generate the BPF JITed image with sub-functions stub addresses, and second, after actual function addresses are known, to apply these address in the respective instructions. In order for that to work correctly fix the bpf_int_jit_compile() method in a way it's done in the rest of the eBPF JIT-capable platforms: save platform-specific eBPF JIT context and re-use it in the extra pass. Signed-off-by: Serge Semin --- arch/mips/net/bpf_jit_comp.c | 81 ++++++++++++++++++++++++++++++++---------- arch/mips/net/bpf_jit_comp.h | 1 + arch/mips/net/bpf_jit_comp32.c | 2 +- arch/mips/net/bpf_jit_comp64.c | 2 +- 4 files changed, 65 insertions(+), 21 deletions(-) diff --git a/arch/mips/net/bpf_jit_comp.c b/arch/mips/net/bpf_jit_comp.c index 6143c8a649c82..b10270777d0f3 100644 --- a/arch/mips/net/bpf_jit_comp.c +++ b/arch/mips/net/bpf_jit_comp.c @@ -937,6 +937,24 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) */ if (!prog->jit_requested) return orig_prog; + + /* + * If the BPF-program has been already JITed and the JIT-context hasn't + * been saved, the extra pass isn't required and just return the passed + * image. Otherwise restore the context and procceed with the extra + * pass. + */ + if (prog->jited && !prog->aux->jit_data) { + return orig_prog; + } else if (prog->aux->jit_data) { + ctx = prog->aux->jit_data; + ctx->extra_pass = true; + tmp_idx = ctx->jit_index; + image_ptr = (void *)ctx->target; + image_size = prog->jited_len; + goto skip_init_ctx; + } + /* * If constant blinding was enabled and we failed during blinding * then we must fall back to the interpreter. Otherwise, we save @@ -1002,42 +1020,67 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog) /* Actual pass to generate final JIT code */ ctx->target = (u32 *)image_ptr; - ctx->jit_index = 0; /* - * If building the JITed code fails somehow, - * we fall back to the interpretation. + * If building the JITed code fails somehow fall back to the + * interpretation in both cases - initial and extra passes. */ +skip_init_ctx: + ctx->jit_index = 0; build_prologue(ctx); if (build_body(ctx) < 0) - goto err_free_header; + goto err_clean_prog; build_epilogue(ctx, MIPS_R_RA); - /* Populate line info meta data */ - set_convert_flag(ctx, false); - bpf_prog_fill_jited_linfo(prog, &ctx->descriptors[1]); - - /* Set as read-only exec and flush instruction cache */ - if (bpf_jit_binary_lock_ro(ctx->header)) - goto err_free_header; - flush_icache_range((unsigned long)ctx->header, - (unsigned long)&ctx->target[ctx->jit_index]); - - if (bpf_jit_enable > 1) - bpf_jit_dump(prog->len, image_size, ctx->passes, ctx->target); + /* Sanity check extra JIT-compile pass */ + if (ctx->extra_pass && ctx->jit_index != tmp_idx) { + pr_err_once("multi-func JIT bug %d != %d\n", + ctx->jit_index, tmp_idx); + goto err_clean_prog; + } prog->bpf_func = (void *)ctx->target; prog->jited = 1; prog->jited_len = image_size; + /* + * Finalize the image and free the context after the extra JIT-compile + * pass or if no extra pass implied + */ + if (ctx->extra_pass || !prog->is_func) { + /* Populate line info meta data */ + set_convert_flag(ctx, false); + bpf_prog_fill_jited_linfo(prog, &ctx->descriptors[1]); + + if (bpf_jit_enable > 1) { + bpf_jit_dump(prog->len, image_size, + ctx->passes + ctx->extra_pass, + ctx->target); + } + + /* Set as read-only exec and flush instruction cache */ + if (bpf_jit_binary_lock_ro(ctx->header)) + goto err_clean_prog; + flush_icache_range((unsigned long)ctx->header, + (unsigned long)&ctx->target[ctx->jit_index]); + + /* JIT-compile context is no longer needed */ + prog->aux->jit_data = NULL; + kfree(ctx); + } else { + prog->aux->jit_data = ctx; + } + if (prog != orig_prog) bpf_jit_prog_release_other(prog, orig_prog); - kfree(ctx); - return prog; -err_free_header: +err_clean_prog: + orig_prog->bpf_func = NULL; + orig_prog->jited = 0; + orig_prog->jited_len = 0; + bpf_jit_binary_free(ctx->header); err_free_context: diff --git a/arch/mips/net/bpf_jit_comp.h b/arch/mips/net/bpf_jit_comp.h index 36f6c8b706987..e258d7ff61a05 100644 --- a/arch/mips/net/bpf_jit_comp.h +++ b/arch/mips/net/bpf_jit_comp.h @@ -75,6 +75,7 @@ struct jit_context { struct bpf_prog *program; /* The eBPF program being JITed */ struct bpf_binary_header *header; /* The eBPF JITed binary header */ + bool extra_pass; /* Extra compile pass flag */ u32 passes; /* Number of compile passes performed */ u32 changes; /* Number of PC-relative branch conv */ u32 accessed; /* Bit mask of read eBPF registers */ diff --git a/arch/mips/net/bpf_jit_comp32.c b/arch/mips/net/bpf_jit_comp32.c index 44e8e86b65ceb..ab1fe3b7b0c0f 100644 --- a/arch/mips/net/bpf_jit_comp32.c +++ b/arch/mips/net/bpf_jit_comp32.c @@ -1266,7 +1266,7 @@ static int emit_call(struct jit_context *ctx, const struct bpf_insn *insn) u64 addr; /* Decode the call address */ - if (bpf_jit_get_func_addr(ctx->program, insn, false, + if (bpf_jit_get_func_addr(ctx->program, insn, ctx->extra_pass, &addr, &fixed) < 0) return -1; diff --git a/arch/mips/net/bpf_jit_comp64.c b/arch/mips/net/bpf_jit_comp64.c index 13226819b5f0b..ba58870009024 100644 --- a/arch/mips/net/bpf_jit_comp64.c +++ b/arch/mips/net/bpf_jit_comp64.c @@ -460,7 +460,7 @@ static int emit_call(struct jit_context *ctx, const struct bpf_insn *insn) u64 addr; /* Decode the call address */ - if (bpf_jit_get_func_addr(ctx->program, insn, false, + if (bpf_jit_get_func_addr(ctx->program, insn, ctx->extra_pass, &addr, &fixed) < 0) return -1; -- cgit v1.2.3 From 6e2faed87587e079b407e6e330c1aea107c35e11 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 23 Oct 2025 18:01:20 +0300 Subject: mips: CPS: Fix secondary CPU failed to boot on EVA EVA-based memory mapping can be any but normally the segments have the KSEG0 extended downwards (like on Malta and Baikal-T1). In these cases it will be fatal to jump over the absolute address, i.e. to use l*r instructions, before the EVA mapping is deployed since kernel may reside at a range not suitably remapped yet. So let's move the eva_init code execution to be done before jumping to the actual kernel address. The same order can be found on the core 0 initial boot up assembler code. Fixes: 6521d9a436a6 ("MIPS: CPS: Initialize EVA before bringing up VPEs from secondary cores") Signed-off-by: Serge Semin --- arch/mips/kernel/cps-vec.S | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/arch/mips/kernel/cps-vec.S b/arch/mips/kernel/cps-vec.S index 2ae7034a3d5c4..ac8f7c91b0fdf 100644 --- a/arch/mips/kernel/cps-vec.S +++ b/arch/mips/kernel/cps-vec.S @@ -117,19 +117,19 @@ LEAF(mips_cps_core_boot) mtc0 t0, CP0_CONFIG ehb - /* Jump to kseg0 */ - PTR_LA t0, 1f - jr t0 - nop - /* * We're up, cached & coherent. Perform any EVA initialization necessary * before we access memory. */ -1: eva_init + eva_init + + /* Jump to the actual kernel address kseg0/xkseg (legacy/EVA) */ + PTR_LA t0, 1f + jr t0 + nop /* Retrieve boot configuration pointers */ - jal mips_cps_get_bootcfg +1: jal mips_cps_get_bootcfg nop /* Skip core-level init if we started up coherent */ -- cgit v1.2.3 From 893641221f610b67bbc9adfb9f6a4bba8dca3d7e Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 23 Oct 2025 18:22:15 +0300 Subject: mips: mm: Re-map uncached address on DMA-noncoherent EVA EVA mapping provides a way to completely re-define the kernel virtual address space map. But normally it implies just the cached lowmem region extending. That's what done on the Malta and Baikal-T1 CPUs with EVA feature enabled. In the meantime the directly mapped uncached region is still limited to be less than 512M including the IO-memory sub-space. So in order to support the DMA-coherent memory allocated from all the low-memory space even on the EVA kernel let's use the KSEG2/KSEG3 regions to uncached-remap the requested address via MMU. Note the arch_dma_set_uncached() caller antagonist implies to call the vunmap() method (like it's done in dma_direct_alloc()/dma_direct_free() pair). Fixes: c9fede2afc00 ("MIPS: malta: spaces.h: Add spaces.h file for Malta (EVA)") Signed-off-by: Serge Semin --- arch/mips/mm/dma-noncoherent.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/arch/mips/mm/dma-noncoherent.c b/arch/mips/mm/dma-noncoherent.c index ab4f2a75a7d01..faba6778eb4c5 100644 --- a/arch/mips/mm/dma-noncoherent.c +++ b/arch/mips/mm/dma-noncoherent.c @@ -51,7 +51,24 @@ void arch_dma_prep_coherent(struct page *page, size_t size) void *arch_dma_set_uncached(void *addr, size_t size) { - return (void *)(__pa(addr) + UNCAC_BASE); + phys_addr_t paddr = virt_to_phys(addr); + + /* + * Directly-mapped cached region can be up to 2.5GB on EVA with + * still reserving 512MB for the uncached direct mapping. Use the + * mapped space to coherently access the 512MB+ memory. Make sure + * the page is vunmapped by the callee antagonist like + * dma_direct_free(). + */ + if (IS_ENABLED(CONFIG_EVA) && paddr >= CSEGX_SIZE) { + pgprot_t prot = pgprot_noncached(PAGE_KERNEL); + struct page *page = virt_to_page(addr); + + return dma_common_contiguous_remap(page, size, prot, + __builtin_return_address(0)); + } + + return (void *)((unsigned long)paddr + UNCAC_BASE); } static inline void dma_sync_virt_for_device(void *addr, size_t size, -- cgit v1.2.3 From 723d5cc089cfb181f45e22a585aee9726dcbcf25 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 23 Oct 2025 18:34:43 +0300 Subject: mips: Permit up to KSEG2 direct virtual addressing on EVA Indeed the kernel won't work correctly without the MAP_BASE region. It's defined above the KSEG2/KSEG3 legacy regions and above the CFG0/CFG1 EVA virtual segments on all the currently EVA-capable CPUs (Malta and Baikal-T1). Thus at least on this stage it's incorrect to omit the upper limit in the __debug_virt_addr_valid() method in the EVA kernel. Let's fix that. Fixes: 8a2b456665d1 ("MIPS: Fixed __debug_virt_addr_valid()") Signed-off-by: Serge Semin --- arch/mips/mm/physaddr.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/arch/mips/mm/physaddr.c b/arch/mips/mm/physaddr.c index a6b1bf82057a1..8a135bef0b1d9 100644 --- a/arch/mips/mm/physaddr.c +++ b/arch/mips/mm/physaddr.c @@ -22,9 +22,7 @@ static inline bool __debug_virt_addr_valid(unsigned long x) if (x == MAX_DMA_ADDRESS) return true; - return x >= PAGE_OFFSET && (KSEGX(x) < KSEG2 || - IS_ENABLED(CONFIG_EVA) || - !IS_ENABLED(CONFIG_HIGHMEM)); + return x >= PAGE_OFFSET && (KSEGX(x) < KSEG2 || !IS_ENABLED(CONFIG_HIGHMEM)); } phys_addr_t __virt_to_phys(volatile const void *x) -- cgit v1.2.3 From 36bc90cac2a64e05e7131ad7ea6afdfbef4b4457 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 23 Oct 2025 11:56:05 +0300 Subject: mips: Drop EVA and HIGHMEM mutual exclusivity There is absolutely no reason to have these features being mutually exclusive. EVA stands for Enhanced Virtual Addressing and it's purpose to make the CPU virtual address space configurable primarily to overcome the 512M lowmem constraint and have up to 3G directly addressable memory in kernel. But that doesn't mean that EVA has no mapped memory support. It has and is normally enabled at the same virtual address space as on the legacy memory segments. That makes HIGHMEM and EVA configs being incorrectly anti-dependent. Let's drop the dependency then. The change has been tested on a MIPS32 P5600 CPU-based platform with 8GB memory available. The system works just fine. Fixes: a6e18781c5b8 ("MIPS: Kconfig: Add Kconfig symbols for EVA support") Signed-off-by: Serge Semin --- arch/mips/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig index e48b62b4dc489..a8e65dc5a3d71 100644 --- a/arch/mips/Kconfig +++ b/arch/mips/Kconfig @@ -2578,7 +2578,7 @@ config WAR_MIPS34K_MISSED_ITLB # config HIGHMEM bool "High Memory Support" - depends on 32BIT && CPU_SUPPORTS_HIGHMEM && SYS_SUPPORTS_HIGHMEM && !CPU_MIPS32_3_5_EVA + depends on 32BIT && CPU_SUPPORTS_HIGHMEM && SYS_SUPPORTS_HIGHMEM select KMAP_LOCAL config CPU_SUPPORTS_HIGHMEM -- cgit v1.2.3 From 7c3f9171dbf761f082e3033466eb2f2828ecaf7b Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 23 Oct 2025 11:32:18 +0300 Subject: mips: Drop EVA and XPA mutual exclusivity There is absolutely no reason to have these features being mutually exclusive. XPA stands for EXtended Physical Addressing and is dedicated for having more than 4G physical memory available on MIPS32 platforms. EVA stands for Enhanced Virtual Addressing and it's purpose to make the CPU virtual address configurable primarily to overcome the 512M lowmem constraint and have up to 3G directly addressable memory in kernel. The MIPS-arch implementation of the Linux kernel currently doesn't have anything that makes these features not working together. Moreover it has been tested on a MIPS32 P5600 CPU-based platform with 8GB memory available. The system works just fine. Let's drop the XPA-EVA anti-dependency then. Fixes: c5b367835cfc ("MIPS: Add support for XPA.") Signed-off-by: Serge Semin --- arch/mips/Kconfig | 1 - 1 file changed, 1 deletion(-) diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig index a8e65dc5a3d71..ca6ff14303b67 100644 --- a/arch/mips/Kconfig +++ b/arch/mips/Kconfig @@ -1783,7 +1783,6 @@ config CPU_MIPS32_R5_FEATURES config CPU_MIPS32_R5_XPA bool "Extended Physical Addressing (XPA)" depends on CPU_MIPS32_R5_FEATURES - depends on !EVA depends on !PAGE_SIZE_4KB depends on SYS_SUPPORTS_HIGHMEM select XPA -- cgit v1.2.3 From 263c75414970e555d5569a4d8a13f8235e35419b Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 24 Oct 2025 15:01:58 +0300 Subject: mips: relocatable: Take vmlinux base address into account The Linux kernel can be built to run anywhere within the valid virtual address space. In case of the legacy MIPS memory mapping it's indeed based in the KSEG0 region. So the kernel relocation to 0x81000000 should be fine in the most of the cases. But if the kernel is built for EVA mapping with extended KSEG0 (XKSEG) and the platform has no traditional KSEG0 aliasing, then the default 0x81000000 address won't be suitable since at least it will lack having the directly mapped uncached region where the exception handlers reside. Let's fix that by defaulting the relocated kernel base address to being with respect to the platform-specific VMLINUX_LOAD_ADDRESS. In all of the currently supported platforms it's 0x80100000. So the commit won't change much for them. But if the kernel is built for EVA and placed out of the legacy KSEG0 region, it will be taken into account in the default relocation address. Signed-off-by: Serge Semin --- arch/mips/kernel/relocate.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arch/mips/kernel/relocate.c b/arch/mips/kernel/relocate.c index 59833210542ff..f10601c8e53ae 100644 --- a/arch/mips/kernel/relocate.c +++ b/arch/mips/kernel/relocate.c @@ -286,10 +286,10 @@ static inline void __init *determine_relocation_address(void) static inline void __init *determine_relocation_address(void) { /* - * Choose a new address for the kernel - * For now we'll hard code the destination + * Choose a new address for the kernel. For now we'll hard code + * the destination to vmlinux base + 16MB. */ - return (void *)0xffffffff81000000; + return (void *)(VMLINUX_LOAD_ADDRESS + 0x01000000); } #endif -- cgit v1.2.3 From 955b63d91858285a8b691017087587abbbef5441 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 23 Oct 2025 17:30:14 +0300 Subject: mips: spaces: Convert CAC_BASE to being pre-defined The macro defines the base address of the kernel directly mapped cacheable region. It normally matches to the (C)KSEG0/XKPHYS regions. But EVA (Enhanced Virtual Addressing) feature changes that since KSEG0 is no longer a static region. Basically the EVA mapping can be any but normally it just implies the KSEG0 extending downward. In order to have that reflected in the CAC_BASE macro let's make it optionally pre-defined by the platform-specific space.h header-file on MIPS32 in the same way as it can be done on MIPS64 CPUs. Signed-off-by: Serge Semin --- arch/mips/include/asm/mach-generic/spaces.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/mips/include/asm/mach-generic/spaces.h b/arch/mips/include/asm/mach-generic/spaces.h index 6332b6cbf7eef..cb79b57579343 100644 --- a/arch/mips/include/asm/mach-generic/spaces.h +++ b/arch/mips/include/asm/mach-generic/spaces.h @@ -30,7 +30,9 @@ #endif /* __ASSEMBLER__ */ #ifdef CONFIG_32BIT +#ifndef CAC_BASE #define CAC_BASE _AC(0x80000000, UL) +#endif #ifndef IO_BASE #define IO_BASE _AC(0xa0000000, UL) #endif -- cgit v1.2.3 From cd67785c8d2e6a268bb2910eb1acf1dcca10b27a Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 11 Jul 2022 10:03:12 +0300 Subject: dt-bindings: dmaengine: Add Baikal-T1 DMA Controller binding Baikal-T1 is equipped with DW APB DMAC IP-core of v2.14a with eight slave channels and two master channels. First master is directed towards the system memory thus it has up to 16 byte data bus width, while the second master is supposed to work with the low-speed devices and has only 4 data bytes bus. TS block is limited with 4095 bytes with no linked-lists support (sadly). Max burst length equals to 16 bytes on the first and second channels, and 4 bytes - on the rest of them. The DMA engine has hand-shaking interface attached to the next low-speed peripherals: 1) UART controller: requests #0, #1, #2, #3; 2) SPI controller: requests #4, #5, #6, #7; 3) I2C controller: requests #8, #9, #10, #11. Signed-off-by: Serge Semin --- Documentation/devicetree/bindings/dma/snps,dma-spear1340.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/dma/snps,dma-spear1340.yaml b/Documentation/devicetree/bindings/dma/snps,dma-spear1340.yaml index 18c0a7c18bc85..55e69f39d774b 100644 --- a/Documentation/devicetree/bindings/dma/snps,dma-spear1340.yaml +++ b/Documentation/devicetree/bindings/dma/snps,dma-spear1340.yaml @@ -21,6 +21,7 @@ properties: - enum: - renesas,r9a06g032-dma - const: renesas,rzn1-dma + - const: baikal,bt1-dmac "#dma-cells": minimum: 3 -- cgit v1.2.3 From 07859c96a4376695bd290edbc45e8c843ce78ff6 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sun, 1 Sep 2024 22:49:42 +0300 Subject: dmaengine: dw: Prevent tx-status calling DMA-desc callback The dmaengine_tx_status() method implemented in the DW DMAC driver is responsible for not just DMA-transfer status getting, but may also cause the transfer finalization with the Tx-descriptors callback invocation. This makes the simple DMA-transfer status getting being much more complex than it seems with a wider room for possible bugs. In particular a deadlock has been discovered in the DW 8250 UART device driver interacting with the DW DMA controller channels. Here is the call-trace causing the deadlock: serial8250_handle_irq() uart_port_lock_irqsave(port); ----------------------+ handle_rx_dma() | serial8250_rx_dma_flush() | __dma_rx_complete() | dmaengine_tx_status() | dwc_scan_descriptors() | dwc_complete_all() | dwc_descriptor_complete() | dmaengine_desc_callback_invoke() | cb->callback(cb->callback_param); | || | dma_rx_complete(); | uart_port_lock_irqsave(port); ----+ <- Deadlock! So in case if the DMA-engine finished working at some point before the serial8250_rx_dma_flush() invocation and the respective tasklet hasn't been executed yet to finalize the DMA transfer, then calling the dmaengine_tx_status() will cause the DMA-descriptors status update and the Tx-descriptor callback invocation. Generalizing the case up: if the dmaengine_tx_status() method callee and the Tx-descriptor callback refer to the related critical section, then calling dmaengine_tx_status() from the Tx-descriptor callback will inevitably cause a deadlock around the guarding lock as it happens in the Serial 8250 DMA implementation above. (Note the deadlock doesn't happen very often, but can be eventually discovered if the being received data size is greater than the Rx DMA-buffer size defined in the 8250_dma.c driver. In my case reducing the Rx DMA-buffer size increased the deadlock probability.) Alas there is no obvious way to prevent the deadlock by fixing the 8250-port drivers because the UART-port lock must be held for the entire port IRQ handling procedure. Thus the best way to fix the discovered problem (and prevent similar ones in the drivers using the DW DMAC device channels) is to simplify the DMA-transfer status getter by removing the Tx-descriptors state update from there and making the function to serve just one purpose - calculate the DMA-transfer residue and return the transfer status. The DMA-transfer status update will be performed in the bottom-half procedure only. Fixes: 3bfb1d20b547 ("dmaengine: Driver for the Synopsys DesignWare DMA controller") Signed-off-by: Serge Semin --- Changelog RFC: - Instead of just dropping the dwc_scan_descriptors() method invocation calculate the residue in the Tx-status getter. --- drivers/dma/dw/core.c | 90 ++++++++++++++++++++++++++++----------------------- 1 file changed, 50 insertions(+), 40 deletions(-) diff --git a/drivers/dma/dw/core.c b/drivers/dma/dw/core.c index dd75f97a33b3d..af1871646eb9e 100644 --- a/drivers/dma/dw/core.c +++ b/drivers/dma/dw/core.c @@ -39,6 +39,8 @@ BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | \ BIT(DMA_SLAVE_BUSWIDTH_4_BYTES) +static u32 dwc_get_hard_llp_desc_residue(struct dw_dma_chan *dwc, struct dw_desc *desc); + /*----------------------------------------------------------------------*/ static struct device *chan2dev(struct dma_chan *chan) @@ -297,14 +299,12 @@ static inline u32 dwc_get_sent(struct dw_dma_chan *dwc) static void dwc_scan_descriptors(struct dw_dma *dw, struct dw_dma_chan *dwc) { - dma_addr_t llp; struct dw_desc *desc, *_desc; struct dw_desc *child; u32 status_xfer; unsigned long flags; spin_lock_irqsave(&dwc->lock, flags); - llp = channel_readl(dwc, LLP); status_xfer = dma_readl(dw, RAW.XFER); if (status_xfer & dwc->mask) { @@ -358,41 +358,16 @@ static void dwc_scan_descriptors(struct dw_dma *dw, struct dw_dma_chan *dwc) return; } - dev_vdbg(chan2dev(&dwc->chan), "%s: llp=%pad\n", __func__, &llp); + dev_vdbg(chan2dev(&dwc->chan), "%s: hard LLP mode\n", __func__); list_for_each_entry_safe(desc, _desc, &dwc->active_list, desc_node) { - /* Initial residue value */ - desc->residue = desc->total_len; - - /* Check first descriptors addr */ - if (desc->txd.phys == DWC_LLP_LOC(llp)) { - spin_unlock_irqrestore(&dwc->lock, flags); - return; - } - - /* Check first descriptors llp */ - if (lli_read(desc, llp) == llp) { - /* This one is currently in progress */ - desc->residue -= dwc_get_sent(dwc); + desc->residue = dwc_get_hard_llp_desc_residue(dwc, desc); + if (desc->residue) { spin_unlock_irqrestore(&dwc->lock, flags); return; } - desc->residue -= desc->len; - list_for_each_entry(child, &desc->tx_list, desc_node) { - if (lli_read(child, llp) == llp) { - /* Currently in progress */ - desc->residue -= dwc_get_sent(dwc); - spin_unlock_irqrestore(&dwc->lock, flags); - return; - } - desc->residue -= child->len; - } - - /* - * No descriptors so far seem to be in progress, i.e. - * this one must be done. - */ + /* No data left to be send. Finalize the transfer then */ spin_unlock_irqrestore(&dwc->lock, flags); dwc_descriptor_complete(dwc, desc, true); spin_lock_irqsave(&dwc->lock, flags); @@ -976,6 +951,45 @@ static struct dw_desc *dwc_find_desc(struct dw_dma_chan *dwc, dma_cookie_t c) return NULL; } +static u32 dwc_get_soft_llp_desc_residue(struct dw_dma_chan *dwc, struct dw_desc *desc) +{ + u32 residue = desc->residue; + + if (residue) + residue -= dwc_get_sent(dwc); + + return residue; +} + +static u32 dwc_get_hard_llp_desc_residue(struct dw_dma_chan *dwc, struct dw_desc *desc) +{ + u32 residue = desc->total_len; + struct dw_desc *child; + dma_addr_t llp; + + llp = channel_readl(dwc, LLP); + + /* Check first descriptor for been pending to be fetched by DMAC */ + if (desc->txd.phys == DWC_LLP_LOC(llp)) + return residue; + + /* Check first descriptor LLP to see if it's currently in-progress */ + if (lli_read(desc, llp) == llp) + return residue - dwc_get_sent(dwc); + + /* Check subordinate LLPs to find the currently in-progress desc */ + residue -= desc->len; + list_for_each_entry(child, &desc->tx_list, desc_node) { + if (lli_read(child, llp) == llp) + return residue - dwc_get_sent(dwc); + + residue -= child->len; + } + + /* Shall return zero if no in-progress desc found */ + return residue; +} + static u32 dwc_get_residue_and_status(struct dw_dma_chan *dwc, dma_cookie_t cookie, enum dma_status *status) { @@ -988,9 +1002,11 @@ static u32 dwc_get_residue_and_status(struct dw_dma_chan *dwc, dma_cookie_t cook desc = dwc_find_desc(dwc, cookie); if (desc) { if (desc == dwc_first_active(dwc)) { - residue = desc->residue; - if (test_bit(DW_DMA_IS_SOFT_LLP, &dwc->flags) && residue) - residue -= dwc_get_sent(dwc); + if (test_bit(DW_DMA_IS_SOFT_LLP, &dwc->flags)) + residue = dwc_get_soft_llp_desc_residue(dwc, desc); + else + residue = dwc_get_hard_llp_desc_residue(dwc, desc); + if (test_bit(DW_DMA_IS_PAUSED, &dwc->flags)) *status = DMA_PAUSED; } else { @@ -1012,12 +1028,6 @@ dwc_tx_status(struct dma_chan *chan, struct dw_dma_chan *dwc = to_dw_dma_chan(chan); enum dma_status ret; - ret = dma_cookie_status(chan, cookie, txstate); - if (ret == DMA_COMPLETE) - return ret; - - dwc_scan_descriptors(to_dw_dma(chan->device), dwc); - ret = dma_cookie_status(chan, cookie, txstate); if (ret == DMA_COMPLETE) return ret; -- cgit v1.2.3 From de68a420b2f679e2b87d337f287e85248b1c96f0 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 11 Sep 2024 19:39:57 +0300 Subject: dmaengine: dw: Fix XFER bit set, but channel not idle error If a client driver gets to use the DW DMAC engine device tougher than usual, with occasional DMA-transfers termination and restart, then the next error can be randomly spotted in the system log: > dma dma0chan0: BUG: XFER bit set, but channel not idle! For instance that happens in case of the 8250 UART port driver handling the looped back high-speed traffic (in my case > 1.5Mbaud) by means of the DMA-engine interface. The error happens due to the two-staged nature of the DW DMAC IRQs handling procedure and due to the critical section break in the meantime. In particular in case if the DMA-transfer is terminated and restarted: 1. after the IRQ-handler submitted the tasklet but before the tasklet started handling the DMA-descriptors in dwc_scan_descriptors(); 2. after the XFER completion flag was detected in the dwc_scan_descriptors() method, but before the dwc_complete_all() method is called the error denoted above is printed due to the overlap of the last transfer completion and the new transfer execution stages. There are two places need to be altered in order to fix the problem. 1. Clear the IRQs in the dwc_chan_disable() method. That will prevent the dwc_scan_descriptors() method call in case if the DMA-transfer is restarted in the middle of the two-staged IRQs-handling procedure. 2. Move the dwc_complete_all() code to being executed inseparably (in the same atomic section) from the DMA-descriptors scanning procedure. That will prevent the DMA-transfer restarts after the DMA-transfer completion was spotted but before the actual completion is executed. Fixes: 69cea5a00d31 ("dmaengine/dw_dmac: Replace spin_lock* with irqsave variants and enable submission from callback") Fixes: 3bfb1d20b547 ("dmaengine: Driver for the Synopsys DesignWare DMA controller") Signed-off-by: Serge Semin --- drivers/dma/dw/core.c | 54 ++++++++++++++++++++++++--------------------------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/drivers/dma/dw/core.c b/drivers/dma/dw/core.c index af1871646eb9e..fbc46cbfe2591 100644 --- a/drivers/dma/dw/core.c +++ b/drivers/dma/dw/core.c @@ -143,6 +143,12 @@ static inline void dwc_chan_disable(struct dw_dma *dw, struct dw_dma_chan *dwc) channel_clear_bit(dw, CH_EN, dwc->mask); while (dma_readl(dw, CH_EN) & dwc->mask) cpu_relax(); + + dma_writel(dw, CLEAR.XFER, dwc->mask); + dma_writel(dw, CLEAR.BLOCK, dwc->mask); + dma_writel(dw, CLEAR.SRC_TRAN, dwc->mask); + dma_writel(dw, CLEAR.DST_TRAN, dwc->mask); + dma_writel(dw, CLEAR.ERROR, dwc->mask); } /*----------------------------------------------------------------------*/ @@ -259,34 +265,6 @@ dwc_descriptor_complete(struct dw_dma_chan *dwc, struct dw_desc *desc, dmaengine_desc_callback_invoke(&cb, NULL); } -static void dwc_complete_all(struct dw_dma *dw, struct dw_dma_chan *dwc) -{ - struct dw_desc *desc, *_desc; - LIST_HEAD(list); - unsigned long flags; - - spin_lock_irqsave(&dwc->lock, flags); - if (dma_readl(dw, CH_EN) & dwc->mask) { - dev_err(chan2dev(&dwc->chan), - "BUG: XFER bit set, but channel not idle!\n"); - - /* Try to continue after resetting the channel... */ - dwc_chan_disable(dw, dwc); - } - - /* - * Submit queued descriptors ASAP, i.e. before we go through - * the completed ones. - */ - list_splice_init(&dwc->active_list, &list); - dwc_dostart_first_queued(dwc); - - spin_unlock_irqrestore(&dwc->lock, flags); - - list_for_each_entry_safe(desc, _desc, &list, desc_node) - dwc_descriptor_complete(dwc, desc, true); -} - /* Returns how many bytes were already received from source */ static inline u32 dwc_get_sent(struct dw_dma_chan *dwc) { @@ -303,6 +281,7 @@ static void dwc_scan_descriptors(struct dw_dma *dw, struct dw_dma_chan *dwc) struct dw_desc *child; u32 status_xfer; unsigned long flags; + LIST_HEAD(list); spin_lock_irqsave(&dwc->lock, flags); status_xfer = dma_readl(dw, RAW.XFER); @@ -341,9 +320,26 @@ static void dwc_scan_descriptors(struct dw_dma *dw, struct dw_dma_chan *dwc) clear_bit(DW_DMA_IS_SOFT_LLP, &dwc->flags); } + /* + * No more active descriptors left to handle. So submit the + * queued descriptors and finish up the already handled ones. + */ + if (dma_readl(dw, CH_EN) & dwc->mask) { + dev_err(chan2dev(&dwc->chan), + "BUG: XFER bit set, but channel not idle!\n"); + + /* Try to continue after resetting the channel... */ + dwc_chan_disable(dw, dwc); + } + + list_splice_init(&dwc->active_list, &list); + dwc_dostart_first_queued(dwc); + spin_unlock_irqrestore(&dwc->lock, flags); - dwc_complete_all(dw, dwc); + list_for_each_entry_safe(desc, _desc, &list, desc_node) + dwc_descriptor_complete(dwc, desc, true); + return; } -- cgit v1.2.3 From c036078b649e830991710b089146af0c57ebeb45 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Tue, 22 Mar 2022 23:11:59 +0300 Subject: MAINTAINERS: Add maintainer for Baikal-T1 PCIe driver Add myself as a maintainer of the Baikal-T1 PCIe Root Port driver. Signed-off-by: Serge Semin --- MAINTAINERS | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 7a292680f9877..8d0533281bf09 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20481,6 +20481,13 @@ S: Maintained F: Documentation/devicetree/bindings/pci/axis,artpec* F: drivers/pci/controller/dwc/*artpec* +PCIE DRIVER FOR BAIKAL-T1 +M: Serge Semin +L: linux-pci@vger.kernel.org +S: Supported +F: Documentation/devicetree/bindings/pci/baikal,bt1-pcie.yaml +F: drivers/pci/controller/dwc/pcie-bt1.c + PCIE DRIVER FOR CAVIUM THUNDERX M: Robert Richter L: linux-pci@vger.kernel.org -- cgit v1.2.3 From 9efaffc979cb5beb1115ef52895981942a3622a1 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 6 Aug 2026 02:27:36 +0300 Subject: MAINTAINERS: Return Baikal-T1 PVT hwmon driver entry The Baikal-T1 PVT driver has been and will be supported by the original maintainer. Get back the respective entry to the MAINTAINERS file. Signed-off-by: Serge Semin --- MAINTAINERS | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index d1cc0e12fe1f0..fad7b4df70886 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4412,6 +4412,14 @@ F: drivers/video/backlight/ F: include/linux/backlight.h F: include/linux/pwm_backlight.h +BAIKAL-T1 PVT HARDWARE MONITOR DRIVER +M: Serge Semin +L: linux-hwmon@vger.kernel.org +S: Supported +F: Documentation/devicetree/bindings/hwmon/baikal,bt1-pvt.yaml +F: Documentation/hwmon/bt1-pvt.rst +F: drivers/hwmon/bt1-pvt.[ch] + BARCO P50 GPIO DRIVER M: Santosh Kumar Yadav M: Peter Korsgaard -- cgit v1.2.3 From 45c90e8e44dcf2903cc4320a0d8ab20403b625dd Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sat, 25 Oct 2025 14:25:26 +0300 Subject: hwmon: bt1-pvt: Fix incorrect alarm attribute notification If PVT alarms are enabled the driver gets to be capable to detect the converted value exceeding the specified thresholds. These events are supposed to be reported via the sysfs attributed like: {hwmon_temp,hwmon_in}_{min_alarm,max_alarm}. But even though the pvt_soft_isr() function correctly calls the hwmon_notify_event() method with a correct "pvt_sensor_info::{attr_min_alarm,attr_max_alarm}" field passed, the value of the later fields have been wrongly initialized in the macros PVT_SENSOR_INFO(). As a result the threshold events are now reported via the {hwmon_temp,hwmon_in}_{min,max} attributes, which is definitely wrong. Let's fix the inconsistency above by assigning a correct min/max-alarm attribute value to the pvt_sensor_info::{attr_min_alarm,attr_max_alarm} fields. Fixes: 87976ce2825d ("hwmon: Add Baikal-T1 PVT sensor driver") Signed-off-by: Serge Semin --- drivers/hwmon/bt1-pvt.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/bt1-pvt.h b/drivers/hwmon/bt1-pvt.h index 93b8dd5e7c944..64acc79e6af49 100644 --- a/drivers/hwmon/bt1-pvt.h +++ b/drivers/hwmon/bt1-pvt.h @@ -166,8 +166,8 @@ struct pvt_sensor_info { .thres_sts_lo = PVT_INTR_ ##_thres## _LO, \ .thres_sts_hi = PVT_INTR_ ##_thres## _HI, \ .type = _type, \ - .attr_min_alarm = _type## _min, \ - .attr_max_alarm = _type## _max, \ + .attr_min_alarm = _type## _min_alarm, \ + .attr_max_alarm = _type## _max_alarm, \ } /* -- cgit v1.2.3 From cd45a387d87df906cadb9ddf8da98e4d3acef24d Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sat, 10 Feb 2024 22:27:43 +0300 Subject: dt-bindings: mips: Add ImagTech P5600 CPU compat string Imagination Technology released the P5600 Warrior 2013. It's based on the MIPS32r5 ISA. Baikal-T1 SoC is one of the real device it's utilized in. Let's add the P5600 compatible string to the MIPS CPU bindings before adding the actual SoC support. Signed-off-by: Serge Semin --- Documentation/devicetree/bindings/mips/cpus.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/mips/cpus.yaml b/Documentation/devicetree/bindings/mips/cpus.yaml index d3677f53f1423..c20b81f228b18 100644 --- a/Documentation/devicetree/bindings/mips/cpus.yaml +++ b/Documentation/devicetree/bindings/mips/cpus.yaml @@ -24,6 +24,7 @@ properties: - brcm,bmips5000 - brcm,bmips5200 - img,i6500 + - img,p5600 - ingenic,xburst-fpu1.0-mxu1.1 - ingenic,xburst-fpu2.0-mxu2.0 - ingenic,xburst-mxu1.0 -- cgit v1.2.3 From 28f224547f0d875e8dcbeee0a2de2b0d301b9f61 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 6 Aug 2026 03:16:19 +0300 Subject: spi: dt-bindings: snps,dw-apb-ssi: Add reg-names to Baikal-T1 SPI Besides of the normal CSR space the Baikal-T1 system SSI controller also provides a memory region for the direct memory-mapped SPI-flash access. These spaces need to be distinguished somehow at least by the driver. It can be done by parsing a reg-names list indicating the order the regions specified. So let's add the property to the DW APB SSI DT-bindings then and make it specific to the "baikal,bt1-sys-ssi" compatible node. Signed-off-by: Serge Semin --- Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml b/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml index 81838577cf9cd..94af951f8cd02 100644 --- a/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml +++ b/Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml @@ -32,8 +32,13 @@ allOf: properties: mux-controls: maxItems: 1 + reg-names: + items: + - const: config + - const: map required: - mux-controls + - reg-names else: required: - interrupts @@ -195,6 +200,7 @@ examples: compatible = "baikal,bt1-sys-ssi"; reg = <0x1f040100 0x900>, <0x1c000000 0x1000000>; + reg-names = "config", "map"; #address-cells = <1>; #size-cells = <0>; mux-controls = <&boot_mux>; -- cgit v1.2.3 From 2d9925b2c38d92fc5a9b95937564103cf391088b Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 5 Aug 2026 23:26:42 +0300 Subject: Revert "spi: dw: Remove not-going-to-be-supported code for Baikal SoC" This reverts commit da0a672268b34279aa999664860e6becc38f3f51. The Baikal-T1 SPI driver has been and will be supported by the original maintainer. Get it back by reverting the commit removed it from the Linux kernel source tree. Signed-off-by: Serge Semin --- drivers/spi/Kconfig | 27 ++++ drivers/spi/Makefile | 1 + drivers/spi/spi-dw-bt1.c | 331 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 359 insertions(+) create mode 100644 drivers/spi/spi-dw-bt1.c diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index c3b2f02f5912e..a5ffae9f56680 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -385,6 +385,33 @@ config SPI_DW_MMIO tristate "Memory-mapped io interface driver for DW SPI core" depends on HAS_IOMEM +config SPI_DW_BT1 + tristate "Baikal-T1 SPI driver for DW SPI core" + depends on MIPS_BAIKAL_T1 || COMPILE_TEST + select MULTIPLEXER + help + Baikal-T1 SoC is equipped with three DW APB SSI-based MMIO SPI + controllers. Two of them are pretty much normal: with IRQ, DMA, + FIFOs of 64 words depth, 4x CSs, but the third one as being a + part of the Baikal-T1 System Boot Controller has got a very + limited resources: no IRQ, no DMA, only a single native + chip-select and Tx/Rx FIFO with just 8 words depth available. + The later one is normally connected to an external SPI-nor flash + of 128Mb (in general can be of bigger size). + +config SPI_DW_BT1_DIRMAP + bool "Directly mapped Baikal-T1 Boot SPI flash support" + depends on SPI_DW_BT1 + help + Directly mapped SPI flash memory is an interface specific to the + Baikal-T1 System Boot Controller. It is a 16MB MMIO region, which + can be used to access a peripheral memory device just by + reading/writing data from/to it. Note that the system APB bus + will stall during each IO from/to the dirmap region until the + operation is finished. So try not to use it concurrently with + time-critical tasks (like the SPI memory operations implemented + in this driver). + endif config SPI_DLN2 diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 9d36190a98848..9f44b508402b1 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -54,6 +54,7 @@ obj-$(CONFIG_SPI_DLN2) += spi-dln2.o obj-$(CONFIG_SPI_DESIGNWARE) += spi-dw.o spi-dw-y := spi-dw-core.o spi-dw-$(CONFIG_SPI_DW_DMA) += spi-dw-dma.o +obj-$(CONFIG_SPI_DW_BT1) += spi-dw-bt1.o obj-$(CONFIG_SPI_DW_MMIO) += spi-dw-mmio.o obj-$(CONFIG_SPI_DW_PCI) += spi-dw-pci.o obj-$(CONFIG_SPI_EP93XX) += spi-ep93xx.o diff --git a/drivers/spi/spi-dw-bt1.c b/drivers/spi/spi-dw-bt1.c new file mode 100644 index 0000000000000..91642e05ac607 --- /dev/null +++ b/drivers/spi/spi-dw-bt1.c @@ -0,0 +1,331 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright (C) 2020 BAIKAL ELECTRONICS, JSC +// +// Authors: +// Ramil Zaripov +// Serge Semin +// +// Baikal-T1 DW APB SPI and System Boot SPI driver +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "spi-dw.h" + +#define BT1_BOOT_DIRMAP 0 +#define BT1_BOOT_REGS 1 + +struct dw_spi_bt1 { + struct dw_spi dws; + struct clk *clk; + struct mux_control *mux; + +#ifdef CONFIG_SPI_DW_BT1_DIRMAP + void __iomem *map; + resource_size_t map_len; +#endif +}; +#define to_dw_spi_bt1(_ctlr) \ + container_of(spi_controller_get_devdata(_ctlr), struct dw_spi_bt1, dws) + +typedef int (*dw_spi_bt1_init_cb)(struct platform_device *pdev, + struct dw_spi_bt1 *dwsbt1); + +#ifdef CONFIG_SPI_DW_BT1_DIRMAP + +static int dw_spi_bt1_dirmap_create(struct spi_mem_dirmap_desc *desc) +{ + struct dw_spi_bt1 *dwsbt1 = to_dw_spi_bt1(desc->mem->spi->controller); + + if (!dwsbt1->map || + !dwsbt1->dws.mem_ops.supports_op(desc->mem, &desc->info.op_tmpl)) + return -EOPNOTSUPP; + + if (desc->info.op_tmpl.data.dir != SPI_MEM_DATA_IN) + return -EOPNOTSUPP; + + /* + * Make sure the requested region doesn't go out of the physically + * mapped flash memory bounds. + */ + if (desc->info.offset + desc->info.length > dwsbt1->map_len) + return -EINVAL; + + return 0; +} + +/* + * Directly mapped SPI memory region is only accessible in the dword chunks. + * That's why we have to create a dedicated read-method to copy data from there + * to the passed buffer. + */ +static void dw_spi_bt1_dirmap_copy_from_map(void *to, void __iomem *from, size_t len) +{ + size_t shift, chunk; + u32 data; + + /* + * We split the copying up into the next three stages: unaligned head, + * aligned body, unaligned tail. + */ + shift = (size_t)from & 0x3; + if (shift) { + chunk = min_t(size_t, 4 - shift, len); + data = readl_relaxed(from - shift); + memcpy(to, (char *)&data + shift, chunk); + from += chunk; + to += chunk; + len -= chunk; + } + + while (len >= 4) { + data = readl_relaxed(from); + memcpy(to, &data, 4); + from += 4; + to += 4; + len -= 4; + } + + if (len) { + data = readl_relaxed(from); + memcpy(to, &data, len); + } +} + +static ssize_t dw_spi_bt1_dirmap_read(struct spi_mem_dirmap_desc *desc, + u64 offs, size_t len, void *buf) +{ + struct dw_spi_bt1 *dwsbt1 = to_dw_spi_bt1(desc->mem->spi->controller); + struct dw_spi *dws = &dwsbt1->dws; + struct spi_mem *mem = desc->mem; + struct dw_spi_cfg cfg; + int ret; + + /* + * Make sure the requested operation length is valid. Truncate the + * length if it's greater than the length of the MMIO region. + */ + if (offs >= dwsbt1->map_len || !len) + return 0; + + len = min_t(size_t, len, dwsbt1->map_len - offs); + + /* Collect the controller configuration required by the operation */ + cfg.tmode = DW_SPI_CTRLR0_TMOD_EPROMREAD; + cfg.dfs = 8; + cfg.ndf = 4; + cfg.freq = mem->spi->max_speed_hz; + + /* Make sure the corresponding CS is de-asserted on transmission */ + dw_spi_set_cs(mem->spi, false); + + dw_spi_enable_chip(dws, 0); + + dw_spi_update_config(dws, mem->spi, &cfg); + + dw_spi_umask_intr(dws, DW_SPI_INT_RXFI); + + dw_spi_enable_chip(dws, 1); + + /* + * Enable the transparent mode of the System Boot Controller. + * The SPI core IO should have been locked before calling this method + * so noone would be touching the controller' registers during the + * dirmap operation. + */ + ret = mux_control_select(dwsbt1->mux, BT1_BOOT_DIRMAP); + if (ret) + return ret; + + dw_spi_bt1_dirmap_copy_from_map(buf, dwsbt1->map + offs, len); + + mux_control_deselect(dwsbt1->mux); + + dw_spi_set_cs(mem->spi, true); + + ret = dw_spi_check_status(dws, true); + + return ret ?: len; +} + +#endif /* CONFIG_SPI_DW_BT1_DIRMAP */ + +static int dw_spi_bt1_std_init(struct platform_device *pdev, + struct dw_spi_bt1 *dwsbt1) +{ + struct dw_spi *dws = &dwsbt1->dws; + + dws->irq = platform_get_irq(pdev, 0); + if (dws->irq < 0) + return dws->irq; + + dws->num_cs = 4; + + /* + * Baikal-T1 Normal SPI Controllers don't always keep up with full SPI + * bus speed especially when it comes to the concurrent access to the + * APB bus resources. Thus we have no choice but to set a constraint on + * the SPI bus frequency for the memory operations which require to + * read/write data as fast as possible. + */ + dws->max_mem_freq = 20000000U; + + dw_spi_dma_setup_generic(dws); + + return 0; +} + +static int dw_spi_bt1_sys_init(struct platform_device *pdev, + struct dw_spi_bt1 *dwsbt1) +{ + struct resource *mem __maybe_unused; + struct dw_spi *dws = &dwsbt1->dws; + + /* + * Baikal-T1 System Boot Controller is equipped with a mux, which + * switches between the directly mapped SPI flash access mode and + * IO access to the DW APB SSI registers. Note the mux controller + * must be setup to preserve the registers being accessible by default + * (on idle-state). + */ + dwsbt1->mux = devm_mux_control_get(&pdev->dev, NULL); + if (IS_ERR(dwsbt1->mux)) + return PTR_ERR(dwsbt1->mux); + + /* + * Directly mapped SPI flash memory is a 16MB MMIO region, which can be + * used to access a peripheral memory device just by reading/writing + * data from/to it. Note the system APB bus will stall during each IO + * from/to the dirmap region until the operation is finished. So don't + * use it concurrently with time-critical tasks (like the SPI memory + * operations implemented in the DW APB SSI driver). + */ +#ifdef CONFIG_SPI_DW_BT1_DIRMAP + mem = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (mem) { + dwsbt1->map = devm_ioremap_resource(&pdev->dev, mem); + if (!IS_ERR(dwsbt1->map)) { + dwsbt1->map_len = resource_size(mem); + dws->mem_ops.dirmap_create = dw_spi_bt1_dirmap_create; + dws->mem_ops.dirmap_read = dw_spi_bt1_dirmap_read; + } else { + dwsbt1->map = NULL; + } + } +#endif /* CONFIG_SPI_DW_BT1_DIRMAP */ + + /* + * There is no IRQ, no DMA and just one CS available on the System Boot + * SPI controller. + */ + dws->irq = IRQ_NOTCONNECTED; + dws->num_cs = 1; + + /* + * Baikal-T1 System Boot SPI Controller doesn't keep up with the full + * SPI bus speed due to relatively slow APB bus and races for it' + * resources from different CPUs. The situation is worsen by a small + * FIFOs depth (just 8 words). It works better in a single CPU mode + * though, but still tends to be not fast enough at low CPU + * frequencies. + */ + if (num_possible_cpus() > 1) + dws->max_mem_freq = 10000000U; + else + dws->max_mem_freq = 20000000U; + + return 0; +} + +static int dw_spi_bt1_probe(struct platform_device *pdev) +{ + dw_spi_bt1_init_cb init_func; + struct dw_spi_bt1 *dwsbt1; + struct resource *mem; + struct dw_spi *dws; + int ret; + + dwsbt1 = devm_kzalloc(&pdev->dev, sizeof(struct dw_spi_bt1), GFP_KERNEL); + if (!dwsbt1) + return -ENOMEM; + + dws = &dwsbt1->dws; + + dws->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &mem); + if (IS_ERR(dws->regs)) + return PTR_ERR(dws->regs); + + dws->paddr = mem->start; + + dwsbt1->clk = devm_clk_get_enabled(&pdev->dev, NULL); + if (IS_ERR(dwsbt1->clk)) + return PTR_ERR(dwsbt1->clk); + + dws->bus_num = pdev->id; + dws->reg_io_width = 4; + dws->max_freq = clk_get_rate(dwsbt1->clk); + if (!dws->max_freq) + return -EINVAL; + + init_func = device_get_match_data(&pdev->dev); + ret = init_func(pdev, dwsbt1); + if (ret) + return ret; + + pm_runtime_enable(&pdev->dev); + + ret = dw_spi_add_controller(&pdev->dev, dws); + if (ret) { + pm_runtime_disable(&pdev->dev); + return ret; + } + + platform_set_drvdata(pdev, dwsbt1); + + return 0; +} + +static void dw_spi_bt1_remove(struct platform_device *pdev) +{ + struct dw_spi_bt1 *dwsbt1 = platform_get_drvdata(pdev); + + dw_spi_remove_controller(&dwsbt1->dws); + + pm_runtime_disable(&pdev->dev); +} + +static const struct of_device_id dw_spi_bt1_of_match[] = { + { .compatible = "baikal,bt1-ssi", .data = dw_spi_bt1_std_init}, + { .compatible = "baikal,bt1-sys-ssi", .data = dw_spi_bt1_sys_init}, + { } +}; +MODULE_DEVICE_TABLE(of, dw_spi_bt1_of_match); + +static struct platform_driver dw_spi_bt1_driver = { + .probe = dw_spi_bt1_probe, + .remove = dw_spi_bt1_remove, + .driver = { + .name = "bt1-sys-ssi", + .of_match_table = dw_spi_bt1_of_match, + }, +}; +module_platform_driver(dw_spi_bt1_driver); + +MODULE_AUTHOR("Serge Semin "); +MODULE_DESCRIPTION("Baikal-T1 System Boot SPI Controller driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("SPI_DW_CORE"); -- cgit v1.2.3 From 5a56c3197471868f0985796d07a31f33a38c49cd Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 19 Jun 2026 17:42:44 +0300 Subject: spi: dw: Inform core subsystem with a max number of native CS The driver has been capable of auto-detecting the number of native CS since commit e164be7e1e93 ("spi: dw: Add a number of native CS auto-detection") unless the glue-code has the actual number of the CS defined. Let's use that data to inform the SPI core about that. Thus it will be able to perform additional sanity checks on the GPIO CS initialization procedure. It will be especially useful seeing the driver sets the SPI_CONTROLLER_GPIO_SS flag requiring the native CS being enabled in the GPIO-based CS case. Signed-off-by: Serge Semin --- drivers/spi/spi-dw-core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c index b47637888c5c6..fe94fa134866e 100644 --- a/drivers/spi/spi-dw-core.c +++ b/drivers/spi/spi-dw-core.c @@ -960,6 +960,7 @@ int dw_spi_add_controller(struct device *dev, struct dw_spi *dws) ctlr->bits_per_word_mask = SPI_BPW_RANGE_MASK(4, 16); ctlr->bus_num = dws->bus_num; ctlr->num_chipselect = dws->num_cs; + ctlr->max_native_cs = dws->num_cs; ctlr->setup = dw_spi_setup; ctlr->cleanup = dw_spi_cleanup; ctlr->transfer_one = dw_spi_transfer_one; -- cgit v1.2.3 From 61eba2825d405dff6e0ef649f29cfdef42e242d2 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sat, 10 Oct 2020 16:51:18 +0300 Subject: dt-bindings: syscon: Extend number of "reg" property entries In general the System Controllers can have more than a single CSR range. Let's modify the syscon DT schema then to permit any number of "reg" property entries. Signed-off-by: Serge Semin --- Documentation/devicetree/bindings/mfd/syscon.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/mfd/syscon.yaml b/Documentation/devicetree/bindings/mfd/syscon.yaml index e57add2bacd30..5df388e02ea3a 100644 --- a/Documentation/devicetree/bindings/mfd/syscon.yaml +++ b/Documentation/devicetree/bindings/mfd/syscon.yaml @@ -260,8 +260,7 @@ properties: - const: microchip,mpfs-sysreg-scb - const: syscon - reg: - maxItems: 1 + reg: true resets: maxItems: 1 -- cgit v1.2.3 From cb8a9275c9edc111a66fdc2e171828bd41774749 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sat, 2 May 2020 13:56:31 +0300 Subject: dt-bindings: mfd: Add Baikal-T1 System Controller binding Baikal-T1 SoC is equipped with a System Controller which is responsible for the SoC components setting up and consists of multiple sub-blocks, like CCU components, I2C and SPI controller, reboot and reboor-mode blocks, etc. These sub-blocks DT nodes are supposed to be placed in the system controller DT node. Signed-off-by: Serge Semin --- .../bindings/mfd/baikal,bt1-sys-con.yaml | 133 +++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 Documentation/devicetree/bindings/mfd/baikal,bt1-sys-con.yaml diff --git a/Documentation/devicetree/bindings/mfd/baikal,bt1-sys-con.yaml b/Documentation/devicetree/bindings/mfd/baikal,bt1-sys-con.yaml new file mode 100644 index 0000000000000..4d36257d8676a --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/baikal,bt1-sys-con.yaml @@ -0,0 +1,133 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +# Copyright (C) 2022 BAIKAL ELECTRONICS, JSC +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mfd/baikal,bt1-sys-con.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Baikal-T1 SoC System Controller + +maintainers: + - Serge Semin + +description: + Baikal-T1 SoC is equipped with a System Controller which is responsible for + the SoC components setting up and consists of the next sub-blocks':' + PLL/AXI-bus/System devices Clocks Control Units, P5600 CM2 L2-RAM controller, + CPU cores reboot flag, persistent across reboots register, indirectly + accessible DW APB I2C controller, Boot Controller with a pre-installed memory + mapped firmware and a resource limited DW APB SSI, which is also can be used + to transparently access an external SPI flash by means of a dedicated IO + memory region. + +allOf: + - $ref: /schemas/mfd/syscon.yaml# + +properties: + compatible: + items: + - const: baikal,bt1-sys-con + - const: syscon + - const: simple-mfd + + reg: + description: + Baikal-T1 System Controller CSR space. It includes CCU (Clock Control + Unit), L2 settings, Reboot flag and Reboot tolerant register, System I2C + controller CSRs. + maxItems: 1 + + reg-names: + const: sys + + "#address-cells": + const: 1 + + "#size-cells": + const: 1 + + ranges: true + + little-endian: true + + clock-controller@1f04d000: + $ref: /schemas/clock/baikal,bt1-ccu-pll.yaml# + + clock-controller@1f04d030: + $ref: /schemas/clock/baikal,bt1-ccu-div.yaml# + + clock-controller@1f04d060: + $ref: /schemas/clock/baikal,bt1-ccu-div.yaml# + + l2@1f04d028: + $ref: /schemas/cache/baikal,bt1-l2-ctl.yaml# + + reboot: + $ref: /schemas/power/reset/syscon-reboot.yaml# + + reboot-mode: + $ref: /schemas/power/reset/syscon-reboot-mode.yaml# + + i2c@1f04d100: + $ref: /schemas/i2c/snps,designware-i2c.yaml# + +required: + - compatible + - reg + +unevaluatedProperties: false + +examples: + - | + syscon@1f04d000 { + compatible = "baikal,bt1-sys-con", "syscon", "simple-mfd"; + reg = <0x1f04d000 0x1000>; + #address-cells = <1>; + #size-cells = <1>; + ranges; + + little-endian; + reg-io-width = <4>; + + clock-controller@1f04d000 { + compatible = "baikal,bt1-ccu-pll"; + reg = <0x1f04d000 0x028>; + #clock-cells = <1>; + + clocks = <&clk25m>; + clock-names = "ref_clk"; + }; + + clock-controller@1f04d030 { + compatible = "baikal,bt1-ccu-axi"; + reg = <0x1f04d030 0x030>; + #clock-cells = <1>; + #reset-cells = <1>; + + clocks = <&ccu_pll 1>, + <&ccu_pll 2>, + <&ccu_pll 3>; + clock-names = "sata_clk", "pcie_clk", "eth_clk"; + }; + + l2@1f04d028 { + compatible = "baikal,bt1-l2-ctl"; + reg = <0x1f04d028 0x004>; + + baikal,l2-ws-latency = <0>; + baikal,l2-tag-latency = <0>; + baikal,l2-data-latency = <1>; + }; + + i2c@1f04d100 { + compatible = "baikal,bt1-sys-i2c"; + reg = <0x1f04d100 0x010>; + #address-cells = <1>; + #size-cells = <0>; + + interrupts = <0 32 4>; + + clocks = <&ccu_sys 1>; + }; + }; +... -- cgit v1.2.3 From 628faa4d63b72d26944379daf678474d5086eaa3 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sun, 24 Apr 2022 22:17:30 +0300 Subject: dt-bindings: mfd: Add Baikal-T1 Boot Controller binding Baikal-T1 SoC is equipped with a Boot Controller which is responsible for the SoC proper boot up procedure. Depending on the external pin state the system can boot up either from the internal ROM or from the externally attached SPI flash (at least of 16MB) or from the internal SRAM (the 64KB of executional code is pre-loaded from the external SPI flash). The multiplexer responsible for the bootup source device selection is defined as the subnode of the boot controller DT node together with the physically mapped ROM and the SPI controller. Signed-off-by: Serge Semin --- .../bindings/mfd/baikal,bt1-boot-con.yaml | 112 +++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 Documentation/devicetree/bindings/mfd/baikal,bt1-boot-con.yaml diff --git a/Documentation/devicetree/bindings/mfd/baikal,bt1-boot-con.yaml b/Documentation/devicetree/bindings/mfd/baikal,bt1-boot-con.yaml new file mode 100644 index 0000000000000..0827e98c96a31 --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/baikal,bt1-boot-con.yaml @@ -0,0 +1,112 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +# Copyright (C) 2022 BAIKAL ELECTRONICS, JSC +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mfd/baikal,bt1-boot-con.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Baikal-T1 SoC Boot Controller + +maintainers: + - Serge Semin + +description: + Baikal-T1 SoC is equipped with a Boot Controller which is responsible for + the SoC proper boot up procedure. Depending on the external pin state the + system can boot up either from the internal ROM or from the externally attached + SPI flash (at least of 16MB) or from the internal SRAM (the 64KB of executional + code is pre-loaded from the external SPI flash). + +allOf: + - $ref: /schemas/mfd/syscon.yaml# + +properties: + compatible: + items: + - const: baikal,bt1-boot-con + - const: syscon + - const: simple-mfd + + reg: + items: + - description: + Baikal-T1 Boot Controller CSR space. It doesn't include many + settings':' corrent boot mode, SPI controller access mux, SRAM + access mux and device ID. + - description: Mirrored first 4MB of the boot SPI flash memory + + reg-names: + items: + - const: boot + - const: mirror + + "#address-cells": + const: 1 + + "#size-cells": + const: 1 + + ranges: true + + little-endian: true + + mux-controller: + $ref: /schemas/mux/reg-mux.yaml# + + rom@1bfc0000: + $ref: /schemas/mtd/mtd-physmap.yaml# + + spi@1f040100: + $ref: /schemas/spi/snps,dw-apb-ssi.yaml# + +required: + - compatible + - reg + +unevaluatedProperties: false + +examples: + - | + syscon@1f04d000 { + compatible = "baikal,bt1-boot-con", "syscon", "simple-mfd"; + reg = <0x1f040000 0x1000>, + <0x1fc00000 0x400000>; + reg-names = "boot", "mirror"; + #address-cells = <1>; + #size-cells = <1>; + ranges; + + little-endian; + reg-io-width = <4>; + + mux-controller { + compatible = "mmio-mux"; + #mux-control-cells = <1>; + + mux-reg-masks = <0x0 0x100>, <0x4 0x1>; + idle-states = <0x1>, <0x0>; + }; + + rom@1bfc0000 { + compatible = "baikal,bt1-int-rom", "mtd-rom"; + reg = <0x1bfc0000 0x10000>; + + no-unaligned-direct-access; + bank-width = <4>; + }; + + spi@1f040100 { + compatible = "baikal,bt1-sys-ssi"; + reg = <0x1f040100 0x900>, + <0x1c000000 0x1000000>; + reg-names = "config", "map"; + #address-cells = <1>; + #size-cells = <0>; + + mux-controls = <&boot_mux 0>; + + clocks = <&ccu_sys 1>; + clock-names = "ssi_clk"; + }; + }; +... -- cgit v1.2.3 From 3f32899b1144fa21e5cfa4e4e38a4572460c288e Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 3 Sep 2025 00:32:13 +0300 Subject: mips: Add no-unaligned load/store config to MIPSr5 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Based on [1,2] MIPS32r5 and MIPS64r5 don't support misaligned load/store instruction: "In Release 5 all misaligned memory accesses other than MSA continue to produce the Address Error Exception with the appropriate ErrorCode." The exception causes the instructions to be emulated by software. In order to prevent from execution the kernel code relying on the accelerated unaligned access instructions, let's declare the MIPS r5 architecture as not supporting such instructions by adding the CPU_NO_LOAD_STORE_LR config. This shall significantly speed up at least the IP-header csum calculation. [1] MIPS® Architecture For Programmers Volume II-A: The MIPS32 Instruction Set, Revision 5.04, 2013, p.354 [1] MIPS® Architecture For Programmers Volume II-A: The MIPS64 Instruction Set, Revision 5.04, 2013, p.422 Fixes: 18d84e2e55b6 ("MIPS: make CPU_HAS_LOAD_STORE_LR opt-out") Signed-off-by: Serge Semin --- arch/mips/Kconfig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig index e48b62b4dc489..238b5483a7d2c 100644 --- a/arch/mips/Kconfig +++ b/arch/mips/Kconfig @@ -1453,6 +1453,7 @@ config CPU_MIPS32_R5 bool "MIPS32 Release 5" depends on SYS_HAS_CPU_MIPS32_R5 select CPU_HAS_PREFETCH + select CPU_NO_LOAD_STORE_LR select CPU_SUPPORTS_32BIT_KERNEL select CPU_SUPPORTS_HIGHMEM select CPU_SUPPORTS_MSA @@ -1519,6 +1520,7 @@ config CPU_MIPS64_R5 bool "MIPS64 Release 5" depends on SYS_HAS_CPU_MIPS64_R5 select CPU_HAS_PREFETCH + select CPU_NO_LOAD_STORE_LR select CPU_SUPPORTS_32BIT_KERNEL select CPU_SUPPORTS_64BIT_KERNEL select CPU_SUPPORTS_HIGHMEM @@ -1554,6 +1556,7 @@ config CPU_P5600 bool "MIPS Warrior P5600" depends on SYS_HAS_CPU_P5600 select CPU_HAS_PREFETCH + select CPU_NO_LOAD_STORE_LR select CPU_SUPPORTS_32BIT_KERNEL select CPU_SUPPORTS_HIGHMEM select CPU_SUPPORTS_MSA -- cgit v1.2.3 From 12a3a16d7d640e8b6e6208a7d2bcea79b2379d9c Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 24 Oct 2025 15:53:42 +0300 Subject: mips: video: Combine writes to video/FB memory Modern MIPS CPUs support uncached accelerated pages mapping (UCA CCA). It implies the writes combining and uncached reads. The feature significantly speeds up the write accesses to the memory, which is a very useful gain to the frame-buffer-based video drivers. Let's use the generic pgprot_framebuffer() method which is unrolled to just the pgprot_writecombine() macros. The later one returns the UCA pages mapping prototype if it's supported by the CPU. Otherwise it's just the uncached CCA as before this change. Signed-off-by: Serge Semin --- arch/mips/include/asm/video.h | 8 -------- 1 file changed, 8 deletions(-) diff --git a/arch/mips/include/asm/video.h b/arch/mips/include/asm/video.h index 007c106d980fd..c79e777ea465c 100644 --- a/arch/mips/include/asm/video.h +++ b/arch/mips/include/asm/video.h @@ -3,14 +3,6 @@ #include -static inline pgprot_t pgprot_framebuffer(pgprot_t prot, - unsigned long vm_start, unsigned long vm_end, - unsigned long offset) -{ - return pgprot_noncached(prot); -} -#define pgprot_framebuffer pgprot_framebuffer - /* * MIPS doesn't define __raw_ I/O macros, so the helpers * in don't generate fb_readq() and -- cgit v1.2.3 From db96d7d40b6833081ea1dcf661fe563ba91b6d93 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sun, 13 Sep 2020 21:20:15 +0300 Subject: mips: Add strong UC ordering config In accordance with [1, 2] memory transactions using CCA=2 (Uncached Cacheability and Coherency Attribute) are always strongly ordered. This means the younger memory accesses using CCA=2 are never allowed to be executed before older memory accesses using CCA=2 (no bypassing is allowed), and Loads and Stores using CCA=2 are never speculative. It is expected by the specification that the rest of the system maintains these properties for processor initiated uncached accesses. So the system IO interconnect doesn't reorder uncached transactions once they have left the processor subsystem. Taking into account these properties and what [3] says about the relaxed IO-accessors we can infer that normal Loads and Stores from/to CCA=2 memory and without any additional execution barriers will fully comply with the {read,write}X_relaxed() methods requirements. Let's convert then currently generated relaxed IO-accessors to being pure Loads and Stores. Seeing the commit 3d474dacae72 ("MIPS: Enforce strong ordering for MMIO accessors") and commit 8b656253a7a4 ("MIPS: Provide actually relaxed MMIO accessors") have already made a preparation in the corresponding macro, we can do that just by replacing the "barrier" parameter utilization with the "relax" one. Note the "barrier" macro argument can be removed, since it isn't fully used anyway other than being always assigned to 1. Of course it would be fullish to believe that all the available MIPS-based CPUs completely follow the denoted specification, especially considering how old the architecture is. Instead we introduced a dedicated kernel config, which when enabled will convert the relaxed IO-accessors to being pure Loads and Stores without any additional barriers around. So if some CPU supports the strongly ordered UC memory access, it can enable that config and use a fully optimized relaxed IO-methods. For instance, Baikal-T1 architecture support code will do that. [1] MIPS Coherence Protocol Specification, Document Number: MD00605, Revision 01.01. September 14, 2015, 4.2 Execution Order Behavior, p. 33 [2] MIPS Coherence Protocol Specification, Document Number: MD00605, Revision 01.01. September 14, 2015, 4.8.1 IO Device Access, p. 58 [3] "LINUX KERNEL MEMORY BARRIERS", Documentation/memory-barriers.txt, Section "KERNEL I/O BARRIER EFFECTS" Signed-off-by: Serge Semin Reviewed-by: Jiaxun Yang Cc: Maciej W. Rozycki --- arch/mips/Kconfig | 8 ++++++++ arch/mips/include/asm/io.h | 18 +++++++++--------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig index 238b5483a7d2c..983749d076aa0 100644 --- a/arch/mips/Kconfig +++ b/arch/mips/Kconfig @@ -1989,6 +1989,14 @@ config WEAK_ORDERING # config WEAK_REORDERING_BEYOND_LLSC bool + +# +# CPU may not reorder reads and writes R->R, R->W, W->R, W->W within Uncached +# Cacheability and Coherency Attribute (CCA=2) +# +config STRONG_UC_ORDERING + bool + endmenu # diff --git a/arch/mips/include/asm/io.h b/arch/mips/include/asm/io.h index 980187a830536..f26828b475352 100644 --- a/arch/mips/include/asm/io.h +++ b/arch/mips/include/asm/io.h @@ -177,7 +177,7 @@ void iounmap(const volatile void __iomem *addr); #define war_io_reorder_wmb() barrier() #endif -#define __BUILD_MEMORY_SINGLE(pfx, bwlq, type, barrier, relax, irq) \ +#define __BUILD_MEMORY_SINGLE(pfx, bwlq, type, relax, irq) \ \ static inline void pfx##write##bwlq(type val, \ volatile void __iomem *mem) \ @@ -185,7 +185,7 @@ static inline void pfx##write##bwlq(type val, \ volatile type *__mem; \ type __val; \ \ - if (barrier) \ + if (!(relax && IS_ENABLED(CONFIG_STRONG_UC_ORDERING))) \ iobarrier_rw(); \ else \ war_io_reorder_wmb(); \ @@ -226,7 +226,7 @@ static inline type pfx##read##bwlq(const volatile void __iomem *mem) \ \ __mem = (void *)__swizzle_addr_##bwlq((unsigned long)(mem)); \ \ - if (barrier) \ + if (!(relax && IS_ENABLED(CONFIG_STRONG_UC_ORDERING))) \ iobarrier_rw(); \ \ if (sizeof(type) != sizeof(u64) || sizeof(u64) == sizeof(long)) \ @@ -258,14 +258,14 @@ static inline type pfx##read##bwlq(const volatile void __iomem *mem) \ return pfx##ioswab##bwlq(__mem, __val); \ } -#define __BUILD_IOPORT_SINGLE(pfx, bwlq, type, barrier, relax) \ +#define __BUILD_IOPORT_SINGLE(pfx, bwlq, type, relax) \ \ static inline void pfx##out##bwlq(type val, unsigned long port) \ { \ volatile type *__addr; \ type __val; \ \ - if (barrier) \ + if (!(relax && IS_ENABLED(CONFIG_STRONG_UC_ORDERING))) \ iobarrier_rw(); \ else \ war_io_reorder_wmb(); \ @@ -289,7 +289,7 @@ static inline type pfx##in##bwlq(unsigned long port) \ \ BUILD_BUG_ON(sizeof(type) > sizeof(unsigned long)); \ \ - if (barrier) \ + if (!(relax && IS_ENABLED(CONFIG_STRONG_UC_ORDERING))) \ iobarrier_rw(); \ \ __val = *__addr; \ @@ -302,7 +302,7 @@ static inline type pfx##in##bwlq(unsigned long port) \ #define __BUILD_MEMORY_PFX(bus, bwlq, type, relax) \ \ -__BUILD_MEMORY_SINGLE(bus, bwlq, type, 1, relax, 1) +__BUILD_MEMORY_SINGLE(bus, bwlq, type, relax, 1) #define BUILDIO_MEM(bwlq, type) \ \ @@ -322,7 +322,7 @@ __BUILD_MEMORY_PFX(__mem_, q, u64, 0) #endif #define __BUILD_IOPORT_PFX(bus, bwlq, type) \ - __BUILD_IOPORT_SINGLE(bus, bwlq, type, 1, 0) + __BUILD_IOPORT_SINGLE(bus, bwlq, type, 0) #define BUILDIO_IOPORT(bwlq, type) \ __BUILD_IOPORT_PFX(_, bwlq, type) \ @@ -337,7 +337,7 @@ BUILDIO_IOPORT(q, u64) #define __BUILDIO(bwlq, type) \ \ -__BUILD_MEMORY_SINGLE(____raw_, bwlq, type, 1, 0, 0) +__BUILD_MEMORY_SINGLE(____raw_, bwlq, type, 0, 0) __BUILDIO(q, u64) -- cgit v1.2.3 From 2fbacd7dba78b30958f772bb11c40ea2cebed274 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sun, 13 Sep 2020 22:39:58 +0300 Subject: mips: Introduce MIPS CM2 GCR Control register accessors For some reason these accessors have been absent from the MIPS kernel, while some of them can be used to tune the MIPS code execution up (the default value are fully acceptable though). For instance, in the framework of MIPS P5600/P6600 (see [1] for details) if we are sure the IO interconnect doesn't reorder the requests we can freely set GCR_CONTROL.SYNCDIS, which will make CM2 to respond on SYNCs just after a request is accepted on the L2/Memory interface instead of executing the legacy SYNC and waiting for the full response from L2/Memory. Needless to say that this will significantly speed the {read,write}X() IO-accessors due to having more lightweight barriers around the IO Loads and Stores. There are others MIPS Coherency Manager optimizations available in framework of that register like cache ops serialization limits, speculative read enable, etc, which can be useful for the various MIPS platforms. [1] MIPS32 P5600 Multiprocessing System Software User's Manual, Document Number: MD01025, Revision 01.60, April 19, 2016, p. 400 Signed-off-by: Serge Semin --- arch/mips/include/asm/mips-cm.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/arch/mips/include/asm/mips-cm.h b/arch/mips/include/asm/mips-cm.h index 407f253bb4a1c..75afedf940dcd 100644 --- a/arch/mips/include/asm/mips-cm.h +++ b/arch/mips/include/asm/mips-cm.h @@ -178,6 +178,21 @@ GCR_ACCESSOR_RW(64, 0x008, base) #define CM_GCR_BASE_CMDEFTGT_IOCU0 2 #define CM_GCR_BASE_CMDEFTGT_IOCU1 3 +/* GCR_CONTROL - Global CM2 Settings */ +GCR_ACCESSOR_RW(64, 0x010, control) +#define CM_GCR_CONTROL_SYNCCTL BIT(16) +#define CM_GCR_CONTROL_SYNCDIS BIT(5) +#define CM_GCR_CONTROL_IVU_EN BIT(4) +#define CM_GCR_CONTROL_SHST_EN BIT(3) +#define CM_GCR_CONTROL_PARK_EN BIT(2) +#define CM_GCR_CONTROL_MMIO_LIMIT_DIS BIT(1) +#define CM_GCR_CONTROL_SPEC_READ_EN BIT(0) + +/* GCR_CONTROL2 - Global CM2 Settings (continue) */ +GCR_ACCESSOR_RW(64, 0x018, control2) +#define CM_GCR_CONTROL2_L2_CACHEOP_LIMIT GENMASK(19, 16) +#define CM_GCR_CONTROL2_L1_CACHEOP_LIMIT GENMASK(3, 0) + /* GCR_ACCESS - Controls core/IOCU access to GCRs */ GCR_ACCESSOR_RW(32, 0x020, access) #define CM_GCR_ACCESS_ACCESSEN GENMASK(7, 0) -- cgit v1.2.3 From 67b443090e66dcd809f92667eaaed069f4a7c351 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 20 Jul 2022 18:05:23 +0300 Subject: EDAC/synopsys: Fix generic device type detection procedure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First of all the enum dev_type constants describe the memory DRAM chips used at the stick, not the entire DQ-bus width (see the enumeration kdoc for details). So what is returned from the zynqmp_get_dtype() function and then specified to the dimm_info->dtype field is definitely incorrect because the DDR controller DQ-bus width doesn't determine a strict DDR-chips configuration on the attached memory device. Secondly the DRAM chips type has nothing to do with the data bus width specified in the MSTR.data_bus_width CSR field. The CSR field just determines the _part_ of the whole DQ-bus currently used to access the data from the all DRAM memory chips. So it doesn't indicate the individual chips type. Thirdly in case of the DW uMCTL2 controllers the DRAM chips type can be firmly determined only in case of the DDR4 protocol by means of the MSTR.device_config field state (the field is supposed to be set by the system firmware though). From this and the rest of the perspective the zynqmp_get_dtype() implementation is incorrect. Finally the DW uMCTL2 DDRC ECC capability doesn't depend on the memory chips type. Moreover it doesn't depend on the utilized data bus width in runtime either. The IP-core reference manual says in [1,2] that the ECC support can't be enabled during the IP-core synthesis for the DRAM data bus widths other than 16, 32 or 64. At the same time the bus width mode (MSTR.data_bus_width) doesn't change the ECC feature availability. Thus it was wrong to determine the ECC state with respect to the DQ-bus width mode. From this perspective the zynqmp_get_ecc_state() implementation is incorrect either. It shouldn't rely on the currently utilized DQ-bus part or the attached DDR-chip types. Fix all of the mistakes described above in the zynqmp_get_dtype() and zynqmp_get_ecc_state() methods: determine the actual DRAM chips data width only for the DDR4 protocol and return that it's UNKNOWN in the rest of the cases; determine the ECC availability by the ECCCFG0.ecc_mode field state only (that field can't be modified anyway if the IP-core was synthesized with no ECC support). [1] DesignWare® Cores Enhanced Universal DDR Memory Controller (uMCTL2) Databook, Version 3.91a, October 2020, p. 421. [2] DesignWare® Cores Enhanced Universal DDR Memory Controller (uMCTL2) Databook, Version 3.91a, October 2020, p. 633. Fixes: b500b4a029d5 ("EDAC, synopsys: Add ECC support for ZynqMP DDR controller") Signed-off-by: Serge Semin Reviewed-by: Shubhrajyoti Datta --- Changelog v2: - Include "linux/bitfield.h" header file to get the FIELD_GET macro definition. (@tbot) Changelog v6: - Fix the zynqmp_get_dtype() method kdoc. - Split up the commit log into paragraphs. --- drivers/edac/synopsys_edac.c | 57 +++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index 51143b3257de2..7555484d081d3 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -694,38 +694,46 @@ static enum dev_type zynq_get_dtype(const void __iomem *base) } /** - * zynqmp_get_dtype - Return the controller memory width. + * zynqmp_get_dtype - Return the DDR memory chips type. * @base: DDR memory controller base address. * - * Get the EDAC device type width appropriate for the current controller + * Get the attached DDR chips type based on the current controller * configuration. * - * Return: a device type width enumeration. + * Return: type of the memory DRAM chips. */ static enum dev_type zynqmp_get_dtype(const void __iomem *base) { - enum dev_type dt; - u32 width; - - width = readl(base + CTRL_OFST); - width = (width & ECC_CTRL_BUSWIDTH_MASK) >> ECC_CTRL_BUSWIDTH_SHIFT; - switch (width) { - case DDRCTL_EWDTH_16: - dt = DEV_X2; - break; - case DDRCTL_EWDTH_32: - dt = DEV_X4; - break; - case DDRCTL_EWDTH_64: - dt = DEV_X8; - break; - default: - dt = DEV_UNKNOWN; + u32 regval; + + regval = readl(base + CTRL_OFST); + if (!(regval & MEM_TYPE_DDR4)) + return DEV_UNKNOWN; + + regval = (regval & DDRC_MSTR_CFG_MASK) >> DDRC_MSTR_CFG_SHIFT; + switch (regval) { + case DDRC_MSTR_CFG_X4_MASK: + return DEV_X4; + case DDRC_MSTR_CFG_X8_MASK: + return DEV_X8; + case DDRC_MSTR_CFG_X16_MASK: + return DEV_X16; + case DDRC_MSTR_CFG_X32_MASK: + return DEV_X32; } - return dt; + return DEV_UNKNOWN; } +/** + * get_ecc_state - Return the controller ECC enable/disable status. + * @priv: DDR memory controller private instance data. + * + * Get the ECC enable/disable status for the controller and clear out + * the ECC counters if applicatble. + * + * Return: a ECC status boolean i.e true/false - enabled/disabled. + */ static bool get_ecc_state(struct synps_edac_priv *priv) { u32 ecctype, clearval; @@ -744,13 +752,8 @@ static bool get_ecc_state(struct synps_edac_priv *priv) return true; } } else { - dt = zynqmp_get_dtype(priv->baseaddr); - if (dt == DEV_UNKNOWN) - return false; - ecctype = readl(priv->baseaddr + ECC_CFG0_OFST) & SCRUB_MODE_MASK; - if (ecctype == SCRUB_MODE_SECDED && - (dt == DEV_X2 || dt == DEV_X4 || dt == DEV_X8)) { + if (ecctype == SCRUB_MODE_SECDED) { clearval = readl(priv->baseaddr + ECC_CLR_OFST) | ECC_CTRL_CLR_CE_ERR | ECC_CTRL_CLR_CE_ERRCNT | ECC_CTRL_CLR_UE_ERR | ECC_CTRL_CLR_UE_ERRCNT; -- cgit v1.2.3 From fda3a4565b71439259497e7923e1377023c0fa5b Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 20 Jul 2022 14:58:47 +0300 Subject: EDAC/synopsys: Fix mci->scrub_cap field setting The mem_ctl_info.scrub_cap field is supposed to be set with the ECC scrub-related flags. Instead the driver erroneously initializes it with the SCRUB_HW_SRC flag ID. It's definitely wrong, but it hasn't caused any problem so far since the structure field isn't used by the EDAC core. Fix it anyway by using the SCRUB_FLAG_HW_SRC macro to initialize the field. Fixes: ae9b56e3996d ("EDAC, synps: Add EDAC support for zynq ddr ecc controller") Signed-off-by: Serge Semin --- drivers/edac/synopsys_edac.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index 7555484d081d3..c44a73bc6923b 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -884,7 +884,7 @@ static void mc_init(struct mem_ctl_info *mci, struct platform_device *pdev) /* Initialize controller capabilities and configuration */ mci->mtype_cap = MEM_FLAG_DDR3 | MEM_FLAG_DDR2; mci->edac_ctl_cap = EDAC_FLAG_NONE | EDAC_FLAG_SECDED; - mci->scrub_cap = SCRUB_HW_SRC; + mci->scrub_cap = SCRUB_FLAG_HW_SRC; mci->scrub_mode = SCRUB_NONE; mci->edac_cap = EDAC_FLAG_SECDED; -- cgit v1.2.3 From 6b7d4710695659b6d3eead04070be51144c09d21 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 3 Aug 2022 17:30:16 +0300 Subject: EDAC/synopsys: Drop erroneous ADDRMAP4.addrmap_col_b10 parse Currently the ADDRMAP4.addrmap_col_b10 field gets to be parsed in case of the LPDDR3 memory and Quarter DQ bus width mode. It's wrong since that field is marked as unused for that mode in all the available DW uMCTL2 DDRC releases (up to IP-core v3.91a). Most likely the field parsing was added by mistake as a result of the copy-paste from the Half DQ bus width mode part of the same function. Even though the field is supposed to be always set to the UNUSED value (0x1F) drop parsing it anyway so to simplify the setup_column_address_map() method a tiny bit. Fixes: 1a81361f75d8 ("EDAC, synopsys: Add Error Injection support for ZynqMP DDR controller") Signed-off-by: Serge Semin Reviewed-by: Shubhrajyoti Datta --- drivers/edac/synopsys_edac.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index c44a73bc6923b..8a50fed42fe0e 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -1274,10 +1274,6 @@ static void setup_column_address_map(struct synps_edac_priv *priv, u32 *addrmap) COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 : (((addrmap[3] >> 24) & COL_MAX_VAL_MASK) + COL_B9_BASE); - priv->col_shift[13] = ((addrmap[4] & - COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 : - ((addrmap[4] & COL_MAX_VAL_MASK) + - COL_B10_BASE); } else { priv->col_shift[11] = (((addrmap[3] >> 16) & COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 : -- cgit v1.2.3 From 215dec503345fbe4fc7330d165b864f6e8a05102 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 15 Jul 2022 19:31:31 +0300 Subject: EDAC/synopsys: Fix reading errors count before ECC status Aside with fixing the errors count CSR usage the commit e2932d1f6f05 ("EDAC/synopsys: Read the error count from the correct register") all of the sudden has also changed the order of the errors status check procedure. So now the errors handler method first reads the number of CE and UE and only then makes sure that any of these errors have actually happened. It doesn't make sense. Fix that by getting back the correct procedures order: first check the ECC status, then read the number of errors. Fixes: e2932d1f6f05 ("EDAC/synopsys: Read the error count from the correct register") Signed-off-by: Serge Semin Reviewed-by: Shubhrajyoti Datta --- drivers/edac/synopsys_edac.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index 8a50fed42fe0e..fcdb419cc4e73 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -448,18 +448,18 @@ static int zynqmp_get_error_info(struct synps_edac_priv *priv) base = priv->baseaddr; p = &priv->stat; - regval = readl(base + ECC_ERRCNT_OFST); - p->ce_cnt = regval & ECC_ERRCNT_CECNT_MASK; - p->ue_cnt = (regval & ECC_ERRCNT_UECNT_MASK) >> ECC_ERRCNT_UECNT_SHIFT; - if (!p->ce_cnt) - goto ue_err; - regval = readl(base + ECC_STAT_OFST); if (!regval) return 1; p->ceinfo.bitpos = (regval & ECC_STAT_BITNUM_MASK); + regval = readl(base + ECC_ERRCNT_OFST); + p->ce_cnt = regval & ECC_ERRCNT_CECNT_MASK; + p->ue_cnt = (regval & ECC_ERRCNT_UECNT_MASK) >> ECC_ERRCNT_UECNT_SHIFT; + if (!p->ce_cnt) + goto ue_err; + regval = readl(base + ECC_CEADDR0_OFST); p->ceinfo.row = (regval & ECC_CEADDR0_RW_MASK); regval = readl(base + ECC_CEADDR1_OFST); -- cgit v1.2.3 From 88645f3ce18677f852979b609324e1cb176bf18f Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 30 Aug 2023 15:44:06 +0300 Subject: EDAC/synopsys: Fix misleading IRQ self-cleared quirk flag The DDR_ECC_INTR_SELF_CLEAR quirk flag was initially added in the commit f7824ded4149 ("EDAC/synopsys: Add support for version 3 of the Synopsys EDAC DDR") in order to distinguish the ZynqMP DDRC (based on DW uMCTL2 DDRC v2.40a) and the announced in that commit Synopsys DDR controller v3.80a. The selected name is misleading for the next reasons: 1. None of the Synopsys DW uMCTL2 DDR IP-core has the UE/CE IRQs auto or self cleared. The IRQ signals (ecc_corrected_err_intr and ecc_uncorrected_err_intr) are cleared together with the rest of the ECC error data by means of writing 1's to the respective ECCCLR bits. It worked like that in DW uMCTL2 DDRC v2.x IP-core and it's still true for the modern DW uMCTL2 DDRC v3.x. 2. The IRQ-related registers accessed unless the denoted quirk is specified are actually Xilinx Zynq-specific. None of the Synopsys DW uMCTL DDRC IP-core have any registers at the offsets 0x20200/0x20208/0x2020C. The most modern DW uMCTL2 DDRC v3.91a IP-core available has CSRs space end at the 0x43dc offset. The older IP-cores have even smaller registers space. 3. What was actually introduced in the DW uMCTL2 DDRC v3.10 by Synopsys is the IRQ enable flags which older DW uMCTL2 DDRC IP-core didn't have. They were added to the ECCCLR register (the CSR was also renamed to ECCCTL in the v3.10 IP-core HW databook). So since then there have been no point in having a vendor-specific IRQs masking solution like described in 2. and the IRQ signal can be now shared even for the native DW uMCTL2 DDR controllers. So let's harmonize the quirked IRQs code based on the statements above: rename the DDR_ECC_INTR_SELF_CLEAR quirk flag to SYNPS_ZYNQMP_IRQ_REGS thus indicating the ZynqMP-specific IRQ CSRs; add the new quirk flag to the ZynqMP platform data; drop the misleading comments about the auto-cleared ue/ce flags; add a comment about the new IRQ enable flags added in v3.10 IP-core. Signed-off-by: Serge Semin Reviewed-by: Shubhrajyoti Datta --- Changelog v4: - This is a new patch detached from [PATCH v3 01/17] EDAC/synopsys: Fix native uMCTL2 IRQs handling procedure --- drivers/edac/synopsys_edac.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index fcdb419cc4e73..a75fbb05b7242 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -89,7 +89,7 @@ /* DDR ECC Quirks */ #define DDR_ECC_INTR_SUPPORT BIT(0) #define DDR_ECC_DATA_POISON_SUPPORT BIT(1) -#define DDR_ECC_INTR_SELF_CLEAR BIT(2) +#define SYNPS_ZYNQMP_IRQ_REGS BIT(2) /* ZynqMP Enhanced DDR memory controller registers that are relevant to ECC */ /* ECC Configuration Registers */ @@ -556,7 +556,7 @@ static void enable_intr(struct synps_edac_priv *priv) unsigned long flags; /* Enable UE/CE Interrupts */ - if (!(priv->p_data->quirks & DDR_ECC_INTR_SELF_CLEAR)) { + if (priv->p_data->quirks & SYNPS_ZYNQMP_IRQ_REGS) { writel(DDR_QOSUE_MASK | DDR_QOSCE_MASK, priv->baseaddr + DDR_QOS_IRQ_EN_OFST); @@ -565,6 +565,10 @@ static void enable_intr(struct synps_edac_priv *priv) spin_lock_irqsave(&priv->reglock, flags); + /* + * IRQs Enable/Disable flags have been available since v3.10a. + * This is noop for the older controllers. + */ writel(DDR_UE_MASK | DDR_CE_MASK, priv->baseaddr + ECC_CLR_OFST); @@ -576,7 +580,7 @@ static void disable_intr(struct synps_edac_priv *priv) unsigned long flags; /* Disable UE/CE Interrupts */ - if (!(priv->p_data->quirks & DDR_ECC_INTR_SELF_CLEAR)) { + if (priv->p_data->quirks & SYNPS_ZYNQMP_IRQ_REGS) { writel(DDR_QOSUE_MASK | DDR_QOSCE_MASK, priv->baseaddr + DDR_QOS_IRQ_DB_OFST); @@ -607,11 +611,7 @@ static irqreturn_t intr_handler(int irq, void *dev_id) priv = mci->pvt_info; p_data = priv->p_data; - /* - * v3.0 of the controller has the ce/ue bits cleared automatically, - * so this condition does not apply. - */ - if (!(priv->p_data->quirks & DDR_ECC_INTR_SELF_CLEAR)) { + if (priv->p_data->quirks & SYNPS_ZYNQMP_IRQ_REGS) { regval = readl(priv->baseaddr + DDR_QOS_IRQ_STAT_OFST); regval &= (DDR_QOSCE_MASK | DDR_QOSUE_MASK); if (!(regval & ECC_CE_UE_INTR_MASK)) @@ -628,8 +628,8 @@ static irqreturn_t intr_handler(int irq, void *dev_id) edac_dbg(3, "Total error count CE %d UE %d\n", priv->ce_cnt, priv->ue_cnt); - /* v3.0 of the controller does not have this register */ - if (!(priv->p_data->quirks & DDR_ECC_INTR_SELF_CLEAR)) + + if (priv->p_data->quirks & SYNPS_ZYNQMP_IRQ_REGS) writel(regval, priv->baseaddr + DDR_QOS_IRQ_STAT_OFST); return IRQ_HANDLED; @@ -945,7 +945,7 @@ static const struct synps_platform_data zynqmp_edac_def = { #ifdef CONFIG_EDAC_DEBUG .get_mem_info = zynqmp_get_mem_info, #endif - .quirks = (DDR_ECC_INTR_SUPPORT + .quirks = (DDR_ECC_INTR_SUPPORT | SYNPS_ZYNQMP_IRQ_REGS #ifdef CONFIG_EDAC_DEBUG | DDR_ECC_DATA_POISON_SUPPORT #endif @@ -957,7 +957,7 @@ static const struct synps_platform_data synopsys_edac_def = { .get_error_info = zynqmp_get_error_info, .get_mtype = zynqmp_get_mtype, .get_dtype = zynqmp_get_dtype, - .quirks = (DDR_ECC_INTR_SUPPORT | DDR_ECC_INTR_SELF_CLEAR + .quirks = (DDR_ECC_INTR_SUPPORT #ifdef CONFIG_EDAC_DEBUG | DDR_ECC_DATA_POISON_SUPPORT #endif -- cgit v1.2.3 From 80ceb6ad211dabb0824404f445bca58513ea139f Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sat, 16 Jul 2022 01:26:11 +0300 Subject: EDAC/synopsys: Drop internal CE and UE counters First of all these counters aren't exposed anyhow from the driver. Secondly the EDAC core already tracks the total amount of the correctable and uncorrectable errors (see mem_ctl_info.{ce_mc,ue_mc} fields usage). Drop the useless internal counters then for good. Signed-off-by: Serge Semin --- Changelog v4: - Drop redundant empty line. - Drop private counters access from the check_errors() method too. --- drivers/edac/synopsys_edac.c | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index a75fbb05b7242..459c1cf4e4ffc 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -305,8 +305,6 @@ struct synps_ecc_status { * @message: Buffer for framing the event specific info. * @stat: ECC status information. * @p_data: Platform data. - * @ce_cnt: Correctable Error count. - * @ue_cnt: Uncorrectable Error count. * @poison_addr: Data poison address. * @row_shift: Bit shifts for row bit. * @col_shift: Bit shifts for column bit. @@ -320,8 +318,6 @@ struct synps_edac_priv { char message[SYNPS_EDAC_MSG_SIZE]; struct synps_ecc_status stat; const struct synps_platform_data *p_data; - u32 ce_cnt; - u32 ue_cnt; #ifdef CONFIG_EDAC_DEBUG ulong poison_addr; u32 row_shift[18]; @@ -622,13 +618,8 @@ static irqreturn_t intr_handler(int irq, void *dev_id) if (status) return IRQ_NONE; - priv->ce_cnt += priv->stat.ce_cnt; - priv->ue_cnt += priv->stat.ue_cnt; handle_error(mci, &priv->stat); - edac_dbg(3, "Total error count CE %d UE %d\n", - priv->ce_cnt, priv->ue_cnt); - if (priv->p_data->quirks & SYNPS_ZYNQMP_IRQ_REGS) writel(regval, priv->baseaddr + DDR_QOS_IRQ_STAT_OFST); @@ -654,12 +645,7 @@ static void check_errors(struct mem_ctl_info *mci) if (status) return; - priv->ce_cnt += priv->stat.ce_cnt; - priv->ue_cnt += priv->stat.ue_cnt; handle_error(mci, &priv->stat); - - edac_dbg(3, "Total error count CE %d UE %d\n", - priv->ce_cnt, priv->ue_cnt); } /** -- cgit v1.2.3 From 7d336d789a018fb6e83c3e50e49b734babeed7d7 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sat, 16 Jul 2022 12:44:15 +0300 Subject: EDAC/synopsys: Drop local to_mci() macro definition The to_mci() macro was added in commit 1a81361f75d8 ("EDAC, synopsys: Add Error Injection support for ZynqMP DDR controller") together with the errors injection debug feature. It turns out absolutely the same macro-function has already been defined in the edac_mc.h (former edac_core.h) header file. No idea why it was needed to have a local version of the macro, but there is no point in it now. Drop the local macro-function definition for good. Signed-off-by: Serge Semin Reviewed-by: Shubhrajyoti Datta --- drivers/edac/synopsys_edac.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index 459c1cf4e4ffc..f186d98debdfa 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -972,7 +972,6 @@ static const struct of_device_id synps_edac_match[] = { MODULE_DEVICE_TABLE(of, synps_edac_match); #ifdef CONFIG_EDAC_DEBUG -#define to_mci(k) container_of(k, struct mem_ctl_info, dev) /** * ddr_poison_setup - Update poison registers. -- cgit v1.2.3 From 5ae27c705d3365805fef58cbc4752cdacc928de0 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 15 Jul 2022 00:25:39 +0300 Subject: EDAC/synopsys: Drop struct ecc_error_info.blknr field Even though the ECC(C|U)ADDR1 CSR description indeed says it's a "Block number" in the DW uMCTL2 DDRC IP-core databooks, the corresponding register field is named as ECC(C|U)ADDR1.ecc_(un)corr_col (which means ECC (un)corrected column) and in the rest of the document it's referred as the SDRAM address column. Thus use the already available ecc_error_info.col field to read the column number to and drop the questionable ecc_error_info.blknr field for good. Signed-off-by: Serge Semin Reviewed-by: Shubhrajyoti Datta --- drivers/edac/synopsys_edac.c | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index f186d98debdfa..dafc3872762a5 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -174,7 +174,7 @@ #define ECC_CEADDR0_RNK_MASK BIT(24) #define ECC_CEADDR1_BNKGRP_MASK 0x3000000 #define ECC_CEADDR1_BNKNR_MASK 0x70000 -#define ECC_CEADDR1_BLKNR_MASK 0xFFF +#define ECC_CEADDR1_COL_MASK 0xFFF #define ECC_CEADDR1_BNKGRP_SHIFT 24 #define ECC_CEADDR1_BNKNR_SHIFT 16 @@ -272,7 +272,6 @@ * @bitpos: Bit position. * @data: Data causing the error. * @bankgrpnr: Bank group number. - * @blknr: Block number. */ struct ecc_error_info { u32 row; @@ -281,7 +280,6 @@ struct ecc_error_info { u32 bitpos; u32 data; u32 bankgrpnr; - u32 blknr; }; /** @@ -463,7 +461,7 @@ static int zynqmp_get_error_info(struct synps_edac_priv *priv) ECC_CEADDR1_BNKNR_SHIFT; p->ceinfo.bankgrpnr = (regval & ECC_CEADDR1_BNKGRP_MASK) >> ECC_CEADDR1_BNKGRP_SHIFT; - p->ceinfo.blknr = (regval & ECC_CEADDR1_BLKNR_MASK); + p->ceinfo.col = (regval & ECC_CEADDR1_COL_MASK); p->ceinfo.data = readl(base + ECC_CSYND0_OFST); edac_dbg(2, "ECCCSYN0: 0x%08X ECCCSYN1: 0x%08X ECCCSYN2: 0x%08X\n", readl(base + ECC_CSYND0_OFST), readl(base + ECC_CSYND1_OFST), @@ -479,7 +477,7 @@ ue_err: ECC_CEADDR1_BNKGRP_SHIFT; p->ueinfo.bank = (regval & ECC_CEADDR1_BNKNR_MASK) >> ECC_CEADDR1_BNKNR_SHIFT; - p->ueinfo.blknr = (regval & ECC_CEADDR1_BLKNR_MASK); + p->ueinfo.col = (regval & ECC_CEADDR1_COL_MASK); p->ueinfo.data = readl(base + ECC_UESYND0_OFST); out: spin_lock_irqsave(&priv->reglock, flags); @@ -510,10 +508,9 @@ static void handle_error(struct mem_ctl_info *mci, struct synps_ecc_status *p) pinf = &p->ceinfo; if (priv->p_data->quirks & DDR_ECC_INTR_SUPPORT) { snprintf(priv->message, SYNPS_EDAC_MSG_SIZE, - "DDR ECC error type:%s Row %d Bank %d BankGroup Number %d Block Number %d Bit Position: %d Data: 0x%08x", - "CE", pinf->row, pinf->bank, - pinf->bankgrpnr, pinf->blknr, - pinf->bitpos, pinf->data); + "DDR ECC error type:%s Row %d Col %d Bank %d BankGroup Number %d Bit Position: %d Data: 0x%08x", + "CE", pinf->row, pinf->col, pinf->bank, + pinf->bankgrpnr, pinf->bitpos, pinf->data); } else { snprintf(priv->message, SYNPS_EDAC_MSG_SIZE, "DDR ECC error type:%s Row %d Bank %d Col %d Bit Position: %d Data: 0x%08x", @@ -530,9 +527,9 @@ static void handle_error(struct mem_ctl_info *mci, struct synps_ecc_status *p) pinf = &p->ueinfo; if (priv->p_data->quirks & DDR_ECC_INTR_SUPPORT) { snprintf(priv->message, SYNPS_EDAC_MSG_SIZE, - "DDR ECC error type :%s Row %d Bank %d BankGroup Number %d Block Number %d", - "UE", pinf->row, pinf->bank, - pinf->bankgrpnr, pinf->blknr); + "DDR ECC error type :%s Row %d Col %d Bank %d BankGroup Number %d", + "UE", pinf->row, pinf->col, pinf->bank, + pinf->bankgrpnr); } else { snprintf(priv->message, SYNPS_EDAC_MSG_SIZE, "DDR ECC error type :%s Row %d Bank %d Col %d ", -- cgit v1.2.3 From f725d9259de97c174941a1518eef5445a4190fec Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 15 Jul 2022 15:58:57 +0300 Subject: EDAC/synopsys: Shorten out struct ecc_error_info.bankgrpnr field name None of the ecc_error_info structure fields have "nr" suffix even though each of them do represent some number (row number, column number, bank number). Drop the suffix from the bankgrpnr field name for the sake of unification then. Similarly drop the word "Number" from the CE/UE error messages too since it doesn't give any helpful info there. Signed-off-by: Serge Semin Reviewed-by: Shubhrajyoti Datta --- drivers/edac/synopsys_edac.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index dafc3872762a5..615c2377984db 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -269,17 +269,17 @@ * @row: Row number. * @col: Column number. * @bank: Bank number. + * @bankgrp: Bank group number. * @bitpos: Bit position. * @data: Data causing the error. - * @bankgrpnr: Bank group number. */ struct ecc_error_info { u32 row; u32 col; u32 bank; + u32 bankgrp; u32 bitpos; u32 data; - u32 bankgrpnr; }; /** @@ -459,7 +459,7 @@ static int zynqmp_get_error_info(struct synps_edac_priv *priv) regval = readl(base + ECC_CEADDR1_OFST); p->ceinfo.bank = (regval & ECC_CEADDR1_BNKNR_MASK) >> ECC_CEADDR1_BNKNR_SHIFT; - p->ceinfo.bankgrpnr = (regval & ECC_CEADDR1_BNKGRP_MASK) >> + p->ceinfo.bankgrp = (regval & ECC_CEADDR1_BNKGRP_MASK) >> ECC_CEADDR1_BNKGRP_SHIFT; p->ceinfo.col = (regval & ECC_CEADDR1_COL_MASK); p->ceinfo.data = readl(base + ECC_CSYND0_OFST); @@ -473,7 +473,7 @@ ue_err: regval = readl(base + ECC_UEADDR0_OFST); p->ueinfo.row = (regval & ECC_CEADDR0_RW_MASK); regval = readl(base + ECC_UEADDR1_OFST); - p->ueinfo.bankgrpnr = (regval & ECC_CEADDR1_BNKGRP_MASK) >> + p->ueinfo.bankgrp = (regval & ECC_CEADDR1_BNKGRP_MASK) >> ECC_CEADDR1_BNKGRP_SHIFT; p->ueinfo.bank = (regval & ECC_CEADDR1_BNKNR_MASK) >> ECC_CEADDR1_BNKNR_SHIFT; @@ -508,9 +508,9 @@ static void handle_error(struct mem_ctl_info *mci, struct synps_ecc_status *p) pinf = &p->ceinfo; if (priv->p_data->quirks & DDR_ECC_INTR_SUPPORT) { snprintf(priv->message, SYNPS_EDAC_MSG_SIZE, - "DDR ECC error type:%s Row %d Col %d Bank %d BankGroup Number %d Bit Position: %d Data: 0x%08x", + "DDR ECC error type:%s Row %d Col %d Bank %d Bank Group %d Bit Position: %d Data: 0x%08x", "CE", pinf->row, pinf->col, pinf->bank, - pinf->bankgrpnr, pinf->bitpos, pinf->data); + pinf->bankgrp, pinf->bitpos, pinf->data); } else { snprintf(priv->message, SYNPS_EDAC_MSG_SIZE, "DDR ECC error type:%s Row %d Bank %d Col %d Bit Position: %d Data: 0x%08x", @@ -527,9 +527,9 @@ static void handle_error(struct mem_ctl_info *mci, struct synps_ecc_status *p) pinf = &p->ueinfo; if (priv->p_data->quirks & DDR_ECC_INTR_SUPPORT) { snprintf(priv->message, SYNPS_EDAC_MSG_SIZE, - "DDR ECC error type :%s Row %d Col %d Bank %d BankGroup Number %d", + "DDR ECC error type :%s Row %d Col %d Bank %d Bank Group %d", "UE", pinf->row, pinf->col, pinf->bank, - pinf->bankgrpnr); + pinf->bankgrp); } else { snprintf(priv->message, SYNPS_EDAC_MSG_SIZE, "DDR ECC error type :%s Row %d Bank %d Col %d ", -- cgit v1.2.3 From 27ffeed1f044b23645aef92b91e0c817e7a4127b Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Tue, 2 Aug 2022 15:35:35 +0300 Subject: EDAC/synopsys: Drop redundant info from the error messages Currently the custom error messages are needlessly long so the logged text gets to be printed in several lines in console. There is some duplicated/redundant information which can be freely removed from it: drop the message prefix "DDR ECC error type:%s" since the resultant text printed to the log by the edac_mc_printk() method will contain the error type and the memory controller id referring to the device detected the error anyway; with no harm to readability shorten out the phrase "Bit Position" to being just "Bit". Signed-off-by: Serge Semin --- drivers/edac/synopsys_edac.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index 615c2377984db..a51d90d48ab68 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -508,13 +508,13 @@ static void handle_error(struct mem_ctl_info *mci, struct synps_ecc_status *p) pinf = &p->ceinfo; if (priv->p_data->quirks & DDR_ECC_INTR_SUPPORT) { snprintf(priv->message, SYNPS_EDAC_MSG_SIZE, - "DDR ECC error type:%s Row %d Col %d Bank %d Bank Group %d Bit Position: %d Data: 0x%08x", - "CE", pinf->row, pinf->col, pinf->bank, - pinf->bankgrp, pinf->bitpos, pinf->data); + "Row %d Col %d Bank %d Bank Group %d Bit %d Data 0x%08x", + pinf->row, pinf->col, pinf->bank, pinf->bankgrp, + pinf->bitpos, pinf->data); } else { snprintf(priv->message, SYNPS_EDAC_MSG_SIZE, - "DDR ECC error type:%s Row %d Bank %d Col %d Bit Position: %d Data: 0x%08x", - "CE", pinf->row, pinf->bank, pinf->col, + "Row %d Bank %d Col %d Bit: %d Data: 0x%08x", + pinf->row, pinf->bank, pinf->col, pinf->bitpos, pinf->data); } @@ -527,13 +527,12 @@ static void handle_error(struct mem_ctl_info *mci, struct synps_ecc_status *p) pinf = &p->ueinfo; if (priv->p_data->quirks & DDR_ECC_INTR_SUPPORT) { snprintf(priv->message, SYNPS_EDAC_MSG_SIZE, - "DDR ECC error type :%s Row %d Col %d Bank %d Bank Group %d", - "UE", pinf->row, pinf->col, pinf->bank, - pinf->bankgrp); + "Row %d Col %d Bank %d Bank Group %d", + pinf->row, pinf->col, pinf->bank, pinf->bankgrp); } else { snprintf(priv->message, SYNPS_EDAC_MSG_SIZE, - "DDR ECC error type :%s Row %d Bank %d Col %d ", - "UE", pinf->row, pinf->bank, pinf->col); + "Row %d Bank %d Col %d", + pinf->row, pinf->bank, pinf->col); } edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, -- cgit v1.2.3 From 1a805194c200d87a39a84d22ba7938461bb20996 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sun, 7 Aug 2022 14:56:44 +0300 Subject: EDAC/mc: Init DIMM labels in MC registration method Move the DIMM labels initialization to the memory controller registration method as a preparation before adding the generic procedure to allocate an unique MC index. It's required because the DIMM labels contain the MC index as the "mc%u" part of the string, which in case of the auto-generated index isn't available at the moment of the MCI/csrow/dimms descriptor allocation. Signed-off-by: Serge Semin --- drivers/edac/edac_mc.c | 48 +++++++++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/drivers/edac/edac_mc.c b/drivers/edac/edac_mc.c index 11d66333d93b4..48b536fd1badc 100644 --- a/drivers/edac/edac_mc.c +++ b/drivers/edac/edac_mc.c @@ -256,7 +256,6 @@ static int edac_mc_alloc_dimms(struct mem_ctl_info *mci) unsigned int pos[EDAC_MAX_LAYERS]; unsigned int row, chn, idx; int layer; - void *p; /* * Allocate and fill the dimm structs @@ -271,7 +270,6 @@ static int edac_mc_alloc_dimms(struct mem_ctl_info *mci) for (idx = 0; idx < mci->tot_dimms; idx++) { struct dimm_info *dimm; struct rank_info *chan; - int n, len; chan = mci->csrows[row]->channels[chn]; @@ -282,22 +280,9 @@ static int edac_mc_alloc_dimms(struct mem_ctl_info *mci) dimm->mci = mci; dimm->idx = idx; - /* - * Copy DIMM location and initialize it. - */ - len = sizeof(dimm->label); - p = dimm->label; - n = scnprintf(p, len, "mc#%u", mci->mc_idx); - p += n; - len -= n; - for (layer = 0; layer < mci->n_layers; layer++) { - n = scnprintf(p, len, "%s#%u", - edac_layer_name[mci->layers[layer].type], - pos[layer]); - p += n; - len -= n; + /* Copy DIMM location */ + for (layer = 0; layer < mci->n_layers; layer++) dimm->location[layer] = pos[layer]; - } /* Link it to the csrows old API data */ chan->dimm = dimm; @@ -510,6 +495,33 @@ void edac_mc_reset_delay_period(unsigned long value) +/** + * edac_mc_init_labels() - Initialize DIMM labels + * + * @mci: pointer to the mci structure which DIMM labels need to be initialized + * + * .. note:: + * locking model: must be called with the mem_ctls_mutex lock held + */ +static void edac_mc_init_labels(struct mem_ctl_info *mci) +{ + int n, len, layer; + unsigned int idx; + char *p; + + for (idx = 0; idx < mci->tot_dimms; idx++) { + len = sizeof(mci->dimms[idx]->label); + p = mci->dimms[idx]->label; + + n = scnprintf(p, len, "mc#%u", mci->mc_idx); + for (layer = 0; layer < mci->n_layers; layer++) { + n += scnprintf(p + n, len - n, "%s#%u", + edac_layer_name[mci->layers[layer].type], + mci->dimms[idx]->location[layer]); + } + } +} + /* Return 0 on success, 1 on failure. * Before calling this function, caller must * assign a unique value to mci->mc_idx. @@ -637,6 +649,8 @@ int edac_mc_add_mc_with_groups(struct mem_ctl_info *mci, goto fail0; } + edac_mc_init_labels(mci); + if (add_mc_to_global_list(mci)) goto fail0; -- cgit v1.2.3 From cf4fed617bd43ad3d9e2e2285efc75fe0128c64d Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 14 Jul 2022 17:21:28 +0300 Subject: EDAC/mc: Add generic unique MC index allocation procedure Currently the EDAC subsystem relies on the low-level device drivers to select an unique index for each memory controller available in the system. Here are the already implemented approaches: 1. Fixed zero id. The vast majority of the drivers expect to have a single memory controller in the system. 2. Calculate using a platform-specific way (Pre-defined devices order, PCIe-bus address, Numa node ID + PCIe-function number, etc). 3. Use platform_device->id. 4. Use custom ACPI/OF property value. 5. Use locally maintained static MC counter. Create a generic method of the MC index allocation which could be utilized for the case 5 (it doesn't imply any strict memory controller order) and which would prevent the new MC EDAC drivers re-implementing the approaches 3 and 4. Moreover it will be useful for the cases when a platform is equipped with memory-controllers of different types [1] and which are probed by different drivers [2]. [1] Link: https://lore.kernel.org/all/9dc2a947-d2ab-4f00-8ed3-d2499cb6fdfd@BN1BFFO11FD002.protection.gbl/ [2] Link: https://lore.kernel.org/linux-edac/BY5PR12MB4258CB67B70D71F107EC1E9DDB3E9@BY5PR12MB4258.namprd12.prod.outlook.com The suggested implementation is based on the IDA kernel API and implies the next semantics: 1. If a particular MC index is specified it will be registered in the IDR pool unless the specified ID has already been reserved. 2. If a special MC index is specified (EDAC_AUTO_MC_NUM) the EDAC core will check whether there is a "mcID" alias is defined in the device tree and use the ID from there if it's found. 3. Otherwise a next free index will be allocated and assigned to the registered memory controller. Signed-off-by: Serge Semin --- Note the approach implemented here has been partly ported from the SPI core driver using IDA to track/allocate SPI bus numbers. Link: https://elixir.bootlin.com/linux/latest/source/drivers/spi/spi.c#L2957 --- drivers/edac/edac_mc.c | 89 ++++++++++++++++++++++++++++++++++++++++++++++---- drivers/edac/edac_mc.h | 4 +++ 2 files changed, 87 insertions(+), 6 deletions(-) diff --git a/drivers/edac/edac_mc.c b/drivers/edac/edac_mc.c index 48b536fd1badc..5773ac0a07cb6 100644 --- a/drivers/edac/edac_mc.c +++ b/drivers/edac/edac_mc.c @@ -29,6 +29,9 @@ #include #include #include +#include +#include + #include #include "edac_mc.h" #include "edac_module.h" @@ -46,6 +49,7 @@ EXPORT_SYMBOL_GPL(edac_op_state); /* lock to memory controller's control array */ static DEFINE_MUTEX(mem_ctls_mutex); static LIST_HEAD(mc_devices); +static DEFINE_IDR(mc_idr); /* * Used to lock EDAC MC to just one module, avoiding two drivers e. g. @@ -493,7 +497,64 @@ void edac_mc_reset_delay_period(unsigned long value) mutex_unlock(&mem_ctls_mutex); } +/** + * edac_mc_alloc_id() - Allocate unique Memory Controller identifier + * + * @mci: pointer to the mci structure to allocate ID for + * + * Use edac_mc_free_id() to coherently free the MC identifier. + * + * .. note:: + * locking model: must be called with the mem_ctls_mutex lock held + * + * Returns: + * 0 on Success, or an error code on failure + */ +static int edac_mc_alloc_id(struct mem_ctl_info *mci) +{ + struct device_node *np = dev_of_node(mci->pdev); + int ret, min, max; + + if (mci->mc_idx == EDAC_AUTO_MC_NUM) { + ret = of_alias_get_id(np, "mc"); + if (ret >= 0) { + min = ret; + max = ret + 1; + } else { + min = of_alias_get_highest_id("mc"); + if (min >= 0) + min++; + else + min = 0; + + max = 0; + } + } else { + min = mci->mc_idx; + max = mci->mc_idx + 1; + } + + ret = idr_alloc(&mc_idr, mci, min, max, GFP_KERNEL); + if (ret < 0) + return ret == -ENOSPC ? -EBUSY : ret; + + mci->mc_idx = ret; + + return 0; +} +/** + * edac_mc_free_id() - Free Memory Controller identifier + * + * @mci: pointer to the mci structure to free ID from + * + * .. note:: + * locking model: must be called with the mem_ctls_mutex lock held + */ +static void edac_mc_free_id(struct mem_ctl_info *mci) +{ + idr_remove(&mc_idr, mci->mc_idx); +} /** * edac_mc_init_labels() - Initialize DIMM labels @@ -612,7 +673,8 @@ EXPORT_SYMBOL_GPL(edac_get_owner); int edac_mc_add_mc_with_groups(struct mem_ctl_info *mci, const struct attribute_group **groups) { - int ret = -EINVAL; + int ret; + edac_dbg(0, "\n"); #ifdef CONFIG_EDAC_DEBUG @@ -649,20 +711,30 @@ int edac_mc_add_mc_with_groups(struct mem_ctl_info *mci, goto fail0; } + ret = edac_mc_alloc_id(mci); + if (ret) { + edac_printk(KERN_ERR, EDAC_MC, "failed to allocate MC idx %u\n", + mci->mc_idx); + goto fail0; + } + edac_mc_init_labels(mci); - if (add_mc_to_global_list(mci)) - goto fail0; + if (add_mc_to_global_list(mci)) { + ret = -EINVAL; + goto fail1; + } /* set load time so that error rate can be tracked */ mci->start_time = jiffies; mci->bus = edac_get_sysfs_subsys(); - if (edac_create_sysfs_mci_device(mci, groups)) { + ret = edac_create_sysfs_mci_device(mci, groups); + if (ret) { edac_mc_printk(mci, KERN_WARNING, "failed to create sysfs device\n"); - goto fail1; + goto fail2; } if (mci->edac_check) { @@ -686,9 +758,12 @@ int edac_mc_add_mc_with_groups(struct mem_ctl_info *mci, mutex_unlock(&mem_ctls_mutex); return 0; -fail1: +fail2: del_mc_from_global_list(mci); +fail1: + edac_mc_free_id(mci); + fail0: mutex_unlock(&mem_ctls_mutex); return ret; @@ -716,6 +791,8 @@ struct mem_ctl_info *edac_mc_del_mc(struct device *dev) if (del_mc_from_global_list(mci)) edac_mc_owner = NULL; + edac_mc_free_id(mci); + mutex_unlock(&mem_ctls_mutex); if (mci->edac_check) diff --git a/drivers/edac/edac_mc.h b/drivers/edac/edac_mc.h index 881b00eadf7a5..4b6676235b1b4 100644 --- a/drivers/edac/edac_mc.h +++ b/drivers/edac/edac_mc.h @@ -23,6 +23,7 @@ #define _EDAC_MC_H_ #include +#include #include #include #include @@ -37,6 +38,9 @@ #include #include +/* Generate MC identifier automatically */ +#define EDAC_AUTO_MC_NUM UINT_MAX + #if PAGE_SHIFT < 20 #define PAGES_TO_MiB(pages) ((pages) >> (20 - PAGE_SHIFT)) #define MiB_TO_PAGES(mb) ((mb) << (20 - PAGE_SHIFT)) -- cgit v1.2.3 From d652a028f91a7f47d67525f0eb4e22311de0fc53 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 31 Aug 2023 18:12:44 +0300 Subject: EDAC/mc: Re-use generic unique MC index allocation procedure The EDAC drivers locally maintaining a statically defined memory-controllers counter don't care much about the MC index assigned as long as it's unique so the EDAC core perceives it. Convert these drivers to be using the generic MC index allocation procedure recently added to the EDAC core. Signed-off-by: Serge Semin --- Changelog v4: - Initial patch introduction. --- drivers/edac/dmc520_edac.c | 4 +--- drivers/edac/pasemi_edac.c | 5 +---- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/drivers/edac/dmc520_edac.c b/drivers/edac/dmc520_edac.c index 64a4d0a07032c..791890a042903 100644 --- a/drivers/edac/dmc520_edac.c +++ b/drivers/edac/dmc520_edac.c @@ -173,8 +173,6 @@ struct dmc520_edac { int masks[NUMBER_OF_IRQS]; }; -static int dmc520_mc_idx; - static u32 dmc520_read_reg(struct dmc520_edac *pvt, u32 offset) { return readl(pvt->reg_base + offset); @@ -515,7 +513,7 @@ static int dmc520_edac_probe(struct platform_device *pdev) layers[0].size = dmc520_get_rank_count(reg_base); layers[0].is_virt_csrow = true; - mci = edac_mc_alloc(dmc520_mc_idx++, ARRAY_SIZE(layers), layers, sizeof(*pvt)); + mci = edac_mc_alloc(EDAC_AUTO_MC_NUM, ARRAY_SIZE(layers), layers, sizeof(*pvt)); if (!mci) { edac_printk(KERN_ERR, EDAC_MOD_NAME, "Failed to allocate memory for mc instance\n"); diff --git a/drivers/edac/pasemi_edac.c b/drivers/edac/pasemi_edac.c index 1a1c3296ccc83..afebfbda1ea00 100644 --- a/drivers/edac/pasemi_edac.c +++ b/drivers/edac/pasemi_edac.c @@ -57,8 +57,6 @@ #define PASEMI_EDAC_ERROR_GRAIN 64 static int last_page_in_mmc; -static int system_mmc_id; - static u32 pasemi_edac_get_error_info(struct mem_ctl_info *mci) { @@ -203,8 +201,7 @@ static int pasemi_edac_probe(struct pci_dev *pdev, layers[1].type = EDAC_MC_LAYER_CHANNEL; layers[1].size = PASEMI_EDAC_NR_CHANS; layers[1].is_virt_csrow = false; - mci = edac_mc_alloc(system_mmc_id++, ARRAY_SIZE(layers), layers, - 0); + mci = edac_mc_alloc(EDAC_AUTO_MC_NUM, ARRAY_SIZE(layers), layers, 0); if (mci == NULL) return -ENOMEM; -- cgit v1.2.3 From dbd7d40177b5e038e1909874b40587c8a1a63bf6 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 14 Jul 2022 18:54:22 +0300 Subject: EDAC/synopsys: Detach Zynq A05 DDRC support to separate driver The synopsys_edac.c driver currently supports three memory-controllers: 1. Synopsys DW uMCTL2 DDRC v3.80 (with the ZynqMP-specific params). 2. Xilinx ZynqMP DDRC Synopsys DW uMCTL2 DDRC v2.40. 3. Xilinx Zynq A05 DDR controller. If the first two devices are based on the Synopsys DW uMCTL2 IP-cores (ZynqMP MC is based on the DW uMCTL2 v2.40), the later device has absolutely nothing in common with the Synopsys DW uMCTL2 DDR controllers: - the CSRs map is absolutely different; - it doesn't support IRQs unlike the Synopsys memory controllers. Having the driver to support so different devices caused implementing an additional level of abstraction, which will be a great deal of obstacle in about to be added comprehensive set of the Synopsys DDR-controller features (DW uCMTL2 IP-core parameters auto-detection, common SDRAM<->phys address translation, multi-ranked memory, ECC scrubber, individual IRQs, DFI alert_n IRQ, and so on). Moreover the original reason of having these devices support living in a single driver hasn't been introduced for all these years: the synopsys_edac.c driver currently supports only a single memory controller detected on the system. So in order to make the Synopsys driver ready for the new features added move the Xilinx Zynq A05 DDRC support into a separate driver: detach the Zynq-specific callbacks, init/probe/remove methods and move them into the new zynq_edac.c driver. The resultant driver will mostly look similar to the code submitted in the initial commit ae9b56e3996d ("EDAC, synps: Add EDAC support for zynq ddr ecc controller") except a few fixes added afterwards. Note several Zynq-specific macros have been used in the DW uMCTL2 DDRC-specific functions. These macros just for a mere coincident have values suitable for the DW uMCTL2 code but their names either partly or fully unrelated to the Synopsys memory controllers. Since these macros are now moved to another driver introduce a new Synopsys-specific ones and fix the places where the macros have been improperly utilized. Signed-off-by: Serge Semin --- Changelog v3: - Drop the no longer used "priv" pointer from the zynq_mc_init() function. (@tbot) Changelog v5: - Rename handle_error and check_errors to zynq_handle_error and zynq_check_errors in the kdoc comments (@tbot) --- MAINTAINERS | 1 + drivers/edac/Kconfig | 9 +- drivers/edac/Makefile | 1 + drivers/edac/synopsys_edac.c | 242 +++------------------ drivers/edac/zynq_edac.c | 506 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 542 insertions(+), 217 deletions(-) create mode 100644 drivers/edac/zynq_edac.c diff --git a/MAINTAINERS b/MAINTAINERS index d1cc0e12fe1f0..00591e9301667 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3788,6 +3788,7 @@ F: arch/arm/mach-zynq/ F: drivers/clocksource/timer-cadence-ttc.c F: drivers/cpuidle/cpuidle-zynq.c F: drivers/edac/synopsys_edac.c +F: drivers/edac/zynq_edac.c F: drivers/i2c/busses/i2c-cadence.c F: drivers/i2c/busses/i2c-xiic.c F: drivers/mmc/host/sdhci-of-arasan.c diff --git a/drivers/edac/Kconfig b/drivers/edac/Kconfig index a44b85c440cad..fb8cf7cdc3057 100644 --- a/drivers/edac/Kconfig +++ b/drivers/edac/Kconfig @@ -471,7 +471,7 @@ config EDAC_ARMADA_XP config EDAC_SYNOPSYS tristate "Synopsys DDR Memory Controller" - depends on ARCH_ZYNQ || ARCH_ZYNQMP || ARCH_INTEL_SOCFPGA || ARCH_MXC + depends on ARCH_ZYNQMP || ARCH_INTEL_SOCFPGA || ARCH_MXC help Support for error detection and correction on the Synopsys DDR memory controller. @@ -526,6 +526,13 @@ config EDAC_DMC520 Support for error detection and correction on the SoCs with ARM DMC-520 DRAM controller. +config EDAC_ZYNQ + tristate "Xilinx Zynq A05 DDR Memory Controller" + depends on ARCH_ZYNQ || COMPILE_TEST + help + Support for error detection and correction on the Xilinx Zynq A05 + DDR memory controller. + config EDAC_ZYNQMP tristate "Xilinx ZynqMP OCM Controller" depends on ARCH_ZYNQMP || COMPILE_TEST diff --git a/drivers/edac/Makefile b/drivers/edac/Makefile index a37534300ab92..f4b32855096a7 100644 --- a/drivers/edac/Makefile +++ b/drivers/edac/Makefile @@ -86,6 +86,7 @@ obj-$(CONFIG_EDAC_ASPEED) += aspeed_edac.o obj-$(CONFIG_EDAC_BLUEFIELD) += bluefield_edac.o obj-$(CONFIG_EDAC_DMC520) += dmc520_edac.o obj-$(CONFIG_EDAC_NPCM) += npcm_edac.o +obj-$(CONFIG_EDAC_ZYNQ) += zynq_edac.o obj-$(CONFIG_EDAC_ZYNQMP) += zynqmp_edac.o obj-$(CONFIG_EDAC_VERSAL) += versal_edac.o obj-$(CONFIG_EDAC_LOONGSON) += loongson_edac.o diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index a51d90d48ab68..7908a522dee59 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -30,68 +30,16 @@ #define SYNPS_EDAC_MOD_STRING "synps_edac" #define SYNPS_EDAC_MOD_VER "1" -/* Synopsys DDR memory controller registers that are relevant to ECC */ -#define CTRL_OFST 0x0 -#define T_ZQ_OFST 0xA4 - -/* ECC control register */ -#define ECC_CTRL_OFST 0xC4 -/* ECC log register */ -#define CE_LOG_OFST 0xC8 -/* ECC address register */ -#define CE_ADDR_OFST 0xCC -/* ECC data[31:0] register */ -#define CE_DATA_31_0_OFST 0xD0 - -/* Uncorrectable error info registers */ -#define UE_LOG_OFST 0xDC -#define UE_ADDR_OFST 0xE0 -#define UE_DATA_31_0_OFST 0xE4 - -#define STAT_OFST 0xF0 -#define SCRUB_OFST 0xF4 - -/* Control register bit field definitions */ -#define CTRL_BW_MASK 0xC -#define CTRL_BW_SHIFT 2 - -#define DDRCTL_WDTH_16 1 -#define DDRCTL_WDTH_32 0 - -/* ZQ register bit field definitions */ -#define T_ZQ_DDRMODE_MASK 0x2 - -/* ECC control register bit field definitions */ -#define ECC_CTRL_CLR_CE_ERR 0x2 -#define ECC_CTRL_CLR_UE_ERR 0x1 - -/* ECC correctable/uncorrectable error log register definitions */ -#define LOG_VALID 0x1 -#define CE_LOG_BITPOS_MASK 0xFE -#define CE_LOG_BITPOS_SHIFT 1 - -/* ECC correctable/uncorrectable error address register definitions */ -#define ADDR_COL_MASK 0xFFF -#define ADDR_ROW_MASK 0xFFFF000 -#define ADDR_ROW_SHIFT 12 -#define ADDR_BANK_MASK 0x70000000 -#define ADDR_BANK_SHIFT 28 - -/* ECC statistic register definitions */ -#define STAT_UECNT_MASK 0xFF -#define STAT_CECNT_MASK 0xFF00 -#define STAT_CECNT_SHIFT 8 - -/* ECC scrub register definitions */ -#define SCRUB_MODE_MASK 0x7 -#define SCRUB_MODE_SECDED 0x4 - /* DDR ECC Quirks */ #define DDR_ECC_INTR_SUPPORT BIT(0) #define DDR_ECC_DATA_POISON_SUPPORT BIT(1) #define SYNPS_ZYNQMP_IRQ_REGS BIT(2) -/* ZynqMP Enhanced DDR memory controller registers that are relevant to ECC */ +/* Synopsys DDR memory controller registers that are relevant to ECC */ + +/* DDRC Master 0 Register */ +#define DDR_MSTR_OFST 0x0 + /* ECC Configuration Registers */ #define ECC_CFG0_OFST 0x70 #define ECC_CFG1_OFST 0x74 @@ -135,16 +83,22 @@ #define ECC_ADDRMAP0_OFFSET 0x200 /* Control register bitfield definitions */ -#define ECC_CTRL_BUSWIDTH_MASK 0x3000 -#define ECC_CTRL_BUSWIDTH_SHIFT 12 +#define ECC_CTRL_CLR_CE_ERR BIT(0) +#define ECC_CTRL_CLR_UE_ERR BIT(1) #define ECC_CTRL_CLR_CE_ERRCNT BIT(2) #define ECC_CTRL_CLR_UE_ERRCNT BIT(3) /* DDR Control Register width definitions */ +#define DDR_MSTR_BUSWIDTH_MASK 0x3000 +#define DDR_MSTR_BUSWIDTH_SHIFT 12 #define DDRCTL_EWDTH_16 2 #define DDRCTL_EWDTH_32 1 #define DDRCTL_EWDTH_64 0 +/* ECC CFG0 register definitions */ +#define ECC_CFG0_MODE_MASK 0x7 +#define ECC_CFG0_MODE_SECDED 0x4 + /* ECC status register definitions */ #define ECC_STAT_UECNT_MASK 0xF0000 #define ECC_STAT_UECNT_SHIFT 16 @@ -352,61 +306,6 @@ struct synps_platform_data { int quirks; }; -/** - * zynq_get_error_info - Get the current ECC error info. - * @priv: DDR memory controller private instance data. - * - * Return: one if there is no error, otherwise zero. - */ -static int zynq_get_error_info(struct synps_edac_priv *priv) -{ - struct synps_ecc_status *p; - u32 regval, clearval = 0; - void __iomem *base; - - base = priv->baseaddr; - p = &priv->stat; - - regval = readl(base + STAT_OFST); - if (!regval) - return 1; - - p->ce_cnt = (regval & STAT_CECNT_MASK) >> STAT_CECNT_SHIFT; - p->ue_cnt = regval & STAT_UECNT_MASK; - - regval = readl(base + CE_LOG_OFST); - if (!(p->ce_cnt && (regval & LOG_VALID))) - goto ue_err; - - p->ceinfo.bitpos = (regval & CE_LOG_BITPOS_MASK) >> CE_LOG_BITPOS_SHIFT; - regval = readl(base + CE_ADDR_OFST); - p->ceinfo.row = (regval & ADDR_ROW_MASK) >> ADDR_ROW_SHIFT; - p->ceinfo.col = regval & ADDR_COL_MASK; - p->ceinfo.bank = (regval & ADDR_BANK_MASK) >> ADDR_BANK_SHIFT; - p->ceinfo.data = readl(base + CE_DATA_31_0_OFST); - edac_dbg(3, "CE bit position: %d data: %d\n", p->ceinfo.bitpos, - p->ceinfo.data); - clearval = ECC_CTRL_CLR_CE_ERR; - -ue_err: - regval = readl(base + UE_LOG_OFST); - if (!(p->ue_cnt && (regval & LOG_VALID))) - goto out; - - regval = readl(base + UE_ADDR_OFST); - p->ueinfo.row = (regval & ADDR_ROW_MASK) >> ADDR_ROW_SHIFT; - p->ueinfo.col = regval & ADDR_COL_MASK; - p->ueinfo.bank = (regval & ADDR_BANK_MASK) >> ADDR_BANK_SHIFT; - p->ueinfo.data = readl(base + UE_DATA_31_0_OFST); - clearval |= ECC_CTRL_CLR_UE_ERR; - -out: - writel(clearval, base + ECC_CTRL_OFST); - writel(0x0, base + ECC_CTRL_OFST); - - return 0; -} - #ifdef CONFIG_EDAC_DEBUG /** * zynqmp_get_mem_info - Get the current memory info. @@ -644,37 +543,6 @@ static void check_errors(struct mem_ctl_info *mci) handle_error(mci, &priv->stat); } -/** - * zynq_get_dtype - Return the controller memory width. - * @base: DDR memory controller base address. - * - * Get the EDAC device type width appropriate for the current controller - * configuration. - * - * Return: a device type width enumeration. - */ -static enum dev_type zynq_get_dtype(const void __iomem *base) -{ - enum dev_type dt; - u32 width; - - width = readl(base + CTRL_OFST); - width = (width & CTRL_BW_MASK) >> CTRL_BW_SHIFT; - - switch (width) { - case DDRCTL_WDTH_16: - dt = DEV_X2; - break; - case DDRCTL_WDTH_32: - dt = DEV_X4; - break; - default: - dt = DEV_UNKNOWN; - } - - return dt; -} - /** * zynqmp_get_dtype - Return the DDR memory chips type. * @base: DDR memory controller base address. @@ -688,7 +556,7 @@ static enum dev_type zynqmp_get_dtype(const void __iomem *base) { u32 regval; - regval = readl(base + CTRL_OFST); + regval = readl(base + DDR_MSTR_OFST); if (!(regval & MEM_TYPE_DDR4)) return DEV_UNKNOWN; @@ -719,29 +587,14 @@ static enum dev_type zynqmp_get_dtype(const void __iomem *base) static bool get_ecc_state(struct synps_edac_priv *priv) { u32 ecctype, clearval; - enum dev_type dt; - - if (priv->p_data->platform == ZYNQ) { - dt = zynq_get_dtype(priv->baseaddr); - if (dt == DEV_UNKNOWN) - return false; - - ecctype = readl(priv->baseaddr + SCRUB_OFST) & SCRUB_MODE_MASK; - if (ecctype == SCRUB_MODE_SECDED && dt == DEV_X2) { - clearval = ECC_CTRL_CLR_CE_ERR | ECC_CTRL_CLR_UE_ERR; - writel(clearval, priv->baseaddr + ECC_CTRL_OFST); - writel(0x0, priv->baseaddr + ECC_CTRL_OFST); - return true; - } - } else { - ecctype = readl(priv->baseaddr + ECC_CFG0_OFST) & SCRUB_MODE_MASK; - if (ecctype == SCRUB_MODE_SECDED) { - clearval = readl(priv->baseaddr + ECC_CLR_OFST) | - ECC_CTRL_CLR_CE_ERR | ECC_CTRL_CLR_CE_ERRCNT | - ECC_CTRL_CLR_UE_ERR | ECC_CTRL_CLR_UE_ERRCNT; - writel(clearval, priv->baseaddr + ECC_CLR_OFST); - return true; - } + + ecctype = readl(priv->baseaddr + ECC_CFG0_OFST) & ECC_CFG0_MODE_MASK; + if (ecctype == ECC_CFG0_MODE_SECDED) { + clearval = readl(priv->baseaddr + ECC_CLR_OFST) | + ECC_CTRL_CLR_CE_ERR | ECC_CTRL_CLR_CE_ERRCNT | + ECC_CTRL_CLR_UE_ERR | ECC_CTRL_CLR_UE_ERRCNT; + writel(clearval, priv->baseaddr + ECC_CLR_OFST); + return true; } return false; @@ -761,30 +614,6 @@ static u32 get_memsize(void) return inf.totalram * inf.mem_unit; } -/** - * zynq_get_mtype - Return the controller memory type. - * @base: Synopsys ECC status structure. - * - * Get the EDAC memory type appropriate for the current controller - * configuration. - * - * Return: a memory type enumeration. - */ -static enum mem_type zynq_get_mtype(const void __iomem *base) -{ - enum mem_type mt; - u32 memtype; - - memtype = readl(base + T_ZQ_OFST); - - if (memtype & T_ZQ_DDRMODE_MASK) - mt = MEM_DDR3; - else - mt = MEM_DDR2; - - return mt; -} - /** * zynqmp_get_mtype - Returns controller memory type. * @base: Synopsys ECC status structure. @@ -799,7 +628,7 @@ static enum mem_type zynqmp_get_mtype(const void __iomem *base) enum mem_type mt; u32 memtype; - memtype = readl(base + CTRL_OFST); + memtype = readl(base + DDR_MSTR_OFST); if ((memtype & MEM_TYPE_DDR3) || (memtype & MEM_TYPE_LPDDR3)) mt = MEM_DDR3; @@ -911,14 +740,6 @@ static int setup_irq(struct mem_ctl_info *mci, return 0; } -static const struct synps_platform_data zynq_edac_def = { - .platform = ZYNQ, - .get_error_info = zynq_get_error_info, - .get_mtype = zynq_get_mtype, - .get_dtype = zynq_get_dtype, - .quirks = 0, -}; - static const struct synps_platform_data zynqmp_edac_def = { .platform = ZYNQMP, .get_error_info = zynqmp_get_error_info, @@ -948,10 +769,6 @@ static const struct synps_platform_data synopsys_edac_def = { static const struct of_device_id synps_edac_match[] = { - { - .compatible = "xlnx,zynq-ddrc-a05", - .data = (void *)&zynq_edac_def - }, { .compatible = "xlnx,zynqmp-ddrc-2.40a", .data = (void *)&zynqmp_edac_def @@ -1179,8 +996,8 @@ static void setup_column_address_map(struct synps_edac_priv *priv, u32 *addrmap) u32 width, memtype; int index; - memtype = readl(priv->baseaddr + CTRL_OFST); - width = (memtype & ECC_CTRL_BUSWIDTH_MASK) >> ECC_CTRL_BUSWIDTH_SHIFT; + memtype = readl(priv->baseaddr + DDR_MSTR_OFST); + width = (memtype & DDR_MSTR_BUSWIDTH_MASK) >> DDR_MSTR_BUSWIDTH_SHIFT; priv->col_shift[0] = 0; priv->col_shift[1] = 1; @@ -1371,7 +1188,7 @@ static int mc_probe(struct platform_device *pdev) layers[1].size = SYNPS_EDAC_NR_CHANS; layers[1].is_virt_csrow = false; - mci = edac_mc_alloc(0, ARRAY_SIZE(layers), layers, + mci = edac_mc_alloc(EDAC_AUTO_MC_NUM, ARRAY_SIZE(layers), layers, sizeof(struct synps_edac_priv)); if (!mci) { edac_printk(KERN_ERR, EDAC_MC, @@ -1419,13 +1236,6 @@ static int mc_probe(struct platform_device *pdev) setup_address_map(priv); #endif - /* - * Start capturing the correctable and uncorrectable errors. A write of - * 0 starts the counters. - */ - if (!(priv->p_data->quirks & DDR_ECC_INTR_SUPPORT)) - writel(0x0, baseaddr + ECC_CTRL_OFST); - return rc; free_edac_mc: diff --git a/drivers/edac/zynq_edac.c b/drivers/edac/zynq_edac.c new file mode 100644 index 0000000000000..f1b5771bbee17 --- /dev/null +++ b/drivers/edac/zynq_edac.c @@ -0,0 +1,506 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Zynq DDR ECC Driver + * This driver is based on ppc4xx_edac.c drivers + * + * Copyright (C) 2012 - 2014 Xilinx, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "edac_module.h" + +/* Number of cs_rows needed per memory controller */ +#define ZYNQ_EDAC_NR_CSROWS 1 + +/* Number of channels per memory controller */ +#define ZYNQ_EDAC_NR_CHANS 1 + +/* Granularity of reported error in bytes */ +#define ZYNQ_EDAC_ERR_GRAIN 1 + +#define ZYNQ_EDAC_MSG_SIZE 256 + +#define ZYNQ_EDAC_MOD_STRING "zynq_edac" +#define ZYNQ_EDAC_MOD_VER "1" + +/* Zynq DDR memory controller ECC registers */ +#define ZYNQ_CTRL_OFST 0x0 +#define ZYNQ_T_ZQ_OFST 0xA4 + +/* ECC control register */ +#define ZYNQ_ECC_CTRL_OFST 0xC4 +/* ECC log register */ +#define ZYNQ_CE_LOG_OFST 0xC8 +/* ECC address register */ +#define ZYNQ_CE_ADDR_OFST 0xCC +/* ECC data[31:0] register */ +#define ZYNQ_CE_DATA_31_0_OFST 0xD0 + +/* Uncorrectable error info registers */ +#define ZYNQ_UE_LOG_OFST 0xDC +#define ZYNQ_UE_ADDR_OFST 0xE0 +#define ZYNQ_UE_DATA_31_0_OFST 0xE4 + +#define ZYNQ_STAT_OFST 0xF0 +#define ZYNQ_SCRUB_OFST 0xF4 + +/* Control register bit field definitions */ +#define ZYNQ_CTRL_BW_MASK 0xC +#define ZYNQ_CTRL_BW_SHIFT 2 + +#define ZYNQ_DDRCTL_WDTH_16 1 +#define ZYNQ_DDRCTL_WDTH_32 0 + +/* ZQ register bit field definitions */ +#define ZYNQ_T_ZQ_DDRMODE_MASK 0x2 + +/* ECC control register bit field definitions */ +#define ZYNQ_ECC_CTRL_CLR_CE_ERR 0x2 +#define ZYNQ_ECC_CTRL_CLR_UE_ERR 0x1 + +/* ECC correctable/uncorrectable error log register definitions */ +#define ZYNQ_LOG_VALID 0x1 +#define ZYNQ_CE_LOG_BITPOS_MASK 0xFE +#define ZYNQ_CE_LOG_BITPOS_SHIFT 1 + +/* ECC correctable/uncorrectable error address register definitions */ +#define ZYNQ_ADDR_COL_MASK 0xFFF +#define ZYNQ_ADDR_ROW_MASK 0xFFFF000 +#define ZYNQ_ADDR_ROW_SHIFT 12 +#define ZYNQ_ADDR_BANK_MASK 0x70000000 +#define ZYNQ_ADDR_BANK_SHIFT 28 + +/* ECC statistic register definitions */ +#define ZYNQ_STAT_UECNT_MASK 0xFF +#define ZYNQ_STAT_CECNT_MASK 0xFF00 +#define ZYNQ_STAT_CECNT_SHIFT 8 + +/* ECC scrub register definitions */ +#define ZYNQ_SCRUB_MODE_MASK 0x7 +#define ZYNQ_SCRUB_MODE_SECDED 0x4 + +/** + * struct zynq_ecc_error_info - ECC error log information. + * @row: Row number. + * @col: Column number. + * @bank: Bank number. + * @bitpos: Bit position. + * @data: Data causing the error. + */ +struct zynq_ecc_error_info { + u32 row; + u32 col; + u32 bank; + u32 bitpos; + u32 data; +}; + +/** + * struct zynq_ecc_status - ECC status information to report. + * @ce_cnt: Correctable error count. + * @ue_cnt: Uncorrectable error count. + * @ceinfo: Correctable error log information. + * @ueinfo: Uncorrectable error log information. + */ +struct zynq_ecc_status { + u32 ce_cnt; + u32 ue_cnt; + struct zynq_ecc_error_info ceinfo; + struct zynq_ecc_error_info ueinfo; +}; + +/** + * struct zynq_edac_priv - DDR memory controller private instance data. + * @baseaddr: Base address of the DDR controller. + * @message: Buffer for framing the event specific info. + * @stat: ECC status information. + */ +struct zynq_edac_priv { + void __iomem *baseaddr; + char message[ZYNQ_EDAC_MSG_SIZE]; + struct zynq_ecc_status stat; +}; + +/** + * zynq_get_error_info - Get the current ECC error info. + * @priv: DDR memory controller private instance data. + * + * Return: one if there is no error, otherwise zero. + */ +static int zynq_get_error_info(struct zynq_edac_priv *priv) +{ + struct zynq_ecc_status *p; + u32 regval, clearval = 0; + void __iomem *base; + + base = priv->baseaddr; + p = &priv->stat; + + regval = readl(base + ZYNQ_STAT_OFST); + if (!regval) + return 1; + + p->ce_cnt = (regval & ZYNQ_STAT_CECNT_MASK) >> ZYNQ_STAT_CECNT_SHIFT; + p->ue_cnt = regval & ZYNQ_STAT_UECNT_MASK; + + regval = readl(base + ZYNQ_CE_LOG_OFST); + if (!(p->ce_cnt && (regval & ZYNQ_LOG_VALID))) + goto ue_err; + + p->ceinfo.bitpos = (regval & ZYNQ_CE_LOG_BITPOS_MASK) >> ZYNQ_CE_LOG_BITPOS_SHIFT; + regval = readl(base + ZYNQ_CE_ADDR_OFST); + p->ceinfo.row = (regval & ZYNQ_ADDR_ROW_MASK) >> ZYNQ_ADDR_ROW_SHIFT; + p->ceinfo.col = regval & ZYNQ_ADDR_COL_MASK; + p->ceinfo.bank = (regval & ZYNQ_ADDR_BANK_MASK) >> ZYNQ_ADDR_BANK_SHIFT; + p->ceinfo.data = readl(base + ZYNQ_CE_DATA_31_0_OFST); + edac_dbg(3, "CE bit position: %d data: %d\n", p->ceinfo.bitpos, + p->ceinfo.data); + clearval = ZYNQ_ECC_CTRL_CLR_CE_ERR; + +ue_err: + regval = readl(base + ZYNQ_UE_LOG_OFST); + if (!(p->ue_cnt && (regval & ZYNQ_LOG_VALID))) + goto out; + + regval = readl(base + ZYNQ_UE_ADDR_OFST); + p->ueinfo.row = (regval & ZYNQ_ADDR_ROW_MASK) >> ZYNQ_ADDR_ROW_SHIFT; + p->ueinfo.col = regval & ZYNQ_ADDR_COL_MASK; + p->ueinfo.bank = (regval & ZYNQ_ADDR_BANK_MASK) >> ZYNQ_ADDR_BANK_SHIFT; + p->ueinfo.data = readl(base + ZYNQ_UE_DATA_31_0_OFST); + clearval |= ZYNQ_ECC_CTRL_CLR_UE_ERR; + +out: + writel(clearval, base + ZYNQ_ECC_CTRL_OFST); + writel(0x0, base + ZYNQ_ECC_CTRL_OFST); + + return 0; +} + +/** + * zynq_handle_error - Handle Correctable and Uncorrectable errors. + * @mci: EDAC memory controller instance. + * @p: Zynq ECC status structure. + * + * Handles ECC correctable and uncorrectable errors. + */ +static void zynq_handle_error(struct mem_ctl_info *mci, struct zynq_ecc_status *p) +{ + struct zynq_edac_priv *priv = mci->pvt_info; + struct zynq_ecc_error_info *pinf; + + if (p->ce_cnt) { + pinf = &p->ceinfo; + + snprintf(priv->message, ZYNQ_EDAC_MSG_SIZE, + "Row %d Bank %d Col %d Bit %d Data 0x%08x", + pinf->row, pinf->bank, pinf->col, + pinf->bitpos, pinf->data); + + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, + p->ce_cnt, 0, 0, 0, 0, 0, -1, + priv->message, ""); + } + + if (p->ue_cnt) { + pinf = &p->ueinfo; + + snprintf(priv->message, ZYNQ_EDAC_MSG_SIZE, + "Row %d Bank %d Col %d", + pinf->row, pinf->bank, pinf->col); + + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, + p->ue_cnt, 0, 0, 0, 0, 0, -1, + priv->message, ""); + } + + memset(p, 0, sizeof(*p)); +} + +/** + * zynq_check_errors - Check controller for ECC errors. + * @mci: EDAC memory controller instance. + * + * Check and post ECC errors. Called by the polling thread. + */ +static void zynq_check_errors(struct mem_ctl_info *mci) +{ + struct zynq_edac_priv *priv = mci->pvt_info; + int status; + + status = zynq_get_error_info(priv); + if (status) + return; + + zynq_handle_error(mci, &priv->stat); +} + +/** + * zynq_get_dtype - Return the controller memory width. + * @base: DDR memory controller base address. + * + * Get the EDAC device type width appropriate for the current controller + * configuration. + * + * Return: a device type width enumeration. + */ +static enum dev_type zynq_get_dtype(const void __iomem *base) +{ + enum dev_type dt; + u32 width; + + width = readl(base + ZYNQ_CTRL_OFST); + width = (width & ZYNQ_CTRL_BW_MASK) >> ZYNQ_CTRL_BW_SHIFT; + + switch (width) { + case ZYNQ_DDRCTL_WDTH_16: + dt = DEV_X2; + break; + case ZYNQ_DDRCTL_WDTH_32: + dt = DEV_X4; + break; + default: + dt = DEV_UNKNOWN; + } + + return dt; +} + +/** + * zynq_get_ecc_state - Return the controller ECC enable/disable status. + * @base: DDR memory controller base address. + * + * Get the ECC enable/disable status for the controller and clear out + * the ECC counters if applicatble. + * + * Return: true if enabled, otherwise false. + */ +static bool zynq_get_ecc_state(void __iomem *base) +{ + u32 ecctype, clearval; + enum dev_type dt; + + dt = zynq_get_dtype(base); + if (dt == DEV_UNKNOWN) + return false; + + ecctype = readl(base + ZYNQ_SCRUB_OFST) & ZYNQ_SCRUB_MODE_MASK; + if ((ecctype == ZYNQ_SCRUB_MODE_SECDED) && (dt == DEV_X2)) { + clearval = ECC_CTRL_CLR_CE_ERR | ECC_CTRL_CLR_UE_ERR; + writel(clearval, base + ECC_CTRL_OFST); + writel(0x0, base + ECC_CTRL_OFST); + return true; + } + + return false; +} + +/** + * zynq_get_memsize - Read the size of the attached memory device. + * + * Return: the memory size in bytes. + */ +static u32 zynq_get_memsize(void) +{ + struct sysinfo inf; + + si_meminfo(&inf); + + return inf.totalram * inf.mem_unit; +} + +/** + * zynq_get_mtype - Return the controller memory type. + * @base: Zynq ECC status structure. + * + * Get the EDAC memory type appropriate for the current controller + * configuration. + * + * Return: a memory type enumeration. + */ +static enum mem_type zynq_get_mtype(const void __iomem *base) +{ + enum mem_type mt; + u32 memtype; + + memtype = readl(base + ZYNQ_T_ZQ_OFST); + + if (memtype & ZYNQ_T_ZQ_DDRMODE_MASK) + mt = MEM_DDR3; + else + mt = MEM_DDR2; + + return mt; +} + +/** + * zynq_init_csrows - Initialize the csrow data. + * @mci: EDAC memory controller instance. + * + * Initialize the chip select rows associated with the EDAC memory + * controller instance. + */ +static void zynq_init_csrows(struct mem_ctl_info *mci) +{ + struct zynq_edac_priv *priv = mci->pvt_info; + struct csrow_info *csi; + struct dimm_info *dimm; + u32 size, row; + int j; + + for (row = 0; row < mci->nr_csrows; row++) { + csi = mci->csrows[row]; + size = zynq_get_memsize(); + + for (j = 0; j < csi->nr_channels; j++) { + dimm = csi->channels[j]->dimm; + dimm->edac_mode = EDAC_SECDED; + dimm->mtype = zynq_get_mtype(priv->baseaddr); + dimm->nr_pages = (size >> PAGE_SHIFT) / csi->nr_channels; + dimm->grain = ZYNQ_EDAC_ERR_GRAIN; + dimm->dtype = zynq_get_dtype(priv->baseaddr); + } + } +} + +/** + * zynq_mc_init - Initialize one driver instance. + * @mci: EDAC memory controller instance. + * @pdev: platform device. + * + * Perform initialization of the EDAC memory controller instance and + * related driver-private data associated with the memory controller the + * instance is bound to. + */ +static void zynq_mc_init(struct mem_ctl_info *mci, struct platform_device *pdev) +{ + mci->pdev = &pdev->dev; + platform_set_drvdata(pdev, mci); + + /* Initialize controller capabilities and configuration */ + mci->mtype_cap = MEM_FLAG_DDR3 | MEM_FLAG_DDR2; + mci->edac_ctl_cap = EDAC_FLAG_NONE | EDAC_FLAG_SECDED; + mci->scrub_cap = SCRUB_FLAG_HW_SRC; + mci->scrub_mode = SCRUB_NONE; + + mci->edac_cap = EDAC_FLAG_SECDED; + mci->ctl_name = "zynq_ddr_controller"; + mci->dev_name = ZYNQ_EDAC_MOD_STRING; + mci->mod_name = ZYNQ_EDAC_MOD_VER; + + edac_op_state = EDAC_OPSTATE_POLL; + mci->edac_check = zynq_check_errors; + + mci->ctl_page_to_phys = NULL; + + zynq_init_csrows(mci); +} + +/** + * zynq_mc_probe - Check controller and bind driver. + * @pdev: platform device. + * + * Probe a specific controller instance for binding with the driver. + * + * Return: 0 if the controller instance was successfully bound to the + * driver; otherwise, < 0 on error. + */ +static int zynq_mc_probe(struct platform_device *pdev) +{ + struct edac_mc_layer layers[2]; + struct zynq_edac_priv *priv; + struct mem_ctl_info *mci; + void __iomem *baseaddr; + int rc; + + baseaddr = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(baseaddr)) + return PTR_ERR(baseaddr); + + if (!zynq_get_ecc_state(baseaddr)) { + edac_printk(KERN_INFO, EDAC_MC, "ECC not enabled\n"); + return -ENXIO; + } + + layers[0].type = EDAC_MC_LAYER_CHIP_SELECT; + layers[0].size = ZYNQ_EDAC_NR_CSROWS; + layers[0].is_virt_csrow = true; + layers[1].type = EDAC_MC_LAYER_CHANNEL; + layers[1].size = ZYNQ_EDAC_NR_CHANS; + layers[1].is_virt_csrow = false; + + mci = edac_mc_alloc(EDAC_AUTO_MC_NUM, ARRAY_SIZE(layers), layers, + sizeof(struct zynq_edac_priv)); + if (!mci) { + edac_printk(KERN_ERR, EDAC_MC, + "Failed memory allocation for mc instance\n"); + return -ENOMEM; + } + + priv = mci->pvt_info; + priv->baseaddr = baseaddr; + + zynq_mc_init(mci, pdev); + + rc = edac_mc_add_mc(mci); + if (rc) { + edac_printk(KERN_ERR, EDAC_MC, + "Failed to register with EDAC core\n"); + goto free_edac_mc; + } + + /* + * Start capturing the correctable and uncorrectable errors. A write of + * 0 starts the counters. + */ + writel(0x0, baseaddr + ZYNQ_ECC_CTRL_OFST); + + return 0; + +free_edac_mc: + edac_mc_free(mci); + + return rc; +} + +/** + * zynq_mc_remove - Unbind driver from controller. + * @pdev: Platform device. + * + * Return: Unconditionally 0 + */ +static int zynq_mc_remove(struct platform_device *pdev) +{ + struct mem_ctl_info *mci = platform_get_drvdata(pdev); + + edac_mc_del_mc(&pdev->dev); + edac_mc_free(mci); + + return 0; +} + +static const struct of_device_id zynq_edac_match[] = { + { .compatible = "xlnx,zynq-ddrc-a05" }, + {} +}; +MODULE_DEVICE_TABLE(of, zynq_edac_match); + +static struct platform_driver zynq_edac_mc_driver = { + .driver = { + .name = "zynq-edac", + .of_match_table = zynq_edac_match, + }, + .probe = zynq_mc_probe, + .remove = zynq_mc_remove, +}; +module_platform_driver(zynq_edac_mc_driver); + +MODULE_AUTHOR("Xilinx Inc"); +MODULE_DESCRIPTION("Zynq DDR ECC driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 88eb3fbbe5dfcd39dc6addb4b5834d2a962e7bda Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 14 Jul 2022 22:49:29 +0300 Subject: EDAC/synopsys: Drop unused platform-specific setup API The driver now works with the Synopsys DW uMCTL2 DDRC IP-core-based devices only (Xilinx Zynq A05 DDRC support has been moved to the separate driver). All the currently available IP-core revisions have got almost the same ECC and main part of the DDR-config CSRs map. Thus there is no point in supporting the no longer used internal abstraction layer like the callbacks responsible for getting the ECC errors info, memory and device types, ECC state. All of that data can be retrieved in the same way from all the Synopys DW uMCTL2 DDR controller versions. Similarly there is no longer need in the DDR_ECC_INTR_SUPPORT and DDR_ECC_DATA_POISON_SUPPORT quirk flags since DW uMCTL2 always supports IRQs and data poisoning (as long as the ECC is supported). Drop that infrastructure for good then. Note the only callback preserved in the driver is get_mem_info(), which has been recently added by the commit 35e6dbfe1846 ("EDAC/synopsys: Fix error injection on Zynq UltraScale+"). It will be dropped later after introducing a generic way of mapping from the possibly disjoint system address space to the continuous application address space. While at it move the module device table to being defined at the bottom of the file, above the platform driver descriptor definition. Signed-off-by: Serge Semin --- Changelog v2: - Drop the no longer used "priv" pointer from the mc_init() function. (@tbot) Changelog v7: - Add a note regarding the get_mem_info() callback. --- drivers/edac/synopsys_edac.c | 199 +++++++++++-------------------------------- 1 file changed, 51 insertions(+), 148 deletions(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index 7908a522dee59..631fc3d7c7674 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -31,9 +31,7 @@ #define SYNPS_EDAC_MOD_VER "1" /* DDR ECC Quirks */ -#define DDR_ECC_INTR_SUPPORT BIT(0) -#define DDR_ECC_DATA_POISON_SUPPORT BIT(1) -#define SYNPS_ZYNQMP_IRQ_REGS BIT(2) +#define SYNPS_ZYNQMP_IRQ_REGS BIT(0) /* Synopsys DDR memory controller registers that are relevant to ECC */ @@ -280,30 +278,16 @@ struct synps_edac_priv { #endif }; -enum synps_platform_type { - ZYNQ, - ZYNQMP, - SYNPS, -}; - /** - * struct synps_platform_data - synps platform data structure. - * @platform: Identifies the target hardware platform - * @get_error_info: Get EDAC error info. - * @get_mtype: Get mtype. - * @get_dtype: Get dtype. + * struct synps_platform_data - Synopsys uMCTL2 DDRC platform data. * @get_mem_info: Get EDAC memory info - * @quirks: To differentiate IPs. + * @quirks: IP-core specific quirks. */ struct synps_platform_data { - enum synps_platform_type platform; - int (*get_error_info)(struct synps_edac_priv *priv); - enum mem_type (*get_mtype)(const void __iomem *base); - enum dev_type (*get_dtype)(const void __iomem *base); #ifdef CONFIG_EDAC_DEBUG u64 (*get_mem_info)(struct synps_edac_priv *priv); #endif - int quirks; + u32 quirks; }; #ifdef CONFIG_EDAC_DEBUG @@ -326,12 +310,12 @@ static u64 zynqmp_get_mem_info(struct synps_edac_priv *priv) #endif /** - * zynqmp_get_error_info - Get the current ECC error info. + * synps_get_error_info - Get the current ECC error info. * @priv: DDR memory controller private instance data. * * Return: one if there is no error otherwise returns zero. */ -static int zynqmp_get_error_info(struct synps_edac_priv *priv) +static int synps_get_error_info(struct synps_edac_priv *priv) { struct synps_ecc_status *p; u32 regval, clearval; @@ -405,17 +389,11 @@ static void handle_error(struct mem_ctl_info *mci, struct synps_ecc_status *p) if (p->ce_cnt) { pinf = &p->ceinfo; - if (priv->p_data->quirks & DDR_ECC_INTR_SUPPORT) { - snprintf(priv->message, SYNPS_EDAC_MSG_SIZE, - "Row %d Col %d Bank %d Bank Group %d Bit %d Data 0x%08x", - pinf->row, pinf->col, pinf->bank, pinf->bankgrp, - pinf->bitpos, pinf->data); - } else { - snprintf(priv->message, SYNPS_EDAC_MSG_SIZE, - "Row %d Bank %d Col %d Bit: %d Data: 0x%08x", - pinf->row, pinf->bank, pinf->col, - pinf->bitpos, pinf->data); - } + + snprintf(priv->message, SYNPS_EDAC_MSG_SIZE, + "Row %d Col %d Bank %d Bank Group %d Bit %d Data 0x%08x", + pinf->row, pinf->col, pinf->bank, pinf->bankgrp, + pinf->bitpos, pinf->data); edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, p->ce_cnt, 0, 0, 0, 0, 0, -1, @@ -424,15 +402,10 @@ static void handle_error(struct mem_ctl_info *mci, struct synps_ecc_status *p) if (p->ue_cnt) { pinf = &p->ueinfo; - if (priv->p_data->quirks & DDR_ECC_INTR_SUPPORT) { - snprintf(priv->message, SYNPS_EDAC_MSG_SIZE, - "Row %d Col %d Bank %d Bank Group %d", - pinf->row, pinf->col, pinf->bank, pinf->bankgrp); - } else { - snprintf(priv->message, SYNPS_EDAC_MSG_SIZE, - "Row %d Bank %d Col %d", - pinf->row, pinf->bank, pinf->col); - } + + snprintf(priv->message, SYNPS_EDAC_MSG_SIZE, + "Row %d Col %d Bank %d Bank Group %d", + pinf->row, pinf->col, pinf->bank, pinf->bankgrp); edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, p->ue_cnt, 0, 0, 0, 0, 0, -1, @@ -494,13 +467,11 @@ static void disable_intr(struct synps_edac_priv *priv) */ static irqreturn_t intr_handler(int irq, void *dev_id) { - const struct synps_platform_data *p_data; struct mem_ctl_info *mci = dev_id; struct synps_edac_priv *priv; int status, regval; priv = mci->pvt_info; - p_data = priv->p_data; if (priv->p_data->quirks & SYNPS_ZYNQMP_IRQ_REGS) { regval = readl(priv->baseaddr + DDR_QOS_IRQ_STAT_OFST); @@ -509,7 +480,7 @@ static irqreturn_t intr_handler(int irq, void *dev_id) return IRQ_NONE; } - status = p_data->get_error_info(priv); + status = synps_get_error_info(priv); if (status) return IRQ_NONE; @@ -522,29 +493,7 @@ static irqreturn_t intr_handler(int irq, void *dev_id) } /** - * check_errors - Check controller for ECC errors. - * @mci: EDAC memory controller instance. - * - * Check and post ECC errors. Called by the polling thread. - */ -static void check_errors(struct mem_ctl_info *mci) -{ - const struct synps_platform_data *p_data; - struct synps_edac_priv *priv; - int status; - - priv = mci->pvt_info; - p_data = priv->p_data; - - status = p_data->get_error_info(priv); - if (status) - return; - - handle_error(mci, &priv->stat); -} - -/** - * zynqmp_get_dtype - Return the DDR memory chips type. + * synps_get_dtype - Return the DDR memory chips type. * @base: DDR memory controller base address. * * Get the attached DDR chips type based on the current controller @@ -552,7 +501,7 @@ static void check_errors(struct mem_ctl_info *mci) * * Return: type of the memory DRAM chips. */ -static enum dev_type zynqmp_get_dtype(const void __iomem *base) +static enum dev_type synps_get_dtype(const void __iomem *base) { u32 regval; @@ -615,7 +564,7 @@ static u32 get_memsize(void) } /** - * zynqmp_get_mtype - Returns controller memory type. + * synps_get_mtype - Returns controller memory type. * @base: Synopsys ECC status structure. * * Get the EDAC memory type appropriate for the current controller @@ -623,7 +572,7 @@ static u32 get_memsize(void) * * Return: a memory type enumeration. */ -static enum mem_type zynqmp_get_mtype(const void __iomem *base) +static enum mem_type synps_get_mtype(const void __iomem *base) { enum mem_type mt; u32 memtype; @@ -652,14 +601,11 @@ static enum mem_type zynqmp_get_mtype(const void __iomem *base) static void init_csrows(struct mem_ctl_info *mci) { struct synps_edac_priv *priv = mci->pvt_info; - const struct synps_platform_data *p_data; struct csrow_info *csi; struct dimm_info *dimm; u32 size, row; int j; - p_data = priv->p_data; - for (row = 0; row < mci->nr_csrows; row++) { csi = mci->csrows[row]; size = get_memsize(); @@ -667,10 +613,10 @@ static void init_csrows(struct mem_ctl_info *mci) for (j = 0; j < csi->nr_channels; j++) { dimm = csi->channels[j]->dimm; dimm->edac_mode = EDAC_SECDED; - dimm->mtype = p_data->get_mtype(priv->baseaddr); + dimm->mtype = synps_get_mtype(priv->baseaddr); dimm->nr_pages = (size >> PAGE_SHIFT) / csi->nr_channels; dimm->grain = SYNPS_EDAC_ERR_GRAIN; - dimm->dtype = p_data->get_dtype(priv->baseaddr); + dimm->dtype = synps_get_dtype(priv->baseaddr); } } } @@ -686,10 +632,7 @@ static void init_csrows(struct mem_ctl_info *mci) */ static void mc_init(struct mem_ctl_info *mci, struct platform_device *pdev) { - struct synps_edac_priv *priv; - mci->pdev = &pdev->dev; - priv = mci->pvt_info; platform_set_drvdata(pdev, mci); /* Initialize controller capabilities and configuration */ @@ -703,12 +646,7 @@ static void mc_init(struct mem_ctl_info *mci, struct platform_device *pdev) mci->dev_name = SYNPS_EDAC_MOD_STRING; mci->mod_name = SYNPS_EDAC_MOD_VER; - if (priv->p_data->quirks & DDR_ECC_INTR_SUPPORT) { - edac_op_state = EDAC_OPSTATE_INT; - } else { - edac_op_state = EDAC_OPSTATE_POLL; - mci->edac_check = check_errors; - } + edac_op_state = EDAC_OPSTATE_INT; mci->ctl_page_to_phys = NULL; @@ -740,50 +678,6 @@ static int setup_irq(struct mem_ctl_info *mci, return 0; } -static const struct synps_platform_data zynqmp_edac_def = { - .platform = ZYNQMP, - .get_error_info = zynqmp_get_error_info, - .get_mtype = zynqmp_get_mtype, - .get_dtype = zynqmp_get_dtype, -#ifdef CONFIG_EDAC_DEBUG - .get_mem_info = zynqmp_get_mem_info, -#endif - .quirks = (DDR_ECC_INTR_SUPPORT | SYNPS_ZYNQMP_IRQ_REGS -#ifdef CONFIG_EDAC_DEBUG - | DDR_ECC_DATA_POISON_SUPPORT -#endif - ), -}; - -static const struct synps_platform_data synopsys_edac_def = { - .platform = SYNPS, - .get_error_info = zynqmp_get_error_info, - .get_mtype = zynqmp_get_mtype, - .get_dtype = zynqmp_get_dtype, - .quirks = (DDR_ECC_INTR_SUPPORT -#ifdef CONFIG_EDAC_DEBUG - | DDR_ECC_DATA_POISON_SUPPORT -#endif - ), -}; - - -static const struct of_device_id synps_edac_match[] = { - { - .compatible = "xlnx,zynqmp-ddrc-2.40a", - .data = (void *)&zynqmp_edac_def - }, - { - .compatible = "snps,ddrc-3.80a", - .data = (void *)&synopsys_edac_def - }, - { - /* end of table */ - } -}; - -MODULE_DEVICE_TABLE(of, synps_edac_match); - #ifdef CONFIG_EDAC_DEBUG /** @@ -1180,7 +1074,6 @@ static int mc_probe(struct platform_device *pdev) if (!p_data) return -ENODEV; - layers[0].type = EDAC_MC_LAYER_CHIP_SELECT; layers[0].size = SYNPS_EDAC_NR_CSROWS; layers[0].is_virt_csrow = true; @@ -1209,11 +1102,9 @@ static int mc_probe(struct platform_device *pdev) mc_init(mci, pdev); - if (priv->p_data->quirks & DDR_ECC_INTR_SUPPORT) { - rc = setup_irq(mci, pdev); - if (rc) - goto free_edac_mc; - } + rc = setup_irq(mci, pdev); + if (rc) + goto free_edac_mc; rc = edac_mc_add_mc(mci); if (rc) { @@ -1223,17 +1114,13 @@ static int mc_probe(struct platform_device *pdev) } #ifdef CONFIG_EDAC_DEBUG - if (priv->p_data->quirks & DDR_ECC_DATA_POISON_SUPPORT) { - rc = edac_create_sysfs_attributes(mci); - if (rc) { - edac_printk(KERN_ERR, EDAC_MC, - "Failed to create sysfs entries\n"); - goto free_edac_mc; - } + rc = edac_create_sysfs_attributes(mci); + if (rc) { + edac_printk(KERN_ERR, EDAC_MC, "Failed to create sysfs entries\n"); + goto free_edac_mc; } - if (priv->p_data->quirks & DDR_ECC_INTR_SUPPORT) - setup_address_map(priv); + setup_address_map(priv); #endif return rc; @@ -1255,18 +1142,34 @@ static void mc_remove(struct platform_device *pdev) struct mem_ctl_info *mci = platform_get_drvdata(pdev); struct synps_edac_priv *priv = mci->pvt_info; - if (priv->p_data->quirks & DDR_ECC_INTR_SUPPORT) - disable_intr(priv); + disable_intr(priv); #ifdef CONFIG_EDAC_DEBUG - if (priv->p_data->quirks & DDR_ECC_DATA_POISON_SUPPORT) - edac_remove_sysfs_attributes(mci); + edac_remove_sysfs_attributes(mci); #endif edac_mc_del_mc(&pdev->dev); edac_mc_free(mci); } +static const struct synps_platform_data zynqmp_edac_def = { +#ifdef CONFIG_EDAC_DEBUG + .get_mem_info = zynqmp_get_mem_info, +#endif + .quirks = SYNPS_ZYNQMP_IRQ_REGS, +}; + +static const struct synps_platform_data synopsys_edac_def = { + .quirks = 0, +}; + +static const struct of_device_id synps_edac_match[] = { + { .compatible = "xlnx,zynqmp-ddrc-2.40a", .data = &zynqmp_edac_def }, + { .compatible = "snps,ddrc-3.80a", .data = &synopsys_edac_def }, + { } +}; +MODULE_DEVICE_TABLE(of, synps_edac_match); + static struct platform_driver synps_edac_mc_driver = { .driver = { .name = "synopsys-edac", -- cgit v1.2.3 From d579a93a10d9d26beae401e15b54baa43d4f5f49 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 6 Aug 2026 12:01:36 +0300 Subject: EDAC/synopsys: Check ECC state as early as possible Now as the platform-specific setup API is removed together with the non-Synopsys memory controller code it's possible to get back the ECC status check performed right at the probe() earliest stage (changed in the commit b1dc7f097b78 ("EDAC/synopsys: Clear the ECC counters on init")). Thus the redundant platform data getting and memory allocation will be avoided if the controller doesn't have the ECC feature enabled. Signed-off-by: Serge Semin --- Changelog v8: - Initial patch introduction. --- drivers/edac/synopsys_edac.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index 631fc3d7c7674..2cf6acf6691c2 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -526,23 +526,23 @@ static enum dev_type synps_get_dtype(const void __iomem *base) /** * get_ecc_state - Return the controller ECC enable/disable status. - * @priv: DDR memory controller private instance data. + * @base: DDR memory controller base address. * * Get the ECC enable/disable status for the controller and clear out * the ECC counters if applicatble. * * Return: a ECC status boolean i.e true/false - enabled/disabled. */ -static bool get_ecc_state(struct synps_edac_priv *priv) +static bool get_ecc_state(void __iomem *base) { u32 ecctype, clearval; - ecctype = readl(priv->baseaddr + ECC_CFG0_OFST) & ECC_CFG0_MODE_MASK; + ecctype = readl(base + ECC_CFG0_OFST) & ECC_CFG0_MODE_MASK; if (ecctype == ECC_CFG0_MODE_SECDED) { - clearval = readl(priv->baseaddr + ECC_CLR_OFST) | + clearval = readl(base + ECC_CLR_OFST) | ECC_CTRL_CLR_CE_ERR | ECC_CTRL_CLR_CE_ERRCNT | ECC_CTRL_CLR_UE_ERR | ECC_CTRL_CLR_UE_ERRCNT; - writel(clearval, priv->baseaddr + ECC_CLR_OFST); + writel(clearval, base + ECC_CLR_OFST); return true; } @@ -1070,6 +1070,11 @@ static int mc_probe(struct platform_device *pdev) if (IS_ERR(baseaddr)) return PTR_ERR(baseaddr); + if (!get_ecc_state(baseaddr)) { + edac_printk(KERN_INFO, EDAC_MC, "ECC not enabled\n"); + return -ENODEV; + } + p_data = of_device_get_match_data(&pdev->dev); if (!p_data) return -ENODEV; @@ -1092,11 +1097,6 @@ static int mc_probe(struct platform_device *pdev) priv = mci->pvt_info; priv->baseaddr = baseaddr; priv->p_data = p_data; - if (!get_ecc_state(priv)) { - edac_printk(KERN_INFO, EDAC_MC, "ECC not enabled\n"); - rc = -ENODEV; - goto free_edac_mc; - } spin_lock_init(&priv->reglock); -- cgit v1.2.3 From 119c84957915796c69ffd7050f044a52fe725d85 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 30 Aug 2023 17:03:35 +0300 Subject: EDAC/synopsys: Unify CSRs macro declarations Originally the device CSR macros were supposed to be defined following the next local convention: first CSR offsets are listed, then their fields and flags are described in the same order; CSRs offset macros are supposed to have _OFST suffix; ECC-related macros shall have ECC_ prefix, generic DDR-related macros - DDR_ prefix. After all the years the driver has been living in kernel the CSRs macros have turned to be partly deviated away from the denoted convention. Fix all the related inconsistencies: move several CSRs offset macros to be defined before the CSRs fields and flags macros; replace OFFSET suffix with OFST; replace DDRC_ prefix with DDR_ and ECC_ with DDR_ where it's appropriate; group DDR_MSTR and ECC_CTRL (ECC_CLR) sibling fields macros together and make sure their prefixes match to the CSRs offset macros. In addition to that drop _MASK suffix from the macros which aren't used as masks and add ZYNQMP_ prefix to the ZYNQMP-specific macros to distinguish them from the generic Synopsys memory controller macros. Signed-off-by: Serge Semin --- Changelog v4: - This is a new patch collected from the rest of the series to simplify the review process. --- drivers/edac/synopsys_edac.c | 134 +++++++++++++++++++++---------------------- 1 file changed, 66 insertions(+), 68 deletions(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index 2cf6acf6691c2..cb28b0556f418 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -78,20 +78,34 @@ #define ECC_POISON0_OFST 0xB8 #define ECC_POISON1_OFST 0xBC -#define ECC_ADDRMAP0_OFFSET 0x200 - -/* Control register bitfield definitions */ -#define ECC_CTRL_CLR_CE_ERR BIT(0) -#define ECC_CTRL_CLR_UE_ERR BIT(1) -#define ECC_CTRL_CLR_CE_ERRCNT BIT(2) -#define ECC_CTRL_CLR_UE_ERRCNT BIT(3) - -/* DDR Control Register width definitions */ +/* DDR Address Map Registers */ +#define DDR_ADDRMAP0_OFST 0x200 + +/* DDR Software Control Register */ +#define DDR_SWCTL 0x320 + +/* ZynqMP DDR QOS Registers */ +#define ZYNQMP_DDR_QOS_IRQ_STAT_OFST 0x20200 +#define ZYNQMP_DDR_QOS_IRQ_EN_OFST 0x20208 +#define ZYNQMP_DDR_QOS_IRQ_DB_OFST 0x2020C + +/* DDR Master register definitions */ +#define DDR_MSTR_DEV_CFG_MASK 0xC0000000 +#define DDR_MSTR_DEV_CFG_SHIFT 30 +#define DDR_MSTR_DEV_X4 0 +#define DDR_MSTR_DEV_X8 1 +#define DDR_MSTR_DEV_X16 2 +#define DDR_MSTR_DEV_X32 3 #define DDR_MSTR_BUSWIDTH_MASK 0x3000 #define DDR_MSTR_BUSWIDTH_SHIFT 12 -#define DDRCTL_EWDTH_16 2 -#define DDRCTL_EWDTH_32 1 -#define DDRCTL_EWDTH_64 0 +#define DDR_MSTR_BUSWIDTH_16 2 +#define DDR_MSTR_BUSWIDTH_32 1 +#define DDR_MSTR_BUSWIDTH_64 0 +#define DDR_MSTR_MEM_LPDDR4 0x20 +#define DDR_MSTR_MEM_DDR4 0x10 +#define DDR_MSTR_MEM_LPDDR3 0x8 +#define DDR_MSTR_MEM_DDR2 0x4 +#define DDR_MSTR_MEM_DDR3 0x1 /* ECC CFG0 register definitions */ #define ECC_CFG0_MODE_MASK 0x7 @@ -104,23 +118,19 @@ #define ECC_STAT_CECNT_SHIFT 8 #define ECC_STAT_BITNUM_MASK 0x7F +/* ECC control/clear register definitions */ +#define ECC_CTRL_CLR_CE_ERR BIT(0) +#define ECC_CTRL_CLR_UE_ERR BIT(1) +#define ECC_CTRL_CLR_CE_ERRCNT BIT(2) +#define ECC_CTRL_CLR_UE_ERRCNT BIT(3) +#define ECC_CTRL_EN_CE_IRQ BIT(8) +#define ECC_CTRL_EN_UE_IRQ BIT(9) + /* ECC error count register definitions */ #define ECC_ERRCNT_UECNT_MASK 0xFFFF0000 #define ECC_ERRCNT_UECNT_SHIFT 16 #define ECC_ERRCNT_CECNT_MASK 0xFFFF -/* DDR QOS Interrupt register definitions */ -#define DDR_QOS_IRQ_STAT_OFST 0x20200 -#define DDR_QOSUE_MASK 0x4 -#define DDR_QOSCE_MASK 0x2 -#define ECC_CE_UE_INTR_MASK 0x6 -#define DDR_QOS_IRQ_EN_OFST 0x20208 -#define DDR_QOS_IRQ_DB_OFST 0x2020C - -/* DDR QOS Interrupt register definitions */ -#define DDR_UE_MASK BIT(9) -#define DDR_CE_MASK BIT(8) - /* ECC Corrected Error Register Mask and Shifts*/ #define ECC_CEADDR0_RW_MASK 0x3FFFF #define ECC_CEADDR0_RNK_MASK BIT(24) @@ -142,28 +152,11 @@ #define ECC_POISON1_ROW_SHIFT 0 #define ECC_POISON1_ROW_MASK 0x3FFFF -/* DDR Memory type defines */ -#define MEM_TYPE_DDR3 0x1 -#define MEM_TYPE_LPDDR3 0x8 -#define MEM_TYPE_DDR2 0x4 -#define MEM_TYPE_DDR4 0x10 -#define MEM_TYPE_LPDDR4 0x20 - -/* DDRC Software control register */ -#define DDRC_SWCTL 0x320 - /* DDRC ECC CE & UE poison mask */ #define ECC_CEPOISON_MASK 0x3 #define ECC_UEPOISON_MASK 0x1 -/* DDRC Device config masks */ -#define DDRC_MSTR_CFG_MASK 0xC0000000 -#define DDRC_MSTR_CFG_SHIFT 30 -#define DDRC_MSTR_CFG_X4_MASK 0x0 -#define DDRC_MSTR_CFG_X8_MASK 0x1 -#define DDRC_MSTR_CFG_X16_MASK 0x2 -#define DDRC_MSTR_CFG_X32_MASK 0x3 - +/* DDRC Device config shifts/masks */ #define DDR_MAX_ROW_SHIFT 18 #define DDR_MAX_COL_SHIFT 14 #define DDR_MAX_BANK_SHIFT 3 @@ -216,6 +209,11 @@ #define RANK_B0_BASE 6 +/* ZynqMP DDR QOS Interrupt register definitions */ +#define ZYNQMP_DDR_QOS_UE_MASK 0x4 +#define ZYNQMP_DDR_QOS_CE_MASK 0x2 +#define ZYNQMP_DDR_QOS_IRQ_MASK 0x6 + /** * struct ecc_error_info - ECC error log information. * @row: Row number. @@ -421,8 +419,8 @@ static void enable_intr(struct synps_edac_priv *priv) /* Enable UE/CE Interrupts */ if (priv->p_data->quirks & SYNPS_ZYNQMP_IRQ_REGS) { - writel(DDR_QOSUE_MASK | DDR_QOSCE_MASK, - priv->baseaddr + DDR_QOS_IRQ_EN_OFST); + writel(ZYNQMP_DDR_QOS_UE_MASK | ZYNQMP_DDR_QOS_CE_MASK, + priv->baseaddr + ZYNQMP_DDR_QOS_IRQ_EN_OFST); return; } @@ -433,7 +431,7 @@ static void enable_intr(struct synps_edac_priv *priv) * IRQs Enable/Disable flags have been available since v3.10a. * This is noop for the older controllers. */ - writel(DDR_UE_MASK | DDR_CE_MASK, + writel(ECC_CTRL_EN_CE_IRQ | ECC_CTRL_EN_UE_IRQ, priv->baseaddr + ECC_CLR_OFST); spin_unlock_irqrestore(&priv->reglock, flags); @@ -445,8 +443,8 @@ static void disable_intr(struct synps_edac_priv *priv) /* Disable UE/CE Interrupts */ if (priv->p_data->quirks & SYNPS_ZYNQMP_IRQ_REGS) { - writel(DDR_QOSUE_MASK | DDR_QOSCE_MASK, - priv->baseaddr + DDR_QOS_IRQ_DB_OFST); + writel(ZYNQMP_DDR_QOS_UE_MASK | ZYNQMP_DDR_QOS_CE_MASK, + priv->baseaddr + ZYNQMP_DDR_QOS_IRQ_DB_OFST); return; } @@ -474,9 +472,9 @@ static irqreturn_t intr_handler(int irq, void *dev_id) priv = mci->pvt_info; if (priv->p_data->quirks & SYNPS_ZYNQMP_IRQ_REGS) { - regval = readl(priv->baseaddr + DDR_QOS_IRQ_STAT_OFST); - regval &= (DDR_QOSCE_MASK | DDR_QOSUE_MASK); - if (!(regval & ECC_CE_UE_INTR_MASK)) + regval = readl(priv->baseaddr + ZYNQMP_DDR_QOS_IRQ_STAT_OFST); + regval &= (ZYNQMP_DDR_QOS_CE_MASK | ZYNQMP_DDR_QOS_UE_MASK); + if (!(regval & ZYNQMP_DDR_QOS_IRQ_MASK)) return IRQ_NONE; } @@ -487,7 +485,7 @@ static irqreturn_t intr_handler(int irq, void *dev_id) handle_error(mci, &priv->stat); if (priv->p_data->quirks & SYNPS_ZYNQMP_IRQ_REGS) - writel(regval, priv->baseaddr + DDR_QOS_IRQ_STAT_OFST); + writel(regval, priv->baseaddr + ZYNQMP_DDR_QOS_IRQ_STAT_OFST); return IRQ_HANDLED; } @@ -506,18 +504,18 @@ static enum dev_type synps_get_dtype(const void __iomem *base) u32 regval; regval = readl(base + DDR_MSTR_OFST); - if (!(regval & MEM_TYPE_DDR4)) + if (!(regval & DDR_MSTR_MEM_DDR4)) return DEV_UNKNOWN; - regval = (regval & DDRC_MSTR_CFG_MASK) >> DDRC_MSTR_CFG_SHIFT; + regval = (regval & DDR_MSTR_DEV_CFG_MASK) >> DDR_MSTR_DEV_CFG_SHIFT; switch (regval) { - case DDRC_MSTR_CFG_X4_MASK: + case DDR_MSTR_DEV_X4: return DEV_X4; - case DDRC_MSTR_CFG_X8_MASK: + case DDR_MSTR_DEV_X8: return DEV_X8; - case DDRC_MSTR_CFG_X16_MASK: + case DDR_MSTR_DEV_X16: return DEV_X16; - case DDRC_MSTR_CFG_X32_MASK: + case DDR_MSTR_DEV_X32: return DEV_X32; } @@ -579,11 +577,11 @@ static enum mem_type synps_get_mtype(const void __iomem *base) memtype = readl(base + DDR_MSTR_OFST); - if ((memtype & MEM_TYPE_DDR3) || (memtype & MEM_TYPE_LPDDR3)) + if ((memtype & DDR_MSTR_MEM_DDR3) || (memtype & DDR_MSTR_MEM_LPDDR3)) mt = MEM_DDR3; - else if (memtype & MEM_TYPE_DDR2) + else if (memtype & DDR_MSTR_MEM_DDR2) mt = MEM_RDDR2; - else if ((memtype & MEM_TYPE_LPDDR4) || (memtype & MEM_TYPE_DDR4)) + else if ((memtype & DDR_MSTR_MEM_LPDDR4) || (memtype & DDR_MSTR_MEM_DDR4)) mt = MEM_DDR4; else mt = MEM_EMPTY; @@ -794,12 +792,12 @@ static ssize_t inject_data_poison_store(struct device *dev, struct mem_ctl_info *mci = to_mci(dev); struct synps_edac_priv *priv = mci->pvt_info; - writel(0, priv->baseaddr + DDRC_SWCTL); + writel(0, priv->baseaddr + DDR_SWCTL); if (strncmp(data, "CE", 2) == 0) writel(ECC_CEPOISON_MASK, priv->baseaddr + ECC_CFG1_OFST); else writel(ECC_UEPOISON_MASK, priv->baseaddr + ECC_CFG1_OFST); - writel(1, priv->baseaddr + DDRC_SWCTL); + writel(1, priv->baseaddr + DDR_SWCTL); return count; } @@ -916,8 +914,8 @@ static void setup_column_address_map(struct synps_edac_priv *priv, u32 *addrmap) priv->col_shift[9] = (((addrmap[3] >> 24) & COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 : (((addrmap[3] >> 24) & COL_MAX_VAL_MASK) + COL_B9_BASE); - if (width == DDRCTL_EWDTH_64) { - if (memtype & MEM_TYPE_LPDDR3) { + if (width == DDR_MSTR_BUSWIDTH_64) { + if (memtype & DDR_MSTR_MEM_LPDDR3) { priv->col_shift[10] = ((addrmap[4] & COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 : ((addrmap[4] & COL_MAX_VAL_MASK) + @@ -936,8 +934,8 @@ static void setup_column_address_map(struct synps_edac_priv *priv, u32 *addrmap) (((addrmap[4] >> 8) & COL_MAX_VAL_MASK) + COL_B11_BASE); } - } else if (width == DDRCTL_EWDTH_32) { - if (memtype & MEM_TYPE_LPDDR3) { + } else if (width == DDR_MSTR_BUSWIDTH_32) { + if (memtype & DDR_MSTR_MEM_LPDDR3) { priv->col_shift[10] = (((addrmap[3] >> 24) & COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 : (((addrmap[3] >> 24) & COL_MAX_VAL_MASK) + @@ -957,7 +955,7 @@ static void setup_column_address_map(struct synps_edac_priv *priv, u32 *addrmap) COL_B10_BASE); } } else { - if (memtype & MEM_TYPE_LPDDR3) { + if (memtype & DDR_MSTR_MEM_LPDDR3) { priv->col_shift[10] = (((addrmap[3] >> 16) & COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 : (((addrmap[3] >> 16) & COL_MAX_VAL_MASK) + @@ -1032,7 +1030,7 @@ static void setup_address_map(struct synps_edac_priv *priv) for (index = 0; index < 12; index++) { u32 addrmap_offset; - addrmap_offset = ECC_ADDRMAP0_OFFSET + (index * 4); + addrmap_offset = DDR_ADDRMAP0_OFST + (index * 4); addrmap[index] = readl(priv->baseaddr + addrmap_offset); } -- cgit v1.2.3 From 369ffa5cb91af421ee136f7a7d30d5d8ada579de Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sat, 16 Jul 2022 22:29:02 +0300 Subject: EDAC/synopsys: Unify struct/macro/function prefixes Currently the driver entities naming schema is kind of random. There are structures and methods with the "synps" prefix, there are structures and methods with no driver-specific prefix, there are methods with the "edac" prefix, there are structure instances with "zynqmp" and "synopsys" prefixes, there are macros with "SYNPS", "ECC" and "DDR" prefixes. Moreover some time ago some of function names were shortened out by completely removing the vendor-specific prefixes thus leaving the driver with no strict entities naming convention (see commit bb894bc46ed0 ("EDAC, synopsys: Shorten static function names")). All of that makes the code much harder to read for no much reason (except shorter names utilization) since there is no easy way to distinguish now the local, EDAC core and global name spaces right from the code context. Similarly the kernel code index services (like elixir) gets to find the different functions with the same name, which harden the kernel hacking. Fix all of that by unifying the driver local entity names like functions, structures and non-CSR-related macros especially seeing the same approach has been used in the most of the EDAC LLDD. Use the "snps" prefix here as the shortest version of the controller vendor name. While at it add a more detailed controller name (DW uMCTL2 DDRC) to the driver comments and string literals where it's appropriate. Signed-off-by: Serge Semin --- Note "dw" prefix would be even shorter alternative. But we decided to stick with "snps" since "synopsys" has already been used in the module name. Changelog v2: - Forgot to fix some of the SYNPS_ZYNQMP_IRQ_REGS macro utilizations. (@tbot) --- drivers/edac/synopsys_edac.c | 249 ++++++++++++++++++++++--------------------- 1 file changed, 125 insertions(+), 124 deletions(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index cb28b0556f418..40e12b7192a9e 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Synopsys DDR ECC Driver + * Synopsys DW uMCTL2 DDR ECC Driver * This driver is based on ppc4xx_edac.c drivers * * Copyright (C) 2012 - 2014 Xilinx, Inc. @@ -17,23 +17,23 @@ #include "edac_module.h" /* Number of cs_rows needed per memory controller */ -#define SYNPS_EDAC_NR_CSROWS 1 +#define SNPS_EDAC_NR_CSROWS 1 /* Number of channels per memory controller */ -#define SYNPS_EDAC_NR_CHANS 1 +#define SNPS_EDAC_NR_CHANS 1 /* Granularity of reported error in bytes */ -#define SYNPS_EDAC_ERR_GRAIN 1 +#define SNPS_EDAC_ERR_GRAIN 1 -#define SYNPS_EDAC_MSG_SIZE 256 +#define SNPS_EDAC_MSG_SIZE 256 -#define SYNPS_EDAC_MOD_STRING "synps_edac" -#define SYNPS_EDAC_MOD_VER "1" +#define SNPS_EDAC_MOD_STRING "snps_edac" +#define SNPS_EDAC_MOD_VER "1" /* DDR ECC Quirks */ -#define SYNPS_ZYNQMP_IRQ_REGS BIT(0) +#define SNPS_ZYNQMP_IRQ_REGS BIT(0) -/* Synopsys DDR memory controller registers that are relevant to ECC */ +/* Synopsys uMCTL2 DDR controller registers that are relevant to ECC */ /* DDRC Master 0 Register */ #define DDR_MSTR_OFST 0x0 @@ -215,7 +215,7 @@ #define ZYNQMP_DDR_QOS_IRQ_MASK 0x6 /** - * struct ecc_error_info - ECC error log information. + * struct snps_ecc_error_info - ECC error log information. * @row: Row number. * @col: Column number. * @bank: Bank number. @@ -223,7 +223,7 @@ * @bitpos: Bit position. * @data: Data causing the error. */ -struct ecc_error_info { +struct snps_ecc_error_info { u32 row; u32 col; u32 bank; @@ -233,21 +233,21 @@ struct ecc_error_info { }; /** - * struct synps_ecc_status - ECC status information to report. + * struct snps_ecc_status - ECC status information to report. * @ce_cnt: Correctable error count. * @ue_cnt: Uncorrectable error count. * @ceinfo: Correctable error log information. * @ueinfo: Uncorrectable error log information. */ -struct synps_ecc_status { +struct snps_ecc_status { u32 ce_cnt; u32 ue_cnt; - struct ecc_error_info ceinfo; - struct ecc_error_info ueinfo; + struct snps_ecc_error_info ceinfo; + struct snps_ecc_error_info ueinfo; }; /** - * struct synps_edac_priv - DDR memory controller private instance data. + * struct snps_edac_priv - DDR memory controller private data. * @baseaddr: Base address of the DDR controller. * @reglock: Concurrent CSRs access lock. * @message: Buffer for framing the event specific info. @@ -260,12 +260,12 @@ struct synps_ecc_status { * @bankgrp_shift: Bit shifts for bank group bit. * @rank_shift: Bit shifts for rank bit. */ -struct synps_edac_priv { +struct snps_edac_priv { void __iomem *baseaddr; spinlock_t reglock; - char message[SYNPS_EDAC_MSG_SIZE]; - struct synps_ecc_status stat; - const struct synps_platform_data *p_data; + char message[SNPS_EDAC_MSG_SIZE]; + struct snps_ecc_status stat; + const struct snps_platform_data *p_data; #ifdef CONFIG_EDAC_DEBUG ulong poison_addr; u32 row_shift[18]; @@ -277,13 +277,13 @@ struct synps_edac_priv { }; /** - * struct synps_platform_data - Synopsys uMCTL2 DDRC platform data. + * struct snps_platform_data - Synopsys uMCTL2 DDRC platform data. * @get_mem_info: Get EDAC memory info - * @quirks: IP-core specific quirks. + * @quirks: IP-core specific quirks. */ -struct synps_platform_data { +struct snps_platform_data { #ifdef CONFIG_EDAC_DEBUG - u64 (*get_mem_info)(struct synps_edac_priv *priv); + u64 (*get_mem_info)(struct snps_edac_priv *priv); #endif u32 quirks; }; @@ -295,7 +295,7 @@ struct synps_platform_data { * * Return: host interface address. */ -static u64 zynqmp_get_mem_info(struct synps_edac_priv *priv) +static u64 zynqmp_get_mem_info(struct snps_edac_priv *priv) { u64 hif_addr = 0, linear_addr; @@ -308,14 +308,14 @@ static u64 zynqmp_get_mem_info(struct synps_edac_priv *priv) #endif /** - * synps_get_error_info - Get the current ECC error info. + * snps_get_error_info - Get the current ECC error info. * @priv: DDR memory controller private instance data. * * Return: one if there is no error otherwise returns zero. */ -static int synps_get_error_info(struct synps_edac_priv *priv) +static int snps_get_error_info(struct snps_edac_priv *priv) { - struct synps_ecc_status *p; + struct snps_ecc_status *p; u32 regval, clearval; unsigned long flags; void __iomem *base; @@ -374,21 +374,21 @@ out: } /** - * handle_error - Handle Correctable and Uncorrectable errors. + * snps_handle_error - Handle Correctable and Uncorrectable errors. * @mci: EDAC memory controller instance. * @p: Synopsys ECC status structure. * * Handles ECC correctable and uncorrectable errors. */ -static void handle_error(struct mem_ctl_info *mci, struct synps_ecc_status *p) +static void snps_handle_error(struct mem_ctl_info *mci, struct snps_ecc_status *p) { - struct synps_edac_priv *priv = mci->pvt_info; - struct ecc_error_info *pinf; + struct snps_edac_priv *priv = mci->pvt_info; + struct snps_ecc_error_info *pinf; if (p->ce_cnt) { pinf = &p->ceinfo; - snprintf(priv->message, SYNPS_EDAC_MSG_SIZE, + snprintf(priv->message, SNPS_EDAC_MSG_SIZE, "Row %d Col %d Bank %d Bank Group %d Bit %d Data 0x%08x", pinf->row, pinf->col, pinf->bank, pinf->bankgrp, pinf->bitpos, pinf->data); @@ -401,7 +401,7 @@ static void handle_error(struct mem_ctl_info *mci, struct synps_ecc_status *p) if (p->ue_cnt) { pinf = &p->ueinfo; - snprintf(priv->message, SYNPS_EDAC_MSG_SIZE, + snprintf(priv->message, SNPS_EDAC_MSG_SIZE, "Row %d Col %d Bank %d Bank Group %d", pinf->row, pinf->col, pinf->bank, pinf->bankgrp); @@ -413,12 +413,12 @@ static void handle_error(struct mem_ctl_info *mci, struct synps_ecc_status *p) memset(p, 0, sizeof(*p)); } -static void enable_intr(struct synps_edac_priv *priv) +static void snps_enable_irq(struct snps_edac_priv *priv) { unsigned long flags; /* Enable UE/CE Interrupts */ - if (priv->p_data->quirks & SYNPS_ZYNQMP_IRQ_REGS) { + if (priv->p_data->quirks & SNPS_ZYNQMP_IRQ_REGS) { writel(ZYNQMP_DDR_QOS_UE_MASK | ZYNQMP_DDR_QOS_CE_MASK, priv->baseaddr + ZYNQMP_DDR_QOS_IRQ_EN_OFST); @@ -437,12 +437,12 @@ static void enable_intr(struct synps_edac_priv *priv) spin_unlock_irqrestore(&priv->reglock, flags); } -static void disable_intr(struct synps_edac_priv *priv) +static void snps_disable_irq(struct snps_edac_priv *priv) { unsigned long flags; /* Disable UE/CE Interrupts */ - if (priv->p_data->quirks & SYNPS_ZYNQMP_IRQ_REGS) { + if (priv->p_data->quirks & SNPS_ZYNQMP_IRQ_REGS) { writel(ZYNQMP_DDR_QOS_UE_MASK | ZYNQMP_DDR_QOS_CE_MASK, priv->baseaddr + ZYNQMP_DDR_QOS_IRQ_DB_OFST); @@ -457,41 +457,41 @@ static void disable_intr(struct synps_edac_priv *priv) } /** - * intr_handler - Interrupt Handler for ECC interrupts. + * snps_irq_handler - Interrupt Handler for ECC interrupts. * @irq: IRQ number. * @dev_id: Device ID. * * Return: IRQ_NONE, if interrupt not set or IRQ_HANDLED otherwise. */ -static irqreturn_t intr_handler(int irq, void *dev_id) +static irqreturn_t snps_irq_handler(int irq, void *dev_id) { struct mem_ctl_info *mci = dev_id; - struct synps_edac_priv *priv; + struct snps_edac_priv *priv; int status, regval; priv = mci->pvt_info; - if (priv->p_data->quirks & SYNPS_ZYNQMP_IRQ_REGS) { + if (priv->p_data->quirks & SNPS_ZYNQMP_IRQ_REGS) { regval = readl(priv->baseaddr + ZYNQMP_DDR_QOS_IRQ_STAT_OFST); regval &= (ZYNQMP_DDR_QOS_CE_MASK | ZYNQMP_DDR_QOS_UE_MASK); if (!(regval & ZYNQMP_DDR_QOS_IRQ_MASK)) return IRQ_NONE; } - status = synps_get_error_info(priv); + status = snps_get_error_info(priv); if (status) return IRQ_NONE; - handle_error(mci, &priv->stat); + snps_handle_error(mci, &priv->stat); - if (priv->p_data->quirks & SYNPS_ZYNQMP_IRQ_REGS) + if (priv->p_data->quirks & SNPS_ZYNQMP_IRQ_REGS) writel(regval, priv->baseaddr + ZYNQMP_DDR_QOS_IRQ_STAT_OFST); return IRQ_HANDLED; } /** - * synps_get_dtype - Return the DDR memory chips type. + * snps_get_dtype - Return the DDR memory chips type. * @base: DDR memory controller base address. * * Get the attached DDR chips type based on the current controller @@ -499,7 +499,7 @@ static irqreturn_t intr_handler(int irq, void *dev_id) * * Return: type of the memory DRAM chips. */ -static enum dev_type synps_get_dtype(const void __iomem *base) +static enum dev_type snps_get_dtype(const void __iomem *base) { u32 regval; @@ -523,7 +523,7 @@ static enum dev_type synps_get_dtype(const void __iomem *base) } /** - * get_ecc_state - Return the controller ECC enable/disable status. + * snps_get_ecc_state - Return the controller ECC enable/disable status. * @base: DDR memory controller base address. * * Get the ECC enable/disable status for the controller and clear out @@ -531,7 +531,7 @@ static enum dev_type synps_get_dtype(const void __iomem *base) * * Return: a ECC status boolean i.e true/false - enabled/disabled. */ -static bool get_ecc_state(void __iomem *base) +static bool snps_get_ecc_state(void __iomem *base) { u32 ecctype, clearval; @@ -548,11 +548,11 @@ static bool get_ecc_state(void __iomem *base) } /** - * get_memsize - Read the size of the attached memory device. + * snps_get_memsize - Read the size of the attached memory device. * * Return: the memory size in bytes. */ -static u32 get_memsize(void) +static u32 snps_get_memsize(void) { struct sysinfo inf; @@ -562,7 +562,7 @@ static u32 get_memsize(void) } /** - * synps_get_mtype - Returns controller memory type. + * snps_get_mtype - Returns controller memory type. * @base: Synopsys ECC status structure. * * Get the EDAC memory type appropriate for the current controller @@ -570,7 +570,7 @@ static u32 get_memsize(void) * * Return: a memory type enumeration. */ -static enum mem_type synps_get_mtype(const void __iomem *base) +static enum mem_type snps_get_mtype(const void __iomem *base) { enum mem_type mt; u32 memtype; @@ -590,15 +590,15 @@ static enum mem_type synps_get_mtype(const void __iomem *base) } /** - * init_csrows - Initialize the csrow data. + * snps_init_csrows - Initialize the csrow data. * @mci: EDAC memory controller instance. * * Initialize the chip select rows associated with the EDAC memory * controller instance. */ -static void init_csrows(struct mem_ctl_info *mci) +static void snps_init_csrows(struct mem_ctl_info *mci) { - struct synps_edac_priv *priv = mci->pvt_info; + struct snps_edac_priv *priv = mci->pvt_info; struct csrow_info *csi; struct dimm_info *dimm; u32 size, row; @@ -606,21 +606,21 @@ static void init_csrows(struct mem_ctl_info *mci) for (row = 0; row < mci->nr_csrows; row++) { csi = mci->csrows[row]; - size = get_memsize(); + size = snps_get_memsize(); for (j = 0; j < csi->nr_channels; j++) { dimm = csi->channels[j]->dimm; dimm->edac_mode = EDAC_SECDED; - dimm->mtype = synps_get_mtype(priv->baseaddr); + dimm->mtype = snps_get_mtype(priv->baseaddr); dimm->nr_pages = (size >> PAGE_SHIFT) / csi->nr_channels; - dimm->grain = SYNPS_EDAC_ERR_GRAIN; - dimm->dtype = synps_get_dtype(priv->baseaddr); + dimm->grain = SNPS_EDAC_ERR_GRAIN; + dimm->dtype = snps_get_dtype(priv->baseaddr); } } } /** - * mc_init - Initialize one driver instance. + * snps_mc_init - Initialize one driver instance. * @mci: EDAC memory controller instance. * @pdev: platform device. * @@ -628,7 +628,7 @@ static void init_csrows(struct mem_ctl_info *mci) * related driver-private data associated with the memory controller the * instance is bound to. */ -static void mc_init(struct mem_ctl_info *mci, struct platform_device *pdev) +static void snps_mc_init(struct mem_ctl_info *mci, struct platform_device *pdev) { mci->pdev = &pdev->dev; platform_set_drvdata(pdev, mci); @@ -640,21 +640,22 @@ static void mc_init(struct mem_ctl_info *mci, struct platform_device *pdev) mci->scrub_mode = SCRUB_NONE; mci->edac_cap = EDAC_FLAG_SECDED; - mci->ctl_name = "synps_ddr_controller"; - mci->dev_name = SYNPS_EDAC_MOD_STRING; - mci->mod_name = SYNPS_EDAC_MOD_VER; + mci->ctl_name = "snps_umctl2_ddrc"; + mci->dev_name = SNPS_EDAC_MOD_STRING; + mci->mod_name = SNPS_EDAC_MOD_VER; edac_op_state = EDAC_OPSTATE_INT; mci->ctl_page_to_phys = NULL; - init_csrows(mci); + snps_init_csrows(mci); } -static int setup_irq(struct mem_ctl_info *mci, - struct platform_device *pdev) + + +static int snps_setup_irq(struct mem_ctl_info *mci, struct platform_device *pdev) { - struct synps_edac_priv *priv = mci->pvt_info; + struct snps_edac_priv *priv = mci->pvt_info; int ret, irq; irq = platform_get_irq(pdev, 0); @@ -664,14 +665,14 @@ static int setup_irq(struct mem_ctl_info *mci, return irq; } - ret = devm_request_irq(&pdev->dev, irq, intr_handler, + ret = devm_request_irq(&pdev->dev, irq, snps_irq_handler, 0, dev_name(&pdev->dev), mci); if (ret < 0) { edac_printk(KERN_ERR, EDAC_MC, "Failed to request IRQ\n"); return ret; } - enable_intr(priv); + snps_enable_irq(priv); return 0; } @@ -679,16 +680,16 @@ static int setup_irq(struct mem_ctl_info *mci, #ifdef CONFIG_EDAC_DEBUG /** - * ddr_poison_setup - Update poison registers. + * snps_data_poison_setup - Update poison registers. * @priv: DDR memory controller private instance data. * * Update poison registers as per DDR mapping. * Return: none. */ -static void ddr_poison_setup(struct synps_edac_priv *priv) +static void snps_data_poison_setup(struct snps_edac_priv *priv) { int col = 0, row = 0, bank = 0, bankgrp = 0, rank = 0, regval; - const struct synps_platform_data *p_data; + const struct snps_platform_data *p_data; int index; ulong hif_addr = 0; @@ -749,7 +750,7 @@ static ssize_t inject_data_error_show(struct device *dev, char *data) { struct mem_ctl_info *mci = to_mci(dev); - struct synps_edac_priv *priv = mci->pvt_info; + struct snps_edac_priv *priv = mci->pvt_info; return sprintf(data, "Poison0 Addr: 0x%08x\n\rPoison1 Addr: 0x%08x\n\r" "Error injection Address: 0x%lx\n\r", @@ -763,12 +764,12 @@ static ssize_t inject_data_error_store(struct device *dev, const char *data, size_t count) { struct mem_ctl_info *mci = to_mci(dev); - struct synps_edac_priv *priv = mci->pvt_info; + struct snps_edac_priv *priv = mci->pvt_info; if (kstrtoul(data, 0, &priv->poison_addr)) return -EINVAL; - ddr_poison_setup(priv); + snps_data_poison_setup(priv); return count; } @@ -778,7 +779,7 @@ static ssize_t inject_data_poison_show(struct device *dev, char *data) { struct mem_ctl_info *mci = to_mci(dev); - struct synps_edac_priv *priv = mci->pvt_info; + struct snps_edac_priv *priv = mci->pvt_info; return sprintf(data, "Data Poisoning: %s\n\r", (((readl(priv->baseaddr + ECC_CFG1_OFST)) & 0x3) == 0x3) @@ -790,7 +791,7 @@ static ssize_t inject_data_poison_store(struct device *dev, const char *data, size_t count) { struct mem_ctl_info *mci = to_mci(dev); - struct synps_edac_priv *priv = mci->pvt_info; + struct snps_edac_priv *priv = mci->pvt_info; writel(0, priv->baseaddr + DDR_SWCTL); if (strncmp(data, "CE", 2) == 0) @@ -805,7 +806,7 @@ static ssize_t inject_data_poison_store(struct device *dev, static DEVICE_ATTR_RW(inject_data_error); static DEVICE_ATTR_RW(inject_data_poison); -static int edac_create_sysfs_attributes(struct mem_ctl_info *mci) +static int snps_create_sysfs_attributes(struct mem_ctl_info *mci) { int rc; @@ -818,13 +819,13 @@ static int edac_create_sysfs_attributes(struct mem_ctl_info *mci) return 0; } -static void edac_remove_sysfs_attributes(struct mem_ctl_info *mci) +static void snps_remove_sysfs_attributes(struct mem_ctl_info *mci) { device_remove_file(&mci->dev, &dev_attr_inject_data_error); device_remove_file(&mci->dev, &dev_attr_inject_data_poison); } -static void setup_row_address_map(struct synps_edac_priv *priv, u32 *addrmap) +static void snps_setup_row_address_map(struct snps_edac_priv *priv, u32 *addrmap) { u32 addrmap_row_b2_10; int index; @@ -883,7 +884,7 @@ static void setup_row_address_map(struct synps_edac_priv *priv, u32 *addrmap) ROW_MAX_VAL_MASK) + ROW_B17_BASE); } -static void setup_column_address_map(struct synps_edac_priv *priv, u32 *addrmap) +static void snps_setup_column_address_map(struct snps_edac_priv *priv, u32 *addrmap) { u32 width, memtype; int index; @@ -985,7 +986,7 @@ static void setup_column_address_map(struct synps_edac_priv *priv, u32 *addrmap) } -static void setup_bank_address_map(struct synps_edac_priv *priv, u32 *addrmap) +static void snps_setup_bank_address_map(struct snps_edac_priv *priv, u32 *addrmap) { priv->bank_shift[0] = (addrmap[1] & BANK_MAX_VAL_MASK) + BANK_B0_BASE; priv->bank_shift[1] = ((addrmap[1] >> 8) & @@ -997,7 +998,7 @@ static void setup_bank_address_map(struct synps_edac_priv *priv, u32 *addrmap) } -static void setup_bg_address_map(struct synps_edac_priv *priv, u32 *addrmap) +static void snps_setup_bg_address_map(struct snps_edac_priv *priv, u32 *addrmap) { priv->bankgrp_shift[0] = (addrmap[8] & BANKGRP_MAX_VAL_MASK) + BANKGRP_B0_BASE; @@ -1007,7 +1008,7 @@ static void setup_bg_address_map(struct synps_edac_priv *priv, u32 *addrmap) } -static void setup_rank_address_map(struct synps_edac_priv *priv, u32 *addrmap) +static void snps_setup_rank_address_map(struct snps_edac_priv *priv, u32 *addrmap) { priv->rank_shift[0] = ((addrmap[0] & RANK_MAX_VAL_MASK) == RANK_MAX_VAL_MASK) ? 0 : ((addrmap[0] & @@ -1015,14 +1016,14 @@ static void setup_rank_address_map(struct synps_edac_priv *priv, u32 *addrmap) } /** - * setup_address_map - Set Address Map by querying ADDRMAP registers. + * snps_setup_address_map - Set Address Map by querying ADDRMAP registers. * @priv: DDR memory controller private instance data. * * Set Address Map by querying ADDRMAP registers. * * Return: none. */ -static void setup_address_map(struct synps_edac_priv *priv) +static void snps_setup_address_map(struct snps_edac_priv *priv) { u32 addrmap[12]; int index; @@ -1034,20 +1035,20 @@ static void setup_address_map(struct synps_edac_priv *priv) addrmap[index] = readl(priv->baseaddr + addrmap_offset); } - setup_row_address_map(priv, addrmap); + snps_setup_row_address_map(priv, addrmap); - setup_column_address_map(priv, addrmap); + snps_setup_column_address_map(priv, addrmap); - setup_bank_address_map(priv, addrmap); + snps_setup_bank_address_map(priv, addrmap); - setup_bg_address_map(priv, addrmap); + snps_setup_bg_address_map(priv, addrmap); - setup_rank_address_map(priv, addrmap); + snps_setup_rank_address_map(priv, addrmap); } #endif /* CONFIG_EDAC_DEBUG */ /** - * mc_probe - Check controller and bind driver. + * snps_mc_probe - Check controller and bind driver. * @pdev: platform device. * * Probe a specific controller instance for binding with the driver. @@ -1055,11 +1056,11 @@ static void setup_address_map(struct synps_edac_priv *priv) * Return: 0 if the controller instance was successfully bound to the * driver; otherwise, < 0 on error. */ -static int mc_probe(struct platform_device *pdev) +static int snps_mc_probe(struct platform_device *pdev) { - const struct synps_platform_data *p_data; + const struct snps_platform_data *p_data; struct edac_mc_layer layers[2]; - struct synps_edac_priv *priv; + struct snps_edac_priv *priv; struct mem_ctl_info *mci; void __iomem *baseaddr; int rc; @@ -1068,7 +1069,7 @@ static int mc_probe(struct platform_device *pdev) if (IS_ERR(baseaddr)) return PTR_ERR(baseaddr); - if (!get_ecc_state(baseaddr)) { + if (!snps_get_ecc_state(baseaddr)) { edac_printk(KERN_INFO, EDAC_MC, "ECC not enabled\n"); return -ENODEV; } @@ -1078,14 +1079,14 @@ static int mc_probe(struct platform_device *pdev) return -ENODEV; layers[0].type = EDAC_MC_LAYER_CHIP_SELECT; - layers[0].size = SYNPS_EDAC_NR_CSROWS; + layers[0].size = SNPS_EDAC_NR_CSROWS; layers[0].is_virt_csrow = true; layers[1].type = EDAC_MC_LAYER_CHANNEL; - layers[1].size = SYNPS_EDAC_NR_CHANS; + layers[1].size = SNPS_EDAC_NR_CHANS; layers[1].is_virt_csrow = false; mci = edac_mc_alloc(EDAC_AUTO_MC_NUM, ARRAY_SIZE(layers), layers, - sizeof(struct synps_edac_priv)); + sizeof(struct snps_edac_priv)); if (!mci) { edac_printk(KERN_ERR, EDAC_MC, "Failed memory allocation for mc instance\n"); @@ -1098,9 +1099,9 @@ static int mc_probe(struct platform_device *pdev) spin_lock_init(&priv->reglock); - mc_init(mci, pdev); + snps_mc_init(mci, pdev); - rc = setup_irq(mci, pdev); + rc = snps_setup_irq(mci, pdev); if (rc) goto free_edac_mc; @@ -1112,13 +1113,13 @@ static int mc_probe(struct platform_device *pdev) } #ifdef CONFIG_EDAC_DEBUG - rc = edac_create_sysfs_attributes(mci); + rc = snps_create_sysfs_attributes(mci); if (rc) { edac_printk(KERN_ERR, EDAC_MC, "Failed to create sysfs entries\n"); goto free_edac_mc; } - setup_address_map(priv); + snps_setup_address_map(priv); #endif return rc; @@ -1130,55 +1131,55 @@ free_edac_mc: } /** - * mc_remove - Unbind driver from controller. + * snps_mc_remove - Unbind driver from device. * @pdev: Platform device. * * Return: Unconditionally 0 */ -static void mc_remove(struct platform_device *pdev) + +static void snps_mc_remove(struct platform_device *pdev) { struct mem_ctl_info *mci = platform_get_drvdata(pdev); - struct synps_edac_priv *priv = mci->pvt_info; + struct snps_edac_priv *priv = mci->pvt_info; - disable_intr(priv); + snps_disable_irq(priv); #ifdef CONFIG_EDAC_DEBUG - edac_remove_sysfs_attributes(mci); + snps_remove_sysfs_attributes(mci); #endif edac_mc_del_mc(&pdev->dev); edac_mc_free(mci); } -static const struct synps_platform_data zynqmp_edac_def = { +static const struct snps_platform_data zynqmp_edac_def = { #ifdef CONFIG_EDAC_DEBUG .get_mem_info = zynqmp_get_mem_info, #endif - .quirks = SYNPS_ZYNQMP_IRQ_REGS, + .quirks = SNPS_ZYNQMP_IRQ_REGS, }; -static const struct synps_platform_data synopsys_edac_def = { +static const struct snps_platform_data snps_edac_def = { .quirks = 0, }; -static const struct of_device_id synps_edac_match[] = { +static const struct of_device_id snps_edac_match[] = { { .compatible = "xlnx,zynqmp-ddrc-2.40a", .data = &zynqmp_edac_def }, - { .compatible = "snps,ddrc-3.80a", .data = &synopsys_edac_def }, + { .compatible = "snps,ddrc-3.80a", .data = &snps_edac_def }, { } }; -MODULE_DEVICE_TABLE(of, synps_edac_match); +MODULE_DEVICE_TABLE(of, snps_edac_match); -static struct platform_driver synps_edac_mc_driver = { +static struct platform_driver snps_edac_mc_driver = { .driver = { - .name = "synopsys-edac", - .of_match_table = synps_edac_match, + .name = "snps-edac", + .of_match_table = snps_edac_match, }, - .probe = mc_probe, - .remove = mc_remove, + .probe = snps_mc_probe, + .remove = snps_mc_remove, }; - -module_platform_driver(synps_edac_mc_driver); +module_platform_driver(snps_edac_mc_driver); MODULE_AUTHOR("Xilinx Inc"); -MODULE_DESCRIPTION("Synopsys DDR ECC driver"); +MODULE_DESCRIPTION("Synopsys uMCTL2 DDR ECC driver"); MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 5511ccb6e8b184c6f076106b812884c29072ec1a Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sun, 17 Jul 2022 11:39:36 +0300 Subject: EDAC/synopsys: Convert to using BIT/GENMASK/FIELD_x macros Instead of using the very handy helpers denoted in the subject the driver has been created with the open-coded {mask,shift} statements. It makes the code bulky, prone to mistakes and much harder to read. Seeing there are many places in the driver implementing the CSR fields get/set pattern use the FIELD_GET()/FIELD_PREP() macros introduced in the kernel specifically for that case. In addition use the BIT() and GENMASK() macros to generate the CSR flags/masks. While at it unify the row, column, rank, bank and bank group macros names to be having a suffix similar to the snps_ecc_error_info structure fields name. Signed-off-by: Serge Semin --- drivers/edac/synopsys_edac.c | 137 +++++++++++++++++++++---------------------- 1 file changed, 67 insertions(+), 70 deletions(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index 40e12b7192a9e..3689f2856f212 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -6,6 +6,8 @@ * Copyright (C) 2012 - 2014 Xilinx, Inc. */ +#include +#include #include #include #include @@ -90,33 +92,29 @@ #define ZYNQMP_DDR_QOS_IRQ_DB_OFST 0x2020C /* DDR Master register definitions */ -#define DDR_MSTR_DEV_CFG_MASK 0xC0000000 -#define DDR_MSTR_DEV_CFG_SHIFT 30 +#define DDR_MSTR_DEV_CFG_MASK GENMASK(31, 30) #define DDR_MSTR_DEV_X4 0 #define DDR_MSTR_DEV_X8 1 #define DDR_MSTR_DEV_X16 2 #define DDR_MSTR_DEV_X32 3 -#define DDR_MSTR_BUSWIDTH_MASK 0x3000 -#define DDR_MSTR_BUSWIDTH_SHIFT 12 +#define DDR_MSTR_BUSWIDTH_MASK GENMASK(13, 12) #define DDR_MSTR_BUSWIDTH_16 2 #define DDR_MSTR_BUSWIDTH_32 1 #define DDR_MSTR_BUSWIDTH_64 0 -#define DDR_MSTR_MEM_LPDDR4 0x20 -#define DDR_MSTR_MEM_DDR4 0x10 -#define DDR_MSTR_MEM_LPDDR3 0x8 -#define DDR_MSTR_MEM_DDR2 0x4 -#define DDR_MSTR_MEM_DDR3 0x1 +#define DDR_MSTR_MEM_LPDDR4 BIT(5) +#define DDR_MSTR_MEM_DDR4 BIT(4) +#define DDR_MSTR_MEM_LPDDR3 BIT(3) +#define DDR_MSTR_MEM_DDR2 BIT(2) +#define DDR_MSTR_MEM_DDR3 BIT(0) /* ECC CFG0 register definitions */ -#define ECC_CFG0_MODE_MASK 0x7 +#define ECC_CFG0_MODE_MASK GENMASK(2, 0) #define ECC_CFG0_MODE_SECDED 0x4 /* ECC status register definitions */ -#define ECC_STAT_UECNT_MASK 0xF0000 -#define ECC_STAT_UECNT_SHIFT 16 -#define ECC_STAT_CECNT_MASK 0xF00 -#define ECC_STAT_CECNT_SHIFT 8 -#define ECC_STAT_BITNUM_MASK 0x7F +#define ECC_STAT_UE_MASK GENMASK(23, 16) +#define ECC_STAT_CE_MASK GENMASK(15, 8) +#define ECC_STAT_BITNUM_MASK GENMASK(6, 0) /* ECC control/clear register definitions */ #define ECC_CTRL_CLR_CE_ERR BIT(0) @@ -127,34 +125,26 @@ #define ECC_CTRL_EN_UE_IRQ BIT(9) /* ECC error count register definitions */ -#define ECC_ERRCNT_UECNT_MASK 0xFFFF0000 -#define ECC_ERRCNT_UECNT_SHIFT 16 -#define ECC_ERRCNT_CECNT_MASK 0xFFFF - -/* ECC Corrected Error Register Mask and Shifts*/ -#define ECC_CEADDR0_RW_MASK 0x3FFFF -#define ECC_CEADDR0_RNK_MASK BIT(24) -#define ECC_CEADDR1_BNKGRP_MASK 0x3000000 -#define ECC_CEADDR1_BNKNR_MASK 0x70000 -#define ECC_CEADDR1_COL_MASK 0xFFF -#define ECC_CEADDR1_BNKGRP_SHIFT 24 -#define ECC_CEADDR1_BNKNR_SHIFT 16 - -/* ECC Poison register shifts */ -#define ECC_POISON0_RANK_SHIFT 24 -#define ECC_POISON0_RANK_MASK BIT(24) -#define ECC_POISON0_COLUMN_SHIFT 0 -#define ECC_POISON0_COLUMN_MASK 0xFFF -#define ECC_POISON1_BG_SHIFT 28 -#define ECC_POISON1_BG_MASK 0x30000000 -#define ECC_POISON1_BANKNR_SHIFT 24 -#define ECC_POISON1_BANKNR_MASK 0x7000000 -#define ECC_POISON1_ROW_SHIFT 0 -#define ECC_POISON1_ROW_MASK 0x3FFFF +#define ECC_ERRCNT_UECNT_MASK GENMASK(31, 16) +#define ECC_ERRCNT_CECNT_MASK GENMASK(15, 0) + +/* ECC Corrected Error register definitions */ +#define ECC_CEADDR0_RANK_MASK GENMASK(27, 24) +#define ECC_CEADDR0_ROW_MASK GENMASK(17, 0) +#define ECC_CEADDR1_BANKGRP_MASK GENMASK(25, 24) +#define ECC_CEADDR1_BANK_MASK GENMASK(23, 16) +#define ECC_CEADDR1_COL_MASK GENMASK(11, 0) + +/* ECC Poison register definitions */ +#define ECC_POISON0_RANK_MASK GENMASK(27, 24) +#define ECC_POISON0_COL_MASK GENMASK(11, 0) +#define ECC_POISON1_BANKGRP_MASK GENMASK(29, 28) +#define ECC_POISON1_BANK_MASK GENMASK(26, 24) +#define ECC_POISON1_ROW_MASK GENMASK(17, 0) /* DDRC ECC CE & UE poison mask */ -#define ECC_CEPOISON_MASK 0x3 -#define ECC_UEPOISON_MASK 0x1 +#define ECC_CEPOISON_MASK GENMASK(1, 0) +#define ECC_UEPOISON_MASK BIT(0) /* DDRC Device config shifts/masks */ #define DDR_MAX_ROW_SHIFT 18 @@ -210,9 +200,9 @@ #define RANK_B0_BASE 6 /* ZynqMP DDR QOS Interrupt register definitions */ -#define ZYNQMP_DDR_QOS_UE_MASK 0x4 -#define ZYNQMP_DDR_QOS_CE_MASK 0x2 -#define ZYNQMP_DDR_QOS_IRQ_MASK 0x6 +#define ZYNQMP_DDR_QOS_UE_MASK BIT(2) +#define ZYNQMP_DDR_QOS_CE_MASK BIT(1) +#define ZYNQMP_DDR_QOS_IRQ_MASK (ZYNQMP_DDR_QOS_UE_MASK | ZYNQMP_DDR_QOS_CE_MASK) /** * struct snps_ecc_error_info - ECC error log information. @@ -327,38 +317,40 @@ static int snps_get_error_info(struct snps_edac_priv *priv) if (!regval) return 1; - p->ceinfo.bitpos = (regval & ECC_STAT_BITNUM_MASK); + p->ceinfo.bitpos = FIELD_GET(ECC_STAT_BITNUM_MASK, regval); regval = readl(base + ECC_ERRCNT_OFST); - p->ce_cnt = regval & ECC_ERRCNT_CECNT_MASK; - p->ue_cnt = (regval & ECC_ERRCNT_UECNT_MASK) >> ECC_ERRCNT_UECNT_SHIFT; + p->ce_cnt = FIELD_GET(ECC_ERRCNT_CECNT_MASK, regval); + p->ue_cnt = FIELD_GET(ECC_ERRCNT_UECNT_MASK, regval); if (!p->ce_cnt) goto ue_err; regval = readl(base + ECC_CEADDR0_OFST); - p->ceinfo.row = (regval & ECC_CEADDR0_RW_MASK); + p->ceinfo.row = FIELD_GET(ECC_CEADDR0_ROW_MASK, regval); + regval = readl(base + ECC_CEADDR1_OFST); - p->ceinfo.bank = (regval & ECC_CEADDR1_BNKNR_MASK) >> - ECC_CEADDR1_BNKNR_SHIFT; - p->ceinfo.bankgrp = (regval & ECC_CEADDR1_BNKGRP_MASK) >> - ECC_CEADDR1_BNKGRP_SHIFT; - p->ceinfo.col = (regval & ECC_CEADDR1_COL_MASK); + p->ceinfo.bank = FIELD_GET(ECC_CEADDR1_BANK_MASK, regval); + p->ceinfo.bankgrp = FIELD_GET(ECC_CEADDR1_BANKGRP_MASK, regval); + p->ceinfo.col = FIELD_GET(ECC_CEADDR1_COL_MASK, regval); + p->ceinfo.data = readl(base + ECC_CSYND0_OFST); + edac_dbg(2, "ECCCSYN0: 0x%08X ECCCSYN1: 0x%08X ECCCSYN2: 0x%08X\n", readl(base + ECC_CSYND0_OFST), readl(base + ECC_CSYND1_OFST), readl(base + ECC_CSYND2_OFST)); + ue_err: if (!p->ue_cnt) goto out; regval = readl(base + ECC_UEADDR0_OFST); - p->ueinfo.row = (regval & ECC_CEADDR0_RW_MASK); + p->ueinfo.row = FIELD_GET(ECC_CEADDR0_ROW_MASK, regval); + regval = readl(base + ECC_UEADDR1_OFST); - p->ueinfo.bankgrp = (regval & ECC_CEADDR1_BNKGRP_MASK) >> - ECC_CEADDR1_BNKGRP_SHIFT; - p->ueinfo.bank = (regval & ECC_CEADDR1_BNKNR_MASK) >> - ECC_CEADDR1_BNKNR_SHIFT; - p->ueinfo.col = (regval & ECC_CEADDR1_COL_MASK); + p->ueinfo.bankgrp = FIELD_GET(ECC_CEADDR1_BANKGRP_MASK, regval); + p->ueinfo.bank = FIELD_GET(ECC_CEADDR1_BANK_MASK, regval); + p->ueinfo.col = FIELD_GET(ECC_CEADDR1_COL_MASK, regval); + p->ueinfo.data = readl(base + ECC_UESYND0_OFST); out: spin_lock_irqsave(&priv->reglock, flags); @@ -507,7 +499,7 @@ static enum dev_type snps_get_dtype(const void __iomem *base) if (!(regval & DDR_MSTR_MEM_DDR4)) return DEV_UNKNOWN; - regval = (regval & DDR_MSTR_DEV_CFG_MASK) >> DDR_MSTR_DEV_CFG_SHIFT; + regval = FIELD_GET(DDR_MSTR_DEV_CFG_MASK, regval); switch (regval) { case DDR_MSTR_DEV_X4: return DEV_X4; @@ -535,7 +527,8 @@ static bool snps_get_ecc_state(void __iomem *base) { u32 ecctype, clearval; - ecctype = readl(base + ECC_CFG0_OFST) & ECC_CFG0_MODE_MASK; + ecctype = readl(base + ECC_CFG0_OFST); + ecctype = FIELD_GET(ECC_CFG0_MODE_MASK, ecctype); if (ecctype == ECC_CFG0_MODE_SECDED) { clearval = readl(base + ECC_CLR_OFST) | ECC_CTRL_CLR_CE_ERR | ECC_CTRL_CLR_CE_ERRCNT | @@ -735,13 +728,13 @@ static void snps_data_poison_setup(struct snps_edac_priv *priv) if (priv->rank_shift[0]) rank = (hif_addr >> priv->rank_shift[0]) & BIT(0); - regval = (rank << ECC_POISON0_RANK_SHIFT) & ECC_POISON0_RANK_MASK; - regval |= (col << ECC_POISON0_COLUMN_SHIFT) & ECC_POISON0_COLUMN_MASK; + regval = FIELD_PREP(ECC_POISON0_RANK_MASK, rank) | + FIELD_PREP(ECC_POISON0_COL_MASK, col); writel(regval, priv->baseaddr + ECC_POISON0_OFST); - regval = (bankgrp << ECC_POISON1_BG_SHIFT) & ECC_POISON1_BG_MASK; - regval |= (bank << ECC_POISON1_BANKNR_SHIFT) & ECC_POISON1_BANKNR_MASK; - regval |= (row << ECC_POISON1_ROW_SHIFT) & ECC_POISON1_ROW_MASK; + regval = FIELD_PREP(ECC_POISON1_BANKGRP_MASK, bankgrp) | + FIELD_PREP(ECC_POISON1_BANK_MASK, bank) | + FIELD_PREP(ECC_POISON1_ROW_MASK, row); writel(regval, priv->baseaddr + ECC_POISON1_OFST); } @@ -780,10 +773,14 @@ static ssize_t inject_data_poison_show(struct device *dev, { struct mem_ctl_info *mci = to_mci(dev); struct snps_edac_priv *priv = mci->pvt_info; + const char *errstr; + u32 regval; + + regval = readl(priv->baseaddr + ECC_CFG1_OFST); + errstr = FIELD_GET(ECC_CEPOISON_MASK, regval) == ECC_CEPOISON_MASK ? + "Correctable Error" : "UnCorrectable Error"; - return sprintf(data, "Data Poisoning: %s\n\r", - (((readl(priv->baseaddr + ECC_CFG1_OFST)) & 0x3) == 0x3) - ? ("Correctable Error") : ("UnCorrectable Error")); + return sprintf(data, "Data Poisoning: %s\n\r", errstr); } static ssize_t inject_data_poison_store(struct device *dev, @@ -890,7 +887,7 @@ static void snps_setup_column_address_map(struct snps_edac_priv *priv, u32 *addr int index; memtype = readl(priv->baseaddr + DDR_MSTR_OFST); - width = (memtype & DDR_MSTR_BUSWIDTH_MASK) >> DDR_MSTR_BUSWIDTH_SHIFT; + width = FIELD_GET(DDR_MSTR_BUSWIDTH_MASK, memtype); priv->col_shift[0] = 0; priv->col_shift[1] = 1; -- cgit v1.2.3 From bebb9d07cbd4ddb7ba4012870dd7ed420eb4a9c9 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 18 Jul 2022 16:22:48 +0300 Subject: EDAC/synopsys: Convert sysfs nodes to debugfs ones The DW uMCTL2 DDRC EDAC driver supports creating two sysfs nodes: "inject_data_error" and "inject_data_poison". First of them is responsible for setting the error-injecting physical address up. The second one is supposed to be used to enable the hardware capability of the correctable and uncorrectable errors injection. The semantics of these nodes is pure debug. They are even created only if the EDAC_DEBUG kernel config is enabled. Thus there is no point in having these nodes exported in the sysfs especially seeing they aren't listed in the sysfs ABI. Just move them to the device private directory in DebugFS. While at it move the address map initialization procedure call to the DebugFS nodes creating function (it's useless with no DebugFS nodes being available anyway) and create an empty snps_create_debugfs_nodes() method in case if the EDAC_DEBUG config is disabled. Thus the DW uMCTL2 DDRC EDAC probe procedure will get to be a bit simpler and redundant work won't be performed. Signed-off-by: Serge Semin --- drivers/edac/synopsys_edac.c | 221 ++++++++++++++++++++++++------------------- 1 file changed, 122 insertions(+), 99 deletions(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index 3689f2856f212..dcb451aa248e4 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -672,6 +673,16 @@ static int snps_setup_irq(struct mem_ctl_info *mci, struct platform_device *pdev #ifdef CONFIG_EDAC_DEBUG +#define SNPS_DEBUGFS_FOPS(__name, __read, __write) \ + static const struct file_operations __name = { \ + .owner = THIS_MODULE, \ + .open = simple_open, \ + .read = __read, \ + .write = __write, \ + } + +#define SNPS_DBGFS_BUF_LEN 128 + /** * snps_data_poison_setup - Update poison registers. * @priv: DDR memory controller private instance data. @@ -738,90 +749,6 @@ static void snps_data_poison_setup(struct snps_edac_priv *priv) writel(regval, priv->baseaddr + ECC_POISON1_OFST); } -static ssize_t inject_data_error_show(struct device *dev, - struct device_attribute *mattr, - char *data) -{ - struct mem_ctl_info *mci = to_mci(dev); - struct snps_edac_priv *priv = mci->pvt_info; - - return sprintf(data, "Poison0 Addr: 0x%08x\n\rPoison1 Addr: 0x%08x\n\r" - "Error injection Address: 0x%lx\n\r", - readl(priv->baseaddr + ECC_POISON0_OFST), - readl(priv->baseaddr + ECC_POISON1_OFST), - priv->poison_addr); -} - -static ssize_t inject_data_error_store(struct device *dev, - struct device_attribute *mattr, - const char *data, size_t count) -{ - struct mem_ctl_info *mci = to_mci(dev); - struct snps_edac_priv *priv = mci->pvt_info; - - if (kstrtoul(data, 0, &priv->poison_addr)) - return -EINVAL; - - snps_data_poison_setup(priv); - - return count; -} - -static ssize_t inject_data_poison_show(struct device *dev, - struct device_attribute *mattr, - char *data) -{ - struct mem_ctl_info *mci = to_mci(dev); - struct snps_edac_priv *priv = mci->pvt_info; - const char *errstr; - u32 regval; - - regval = readl(priv->baseaddr + ECC_CFG1_OFST); - errstr = FIELD_GET(ECC_CEPOISON_MASK, regval) == ECC_CEPOISON_MASK ? - "Correctable Error" : "UnCorrectable Error"; - - return sprintf(data, "Data Poisoning: %s\n\r", errstr); -} - -static ssize_t inject_data_poison_store(struct device *dev, - struct device_attribute *mattr, - const char *data, size_t count) -{ - struct mem_ctl_info *mci = to_mci(dev); - struct snps_edac_priv *priv = mci->pvt_info; - - writel(0, priv->baseaddr + DDR_SWCTL); - if (strncmp(data, "CE", 2) == 0) - writel(ECC_CEPOISON_MASK, priv->baseaddr + ECC_CFG1_OFST); - else - writel(ECC_UEPOISON_MASK, priv->baseaddr + ECC_CFG1_OFST); - writel(1, priv->baseaddr + DDR_SWCTL); - - return count; -} - -static DEVICE_ATTR_RW(inject_data_error); -static DEVICE_ATTR_RW(inject_data_poison); - -static int snps_create_sysfs_attributes(struct mem_ctl_info *mci) -{ - int rc; - - rc = device_create_file(&mci->dev, &dev_attr_inject_data_error); - if (rc < 0) - return rc; - rc = device_create_file(&mci->dev, &dev_attr_inject_data_poison); - if (rc < 0) - return rc; - return 0; -} - -static void snps_remove_sysfs_attributes(struct mem_ctl_info *mci) -{ - device_remove_file(&mci->dev, &dev_attr_inject_data_error); - device_remove_file(&mci->dev, &dev_attr_inject_data_poison); -} - static void snps_setup_row_address_map(struct snps_edac_priv *priv, u32 *addrmap) { u32 addrmap_row_b2_10; @@ -1042,7 +969,115 @@ static void snps_setup_address_map(struct snps_edac_priv *priv) snps_setup_rank_address_map(priv, addrmap); } -#endif /* CONFIG_EDAC_DEBUG */ + +static ssize_t snps_inject_data_error_read(struct file *filep, char __user *ubuf, + size_t size, loff_t *offp) +{ + struct mem_ctl_info *mci = filep->private_data; + struct snps_edac_priv *priv = mci->pvt_info; + char buf[SNPS_DBGFS_BUF_LEN]; + int pos; + + pos = scnprintf(buf, sizeof(buf), "Poison0 Addr: 0x%08x\n\r", + readl(priv->baseaddr + ECC_POISON0_OFST)); + pos += scnprintf(buf + pos, sizeof(buf) - pos, "Poison1 Addr: 0x%08x\n\r", + readl(priv->baseaddr + ECC_POISON1_OFST)); + pos += scnprintf(buf + pos, sizeof(buf) - pos, "Error injection Address: 0x%lx\n\r", + priv->poison_addr); + + return simple_read_from_buffer(ubuf, size, offp, buf, pos); +} + +static ssize_t snps_inject_data_error_write(struct file *filep, const char __user *ubuf, + size_t size, loff_t *offp) +{ + struct mem_ctl_info *mci = filep->private_data; + struct snps_edac_priv *priv = mci->pvt_info; + int rc; + + rc = kstrtoul_from_user(ubuf, size, 0, &priv->poison_addr); + if (rc) + return rc; + + snps_data_poison_setup(priv); + + return size; +} + +SNPS_DEBUGFS_FOPS(snps_inject_data_error, snps_inject_data_error_read, + snps_inject_data_error_write); + +static ssize_t snps_inject_data_poison_read(struct file *filep, char __user *ubuf, + size_t size, loff_t *offp) +{ + struct mem_ctl_info *mci = filep->private_data; + struct snps_edac_priv *priv = mci->pvt_info; + char buf[SNPS_DBGFS_BUF_LEN]; + const char *errstr; + u32 regval; + int pos; + + regval = readl(priv->baseaddr + ECC_CFG1_OFST); + errstr = FIELD_GET(ECC_CEPOISON_MASK, regval) == ECC_CEPOISON_MASK ? + "Correctable Error" : "UnCorrectable Error"; + + pos = scnprintf(buf, sizeof(buf), "Data Poisoning: %s\n\r", errstr); + + return simple_read_from_buffer(ubuf, size, offp, buf, pos); +} + +static ssize_t snps_inject_data_poison_write(struct file *filep, const char __user *ubuf, + size_t size, loff_t *offp) +{ + struct mem_ctl_info *mci = filep->private_data; + struct snps_edac_priv *priv = mci->pvt_info; + char buf[SNPS_DBGFS_BUF_LEN]; + int rc; + + rc = simple_write_to_buffer(buf, sizeof(buf), offp, ubuf, size); + if (rc < 0) + return rc; + + writel(0, priv->baseaddr + DDR_SWCTL); + if (strncmp(buf, "CE", 2) == 0) + writel(ECC_CEPOISON_MASK, priv->baseaddr + ECC_CFG1_OFST); + else + writel(ECC_UEPOISON_MASK, priv->baseaddr + ECC_CFG1_OFST); + writel(1, priv->baseaddr + DDR_SWCTL); + + return size; +} + +SNPS_DEBUGFS_FOPS(snps_inject_data_poison, snps_inject_data_poison_read, + snps_inject_data_poison_write); + +/** + * snps_create_debugfs_nodes - Create DebugFS nodes. + * @mci: EDAC memory controller instance. + * + * Create DW uMCTL2 EDAC driver DebugFS nodes in the device private + * DebugFS directory. + * + * Return: none. + */ +static void snps_create_debugfs_nodes(struct mem_ctl_info *mci) +{ + struct snps_edac_priv *priv = mci->pvt_info; + + snps_setup_address_map(priv); + + edac_debugfs_create_file("inject_data_error", 0600, mci->debugfs, mci, + &snps_inject_data_error); + + edac_debugfs_create_file("inject_data_poison", 0600, mci->debugfs, mci, + &snps_inject_data_poison); +} + +#else /* !CONFIG_EDAC_DEBUG */ + +static inline void snps_create_debugfs_nodes(struct mem_ctl_info *mci) {} + +#endif /* !CONFIG_EDAC_DEBUG */ /** * snps_mc_probe - Check controller and bind driver. @@ -1109,17 +1144,9 @@ static int snps_mc_probe(struct platform_device *pdev) goto free_edac_mc; } -#ifdef CONFIG_EDAC_DEBUG - rc = snps_create_sysfs_attributes(mci); - if (rc) { - edac_printk(KERN_ERR, EDAC_MC, "Failed to create sysfs entries\n"); - goto free_edac_mc; - } + snps_create_debugfs_nodes(mci); - snps_setup_address_map(priv); -#endif - - return rc; + return 0; free_edac_mc: edac_mc_free(mci); @@ -1141,10 +1168,6 @@ static void snps_mc_remove(struct platform_device *pdev) snps_disable_irq(priv); -#ifdef CONFIG_EDAC_DEBUG - snps_remove_sysfs_attributes(mci); -#endif - edac_mc_del_mc(&pdev->dev); edac_mc_free(mci); } -- cgit v1.2.3 From 2e743cd441c2cf1eca0dae3c77002793a92e6d9a Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 21 Jul 2022 12:19:35 +0300 Subject: EDAC/mc: Extend memtypes with LPDDR(mDDR) and LPDDR2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These are normal memory types [1] which can be met on the real hardware. DW uMCTL2 DDRC IP-core can be configured to have them supported [2,3]. Extend the EDAC memory types enumeration with the corresponding IDs then. [1] https://en.wikipedia.org/wiki/LPDDR [2] DesignWare® Cores Enhanced Universal DDR Memory Controller (uMCTL2) Databook, Version 3.91a, October 2020, p.501 [3] DesignWare® Cores Enhanced Universal DDR Memory Controller (uMCTL2) Databook, Version 3.91a, October 2020, p.1717 Signed-off-by: Serge Semin --- drivers/edac/edac_mc.c | 2 ++ include/linux/edac.h | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/drivers/edac/edac_mc.c b/drivers/edac/edac_mc.c index 5773ac0a07cb6..f6b822c35ac80 100644 --- a/drivers/edac/edac_mc.c +++ b/drivers/edac/edac_mc.c @@ -151,10 +151,12 @@ const char * const edac_mem_types[] = { [MEM_RDR] = "Registered-SDR", [MEM_DDR] = "Unbuffered-DDR", [MEM_RDDR] = "Registered-DDR", + [MEM_LPDDR] = "Low-Power-(m)DDR-RAM", [MEM_RMBS] = "RMBS", [MEM_DDR2] = "Unbuffered-DDR2", [MEM_FB_DDR2] = "FullyBuffered-DDR2", [MEM_RDDR2] = "Registered-DDR2", + [MEM_LPDDR2] = "Low-Power-DDR2-RAM", [MEM_XDR] = "XDR", [MEM_DDR3] = "Unbuffered-DDR3", [MEM_RDDR3] = "Registered-DDR3", diff --git a/include/linux/edac.h b/include/linux/edac.h index fa32f2aca22fd..f7c20693d23ff 100644 --- a/include/linux/edac.h +++ b/include/linux/edac.h @@ -157,6 +157,7 @@ static inline char *mc_event_error_type(const unsigned int err_type) * This is a variant of the DDR memories. * A registered memory has a buffer inside it, hiding * part of the memory details to the memory controller. + * @MEM_LPDDR: Low-Power DDR memory (mDDR). * @MEM_RMBS: Rambus DRAM, used on a few Pentium III/IV controllers. * @MEM_DDR2: DDR2 RAM, as described at JEDEC JESD79-2F. * Those memories are labeled as "PC2-" instead of "PC" to @@ -167,6 +168,7 @@ static inline char *mc_event_error_type(const unsigned int err_type) * a chip select signal. * @MEM_RDDR2: Registered DDR2 RAM * This is a variant of the DDR2 memories. + * @MEM_LPDDR2: Low-Power DDR2 memory. * @MEM_XDR: Rambus XDR * It is an evolution of the original RAMBUS memories, * created to compete with DDR2. Weren't used on any @@ -200,10 +202,12 @@ enum mem_type { MEM_RDR, MEM_DDR, MEM_RDDR, + MEM_LPDDR, MEM_RMBS, MEM_DDR2, MEM_FB_DDR2, MEM_RDDR2, + MEM_LPDDR2, MEM_XDR, MEM_DDR3, MEM_RDDR3, @@ -232,10 +236,12 @@ enum mem_type { #define MEM_FLAG_RDR BIT(MEM_RDR) #define MEM_FLAG_DDR BIT(MEM_DDR) #define MEM_FLAG_RDDR BIT(MEM_RDDR) +#define MEM_FLAG_LPDDR BIT(MEM_LPDDR) #define MEM_FLAG_RMBS BIT(MEM_RMBS) #define MEM_FLAG_DDR2 BIT(MEM_DDR2) #define MEM_FLAG_FB_DDR2 BIT(MEM_FB_DDR2) #define MEM_FLAG_RDDR2 BIT(MEM_RDDR2) +#define MEM_FLAG_LPDDR2 BIT(MEM_LPDDR2) #define MEM_FLAG_XDR BIT(MEM_XDR) #define MEM_FLAG_DDR3 BIT(MEM_DDR3) #define MEM_FLAG_RDDR3 BIT(MEM_RDDR3) -- cgit v1.2.3 From e155d20a271f2270a92f65fece4f59f1184b6fb8 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 21 Jul 2022 17:39:46 +0300 Subject: EDAC/synopsys: Extend memtypes supported by controller MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In accordance with [1] the DW uMCTL2 DDR controllers can support the next DDR protocols: LPDDR, (LP)DDR(2|3|4). Even if the controller is configured to support several of these memory chip types only one of these modes could be enabled at runtime [2]. Taking all of that into account update the snps_get_mtype() procedure so the DW uMCTL2 DDRC driver would be able to detect all the claimed to be supported memory types in accordance with the table defined in [2]. Note alas it's not possible do determine which MEMC DDR configs were enabled at the IP-core synthesize. Therefore there is no other choice but to initialize the EDAC MC mem-types capability field with all the types claimed to be supported by the IP-core. [1] DesignWare® Cores Enhanced Universal DDR Memory Controller (uMCTL2) Databook, Version 3.91a, October 2020, p.501 [2] DesignWare® Cores Enhanced Universal DDR Memory Controller (uMCTL2) Databook, Version 3.91a, October 2020, p.501 Signed-off-by: Serge Semin --- drivers/edac/synopsys_edac.c | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index dcb451aa248e4..83c6f15e63568 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -102,11 +102,14 @@ #define DDR_MSTR_BUSWIDTH_16 2 #define DDR_MSTR_BUSWIDTH_32 1 #define DDR_MSTR_BUSWIDTH_64 0 +#define DDR_MSTR_MEM_MASK GENMASK(5, 0) #define DDR_MSTR_MEM_LPDDR4 BIT(5) #define DDR_MSTR_MEM_DDR4 BIT(4) #define DDR_MSTR_MEM_LPDDR3 BIT(3) -#define DDR_MSTR_MEM_DDR2 BIT(2) +#define DDR_MSTR_MEM_LPDDR2 BIT(2) +#define DDR_MSTR_MEM_LPDDR BIT(1) #define DDR_MSTR_MEM_DDR3 BIT(0) +#define DDR_MSTR_MEM_DDR2 0 /* ECC CFG0 register definitions */ #define ECC_CFG0_MODE_MASK GENMASK(2, 0) @@ -566,21 +569,29 @@ static u32 snps_get_memsize(void) */ static enum mem_type snps_get_mtype(const void __iomem *base) { - enum mem_type mt; - u32 memtype; + u32 regval; - memtype = readl(base + DDR_MSTR_OFST); + regval = readl(base + DDR_MSTR_OFST); + regval = FIELD_GET(DDR_MSTR_MEM_MASK, regval); - if ((memtype & DDR_MSTR_MEM_DDR3) || (memtype & DDR_MSTR_MEM_LPDDR3)) - mt = MEM_DDR3; - else if (memtype & DDR_MSTR_MEM_DDR2) - mt = MEM_RDDR2; - else if ((memtype & DDR_MSTR_MEM_LPDDR4) || (memtype & DDR_MSTR_MEM_DDR4)) - mt = MEM_DDR4; - else - mt = MEM_EMPTY; + switch (regval) { + case DDR_MSTR_MEM_DDR2: + return MEM_DDR2; + case DDR_MSTR_MEM_DDR3: + return MEM_DDR3; + case DDR_MSTR_MEM_LPDDR: + return MEM_LPDDR; + case DDR_MSTR_MEM_LPDDR2: + return MEM_LPDDR2; + case DDR_MSTR_MEM_LPDDR3: + return MEM_LPDDR3; + case DDR_MSTR_MEM_DDR4: + return MEM_DDR4; + case DDR_MSTR_MEM_LPDDR4: + return MEM_LPDDR4; + } - return mt; + return MEM_RESERVED; } /** @@ -628,7 +639,9 @@ static void snps_mc_init(struct mem_ctl_info *mci, struct platform_device *pdev) platform_set_drvdata(pdev, mci); /* Initialize controller capabilities and configuration */ - mci->mtype_cap = MEM_FLAG_DDR3 | MEM_FLAG_DDR2; + mci->mtype_cap = MEM_FLAG_LPDDR | MEM_FLAG_DDR2 | MEM_FLAG_LPDDR2 | + MEM_FLAG_DDR3 | MEM_FLAG_LPDDR3 | + MEM_FLAG_DDR4 | MEM_FLAG_LPDDR4; mci->edac_ctl_cap = EDAC_FLAG_NONE | EDAC_FLAG_SECDED; mci->scrub_cap = SCRUB_FLAG_HW_SRC; mci->scrub_mode = SCRUB_NONE; -- cgit v1.2.3 From 76e71dcdd22260da74d026a3794f60762a8ea114 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 25 Jul 2022 23:09:49 +0300 Subject: EDAC/synopsys: Detach private data from mci instance A comprehensive DW uMCTL2 DDRC parameters detection procedure and some resources requests (clocks and resets) are about to be added to the driver. Since these parameters will be utilized in the various parts of the driver and in particular used for the Memory Controller data instance pre-initialization, they need to be: first retrieved before the MCI is allocated; second preserved in the driver private data. Therefore the best approach would be to add the parameters structure right into the driver private data and just allocate the data separately from the mem_ctl_info instance. For that: add a new static method snps_data_create(), which aside with the snps_edac_priv structure allocation will also perform the private data basic initialization like CSRs region mapping, device data getting, platform data pointer copying and spin-lock initialization; convert the snps_mc_init() method to snps_mc_create(), which from now will be used to allocate and initialize the mem_ctl_info structure instance. Note in order to have an access to the snps_edac_priv structure instance as before this change, the mem_ctl_info.pvt_info field will be initialized with the pointer to that structure instance. Signed-off-by: Serge Semin --- drivers/edac/synopsys_edac.c | 138 ++++++++++++++++++++++++++++--------------- 1 file changed, 89 insertions(+), 49 deletions(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index 83c6f15e63568..3f3533835eb20 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -242,6 +242,7 @@ struct snps_ecc_status { /** * struct snps_edac_priv - DDR memory controller private data. + * @pdev: Platform device. * @baseaddr: Base address of the DDR controller. * @reglock: Concurrent CSRs access lock. * @message: Buffer for framing the event specific info. @@ -255,6 +256,7 @@ struct snps_ecc_status { * @rank_shift: Bit shifts for rank bit. */ struct snps_edac_priv { + struct platform_device *pdev; void __iomem *baseaddr; spinlock_t reglock; char message[SNPS_EDAC_MSG_SIZE]; @@ -486,6 +488,34 @@ static irqreturn_t snps_irq_handler(int irq, void *dev_id) return IRQ_HANDLED; } +/** + * snps_create_data - Create private data. + * @pdev: platform device. + * + * Return: Private data instance or negative errno. + */ +static struct snps_edac_priv *snps_create_data(struct platform_device *pdev) +{ + struct snps_edac_priv *priv; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return ERR_PTR(-ENOMEM); + + priv->baseaddr = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->baseaddr)) + return ERR_CAST(priv->baseaddr); + + priv->p_data = of_device_get_match_data(&pdev->dev); + if (!priv->p_data) + return ERR_PTR(-ENODEV); + + priv->pdev = pdev; + spin_lock_init(&priv->reglock); + + return priv; +} + /** * snps_get_dtype - Return the DDR memory chips type. * @base: DDR memory controller base address. @@ -625,18 +655,36 @@ static void snps_init_csrows(struct mem_ctl_info *mci) } /** - * snps_mc_init - Initialize one driver instance. - * @mci: EDAC memory controller instance. - * @pdev: platform device. + * snps_mc_create - Create and initialize MC instance. + * @priv: DDR memory controller private data. + * + * Allocate the EDAC memory controller descriptor and initialize it + * using the private data info. * - * Perform initialization of the EDAC memory controller instance and - * related driver-private data associated with the memory controller the - * instance is bound to. + * Return: MC data instance or negative errno. */ -static void snps_mc_init(struct mem_ctl_info *mci, struct platform_device *pdev) +static struct mem_ctl_info *snps_mc_create(struct snps_edac_priv *priv) { - mci->pdev = &pdev->dev; - platform_set_drvdata(pdev, mci); + struct edac_mc_layer layers[2]; + struct mem_ctl_info *mci; + + layers[0].type = EDAC_MC_LAYER_CHIP_SELECT; + layers[0].size = SNPS_EDAC_NR_CSROWS; + layers[0].is_virt_csrow = true; + layers[1].type = EDAC_MC_LAYER_CHANNEL; + layers[1].size = SNPS_EDAC_NR_CHANS; + layers[1].is_virt_csrow = false; + + mci = edac_mc_alloc(EDAC_AUTO_MC_NUM, ARRAY_SIZE(layers), layers, 0); + if (!mci) { + edac_printk(KERN_ERR, EDAC_MC, + "Failed memory allocation for mc instance\n"); + return ERR_PTR(-ENOMEM); + } + + mci->pvt_info = priv; + mci->pdev = &priv->pdev->dev; + platform_set_drvdata(priv->pdev, mci); /* Initialize controller capabilities and configuration */ mci->mtype_cap = MEM_FLAG_LPDDR | MEM_FLAG_DDR2 | MEM_FLAG_LPDDR2 | @@ -656,24 +704,41 @@ static void snps_mc_init(struct mem_ctl_info *mci, struct platform_device *pdev) mci->ctl_page_to_phys = NULL; snps_init_csrows(mci); + + return mci; } +/** + * snps_mc_free - Free MC instance. + * @mci: EDAC memory controller instance. + * + * Just revert what was done in the framework of the snps_mc_create(). + * + * Return: MC data instance or negative errno. + */ +static void snps_mc_free(struct mem_ctl_info *mci) +{ + struct snps_edac_priv *priv = mci->pvt_info; + + platform_set_drvdata(priv->pdev, NULL); + edac_mc_free(mci); +} -static int snps_setup_irq(struct mem_ctl_info *mci, struct platform_device *pdev) +static int snps_setup_irq(struct mem_ctl_info *mci) { struct snps_edac_priv *priv = mci->pvt_info; int ret, irq; - irq = platform_get_irq(pdev, 0); + irq = platform_get_irq(priv->pdev, 0); if (irq < 0) { edac_printk(KERN_ERR, EDAC_MC, "No IRQ %d in DT\n", irq); return irq; } - ret = devm_request_irq(&pdev->dev, irq, snps_irq_handler, - 0, dev_name(&pdev->dev), mci); + ret = devm_request_irq(&priv->pdev->dev, irq, snps_irq_handler, + 0, dev_name(&priv->pdev->dev), mci); if (ret < 0) { edac_printk(KERN_ERR, EDAC_MC, "Failed to request IRQ\n"); return ret; @@ -1103,50 +1168,24 @@ static inline void snps_create_debugfs_nodes(struct mem_ctl_info *mci) {} */ static int snps_mc_probe(struct platform_device *pdev) { - const struct snps_platform_data *p_data; - struct edac_mc_layer layers[2]; struct snps_edac_priv *priv; struct mem_ctl_info *mci; - void __iomem *baseaddr; int rc; - baseaddr = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(baseaddr)) - return PTR_ERR(baseaddr); + priv = snps_create_data(pdev); + if (IS_ERR(priv)) + return PTR_ERR(priv); - if (!snps_get_ecc_state(baseaddr)) { + if (!snps_get_ecc_state(priv->baseaddr)) { edac_printk(KERN_INFO, EDAC_MC, "ECC not enabled\n"); return -ENODEV; } - p_data = of_device_get_match_data(&pdev->dev); - if (!p_data) - return -ENODEV; + mci = snps_mc_create(priv); + if (IS_ERR(mci)) + return PTR_ERR(mci); - layers[0].type = EDAC_MC_LAYER_CHIP_SELECT; - layers[0].size = SNPS_EDAC_NR_CSROWS; - layers[0].is_virt_csrow = true; - layers[1].type = EDAC_MC_LAYER_CHANNEL; - layers[1].size = SNPS_EDAC_NR_CHANS; - layers[1].is_virt_csrow = false; - - mci = edac_mc_alloc(EDAC_AUTO_MC_NUM, ARRAY_SIZE(layers), layers, - sizeof(struct snps_edac_priv)); - if (!mci) { - edac_printk(KERN_ERR, EDAC_MC, - "Failed memory allocation for mc instance\n"); - return -ENOMEM; - } - - priv = mci->pvt_info; - priv->baseaddr = baseaddr; - priv->p_data = p_data; - - spin_lock_init(&priv->reglock); - - snps_mc_init(mci, pdev); - - rc = snps_setup_irq(mci, pdev); + rc = snps_setup_irq(mci); if (rc) goto free_edac_mc; @@ -1162,7 +1201,7 @@ static int snps_mc_probe(struct platform_device *pdev) return 0; free_edac_mc: - edac_mc_free(mci); + snps_mc_free(mci); return rc; } @@ -1182,7 +1221,8 @@ static void snps_mc_remove(struct platform_device *pdev) snps_disable_irq(priv); edac_mc_del_mc(&pdev->dev); - edac_mc_free(mci); + + snps_mc_free(mci); } static const struct snps_platform_data zynqmp_edac_def = { -- cgit v1.2.3 From 1430eeac7c7d7e52f517b8e98ddc1b1dff654cb4 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Tue, 26 Jul 2022 12:58:22 +0300 Subject: EDAC/synopsys: Add DDRC basic parameters infrastructure Currently the driver supports a single DW uMCTL2 DDRC IP-core: 64-bit DQ bus, ECC SEC/DED with always on Scrub (HW-src scrub). This makes the driver application being very limited. In addition to that lacking of any controller capabilities/parameters infrastructure makes it harder to add optional features support. Let's overcome all of that by adding a simple DW uMCTL2 DDRC IP-core parameters infrastructure. It's made of the snps_ddrc_info structure and a new method snps_get_ddrc_info() introduced to fill the structure fields in. The structure contains the IP-core parameters needed to create a more comprehensive driver and will be used in the driver to activate/de-activate various features: - ECC Mode: SEC/DED or Advanced X4/X8 ECC features. (Currently SEC/DED is only supported.) - SDRAM mode: (LP)DDR[2-4] memory interfaces. (Required for the HIF/SDRAM address translation.) - Memory Device config: Memory chips detected on the platform. (Applicable for DDR4 setups only.) - DQ-bus width: Maximal DQ-bus width utilized by the device. (Required for the Application/HIF address translation.) - DQ-bus mode: Actual DQ-bus width used to access the memory devices. (Required for the HIF/SDRAM address translation and ECC grain calc.) - HIF/SDRAM burst length. (Required for the Scrubber bandwidth calculation.) - HIF/SDRAM frequency ratio. (Required for the SDRAM bandwidth calculation.) - SDRAM ranks number. (Required for the Application/HIF address translation.) The list can be easily updated should any additional features support is required to be added in future, but at this stage the driver is fixed in a few places to have the new infrastructure utilized: SDRAM column address mapper, MCI csrows initialization. Note getting all of these parameters in a single method is very suitable from two perspectives. First it localizes the IP-core parameters detection thus improving the code readability and maintainability. Second it's very suitable for the platform-specific quirks implementation. Since some of the IP-core parameters can't be auto-detected at run-time, they will be able to be fixed right in the parameters getter by means of the platform quirks. Signed-off-by: Serge Semin --- Changelog v5: - Use '@' in the snps_dq_width enumeration kdoc (@tbot) --- drivers/edac/synopsys_edac.c | 282 +++++++++++++++++++++++++++++++++---------- 1 file changed, 217 insertions(+), 65 deletions(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index 3f3533835eb20..8d2900cdd7206 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -87,6 +88,11 @@ /* DDR Software Control Register */ #define DDR_SWCTL 0x320 +/* ECC Poison Pattern Registers */ +#define ECC_POISONPAT0_OFST 0x37C +#define ECC_POISONPAT1_OFST 0x380 +#define ECC_POISONPAT2_OFST 0x384 + /* ZynqMP DDR QOS Registers */ #define ZYNQMP_DDR_QOS_IRQ_STAT_OFST 0x20200 #define ZYNQMP_DDR_QOS_IRQ_EN_OFST 0x20208 @@ -98,10 +104,10 @@ #define DDR_MSTR_DEV_X8 1 #define DDR_MSTR_DEV_X16 2 #define DDR_MSTR_DEV_X32 3 +#define DDR_MSTR_ACT_RANKS_MASK GENMASK(27, 24) +#define DDR_MSTR_FREQ_RATIO11 BIT(22) +#define DDR_MSTR_BURST_RDWR GENMASK(19, 16) #define DDR_MSTR_BUSWIDTH_MASK GENMASK(13, 12) -#define DDR_MSTR_BUSWIDTH_16 2 -#define DDR_MSTR_BUSWIDTH_32 1 -#define DDR_MSTR_BUSWIDTH_64 0 #define DDR_MSTR_MEM_MASK GENMASK(5, 0) #define DDR_MSTR_MEM_LPDDR4 BIT(5) #define DDR_MSTR_MEM_DDR4 BIT(4) @@ -113,7 +119,6 @@ /* ECC CFG0 register definitions */ #define ECC_CFG0_MODE_MASK GENMASK(2, 0) -#define ECC_CFG0_MODE_SECDED 0x4 /* ECC status register definitions */ #define ECC_STAT_UE_MASK GENMASK(23, 16) @@ -208,6 +213,91 @@ #define ZYNQMP_DDR_QOS_CE_MASK BIT(1) #define ZYNQMP_DDR_QOS_IRQ_MASK (ZYNQMP_DDR_QOS_UE_MASK | ZYNQMP_DDR_QOS_CE_MASK) +/** + * enum snps_dq_width - SDRAM DQ bus width (ECC capable). + * @SNPS_DQ_32: 32-bit memory data width. + * @SNPS_DQ_64: 64-bit memory data width. + */ +enum snps_dq_width { + SNPS_DQ_32 = 2, + SNPS_DQ_64 = 3, +}; + +/** + * enum snps_dq_mode - SDRAM DQ bus mode. + * @SNPS_DQ_FULL: Full DQ bus width. + * @SNPS_DQ_HALF: Half DQ bus width. + * @SNPS_DQ_QRTR: Quarter DQ bus width. + */ +enum snps_dq_mode { + SNPS_DQ_FULL = 0, + SNPS_DQ_HALF = 1, + SNPS_DQ_QRTR = 2, +}; + +/** + * enum snps_burst_length - HIF/SDRAM burst transactions length. + * @SNPS_DDR_BL2: Burst length 2xSDRAM-words. + * @SNPS_DDR_BL4: Burst length 4xSDRAM-words. + * @SNPS_DDR_BL8: Burst length 8xSDRAM-words. + * @SNPS_DDR_BL16: Burst length 16xSDRAM-words. + */ +enum snps_burst_length { + SNPS_DDR_BL2 = 2, + SNPS_DDR_BL4 = 4, + SNPS_DDR_BL8 = 8, + SNPS_DDR_BL16 = 16, +}; + +/** + * enum snps_freq_ratio - HIF:SDRAM frequency ratio mode. + * @SNPS_FREQ_RATIO11: 1:1 frequency mode. + * @SNPS_FREQ_RATIO12: 1:2 frequency mode. + */ +enum snps_freq_ratio { + SNPS_FREQ_RATIO11 = 1, + SNPS_FREQ_RATIO12 = 2, +}; + +/** + * enum snps_ecc_mode - ECC mode. + * @SNPS_ECC_DISABLED: ECC is disabled/unavailable. + * @SNPS_ECC_SECDED: SEC/DED over 1 beat ECC (SideBand/Inline). + * @SNPS_ECC_ADVX4X8: Advanced ECC X4/X8 (SideBand). + */ +enum snps_ecc_mode { + SNPS_ECC_DISABLED = 0, + SNPS_ECC_SECDED = 4, + SNPS_ECC_ADVX4X8 = 5, +}; + +/** + * struct snps_ddrc_info - DDR controller platform parameters. + * @caps: DDR controller capabilities. + * @sdram_mode: Current SDRAM mode selected. + * @dev_cfg: Current memory device config (if applicable). + * @dq_width: Memory data bus width (width of the DQ signals + * connected to SDRAM chips). + * @dq_mode: Proportion of the DQ bus utilized to access SDRAM. + * @sdram_burst_len: SDRAM burst transaction length. + * @hif_burst_len: HIF burst transaction length (Host Interface). + * @freq_ratio: HIF/SDRAM frequency ratio mode. + * @ecc_mode: ECC mode enabled for the DDR controller (SEC/DED, etc). + * @ranks: Number of ranks enabled to access DIMM (1, 2 or 4). + */ +struct snps_ddrc_info { + unsigned int caps; + enum mem_type sdram_mode; + enum dev_type dev_cfg; + enum snps_dq_width dq_width; + enum snps_dq_mode dq_mode; + enum snps_burst_length sdram_burst_len; + enum snps_burst_length hif_burst_len; + enum snps_freq_ratio freq_ratio; + enum snps_ecc_mode ecc_mode; + unsigned int ranks; +}; + /** * struct snps_ecc_error_info - ECC error log information. * @row: Row number. @@ -242,6 +332,7 @@ struct snps_ecc_status { /** * struct snps_edac_priv - DDR memory controller private data. + * @info: DDR controller config info. * @pdev: Platform device. * @baseaddr: Base address of the DDR controller. * @reglock: Concurrent CSRs access lock. @@ -256,6 +347,7 @@ struct snps_ecc_status { * @rank_shift: Bit shifts for rank bit. */ struct snps_edac_priv { + struct snps_ddrc_info info; struct platform_device *pdev; void __iomem *baseaddr; spinlock_t reglock; @@ -518,23 +610,19 @@ static struct snps_edac_priv *snps_create_data(struct platform_device *pdev) /** * snps_get_dtype - Return the DDR memory chips type. - * @base: DDR memory controller base address. + * @mstr: Master CSR value. * * Get the attached DDR chips type based on the current controller * configuration. * * Return: type of the memory DRAM chips. */ -static enum dev_type snps_get_dtype(const void __iomem *base) +static inline enum dev_type snps_get_dtype(u32 mstr) { - u32 regval; - - regval = readl(base + DDR_MSTR_OFST); - if (!(regval & DDR_MSTR_MEM_DDR4)) + if (!(mstr & DDR_MSTR_MEM_DDR4)) return DEV_UNKNOWN; - regval = FIELD_GET(DDR_MSTR_DEV_CFG_MASK, regval); - switch (regval) { + switch (FIELD_GET(DDR_MSTR_DEV_CFG_MASK, mstr)) { case DDR_MSTR_DEV_X4: return DEV_X4; case DDR_MSTR_DEV_X8: @@ -548,32 +636,6 @@ static enum dev_type snps_get_dtype(const void __iomem *base) return DEV_UNKNOWN; } -/** - * snps_get_ecc_state - Return the controller ECC enable/disable status. - * @base: DDR memory controller base address. - * - * Get the ECC enable/disable status for the controller and clear out - * the ECC counters if applicatble. - * - * Return: a ECC status boolean i.e true/false - enabled/disabled. - */ -static bool snps_get_ecc_state(void __iomem *base) -{ - u32 ecctype, clearval; - - ecctype = readl(base + ECC_CFG0_OFST); - ecctype = FIELD_GET(ECC_CFG0_MODE_MASK, ecctype); - if (ecctype == ECC_CFG0_MODE_SECDED) { - clearval = readl(base + ECC_CLR_OFST) | - ECC_CTRL_CLR_CE_ERR | ECC_CTRL_CLR_CE_ERRCNT | - ECC_CTRL_CLR_UE_ERR | ECC_CTRL_CLR_UE_ERRCNT; - writel(clearval, base + ECC_CLR_OFST); - return true; - } - - return false; -} - /** * snps_get_memsize - Read the size of the attached memory device. * @@ -590,21 +652,16 @@ static u32 snps_get_memsize(void) /** * snps_get_mtype - Returns controller memory type. - * @base: Synopsys ECC status structure. + * @mstr: Master CSR value. * * Get the EDAC memory type appropriate for the current controller * configuration. * * Return: a memory type enumeration. */ -static enum mem_type snps_get_mtype(const void __iomem *base) +static inline enum mem_type snps_get_mtype(u32 mstr) { - u32 regval; - - regval = readl(base + DDR_MSTR_OFST); - regval = FIELD_GET(DDR_MSTR_MEM_MASK, regval); - - switch (regval) { + switch (FIELD_GET(DDR_MSTR_MEM_MASK, mstr)) { case DDR_MSTR_MEM_DDR2: return MEM_DDR2; case DDR_MSTR_MEM_DDR3: @@ -624,6 +681,75 @@ static enum mem_type snps_get_mtype(const void __iomem *base) return MEM_RESERVED; } +/** + * snps_get_ddrc_info - Get the DDR controller config data. + * @priv: DDR memory controller private data. + * + * Return: negative errno if no ECC detected, otherwise - zero. + */ +static int snps_get_ddrc_info(struct snps_edac_priv *priv) +{ + int (*init_plat)(struct snps_edac_priv *priv); + u32 regval; + + /* Before getting the DDRC parameters make sure ECC is enabled */ + regval = readl(priv->baseaddr + ECC_CFG0_OFST); + + priv->info.ecc_mode = FIELD_GET(ECC_CFG0_MODE_MASK, regval); + if (priv->info.ecc_mode != SNPS_ECC_SECDED) { + edac_printk(KERN_INFO, EDAC_MC, "SEC/DED ECC not enabled\n"); + return -ENODEV; + } + + /* Auto-detect the basic HIF/SDRAM bus parameters */ + regval = readl(priv->baseaddr + DDR_MSTR_OFST); + + priv->info.sdram_mode = snps_get_mtype(regval); + priv->info.dev_cfg = snps_get_dtype(regval); + + priv->info.dq_mode = FIELD_GET(DDR_MSTR_BUSWIDTH_MASK, regval); + + /* + * Assume HIF burst length matches the SDRAM burst length since it's + * not auto-detectable + */ + priv->info.sdram_burst_len = FIELD_GET(DDR_MSTR_BURST_RDWR, regval) << 1; + priv->info.hif_burst_len = priv->info.sdram_burst_len; + + /* Retrieve the current HIF/SDRAM frequency ratio: 1:1 vs 1:2 */ + priv->info.freq_ratio = !(regval & DDR_MSTR_FREQ_RATIO11) + 1; + + /* Activated ranks field: set bit corresponds to populated rank */ + priv->info.ranks = FIELD_GET(DDR_MSTR_ACT_RANKS_MASK, regval); + priv->info.ranks = hweight_long(priv->info.ranks); + + /* Auto-detect the DQ bus width by using the ECC-poison pattern CSR */ + writel(0, priv->baseaddr + DDR_SWCTL); + + /* + * If poison pattern [32:64] is changeable then DQ is 64-bit wide. + * Note the feature has been available since IP-core v2.51a. + */ + regval = readl(priv->baseaddr + ECC_POISONPAT1_OFST); + writel(~regval, priv->baseaddr + ECC_POISONPAT1_OFST); + if (regval != readl(priv->baseaddr + ECC_POISONPAT1_OFST)) { + priv->info.dq_width = SNPS_DQ_64; + writel(regval, priv->baseaddr + ECC_POISONPAT1_OFST); + } else { + priv->info.dq_width = SNPS_DQ_32; + } + + writel(1, priv->baseaddr + DDR_SWCTL); + + /* Clear out the ECC counters to start from scratch */ + regval = readl(priv->baseaddr + ECC_CLR_OFST) | + ECC_CTRL_CLR_CE_ERR | ECC_CTRL_CLR_CE_ERRCNT | + ECC_CTRL_CLR_UE_ERR | ECC_CTRL_CLR_UE_ERRCNT; + writel(regval, priv->baseaddr + ECC_CLR_OFST); + + return 0; +} + /** * snps_init_csrows - Initialize the csrow data. * @mci: EDAC memory controller instance. @@ -646,10 +772,10 @@ static void snps_init_csrows(struct mem_ctl_info *mci) for (j = 0; j < csi->nr_channels; j++) { dimm = csi->channels[j]->dimm; dimm->edac_mode = EDAC_SECDED; - dimm->mtype = snps_get_mtype(priv->baseaddr); + dimm->mtype = priv->info.sdram_mode; dimm->nr_pages = (size >> PAGE_SHIFT) / csi->nr_channels; dimm->grain = SNPS_EDAC_ERR_GRAIN; - dimm->dtype = snps_get_dtype(priv->baseaddr); + dimm->dtype = priv->info.dev_cfg; } } } @@ -761,6 +887,33 @@ static int snps_setup_irq(struct mem_ctl_info *mci) #define SNPS_DBGFS_BUF_LEN 128 +static int snps_ddrc_info_show(struct seq_file *s, void *data) +{ + struct mem_ctl_info *mci = s->private; + struct snps_edac_priv *priv = mci->pvt_info; + + seq_printf(s, "SDRAM: %s\n", edac_mem_types[priv->info.sdram_mode]); + + seq_printf(s, "DQ bus: %u/%s\n", (BITS_PER_BYTE << priv->info.dq_width), + priv->info.dq_mode == SNPS_DQ_FULL ? "Full" : + priv->info.dq_mode == SNPS_DQ_HALF ? "Half" : + priv->info.dq_mode == SNPS_DQ_QRTR ? "Quarter" : + "Unknown"); + seq_printf(s, "Burst: SDRAM %u HIF %u\n", priv->info.sdram_burst_len, + priv->info.hif_burst_len); + + seq_printf(s, "Ranks: %u\n", priv->info.ranks); + + seq_printf(s, "ECC: %s\n", + priv->info.ecc_mode == SNPS_ECC_SECDED ? "SEC/DED" : + priv->info.ecc_mode == SNPS_ECC_ADVX4X8 ? "Advanced X4/X8" : + "Unknown"); + + return 0; +} + +DEFINE_SHOW_ATTRIBUTE(snps_ddrc_info); + /** * snps_data_poison_setup - Update poison registers. * @priv: DDR memory controller private instance data. @@ -888,12 +1041,8 @@ static void snps_setup_row_address_map(struct snps_edac_priv *priv, u32 *addrmap static void snps_setup_column_address_map(struct snps_edac_priv *priv, u32 *addrmap) { - u32 width, memtype; int index; - memtype = readl(priv->baseaddr + DDR_MSTR_OFST); - width = FIELD_GET(DDR_MSTR_BUSWIDTH_MASK, memtype); - priv->col_shift[0] = 0; priv->col_shift[1] = 1; priv->col_shift[2] = (addrmap[2] & COL_MAX_VAL_MASK) + COL_B2_BASE; @@ -917,8 +1066,8 @@ static void snps_setup_column_address_map(struct snps_edac_priv *priv, u32 *addr priv->col_shift[9] = (((addrmap[3] >> 24) & COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 : (((addrmap[3] >> 24) & COL_MAX_VAL_MASK) + COL_B9_BASE); - if (width == DDR_MSTR_BUSWIDTH_64) { - if (memtype & DDR_MSTR_MEM_LPDDR3) { + if (priv->info.dq_mode == SNPS_DQ_FULL) { + if (priv->info.sdram_mode == MEM_LPDDR3) { priv->col_shift[10] = ((addrmap[4] & COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 : ((addrmap[4] & COL_MAX_VAL_MASK) + @@ -937,8 +1086,8 @@ static void snps_setup_column_address_map(struct snps_edac_priv *priv, u32 *addr (((addrmap[4] >> 8) & COL_MAX_VAL_MASK) + COL_B11_BASE); } - } else if (width == DDR_MSTR_BUSWIDTH_32) { - if (memtype & DDR_MSTR_MEM_LPDDR3) { + } else if (priv->info.dq_mode == SNPS_DQ_HALF) { + if (priv->info.sdram_mode == MEM_LPDDR3) { priv->col_shift[10] = (((addrmap[3] >> 24) & COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 : (((addrmap[3] >> 24) & COL_MAX_VAL_MASK) + @@ -958,7 +1107,7 @@ static void snps_setup_column_address_map(struct snps_edac_priv *priv, u32 *addr COL_B10_BASE); } } else { - if (memtype & DDR_MSTR_MEM_LPDDR3) { + if (priv->info.sdram_mode == MEM_LPDDR3) { priv->col_shift[10] = (((addrmap[3] >> 16) & COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 : (((addrmap[3] >> 16) & COL_MAX_VAL_MASK) + @@ -979,10 +1128,11 @@ static void snps_setup_column_address_map(struct snps_edac_priv *priv, u32 *addr } } - if (width) { - for (index = 9; index > width; index--) { - priv->col_shift[index] = priv->col_shift[index - width]; - priv->col_shift[index - width] = 0; + if (priv->info.dq_mode) { + for (index = 9; index > priv->info.dq_mode; index--) { + priv->col_shift[index] = + priv->col_shift[index - priv->info.dq_mode]; + priv->col_shift[index - priv->info.dq_mode] = 0; } } @@ -1144,6 +1294,9 @@ static void snps_create_debugfs_nodes(struct mem_ctl_info *mci) snps_setup_address_map(priv); + edac_debugfs_create_file("ddrc_info", 0400, mci->debugfs, mci, + &snps_ddrc_info_fops); + edac_debugfs_create_file("inject_data_error", 0600, mci->debugfs, mci, &snps_inject_data_error); @@ -1176,10 +1329,9 @@ static int snps_mc_probe(struct platform_device *pdev) if (IS_ERR(priv)) return PTR_ERR(priv); - if (!snps_get_ecc_state(priv->baseaddr)) { - edac_printk(KERN_INFO, EDAC_MC, "ECC not enabled\n"); - return -ENODEV; - } + rc = snps_get_ddrc_info(priv); + if (rc) + return rc; mci = snps_mc_create(priv); if (IS_ERR(mci)) -- cgit v1.2.3 From f0a3047857e85d8d3156426a0bc33c261fa762b7 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Tue, 26 Jul 2022 14:51:14 +0300 Subject: EDAC/synopsys: Convert plat-data to plat-init function Since DW uMCTL2 device info and capabilities infrastructure is now available there is no point in supporting an additional abstraction like platform quirks. Instead convert the already defined ZynqMP quirk to the ZynqMP-specific capability and add the platform-specific initialization function support. This function will be called after the device parameters are detected and thus fixing some of them if required. Note the new approach will provide a very flexible interface of the platform-specific setups. The platform-specific init() callback can be used not only for the capabilities flags modification, but for example for the resources requests or custom CSRs alterations. Also note the get_mem_info() is temporarily converted to just the direct ZynqMP-specific method call if the SNPS_CAP_ZYNQMP capability is found. It will be completely dropped a few commits later after adding the generic sys-to-app address translation methods. Signed-off-by: Serge Semin --- drivers/edac/synopsys_edac.c | 80 ++++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 43 deletions(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index 8d2900cdd7206..be2484f546428 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -34,8 +34,8 @@ #define SNPS_EDAC_MOD_STRING "snps_edac" #define SNPS_EDAC_MOD_VER "1" -/* DDR ECC Quirks */ -#define SNPS_ZYNQMP_IRQ_REGS BIT(0) +/* DDR capabilities */ +#define SNPS_CAP_ZYNQMP BIT(31) /* Synopsys uMCTL2 DDR controller registers that are relevant to ECC */ @@ -338,7 +338,6 @@ struct snps_ecc_status { * @reglock: Concurrent CSRs access lock. * @message: Buffer for framing the event specific info. * @stat: ECC status information. - * @p_data: Platform data. * @poison_addr: Data poison address. * @row_shift: Bit shifts for row bit. * @col_shift: Bit shifts for column bit. @@ -353,7 +352,6 @@ struct snps_edac_priv { spinlock_t reglock; char message[SNPS_EDAC_MSG_SIZE]; struct snps_ecc_status stat; - const struct snps_platform_data *p_data; #ifdef CONFIG_EDAC_DEBUG ulong poison_addr; u32 row_shift[18]; @@ -364,18 +362,6 @@ struct snps_edac_priv { #endif }; -/** - * struct snps_platform_data - Synopsys uMCTL2 DDRC platform data. - * @get_mem_info: Get EDAC memory info - * @quirks: IP-core specific quirks. - */ -struct snps_platform_data { -#ifdef CONFIG_EDAC_DEBUG - u64 (*get_mem_info)(struct snps_edac_priv *priv); -#endif - u32 quirks; -}; - #ifdef CONFIG_EDAC_DEBUG /** * zynqmp_get_mem_info - Get the current memory info. @@ -508,7 +494,7 @@ static void snps_enable_irq(struct snps_edac_priv *priv) unsigned long flags; /* Enable UE/CE Interrupts */ - if (priv->p_data->quirks & SNPS_ZYNQMP_IRQ_REGS) { + if (priv->info.caps & SNPS_CAP_ZYNQMP) { writel(ZYNQMP_DDR_QOS_UE_MASK | ZYNQMP_DDR_QOS_CE_MASK, priv->baseaddr + ZYNQMP_DDR_QOS_IRQ_EN_OFST); @@ -532,7 +518,7 @@ static void snps_disable_irq(struct snps_edac_priv *priv) unsigned long flags; /* Disable UE/CE Interrupts */ - if (priv->p_data->quirks & SNPS_ZYNQMP_IRQ_REGS) { + if (priv->info.caps & SNPS_CAP_ZYNQMP) { writel(ZYNQMP_DDR_QOS_UE_MASK | ZYNQMP_DDR_QOS_CE_MASK, priv->baseaddr + ZYNQMP_DDR_QOS_IRQ_DB_OFST); @@ -561,7 +547,7 @@ static irqreturn_t snps_irq_handler(int irq, void *dev_id) priv = mci->pvt_info; - if (priv->p_data->quirks & SNPS_ZYNQMP_IRQ_REGS) { + if (priv->info.caps & SNPS_CAP_ZYNQMP) { regval = readl(priv->baseaddr + ZYNQMP_DDR_QOS_IRQ_STAT_OFST); regval &= (ZYNQMP_DDR_QOS_CE_MASK | ZYNQMP_DDR_QOS_UE_MASK); if (!(regval & ZYNQMP_DDR_QOS_IRQ_MASK)) @@ -574,7 +560,7 @@ static irqreturn_t snps_irq_handler(int irq, void *dev_id) snps_handle_error(mci, &priv->stat); - if (priv->p_data->quirks & SNPS_ZYNQMP_IRQ_REGS) + if (priv->info.caps & SNPS_CAP_ZYNQMP) writel(regval, priv->baseaddr + ZYNQMP_DDR_QOS_IRQ_STAT_OFST); return IRQ_HANDLED; @@ -598,16 +584,26 @@ static struct snps_edac_priv *snps_create_data(struct platform_device *pdev) if (IS_ERR(priv->baseaddr)) return ERR_CAST(priv->baseaddr); - priv->p_data = of_device_get_match_data(&pdev->dev); - if (!priv->p_data) - return ERR_PTR(-ENODEV); - priv->pdev = pdev; spin_lock_init(&priv->reglock); return priv; } +/* + * zynqmp_init_plat - ZynqMP-specific platform initialization. + * @priv: DDR memory controller private data. + * + * Return: always zero. + */ +static int zynqmp_init_plat(struct snps_edac_priv *priv) +{ + priv->info.caps |= SNPS_CAP_ZYNQMP; + priv->info.dq_width = SNPS_DQ_64; + + return 0; +} + /** * snps_get_dtype - Return the DDR memory chips type. * @mstr: Master CSR value. @@ -747,7 +743,10 @@ static int snps_get_ddrc_info(struct snps_edac_priv *priv) ECC_CTRL_CLR_UE_ERR | ECC_CTRL_CLR_UE_ERRCNT; writel(regval, priv->baseaddr + ECC_CLR_OFST); - return 0; + /* Apply platform setups after all the configs auto-detection */ + init_plat = device_get_match_data(&priv->pdev->dev); + + return init_plat ? init_plat(priv) : 0; } /** @@ -909,6 +908,15 @@ static int snps_ddrc_info_show(struct seq_file *s, void *data) priv->info.ecc_mode == SNPS_ECC_ADVX4X8 ? "Advanced X4/X8" : "Unknown"); + seq_puts(s, "Caps:"); + if (priv->info.caps) { + if (priv->info.caps & SNPS_CAP_ZYNQMP) + seq_puts(s, " +ZynqMP"); + } else { + seq_puts(s, " -"); + } + seq_putc(s, '\n'); + return 0; } @@ -924,14 +932,11 @@ DEFINE_SHOW_ATTRIBUTE(snps_ddrc_info); static void snps_data_poison_setup(struct snps_edac_priv *priv) { int col = 0, row = 0, bank = 0, bankgrp = 0, rank = 0, regval; - const struct snps_platform_data *p_data; int index; ulong hif_addr = 0; - p_data = priv->p_data; - - if (p_data->get_mem_info) - hif_addr = p_data->get_mem_info(priv); + if (priv->info.caps & SNPS_CAP_ZYNQMP) + hif_addr = zynqmp_get_mem_info(priv); else hif_addr = priv->poison_addr >> 3; @@ -1377,20 +1382,9 @@ static void snps_mc_remove(struct platform_device *pdev) snps_mc_free(mci); } -static const struct snps_platform_data zynqmp_edac_def = { -#ifdef CONFIG_EDAC_DEBUG - .get_mem_info = zynqmp_get_mem_info, -#endif - .quirks = SNPS_ZYNQMP_IRQ_REGS, -}; - -static const struct snps_platform_data snps_edac_def = { - .quirks = 0, -}; - static const struct of_device_id snps_edac_match[] = { - { .compatible = "xlnx,zynqmp-ddrc-2.40a", .data = &zynqmp_edac_def }, - { .compatible = "snps,ddrc-3.80a", .data = &snps_edac_def }, + { .compatible = "xlnx,zynqmp-ddrc-2.40a", .data = zynqmp_init_plat }, + { .compatible = "snps,ddrc-3.80a" }, { } }; MODULE_DEVICE_TABLE(of, snps_edac_match); -- cgit v1.2.3 From bfbe30ef70609331ef5201b9f22d96e93b7d521d Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 3 Aug 2022 22:35:09 +0300 Subject: EDAC/synopsys: Parse ADDRMAP[7-8] CSRs for (LP)DDR4 only These CSRs contain the SDRAM Bank Groups and row[16]/row[17] bits mapping, which are applicable for the DDR4 and LPDDR4 memory chips only. For the rest of the memories the ADDRMAP[7-8] registers are unused by the controller and are zeros by default. The zero values will be perceived by the HIF/SDRAM mapping detection procedure as normal bit positions, which is wrong. So in order to prevent that parse these registers only if they are applicable for the detected DDR protocol. Signed-off-by: Serge Semin --- drivers/edac/synopsys_edac.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index be2484f546428..a7a086bfc44e9 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -1036,12 +1036,15 @@ static void snps_setup_row_address_map(struct snps_edac_priv *priv, u32 *addrmap priv->row_shift[15] = (((addrmap[6] >> 24) & ROW_MAX_VAL_MASK) == ROW_MAX_VAL_MASK) ? 0 : (((addrmap[6] >> 24) & ROW_MAX_VAL_MASK) + ROW_B15_BASE); - priv->row_shift[16] = ((addrmap[7] & ROW_MAX_VAL_MASK) == - ROW_MAX_VAL_MASK) ? 0 : ((addrmap[7] & - ROW_MAX_VAL_MASK) + ROW_B16_BASE); - priv->row_shift[17] = (((addrmap[7] >> 8) & ROW_MAX_VAL_MASK) == - ROW_MAX_VAL_MASK) ? 0 : (((addrmap[7] >> 8) & - ROW_MAX_VAL_MASK) + ROW_B17_BASE); + + if (priv->info.sdram_mode == MEM_DDR4 || priv->info.sdram_mode == MEM_LPDDR4) { + priv->row_shift[16] = ((addrmap[7] & ROW_MAX_VAL_MASK) == + ROW_MAX_VAL_MASK) ? 0 : ((addrmap[7] & + ROW_MAX_VAL_MASK) + ROW_B16_BASE); + priv->row_shift[17] = (((addrmap[7] >> 8) & ROW_MAX_VAL_MASK) == + ROW_MAX_VAL_MASK) ? 0 : (((addrmap[7] >> 8) & + ROW_MAX_VAL_MASK) + ROW_B17_BASE); + } } static void snps_setup_column_address_map(struct snps_edac_priv *priv, u32 *addrmap) @@ -1157,6 +1160,10 @@ static void snps_setup_bank_address_map(struct snps_edac_priv *priv, u32 *addrma static void snps_setup_bg_address_map(struct snps_edac_priv *priv, u32 *addrmap) { + /* Bank group signals are available on the DDR4 memory only */ + if (priv->info.sdram_mode != MEM_DDR4) + return; + priv->bankgrp_shift[0] = (addrmap[8] & BANKGRP_MAX_VAL_MASK) + BANKGRP_B0_BASE; priv->bankgrp_shift[1] = (((addrmap[8] >> 8) & BANKGRP_MAX_VAL_MASK) == -- cgit v1.2.3 From d4597124cbcfb4a8460d581ca52914aa056fac27 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 3 Aug 2022 23:40:13 +0300 Subject: EDAC/synopsys: Parse ADDRMAP[0] CSR for multi-ranks case only The ADDRMAP[0] CSR contains the SDRAM Rank bits mapping (and memory channel mapping but it's irrelevant in this case). Obviously they are applicable for the multi-ranked memory only. If either the attached memory isn't multi-ranked or the controller simply doesn't support the multi-rank memory, parsing the ADDRMAP[0] CSR will be not just pointless, but in the later case erroneous since the CSR fields will contain zeros which will be perceived by the mapping detection procedure as a valid value. So the mapping will get to be invalid. Thus make sure the ADDRMAP[0] register is parsed only if a multi-ranked memory setup has been detected. Signed-off-by: Serge Semin --- drivers/edac/synopsys_edac.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index a7a086bfc44e9..ea05f99758d1a 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -1174,9 +1174,12 @@ static void snps_setup_bg_address_map(struct snps_edac_priv *priv, u32 *addrmap) static void snps_setup_rank_address_map(struct snps_edac_priv *priv, u32 *addrmap) { - priv->rank_shift[0] = ((addrmap[0] & RANK_MAX_VAL_MASK) == - RANK_MAX_VAL_MASK) ? 0 : ((addrmap[0] & - RANK_MAX_VAL_MASK) + RANK_B0_BASE); + /* Ranks mapping is unavailable for the single-ranked memory */ + if (priv->info.ranks > 1) { + priv->rank_shift[0] = ((addrmap[0] & RANK_MAX_VAL_MASK) == + RANK_MAX_VAL_MASK) ? 0 : ((addrmap[0] & + RANK_MAX_VAL_MASK) + RANK_B0_BASE); + } } /** -- cgit v1.2.3 From 320ac78335ac1a364ab1434e5fd9ed9ff35599c5 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Tue, 26 Jul 2022 17:36:59 +0300 Subject: EDAC/synopsys: Set actual DIMM ECC errors grain It was wrong to set the DIMM errors grain parameter to just 1 byte because DW uMCTL2 DDRC calculates ECC for each SDRAM word and passes it as an additional byte of data to the memory chips. SDRAM word is the actual DQ-bus width determined by the DQ-width set during the IP-core synthesize and the DQ-bus mode (part of the DQ-bus actually used to get data from the memory chips) selected during the DDR controller initial setup procedure. Thus set the MCI DIMMs grain based on these parameters determined during the DW uMCTL2 DDRC config getting procedure. Signed-off-by: Serge Semin --- drivers/edac/synopsys_edac.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index ea05f99758d1a..125f1731d76a7 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -26,9 +26,6 @@ /* Number of channels per memory controller */ #define SNPS_EDAC_NR_CHANS 1 -/* Granularity of reported error in bytes */ -#define SNPS_EDAC_ERR_GRAIN 1 - #define SNPS_EDAC_MSG_SIZE 256 #define SNPS_EDAC_MOD_STRING "snps_edac" @@ -761,9 +758,12 @@ static void snps_init_csrows(struct mem_ctl_info *mci) struct snps_edac_priv *priv = mci->pvt_info; struct csrow_info *csi; struct dimm_info *dimm; - u32 size, row; + u32 size, row, width; int j; + /* Actual SDRAM-word width for which ECC is calculated */ + width = 1U << (priv->info.dq_width - priv->info.dq_mode); + for (row = 0; row < mci->nr_csrows; row++) { csi = mci->csrows[row]; size = snps_get_memsize(); @@ -773,7 +773,7 @@ static void snps_init_csrows(struct mem_ctl_info *mci) dimm->edac_mode = EDAC_SECDED; dimm->mtype = priv->info.sdram_mode; dimm->nr_pages = (size >> PAGE_SHIFT) / csi->nr_channels; - dimm->grain = SNPS_EDAC_ERR_GRAIN; + dimm->grain = width; dimm->dtype = priv->info.dev_cfg; } } -- cgit v1.2.3 From 9ee68fea6d5129ef176c0b77c076cc99d3e92065 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Tue, 26 Jul 2022 23:25:34 +0300 Subject: EDAC/synopsys: Get corrected bit position MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since the DQ-bus width is now available in the driver it can be utilized to calculate the exact bit-position corrected by the ECC engine for any Synopsys memory controller setup. A corrected error syndrome is exposed by the ECCSTAT.corrected_bit_num field. A particular erroneous bit position is described in the lookup table [1] which also contains a dependency between the field value and the DQ-bus widths. The syndrome values table basically represents a standard lookup table for the Hamming (64,8)/(32,7)/(16,6) codes (the error-correcting bits placed at the power-of-two positions) except that the zero value means error in the ecc[0] bit. So using the offsets from that table introduce a new inline method snps_get_bitpos() which would provide the actual CE bit-position. The method will be called if a corrected error is detected. [1] DesignWare® Cores Enhanced Universal DDR Memory Controller (uMCTL2) Databook, Version 3.91a, October 2020, p.426-427 Signed-off-by: Serge Semin --- drivers/edac/synopsys_edac.c | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index 125f1731d76a7..6a042e658bce7 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -301,6 +302,7 @@ struct snps_ddrc_info { * @col: Column number. * @bank: Bank number. * @bankgrp: Bank group number. + * @syndrome: Error syndrome. * @bitpos: Bit position. * @data: Data causing the error. */ @@ -309,6 +311,7 @@ struct snps_ecc_error_info { u32 col; u32 bank; u32 bankgrp; + u32 syndrome; u32 bitpos; u32 data; }; @@ -378,6 +381,27 @@ static u64 zynqmp_get_mem_info(struct snps_edac_priv *priv) } #endif +/** + * snps_get_bitpos - Get DQ-bus corrected bit position. + * @syndrome: Error syndrome. + * @dq_width: Controller DQ-bus width. + * + * Return: actual corrected DQ-bus bit position starting from 0. + */ +static inline u32 snps_get_bitpos(u32 syndrome, enum snps_dq_width dq_width) +{ + /* ecc[0] bit */ + if (syndrome == 0) + return BITS_PER_BYTE << dq_width; + + /* ecc[1:x] bit */ + if (is_power_of_2(syndrome)) + return (BITS_PER_BYTE << dq_width) + ilog2(syndrome) + 1; + + /* data[0:y] bit */ + return syndrome - ilog2(syndrome) - 2; +} + /** * snps_get_error_info - Get the current ECC error info. * @priv: DDR memory controller private instance data. @@ -398,7 +422,7 @@ static int snps_get_error_info(struct snps_edac_priv *priv) if (!regval) return 1; - p->ceinfo.bitpos = FIELD_GET(ECC_STAT_BITNUM_MASK, regval); + p->ceinfo.syndrome = FIELD_GET(ECC_STAT_BITNUM_MASK, regval); regval = readl(base + ECC_ERRCNT_OFST); p->ce_cnt = FIELD_GET(ECC_ERRCNT_CECNT_MASK, regval); @@ -406,6 +430,8 @@ static int snps_get_error_info(struct snps_edac_priv *priv) if (!p->ce_cnt) goto ue_err; + p->ceinfo.bitpos = snps_get_bitpos(p->ceinfo.syndrome, priv->info.dq_width); + regval = readl(base + ECC_CEADDR0_OFST); p->ceinfo.row = FIELD_GET(ECC_CEADDR0_ROW_MASK, regval); -- cgit v1.2.3 From 964f1661ac103d7c8753e144b1fbdd6d09f7e120 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sun, 31 Jul 2022 21:08:59 +0300 Subject: EDAC/synopsys: Pass syndrome to EDAC error handler EDAC MC error handler permits specifying a corrected error syndrome which then will be printed as a part of the generic error message. Since it's available in the error info structure pass it to the edac_mc_handle_error() function. Signed-off-by: Serge Semin --- Changelog v4: - Get syndrome from the ECCSTAT.ecc_corrected_bit_num field rather than from ECCCSYN2. The later CSR in fact contains ECC. --- drivers/edac/synopsys_edac.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index 6a042e658bce7..cea6f6528c2ff 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -493,7 +493,7 @@ static void snps_handle_error(struct mem_ctl_info *mci, struct snps_ecc_status * pinf->bitpos, pinf->data); edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, - p->ce_cnt, 0, 0, 0, 0, 0, -1, + p->ce_cnt, 0, 0, pinf->syndrome, 0, 0, -1, priv->message, ""); } -- cgit v1.2.3 From b41b437788336f373ce9686630ce8e00c14cb3fe Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sun, 31 Jul 2022 20:41:30 +0300 Subject: EDAC/synopsys: Read full data+ecc pattern on errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DW uMCTL2 DDRC calculates ECC for the Full DQ-bus word. If non-Full bus width mode is activated the leftover DQ-bits will be padded with zeros, but the ECC code is calculated for the whole width anyway [1]. For some reason the DW uMCTL2 DDRC driver currently doesn't read the whole SDRAM word in if ECC errors happens even though the 64-bits DQ-bus has been supported for a long time. Moreover a Full ECC value is also available in the ECC(C|U)SYN2 register. In a less than 64-bits DQ-bus setups the higher ECC bits are just unused. So update the errors handler to reading the entire data+ecc pattern: extend the data field of the ECC error info structure since it may contain 64-bit data; add a new ECC field there since it's a part of the erroneous data pattern; read the upper 32-bits part of the data pattern only if an ECC error happens and the DDR controller has been configured with the 64-bits DQ bus; read the full ECC value from the ECC(C|U)SYN2 register. The data+ecc couple will be printed as a part of the custom error message passed then to the edac_mc_handle_error() method. Note since the full data+ecc info is now always logged into the EDAC core there is no longer need in the debug print of the Syndrome Registers content. Drop it then. [1] DesignWare® Cores Enhanced Universal DDR Memory Controller (uMCTL2) Databook, Version 3.91a, October 2020, p.424-425 Signed-off-by: Serge Semin --- Changelog v4: - Retrieve ECC too. --- drivers/edac/synopsys_edac.c | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index cea6f6528c2ff..c99064f07114c 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -305,6 +305,7 @@ struct snps_ddrc_info { * @syndrome: Error syndrome. * @bitpos: Bit position. * @data: Data causing the error. + * @ecc: Data ECC. */ struct snps_ecc_error_info { u32 row; @@ -313,7 +314,8 @@ struct snps_ecc_error_info { u32 bankgrp; u32 syndrome; u32 bitpos; - u32 data; + u64 data; + u32 ecc; }; /** @@ -441,10 +443,10 @@ static int snps_get_error_info(struct snps_edac_priv *priv) p->ceinfo.col = FIELD_GET(ECC_CEADDR1_COL_MASK, regval); p->ceinfo.data = readl(base + ECC_CSYND0_OFST); + if (priv->info.dq_width == SNPS_DQ_64) + p->ceinfo.data |= (u64)readl(base + ECC_CSYND1_OFST) << 32; - edac_dbg(2, "ECCCSYN0: 0x%08X ECCCSYN1: 0x%08X ECCCSYN2: 0x%08X\n", - readl(base + ECC_CSYND0_OFST), readl(base + ECC_CSYND1_OFST), - readl(base + ECC_CSYND2_OFST)); + p->ceinfo.ecc = readl(base + ECC_CSYND2_OFST); ue_err: if (!p->ue_cnt) @@ -459,6 +461,11 @@ ue_err: p->ueinfo.col = FIELD_GET(ECC_CEADDR1_COL_MASK, regval); p->ueinfo.data = readl(base + ECC_UESYND0_OFST); + if (priv->info.dq_width == SNPS_DQ_64) + p->ueinfo.data |= (u64)readl(base + ECC_UESYND1_OFST) << 32; + + p->ueinfo.ecc = readl(base + ECC_UESYND2_OFST); + out: spin_lock_irqsave(&priv->reglock, flags); @@ -488,9 +495,9 @@ static void snps_handle_error(struct mem_ctl_info *mci, struct snps_ecc_status * pinf = &p->ceinfo; snprintf(priv->message, SNPS_EDAC_MSG_SIZE, - "Row %d Col %d Bank %d Bank Group %d Bit %d Data 0x%08x", + "Row %d Col %d Bank %d Bank Group %d Bit %d Data 0x%08llx:0x%02x", pinf->row, pinf->col, pinf->bank, pinf->bankgrp, - pinf->bitpos, pinf->data); + pinf->bitpos, pinf->data, pinf->ecc); edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, p->ce_cnt, 0, 0, pinf->syndrome, 0, 0, -1, @@ -501,8 +508,9 @@ static void snps_handle_error(struct mem_ctl_info *mci, struct snps_ecc_status * pinf = &p->ueinfo; snprintf(priv->message, SNPS_EDAC_MSG_SIZE, - "Row %d Col %d Bank %d Bank Group %d", - pinf->row, pinf->col, pinf->bank, pinf->bankgrp); + "Row %d Col %d Bank %d Bank Group %d Data 0x%08llx:0x%02x", + pinf->row, pinf->col, pinf->bank, pinf->bankgrp, + pinf->data, pinf->ecc); edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, p->ue_cnt, 0, 0, 0, 0, 0, -1, -- cgit v1.2.3 From 7152de97ea2d5944158aa85fbd4c7137b07a3688 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Tue, 2 Aug 2022 09:30:53 +0300 Subject: EDAC/synopsys: Introduce System/SDRAM address translation interface Currently the address translation is performed only in the framework of the ECC poison procedures. The available infrastructure is utilized for the device and driver debugging. Meanwhile it would be very useful to know not only the SDRAM address of the runtime ECC errors, but the originating system address accepted on the AXI/AHB ports of the DW uMCTL2 DDR controller. In order to be able to do so the currently available infrastructure needs to be properly updated. First of all move it away from under the EDAC_DEBUG config, since it will be always utilized by the driver. Secondly for the sake of the code simplification split up the translation procedure into the next three stages: 1. System<->Application address translation (just type cast for now). 2. Application<->HIF address translation (DQ-bus width based address shift). 3. HIF<->SDRAM address translation (ADDRMAPx-based mapping). The suggested implementation supports the 1->3 translation only in the same way as it was before this modification (the backward address translation will be added later). Semantically it's the same except the next four aspects: ff-value is used as a marker of the unmapped HIF/SDRAM bits instead of zero-value which in some cases is a valid mapping bit id; DQ-width is used to perform the Application<->HIF address translation instead of the fixed 64-bit DQ-bus width assumption; the HIF/SDRAM address translation procedure searches through the whole dimensions width instead of stopping at the first unmapped bit since in general some of the row/column/bank/etc bits (especially the column bits, like b10) can be left unmapped; the number of supported ranks is extended to four - maximum possible value. Note while at it the code is simplified a bit: encapsulate the mapping table into a dedicated structure (snps_hif_sdram_map); use the FIELD_GET() helper to get ADDRMAP CSR fields; define more descriptive max-value macros. Also note the ZynqMP-specific get_mem_info() method basically performs the System-to-Application-to-HIF address mapping. But in order to reduce a regressions risk it's preserved as is for now. It will be dropped after the generic implementation of the System-to/from-Application address translation is introduced. Signed-off-by: Serge Semin --- drivers/edac/synopsys_edac.c | 774 ++++++++++++++++++++++++++----------------- 1 file changed, 464 insertions(+), 310 deletions(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index c99064f07114c..5aa97fcc7a25e 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -153,17 +153,28 @@ #define ECC_CEPOISON_MASK GENMASK(1, 0) #define ECC_UEPOISON_MASK BIT(0) -/* DDRC Device config shifts/masks */ -#define DDR_MAX_ROW_SHIFT 18 -#define DDR_MAX_COL_SHIFT 14 -#define DDR_MAX_BANK_SHIFT 3 -#define DDR_MAX_BANKGRP_SHIFT 2 - -#define ROW_MAX_VAL_MASK 0xF -#define COL_MAX_VAL_MASK 0xF -#define BANK_MAX_VAL_MASK 0x1F -#define BANKGRP_MAX_VAL_MASK 0x1F -#define RANK_MAX_VAL_MASK 0x1F +/* DDRC address mapping parameters */ +#define DDR_ADDRMAP_NREGS 12 + +#define DDR_MAX_ROW_WIDTH 18 +#define DDR_MAX_COL_WIDTH 14 +#define DDR_MAX_BANK_WIDTH 3 +#define DDR_MAX_BANKGRP_WIDTH 2 +#define DDR_MAX_RANK_WIDTH 2 + +#define DDR_ADDRMAP_B0_M15 GENMASK(3, 0) +#define DDR_ADDRMAP_B8_M15 GENMASK(11, 8) +#define DDR_ADDRMAP_B16_M15 GENMASK(19, 16) +#define DDR_ADDRMAP_B24_M15 GENMASK(27, 24) + +#define DDR_ADDRMAP_B0_M31 GENMASK(4, 0) +#define DDR_ADDRMAP_B8_M31 GENMASK(12, 8) +#define DDR_ADDRMAP_B16_M31 GENMASK(20, 16) +#define DDR_ADDRMAP_B24_M31 GENMASK(28, 24) + +#define DDR_ADDRMAP_UNUSED ((u8)-1) +#define DDR_ADDRMAP_MAX_15 DDR_ADDRMAP_B0_M15 +#define DDR_ADDRMAP_MAX_31 DDR_ADDRMAP_B0_M31 #define ROW_B0_BASE 6 #define ROW_B1_BASE 7 @@ -205,6 +216,7 @@ #define BANKGRP_B1_BASE 3 #define RANK_B0_BASE 6 +#define RANK_B1_BASE 7 /* ZynqMP DDR QOS Interrupt register definitions */ #define ZYNQMP_DDR_QOS_UE_MASK BIT(2) @@ -296,6 +308,41 @@ struct snps_ddrc_info { unsigned int ranks; }; +/** + * struct snps_hif_sdram_map - HIF/SDRAM mapping table. + * @row: HIF bit offsets used as row address bits. + * @col: HIF bit offsets used as column address bits. + * @bank: HIF bit offsets used as bank address bits. + * @bankgrp: HIF bit offsets used as bank group address bits. + * @rank: HIF bit offsets used as rank address bits. + * + * For example, row[0] = 6 means row bit #0 is encoded by the HIF + * address bit #6 and vice-versa. + */ +struct snps_hif_sdram_map { + u8 row[DDR_MAX_ROW_WIDTH]; + u8 col[DDR_MAX_COL_WIDTH]; + u8 bank[DDR_MAX_BANK_WIDTH]; + u8 bankgrp[DDR_MAX_BANKGRP_WIDTH]; + u8 rank[DDR_MAX_RANK_WIDTH]; +}; + +/** + * struct snps_sdram_addr - SDRAM address. + * @row: Row number. + * @col: Column number. + * @bank: Bank number. + * @bankgrp: Bank group number. + * @rank: Rank number. + */ +struct snps_sdram_addr { + u16 row; + u16 col; + u8 bank; + u8 bankgrp; + u8 rank; +}; + /** * struct snps_ecc_error_info - ECC error log information. * @row: Row number. @@ -335,20 +382,17 @@ struct snps_ecc_status { /** * struct snps_edac_priv - DDR memory controller private data. * @info: DDR controller config info. + * @hif_sdram_map: HIF/SDRAM mapping table. * @pdev: Platform device. * @baseaddr: Base address of the DDR controller. * @reglock: Concurrent CSRs access lock. * @message: Buffer for framing the event specific info. * @stat: ECC status information. * @poison_addr: Data poison address. - * @row_shift: Bit shifts for row bit. - * @col_shift: Bit shifts for column bit. - * @bank_shift: Bit shifts for bank bit. - * @bankgrp_shift: Bit shifts for bank group bit. - * @rank_shift: Bit shifts for rank bit. */ struct snps_edac_priv { struct snps_ddrc_info info; + struct snps_hif_sdram_map hif_sdram_map; struct platform_device *pdev; void __iomem *baseaddr; spinlock_t reglock; @@ -356,32 +400,116 @@ struct snps_edac_priv { struct snps_ecc_status stat; #ifdef CONFIG_EDAC_DEBUG ulong poison_addr; - u32 row_shift[18]; - u32 col_shift[14]; - u32 bank_shift[3]; - u32 bankgrp_shift[2]; - u32 rank_shift[1]; #endif }; #ifdef CONFIG_EDAC_DEBUG /** - * zynqmp_get_mem_info - Get the current memory info. + * zynqmp_map_sys_to_hif - Convert Sys addr to HIF addr on ZynqMP DDRC. + * @sys: System address (source). + * @hif: HIF address (destination). + */ +static void zynqmp_map_sys_to_hif(dma_addr_t sys, u64 *hif) +{ + /* Map System address to Application address */ + if (sys >= SZ_32G) + sys = sys - SZ_32G + SZ_2G; + + *hif = sys >> 3; +} +#endif + +/** + * snps_map_app_to_hif - Map Application address to HIF address. + * @priv: DDR memory controller private instance data. + * @app: Application address (source). + * @hif: HIF address (destination). + * + * HIF address is used to perform the DQ bus width aligned burst transactions. + * So in order to perform the Application-to-HIF address translation we just + * need to discard the SDRAM-word bits of the Application address. + */ +static void snps_map_app_to_hif(struct snps_edac_priv *priv, + u64 app, u64 *hif) +{ + *hif = app >> priv->info.dq_width; +} + +/** + * snps_map_hif_to_sdram - Map HIF address to SDRAM address. * @priv: DDR memory controller private instance data. + * @hif: HIF address (source). + * @sdram: SDRAM address (destination). * - * Return: host interface address. + * HIF-SDRAM address mapping is configured with the ADDRMAPx registers, Based + * on the CSRs value the HIF address bits are mapped to the corresponding bits + * in the SDRAM rank/bank/column/row. If an SDRAM address bit is unused (there + * is no any HIF address bit corresponding to it) it will be set to zero. Using + * this fact we can freely set the output SDRAM address with zeros and walk + * over the set HIF address bits only. Similarly the unmapped HIF address bits + * are just ignored. */ -static u64 zynqmp_get_mem_info(struct snps_edac_priv *priv) +static void snps_map_hif_to_sdram(struct snps_edac_priv *priv, + u64 hif, struct snps_sdram_addr *sdram) { - u64 hif_addr = 0, linear_addr; + struct snps_hif_sdram_map *map = &priv->hif_sdram_map; + int i; - linear_addr = priv->poison_addr; - if (linear_addr >= SZ_32G) - linear_addr = linear_addr - SZ_32G + SZ_2G; - hif_addr = linear_addr >> 3; - return hif_addr; + sdram->row = 0; + for (i = 0; i < DDR_MAX_ROW_WIDTH; i++) { + if (map->row[i] != DDR_ADDRMAP_UNUSED && hif & BIT(map->row[i])) + sdram->row |= BIT(i); + } + + sdram->col = 0; + for (i = 0; i < DDR_MAX_COL_WIDTH; i++) { + if (map->col[i] != DDR_ADDRMAP_UNUSED && hif & BIT(map->col[i])) + sdram->col |= BIT(i); + } + + sdram->bank = 0; + for (i = 0; i < DDR_MAX_BANK_WIDTH; i++) { + if (map->bank[i] != DDR_ADDRMAP_UNUSED && hif & BIT(map->bank[i])) + sdram->bank |= BIT(i); + } + + sdram->bankgrp = 0; + for (i = 0; i < DDR_MAX_BANKGRP_WIDTH; i++) { + if (map->bankgrp[i] != DDR_ADDRMAP_UNUSED && hif & BIT(map->bankgrp[i])) + sdram->bankgrp |= BIT(i); + } + + sdram->rank = 0; + for (i = 0; i < DDR_MAX_RANK_WIDTH; i++) { + if (map->rank[i] != DDR_ADDRMAP_UNUSED && hif & BIT(map->rank[i])) + sdram->rank |= BIT(i); + } +} + +/** + * snps_map_sys_to_sdram - Map System address to SDRAM address. + * @priv: DDR memory controller private instance data. + * @sys: System address (source). + * @sdram: SDRAM address (destination). + * + * Perform a full mapping of the system address (detected on the controller + * ports) to the SDRAM address tuple row/column/bank/etc. + */ +static void snps_map_sys_to_sdram(struct snps_edac_priv *priv, + dma_addr_t sys, struct snps_sdram_addr *sdram) +{ + u64 app, hif; + + if (priv->info.caps & SNPS_CAP_ZYNQMP) { + zynqmp_map_sys_to_hif(sys, &hif); + } else { + app = sys; + + snps_map_app_to_hif(priv, app, &hif); + } + + snps_map_hif_to_sdram(priv, hif, sdram); } -#endif /** * snps_get_bitpos - Get DQ-bus corrected bit position. @@ -780,6 +908,301 @@ static int snps_get_ddrc_info(struct snps_edac_priv *priv) return init_plat ? init_plat(priv) : 0; } +/** + * snps_get_hif_row_map - Get HIF/SDRAM-row address map. + * @priv: DDR memory controller private instance data. + * @addrmap: Array with ADDRMAP registers value. + * + * SDRAM-row address is defined by the fields in the ADDRMAP[5-7,9-11] + * registers. Those fields value indicate the HIF address bits used to encode + * the DDR row address. + */ +static void snps_get_hif_row_map(struct snps_edac_priv *priv, u32 *addrmap) +{ + struct snps_hif_sdram_map *map = &priv->hif_sdram_map; + u8 map_row_b2_10; + int i; + + for (i = 0; i < DDR_MAX_ROW_WIDTH; i++) + map->row[i] = DDR_ADDRMAP_UNUSED; + + map->row[0] = FIELD_GET(DDR_ADDRMAP_B0_M15, addrmap[5]) + ROW_B0_BASE; + map->row[1] = FIELD_GET(DDR_ADDRMAP_B8_M15, addrmap[5]) + ROW_B1_BASE; + + map_row_b2_10 = FIELD_GET(DDR_ADDRMAP_B16_M15, addrmap[5]); + if (map_row_b2_10 != DDR_ADDRMAP_MAX_15) { + for (i = 2; i < 11; i++) + map->row[i] = map_row_b2_10 + i + ROW_B0_BASE; + } else { + map->row[2] = FIELD_GET(DDR_ADDRMAP_B0_M15, addrmap[9]) + ROW_B2_BASE; + map->row[3] = FIELD_GET(DDR_ADDRMAP_B8_M15, addrmap[9]) + ROW_B3_BASE; + map->row[4] = FIELD_GET(DDR_ADDRMAP_B16_M15, addrmap[9]) + ROW_B4_BASE; + map->row[5] = FIELD_GET(DDR_ADDRMAP_B24_M15, addrmap[9]) + ROW_B5_BASE; + map->row[6] = FIELD_GET(DDR_ADDRMAP_B0_M15, addrmap[10]) + ROW_B6_BASE; + map->row[7] = FIELD_GET(DDR_ADDRMAP_B8_M15, addrmap[10]) + ROW_B7_BASE; + map->row[8] = FIELD_GET(DDR_ADDRMAP_B16_M15, addrmap[10]) + ROW_B8_BASE; + map->row[9] = FIELD_GET(DDR_ADDRMAP_B24_M15, addrmap[10]) + ROW_B9_BASE; + map->row[10] = FIELD_GET(DDR_ADDRMAP_B0_M15, addrmap[11]) + ROW_B10_BASE; + } + + map->row[11] = FIELD_GET(DDR_ADDRMAP_B24_M15, addrmap[5]); + map->row[11] = map->row[11] == DDR_ADDRMAP_MAX_15 ? + DDR_ADDRMAP_UNUSED : map->row[11] + ROW_B11_BASE; + + map->row[12] = FIELD_GET(DDR_ADDRMAP_B0_M15, addrmap[6]); + map->row[12] = map->row[12] == DDR_ADDRMAP_MAX_15 ? + DDR_ADDRMAP_UNUSED : map->row[12] + ROW_B12_BASE; + + map->row[13] = FIELD_GET(DDR_ADDRMAP_B8_M15, addrmap[6]); + map->row[13] = map->row[13] == DDR_ADDRMAP_MAX_15 ? + DDR_ADDRMAP_UNUSED : map->row[13] + ROW_B13_BASE; + + map->row[14] = FIELD_GET(DDR_ADDRMAP_B16_M15, addrmap[6]); + map->row[14] = map->row[14] == DDR_ADDRMAP_MAX_15 ? + DDR_ADDRMAP_UNUSED : map->row[14] + ROW_B14_BASE; + + map->row[15] = FIELD_GET(DDR_ADDRMAP_B24_M15, addrmap[6]); + map->row[15] = map->row[15] == DDR_ADDRMAP_MAX_15 ? + DDR_ADDRMAP_UNUSED : map->row[15] + ROW_B15_BASE; + + if (priv->info.sdram_mode == MEM_DDR4 || priv->info.sdram_mode == MEM_LPDDR4) { + map->row[16] = FIELD_GET(DDR_ADDRMAP_B0_M15, addrmap[7]); + map->row[16] = map->row[16] == DDR_ADDRMAP_MAX_15 ? + DDR_ADDRMAP_UNUSED : map->row[16] + ROW_B16_BASE; + + map->row[17] = FIELD_GET(DDR_ADDRMAP_B8_M15, addrmap[7]); + map->row[17] = map->row[17] == DDR_ADDRMAP_MAX_15 ? + DDR_ADDRMAP_UNUSED : map->row[17] + ROW_B17_BASE; + } +} + +/** + * snps_get_hif_col_map - Get HIF/SDRAM-column address map. + * @priv: DDR memory controller private instance data. + * @addrmap: Array with ADDRMAP registers value. + * + * SDRAM-column address is defined by the fields in the ADDRMAP[2-4] + * registers. Those fields value indicate the HIF address bits used to encode + * the DDR row address. + */ +static void snps_get_hif_col_map(struct snps_edac_priv *priv, u32 *addrmap) +{ + struct snps_hif_sdram_map *map = &priv->hif_sdram_map; + int i; + + for (i = 0; i < DDR_MAX_COL_WIDTH; i++) + map->col[i] = DDR_ADDRMAP_UNUSED; + + map->col[0] = 0; + map->col[1] = 1; + map->col[2] = FIELD_GET(DDR_ADDRMAP_B0_M15, addrmap[2]) + COL_B2_BASE; + map->col[3] = FIELD_GET(DDR_ADDRMAP_B8_M15, addrmap[2]) + COL_B3_BASE; + + map->col[4] = FIELD_GET(DDR_ADDRMAP_B16_M15, addrmap[2]); + map->col[4] = map->col[4] == DDR_ADDRMAP_MAX_15 ? + DDR_ADDRMAP_UNUSED : map->col[4] + COL_B4_BASE; + + map->col[5] = FIELD_GET(DDR_ADDRMAP_B24_M15, addrmap[2]); + map->col[5] = map->col[5] == DDR_ADDRMAP_MAX_15 ? + DDR_ADDRMAP_UNUSED : map->col[5] + COL_B5_BASE; + + map->col[6] = FIELD_GET(DDR_ADDRMAP_B0_M15, addrmap[3]); + map->col[6] = map->col[6] == DDR_ADDRMAP_MAX_15 ? + DDR_ADDRMAP_UNUSED : map->col[6] + COL_B6_BASE; + + map->col[7] = FIELD_GET(DDR_ADDRMAP_B8_M15, addrmap[3]); + map->col[7] = map->col[7] == DDR_ADDRMAP_MAX_15 ? + DDR_ADDRMAP_UNUSED : map->col[7] + COL_B7_BASE; + + map->col[8] = FIELD_GET(DDR_ADDRMAP_B16_M15, addrmap[3]); + map->col[8] = map->col[8] == DDR_ADDRMAP_MAX_15 ? + DDR_ADDRMAP_UNUSED : map->col[8] + COL_B8_BASE; + + map->col[9] = FIELD_GET(DDR_ADDRMAP_B24_M15, addrmap[3]); + map->col[9] = map->col[9] == DDR_ADDRMAP_MAX_15 ? + DDR_ADDRMAP_UNUSED : map->col[9] + COL_B9_BASE; + + if (priv->info.dq_mode) { + for (i = 9; i > priv->info.dq_mode; i--) { + map->col[i] = map->col[i - priv->info.dq_mode]; + map->col[i - priv->info.dq_mode] = DDR_ADDRMAP_UNUSED; + } + } + + /* + * Per JEDEC DDR2/3/4/mDDR specification, column address bit 10 is + * reserved for indicating auto-precharge, and hence no source + * address bit can be mapped to col[10]. + * Per JEDEC specification, column address bit 12 is reserved + * for the Burst-chop status, so no source address bit mapping + * for col[12] either. + */ + if (priv->info.dq_mode == SNPS_DQ_FULL) { + if (priv->info.sdram_mode == MEM_LPDDR3) { + map->col[10] = FIELD_GET(DDR_ADDRMAP_B0_M15, addrmap[4]); + map->col[10] = map->col[10] == DDR_ADDRMAP_MAX_15 ? + DDR_ADDRMAP_UNUSED : map->col[10] + COL_B10_BASE; + + map->col[11] = FIELD_GET(DDR_ADDRMAP_B8_M15, addrmap[4]); + map->col[11] = map->col[11] == DDR_ADDRMAP_MAX_15 ? + DDR_ADDRMAP_UNUSED : map->col[11] + COL_B11_BASE; + } else { + map->col[11] = FIELD_GET(DDR_ADDRMAP_B0_M15, addrmap[4]); + map->col[11] = map->col[11] == DDR_ADDRMAP_MAX_15 ? + DDR_ADDRMAP_UNUSED : map->col[11] + COL_B10_BASE; + + map->col[13] = FIELD_GET(DDR_ADDRMAP_B8_M15, addrmap[4]); + map->col[13] = map->col[13] == DDR_ADDRMAP_MAX_15 ? + DDR_ADDRMAP_UNUSED : map->col[13] + COL_B11_BASE; + } + } else if (priv->info.dq_mode == SNPS_DQ_HALF) { + if (priv->info.sdram_mode == MEM_LPDDR3) { + map->col[10] = FIELD_GET(DDR_ADDRMAP_B24_M15, addrmap[3]); + map->col[10] = map->col[10] == DDR_ADDRMAP_MAX_15 ? + DDR_ADDRMAP_UNUSED : map->col[10] + COL_B9_BASE; + + map->col[11] = FIELD_GET(DDR_ADDRMAP_B0_M15, addrmap[4]); + map->col[11] = map->col[11] == DDR_ADDRMAP_MAX_15 ? + DDR_ADDRMAP_UNUSED : map->col[11] + COL_B10_BASE; + } else { + map->col[11] = FIELD_GET(DDR_ADDRMAP_B24_M15, addrmap[3]); + map->col[11] = map->col[11] == DDR_ADDRMAP_MAX_15 ? + DDR_ADDRMAP_UNUSED : map->col[11] + COL_B9_BASE; + + map->col[13] = FIELD_GET(DDR_ADDRMAP_B0_M15, addrmap[4]); + map->col[13] = map->col[13] == DDR_ADDRMAP_MAX_15 ? + DDR_ADDRMAP_UNUSED : map->col[13] + COL_B10_BASE; + } + } else { + if (priv->info.sdram_mode == MEM_LPDDR3) { + map->col[10] = FIELD_GET(DDR_ADDRMAP_B16_M15, addrmap[3]); + map->col[10] = map->col[10] == DDR_ADDRMAP_MAX_15 ? + DDR_ADDRMAP_UNUSED : map->col[10] + COL_B8_BASE; + + map->col[11] = FIELD_GET(DDR_ADDRMAP_B24_M15, addrmap[3]); + map->col[11] = map->col[11] == DDR_ADDRMAP_MAX_15 ? + DDR_ADDRMAP_UNUSED : map->col[11] + COL_B9_BASE; + } else { + map->col[11] = FIELD_GET(DDR_ADDRMAP_B16_M15, addrmap[3]); + map->col[11] = map->col[11] == DDR_ADDRMAP_MAX_15 ? + DDR_ADDRMAP_UNUSED : map->col[11] + COL_B8_BASE; + + map->col[11] = FIELD_GET(DDR_ADDRMAP_B24_M15, addrmap[3]); + map->col[13] = map->col[13] == DDR_ADDRMAP_MAX_15 ? + DDR_ADDRMAP_UNUSED : map->col[13] + COL_B9_BASE; + } + } +} + +/** + * snps_get_hif_bank_map - Get HIF/SDRAM-bank address map. + * @priv: DDR memory controller private instance data. + * @addrmap: Array with ADDRMAP registers value. + * + * SDRAM-bank address is defined by the fields in the ADDRMAP[1] + * register. Those fields value indicate the HIF address bits used to encode + * the DDR bank address. + */ +static void snps_get_hif_bank_map(struct snps_edac_priv *priv, u32 *addrmap) +{ + struct snps_hif_sdram_map *map = &priv->hif_sdram_map; + int i; + + for (i = 0; i < DDR_MAX_BANK_WIDTH; i++) + map->bank[i] = DDR_ADDRMAP_UNUSED; + + map->bank[0] = FIELD_GET(DDR_ADDRMAP_B0_M31, addrmap[1]) + BANK_B0_BASE; + map->bank[1] = FIELD_GET(DDR_ADDRMAP_B8_M31, addrmap[1]) + BANK_B1_BASE; + + map->bank[2] = FIELD_GET(DDR_ADDRMAP_B16_M31, addrmap[1]); + map->bank[2] = map->bank[2] == DDR_ADDRMAP_MAX_31 ? + DDR_ADDRMAP_UNUSED : map->bank[2] + BANK_B2_BASE; +} + +/** + * snps_get_hif_bankgrp_map - Get HIF/SDRAM-bank group address map. + * @priv: DDR memory controller private instance data. + * @addrmap: Array with ADDRMAP registers value. + * + * SDRAM-bank group address is defined by the fields in the ADDRMAP[8] + * register. Those fields value indicate the HIF address bits used to encode + * the DDR bank group address. + */ +static void snps_get_hif_bankgrp_map(struct snps_edac_priv *priv, u32 *addrmap) +{ + struct snps_hif_sdram_map *map = &priv->hif_sdram_map; + int i; + + for (i = 0; i < DDR_MAX_BANKGRP_WIDTH; i++) + map->bankgrp[i] = DDR_ADDRMAP_UNUSED; + + /* Bank group signals are available on the DDR4 memory only */ + if (priv->info.sdram_mode != MEM_DDR4) + return; + + map->bankgrp[0] = FIELD_GET(DDR_ADDRMAP_B0_M31, addrmap[8]) + BANKGRP_B0_BASE; + + map->bankgrp[1] = FIELD_GET(DDR_ADDRMAP_B8_M31, addrmap[8]); + map->bankgrp[1] = map->bankgrp[1] == DDR_ADDRMAP_MAX_31 ? + DDR_ADDRMAP_UNUSED : map->bankgrp[1] + BANKGRP_B1_BASE; +} + +/** + * snps_get_hif_rank_map - Get HIF/SDRAM-rank address map. + * @priv: DDR memory controller private instance data. + * @addrmap: Array with ADDRMAP registers value. + * + * SDRAM-rank address is defined by the fields in the ADDRMAP[0] + * register. Those fields value indicate the HIF address bits used to encode + * the DDR rank address. + */ +static void snps_get_hif_rank_map(struct snps_edac_priv *priv, u32 *addrmap) +{ + struct snps_hif_sdram_map *map = &priv->hif_sdram_map; + int i; + + for (i = 0; i < DDR_MAX_RANK_WIDTH; i++) + map->rank[i] = DDR_ADDRMAP_UNUSED; + + if (priv->info.ranks > 1) { + map->rank[0] = FIELD_GET(DDR_ADDRMAP_B0_M31, addrmap[0]); + map->rank[0] = map->rank[0] == DDR_ADDRMAP_MAX_31 ? + DDR_ADDRMAP_UNUSED : map->rank[0] + RANK_B0_BASE; + } + + if (priv->info.ranks > 2) { + map->rank[1] = FIELD_GET(DDR_ADDRMAP_B8_M31, addrmap[0]); + map->rank[1] = map->rank[1] == DDR_ADDRMAP_MAX_31 ? + DDR_ADDRMAP_UNUSED : map->rank[1] + RANK_B1_BASE; + } +} + +/** + * snps_get_addr_map - Get HIF/SDRAM/etc address map from CSRs. + * @priv: DDR memory controller private instance data. + * + * Parse the controller registers content creating the addresses mapping tables. + * They will be used for the erroneous and poison addresses encode/decode. + */ +static void snps_get_addr_map(struct snps_edac_priv *priv) +{ + u32 regval[DDR_ADDRMAP_NREGS]; + int i; + + for (i = 0; i < DDR_ADDRMAP_NREGS; i++) + regval[i] = readl(priv->baseaddr + DDR_ADDRMAP0_OFST + i * 4); + + snps_get_hif_row_map(priv, regval); + + snps_get_hif_col_map(priv, regval); + + snps_get_hif_bank_map(priv, regval); + + snps_get_hif_bankgrp_map(priv, regval); + + snps_get_hif_rank_map(priv, regval); +} + /** * snps_init_csrows - Initialize the csrow data. * @mci: EDAC memory controller instance. @@ -965,288 +1388,21 @@ DEFINE_SHOW_ATTRIBUTE(snps_ddrc_info); */ static void snps_data_poison_setup(struct snps_edac_priv *priv) { - int col = 0, row = 0, bank = 0, bankgrp = 0, rank = 0, regval; - int index; - ulong hif_addr = 0; - - if (priv->info.caps & SNPS_CAP_ZYNQMP) - hif_addr = zynqmp_get_mem_info(priv); - else - hif_addr = priv->poison_addr >> 3; - - for (index = 0; index < DDR_MAX_ROW_SHIFT; index++) { - if (priv->row_shift[index]) - row |= (((hif_addr >> priv->row_shift[index]) & - BIT(0)) << index); - else - break; - } - - for (index = 0; index < DDR_MAX_COL_SHIFT; index++) { - if (priv->col_shift[index] || index < 3) - col |= (((hif_addr >> priv->col_shift[index]) & - BIT(0)) << index); - else - break; - } - - for (index = 0; index < DDR_MAX_BANK_SHIFT; index++) { - if (priv->bank_shift[index]) - bank |= (((hif_addr >> priv->bank_shift[index]) & - BIT(0)) << index); - else - break; - } - - for (index = 0; index < DDR_MAX_BANKGRP_SHIFT; index++) { - if (priv->bankgrp_shift[index]) - bankgrp |= (((hif_addr >> priv->bankgrp_shift[index]) - & BIT(0)) << index); - else - break; - } + struct snps_sdram_addr sdram; + u32 regval; - if (priv->rank_shift[0]) - rank = (hif_addr >> priv->rank_shift[0]) & BIT(0); + snps_map_sys_to_sdram(priv, priv->poison_addr, &sdram); - regval = FIELD_PREP(ECC_POISON0_RANK_MASK, rank) | - FIELD_PREP(ECC_POISON0_COL_MASK, col); + regval = FIELD_PREP(ECC_POISON0_RANK_MASK, sdram.rank) | + FIELD_PREP(ECC_POISON0_COL_MASK, sdram.col); writel(regval, priv->baseaddr + ECC_POISON0_OFST); - regval = FIELD_PREP(ECC_POISON1_BANKGRP_MASK, bankgrp) | - FIELD_PREP(ECC_POISON1_BANK_MASK, bank) | - FIELD_PREP(ECC_POISON1_ROW_MASK, row); + regval = FIELD_PREP(ECC_POISON1_BANKGRP_MASK, sdram.bankgrp) | + FIELD_PREP(ECC_POISON1_BANK_MASK, sdram.bank) | + FIELD_PREP(ECC_POISON1_ROW_MASK, sdram.row); writel(regval, priv->baseaddr + ECC_POISON1_OFST); } -static void snps_setup_row_address_map(struct snps_edac_priv *priv, u32 *addrmap) -{ - u32 addrmap_row_b2_10; - int index; - - priv->row_shift[0] = (addrmap[5] & ROW_MAX_VAL_MASK) + ROW_B0_BASE; - priv->row_shift[1] = ((addrmap[5] >> 8) & - ROW_MAX_VAL_MASK) + ROW_B1_BASE; - - addrmap_row_b2_10 = (addrmap[5] >> 16) & ROW_MAX_VAL_MASK; - if (addrmap_row_b2_10 != ROW_MAX_VAL_MASK) { - for (index = 2; index < 11; index++) - priv->row_shift[index] = addrmap_row_b2_10 + - index + ROW_B0_BASE; - - } else { - priv->row_shift[2] = (addrmap[9] & - ROW_MAX_VAL_MASK) + ROW_B2_BASE; - priv->row_shift[3] = ((addrmap[9] >> 8) & - ROW_MAX_VAL_MASK) + ROW_B3_BASE; - priv->row_shift[4] = ((addrmap[9] >> 16) & - ROW_MAX_VAL_MASK) + ROW_B4_BASE; - priv->row_shift[5] = ((addrmap[9] >> 24) & - ROW_MAX_VAL_MASK) + ROW_B5_BASE; - priv->row_shift[6] = (addrmap[10] & - ROW_MAX_VAL_MASK) + ROW_B6_BASE; - priv->row_shift[7] = ((addrmap[10] >> 8) & - ROW_MAX_VAL_MASK) + ROW_B7_BASE; - priv->row_shift[8] = ((addrmap[10] >> 16) & - ROW_MAX_VAL_MASK) + ROW_B8_BASE; - priv->row_shift[9] = ((addrmap[10] >> 24) & - ROW_MAX_VAL_MASK) + ROW_B9_BASE; - priv->row_shift[10] = (addrmap[11] & - ROW_MAX_VAL_MASK) + ROW_B10_BASE; - } - - priv->row_shift[11] = (((addrmap[5] >> 24) & ROW_MAX_VAL_MASK) == - ROW_MAX_VAL_MASK) ? 0 : (((addrmap[5] >> 24) & - ROW_MAX_VAL_MASK) + ROW_B11_BASE); - priv->row_shift[12] = ((addrmap[6] & ROW_MAX_VAL_MASK) == - ROW_MAX_VAL_MASK) ? 0 : ((addrmap[6] & - ROW_MAX_VAL_MASK) + ROW_B12_BASE); - priv->row_shift[13] = (((addrmap[6] >> 8) & ROW_MAX_VAL_MASK) == - ROW_MAX_VAL_MASK) ? 0 : (((addrmap[6] >> 8) & - ROW_MAX_VAL_MASK) + ROW_B13_BASE); - priv->row_shift[14] = (((addrmap[6] >> 16) & ROW_MAX_VAL_MASK) == - ROW_MAX_VAL_MASK) ? 0 : (((addrmap[6] >> 16) & - ROW_MAX_VAL_MASK) + ROW_B14_BASE); - priv->row_shift[15] = (((addrmap[6] >> 24) & ROW_MAX_VAL_MASK) == - ROW_MAX_VAL_MASK) ? 0 : (((addrmap[6] >> 24) & - ROW_MAX_VAL_MASK) + ROW_B15_BASE); - - if (priv->info.sdram_mode == MEM_DDR4 || priv->info.sdram_mode == MEM_LPDDR4) { - priv->row_shift[16] = ((addrmap[7] & ROW_MAX_VAL_MASK) == - ROW_MAX_VAL_MASK) ? 0 : ((addrmap[7] & - ROW_MAX_VAL_MASK) + ROW_B16_BASE); - priv->row_shift[17] = (((addrmap[7] >> 8) & ROW_MAX_VAL_MASK) == - ROW_MAX_VAL_MASK) ? 0 : (((addrmap[7] >> 8) & - ROW_MAX_VAL_MASK) + ROW_B17_BASE); - } -} - -static void snps_setup_column_address_map(struct snps_edac_priv *priv, u32 *addrmap) -{ - int index; - - priv->col_shift[0] = 0; - priv->col_shift[1] = 1; - priv->col_shift[2] = (addrmap[2] & COL_MAX_VAL_MASK) + COL_B2_BASE; - priv->col_shift[3] = ((addrmap[2] >> 8) & - COL_MAX_VAL_MASK) + COL_B3_BASE; - priv->col_shift[4] = (((addrmap[2] >> 16) & COL_MAX_VAL_MASK) == - COL_MAX_VAL_MASK) ? 0 : (((addrmap[2] >> 16) & - COL_MAX_VAL_MASK) + COL_B4_BASE); - priv->col_shift[5] = (((addrmap[2] >> 24) & COL_MAX_VAL_MASK) == - COL_MAX_VAL_MASK) ? 0 : (((addrmap[2] >> 24) & - COL_MAX_VAL_MASK) + COL_B5_BASE); - priv->col_shift[6] = ((addrmap[3] & COL_MAX_VAL_MASK) == - COL_MAX_VAL_MASK) ? 0 : ((addrmap[3] & - COL_MAX_VAL_MASK) + COL_B6_BASE); - priv->col_shift[7] = (((addrmap[3] >> 8) & COL_MAX_VAL_MASK) == - COL_MAX_VAL_MASK) ? 0 : (((addrmap[3] >> 8) & - COL_MAX_VAL_MASK) + COL_B7_BASE); - priv->col_shift[8] = (((addrmap[3] >> 16) & COL_MAX_VAL_MASK) == - COL_MAX_VAL_MASK) ? 0 : (((addrmap[3] >> 16) & - COL_MAX_VAL_MASK) + COL_B8_BASE); - priv->col_shift[9] = (((addrmap[3] >> 24) & COL_MAX_VAL_MASK) == - COL_MAX_VAL_MASK) ? 0 : (((addrmap[3] >> 24) & - COL_MAX_VAL_MASK) + COL_B9_BASE); - if (priv->info.dq_mode == SNPS_DQ_FULL) { - if (priv->info.sdram_mode == MEM_LPDDR3) { - priv->col_shift[10] = ((addrmap[4] & - COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 : - ((addrmap[4] & COL_MAX_VAL_MASK) + - COL_B10_BASE); - priv->col_shift[11] = (((addrmap[4] >> 8) & - COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 : - (((addrmap[4] >> 8) & COL_MAX_VAL_MASK) + - COL_B11_BASE); - } else { - priv->col_shift[11] = ((addrmap[4] & - COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 : - ((addrmap[4] & COL_MAX_VAL_MASK) + - COL_B10_BASE); - priv->col_shift[13] = (((addrmap[4] >> 8) & - COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 : - (((addrmap[4] >> 8) & COL_MAX_VAL_MASK) + - COL_B11_BASE); - } - } else if (priv->info.dq_mode == SNPS_DQ_HALF) { - if (priv->info.sdram_mode == MEM_LPDDR3) { - priv->col_shift[10] = (((addrmap[3] >> 24) & - COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 : - (((addrmap[3] >> 24) & COL_MAX_VAL_MASK) + - COL_B9_BASE); - priv->col_shift[11] = ((addrmap[4] & - COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 : - ((addrmap[4] & COL_MAX_VAL_MASK) + - COL_B10_BASE); - } else { - priv->col_shift[11] = (((addrmap[3] >> 24) & - COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 : - (((addrmap[3] >> 24) & COL_MAX_VAL_MASK) + - COL_B9_BASE); - priv->col_shift[13] = ((addrmap[4] & - COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 : - ((addrmap[4] & COL_MAX_VAL_MASK) + - COL_B10_BASE); - } - } else { - if (priv->info.sdram_mode == MEM_LPDDR3) { - priv->col_shift[10] = (((addrmap[3] >> 16) & - COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 : - (((addrmap[3] >> 16) & COL_MAX_VAL_MASK) + - COL_B8_BASE); - priv->col_shift[11] = (((addrmap[3] >> 24) & - COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 : - (((addrmap[3] >> 24) & COL_MAX_VAL_MASK) + - COL_B9_BASE); - } else { - priv->col_shift[11] = (((addrmap[3] >> 16) & - COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 : - (((addrmap[3] >> 16) & COL_MAX_VAL_MASK) + - COL_B8_BASE); - priv->col_shift[13] = (((addrmap[3] >> 24) & - COL_MAX_VAL_MASK) == COL_MAX_VAL_MASK) ? 0 : - (((addrmap[3] >> 24) & COL_MAX_VAL_MASK) + - COL_B9_BASE); - } - } - - if (priv->info.dq_mode) { - for (index = 9; index > priv->info.dq_mode; index--) { - priv->col_shift[index] = - priv->col_shift[index - priv->info.dq_mode]; - priv->col_shift[index - priv->info.dq_mode] = 0; - } - } - -} - -static void snps_setup_bank_address_map(struct snps_edac_priv *priv, u32 *addrmap) -{ - priv->bank_shift[0] = (addrmap[1] & BANK_MAX_VAL_MASK) + BANK_B0_BASE; - priv->bank_shift[1] = ((addrmap[1] >> 8) & - BANK_MAX_VAL_MASK) + BANK_B1_BASE; - priv->bank_shift[2] = (((addrmap[1] >> 16) & - BANK_MAX_VAL_MASK) == BANK_MAX_VAL_MASK) ? 0 : - (((addrmap[1] >> 16) & BANK_MAX_VAL_MASK) + - BANK_B2_BASE); - -} - -static void snps_setup_bg_address_map(struct snps_edac_priv *priv, u32 *addrmap) -{ - /* Bank group signals are available on the DDR4 memory only */ - if (priv->info.sdram_mode != MEM_DDR4) - return; - - priv->bankgrp_shift[0] = (addrmap[8] & - BANKGRP_MAX_VAL_MASK) + BANKGRP_B0_BASE; - priv->bankgrp_shift[1] = (((addrmap[8] >> 8) & BANKGRP_MAX_VAL_MASK) == - BANKGRP_MAX_VAL_MASK) ? 0 : (((addrmap[8] >> 8) - & BANKGRP_MAX_VAL_MASK) + BANKGRP_B1_BASE); - -} - -static void snps_setup_rank_address_map(struct snps_edac_priv *priv, u32 *addrmap) -{ - /* Ranks mapping is unavailable for the single-ranked memory */ - if (priv->info.ranks > 1) { - priv->rank_shift[0] = ((addrmap[0] & RANK_MAX_VAL_MASK) == - RANK_MAX_VAL_MASK) ? 0 : ((addrmap[0] & - RANK_MAX_VAL_MASK) + RANK_B0_BASE); - } -} - -/** - * snps_setup_address_map - Set Address Map by querying ADDRMAP registers. - * @priv: DDR memory controller private instance data. - * - * Set Address Map by querying ADDRMAP registers. - * - * Return: none. - */ -static void snps_setup_address_map(struct snps_edac_priv *priv) -{ - u32 addrmap[12]; - int index; - - for (index = 0; index < 12; index++) { - u32 addrmap_offset; - - addrmap_offset = DDR_ADDRMAP0_OFST + (index * 4); - addrmap[index] = readl(priv->baseaddr + addrmap_offset); - } - - snps_setup_row_address_map(priv, addrmap); - - snps_setup_column_address_map(priv, addrmap); - - snps_setup_bank_address_map(priv, addrmap); - - snps_setup_bg_address_map(priv, addrmap); - - snps_setup_rank_address_map(priv, addrmap); -} - static ssize_t snps_inject_data_error_read(struct file *filep, char __user *ubuf, size_t size, loff_t *offp) { @@ -1339,10 +1495,6 @@ SNPS_DEBUGFS_FOPS(snps_inject_data_poison, snps_inject_data_poison_read, */ static void snps_create_debugfs_nodes(struct mem_ctl_info *mci) { - struct snps_edac_priv *priv = mci->pvt_info; - - snps_setup_address_map(priv); - edac_debugfs_create_file("ddrc_info", 0400, mci->debugfs, mci, &snps_ddrc_info_fops); @@ -1382,6 +1534,8 @@ static int snps_mc_probe(struct platform_device *pdev) if (rc) return rc; + snps_get_addr_map(priv); + mci = snps_mc_create(priv); if (IS_ERR(mci)) return PTR_ERR(mci); -- cgit v1.2.3 From 1a11e6d13f055aa2b3f69995605c529cbe57ce89 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sun, 7 Aug 2022 13:15:04 +0300 Subject: EDAC/synopsys: Simplify HIF/SDRAM column mapping get procedure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit What is currently implemented in the driver by means of the multiple if-else-if-else statements in fact is described in the hardware reference manual [1]. It says: 1. All of the column bits shift up 1 bit when only half of the data bus is in use. (In this case, for instance, you need to look at ADDRMAP3.addrmap_col_b6 instead to determine the value of column address bit 7.) 2. All of the column bits shift up 2 bits when only a quarter of the data bus is in use. (In this case, for instance, you need to look at ADDRMAP2.addrmap_col_b5 instead to determine the value of column address bit 7.) 3. In addition to the above, the column bit 10 is reserved for the auto-precharge command in DDR2/3/4/mDDR. So the column bits must be further shifted up 1 bit when one of these DDR protocols is enabled. So taking into account all of the notes above and what the column bit 12 is always reserved, the SDRAM column bits mapping procedure can be significantly simplified: initially read the mapping as if for the LPDDR2/3/4 memory with Full DQ-bus utilized; then shift the column bits up in accordance with the detected DQ-bus width mode. That's it. Simple, canonical and scalable. [1] DesignWare® Cores Enhanced Universal DDR Memory Controller (uMCTL2) Databook, Version 3.91a, October 2020, p.154 Signed-off-by: Serge Semin --- drivers/edac/synopsys_edac.c | 83 ++++++++++++++------------------------------ 1 file changed, 27 insertions(+), 56 deletions(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index 5aa97fcc7a25e..15bf6b5f239a5 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -1022,8 +1022,22 @@ static void snps_get_hif_col_map(struct snps_edac_priv *priv, u32 *addrmap) map->col[9] = map->col[9] == DDR_ADDRMAP_MAX_15 ? DDR_ADDRMAP_UNUSED : map->col[9] + COL_B9_BASE; + map->col[10] = FIELD_GET(DDR_ADDRMAP_B0_M15, addrmap[4]); + map->col[10] = map->col[10] == DDR_ADDRMAP_MAX_15 ? + DDR_ADDRMAP_UNUSED : map->col[10] + COL_B10_BASE; + + map->col[11] = FIELD_GET(DDR_ADDRMAP_B8_M15, addrmap[4]); + map->col[11] = map->col[11] == DDR_ADDRMAP_MAX_15 ? + DDR_ADDRMAP_UNUSED : map->col[11] + COL_B11_BASE; + + /* + * In case of the non-Full DQ bus mode the lowest columns are + * unmapped and used by the controller to read the full DQ word + * in multiple cycles (col[0] for the Half bus mode, col[0:1] for + * the Quarter bus mode). + */ if (priv->info.dq_mode) { - for (i = 9; i > priv->info.dq_mode; i--) { + for (i = 11 + priv->info.dq_mode; i >= priv->info.dq_mode; i--) { map->col[i] = map->col[i - priv->info.dq_mode]; map->col[i - priv->info.dq_mode] = DDR_ADDRMAP_UNUSED; } @@ -1033,65 +1047,22 @@ static void snps_get_hif_col_map(struct snps_edac_priv *priv, u32 *addrmap) * Per JEDEC DDR2/3/4/mDDR specification, column address bit 10 is * reserved for indicating auto-precharge, and hence no source * address bit can be mapped to col[10]. + */ + if (priv->info.sdram_mode == MEM_LPDDR || priv->info.sdram_mode == MEM_DDR2 || + priv->info.sdram_mode == MEM_DDR3 || priv->info.sdram_mode == MEM_DDR4) { + for (i = 12 + priv->info.dq_mode; i > 10; i--) { + map->col[i] = map->col[i - 1]; + map->col[i - 1] = DDR_ADDRMAP_UNUSED; + } + } + + /* * Per JEDEC specification, column address bit 12 is reserved * for the Burst-chop status, so no source address bit mapping * for col[12] either. */ - if (priv->info.dq_mode == SNPS_DQ_FULL) { - if (priv->info.sdram_mode == MEM_LPDDR3) { - map->col[10] = FIELD_GET(DDR_ADDRMAP_B0_M15, addrmap[4]); - map->col[10] = map->col[10] == DDR_ADDRMAP_MAX_15 ? - DDR_ADDRMAP_UNUSED : map->col[10] + COL_B10_BASE; - - map->col[11] = FIELD_GET(DDR_ADDRMAP_B8_M15, addrmap[4]); - map->col[11] = map->col[11] == DDR_ADDRMAP_MAX_15 ? - DDR_ADDRMAP_UNUSED : map->col[11] + COL_B11_BASE; - } else { - map->col[11] = FIELD_GET(DDR_ADDRMAP_B0_M15, addrmap[4]); - map->col[11] = map->col[11] == DDR_ADDRMAP_MAX_15 ? - DDR_ADDRMAP_UNUSED : map->col[11] + COL_B10_BASE; - - map->col[13] = FIELD_GET(DDR_ADDRMAP_B8_M15, addrmap[4]); - map->col[13] = map->col[13] == DDR_ADDRMAP_MAX_15 ? - DDR_ADDRMAP_UNUSED : map->col[13] + COL_B11_BASE; - } - } else if (priv->info.dq_mode == SNPS_DQ_HALF) { - if (priv->info.sdram_mode == MEM_LPDDR3) { - map->col[10] = FIELD_GET(DDR_ADDRMAP_B24_M15, addrmap[3]); - map->col[10] = map->col[10] == DDR_ADDRMAP_MAX_15 ? - DDR_ADDRMAP_UNUSED : map->col[10] + COL_B9_BASE; - - map->col[11] = FIELD_GET(DDR_ADDRMAP_B0_M15, addrmap[4]); - map->col[11] = map->col[11] == DDR_ADDRMAP_MAX_15 ? - DDR_ADDRMAP_UNUSED : map->col[11] + COL_B10_BASE; - } else { - map->col[11] = FIELD_GET(DDR_ADDRMAP_B24_M15, addrmap[3]); - map->col[11] = map->col[11] == DDR_ADDRMAP_MAX_15 ? - DDR_ADDRMAP_UNUSED : map->col[11] + COL_B9_BASE; - - map->col[13] = FIELD_GET(DDR_ADDRMAP_B0_M15, addrmap[4]); - map->col[13] = map->col[13] == DDR_ADDRMAP_MAX_15 ? - DDR_ADDRMAP_UNUSED : map->col[13] + COL_B10_BASE; - } - } else { - if (priv->info.sdram_mode == MEM_LPDDR3) { - map->col[10] = FIELD_GET(DDR_ADDRMAP_B16_M15, addrmap[3]); - map->col[10] = map->col[10] == DDR_ADDRMAP_MAX_15 ? - DDR_ADDRMAP_UNUSED : map->col[10] + COL_B8_BASE; - - map->col[11] = FIELD_GET(DDR_ADDRMAP_B24_M15, addrmap[3]); - map->col[11] = map->col[11] == DDR_ADDRMAP_MAX_15 ? - DDR_ADDRMAP_UNUSED : map->col[11] + COL_B9_BASE; - } else { - map->col[11] = FIELD_GET(DDR_ADDRMAP_B16_M15, addrmap[3]); - map->col[11] = map->col[11] == DDR_ADDRMAP_MAX_15 ? - DDR_ADDRMAP_UNUSED : map->col[11] + COL_B8_BASE; - - map->col[11] = FIELD_GET(DDR_ADDRMAP_B24_M15, addrmap[3]); - map->col[13] = map->col[13] == DDR_ADDRMAP_MAX_15 ? - DDR_ADDRMAP_UNUSED : map->col[13] + COL_B9_BASE; - } - } + map->col[13] = map->col[12]; + map->col[12] = DDR_ADDRMAP_UNUSED; } /** -- cgit v1.2.3 From 54f138aff1388781fa44361bb58aa69f4c39fe4e Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 3 Aug 2022 16:46:17 +0300 Subject: EDAC/synopsys: Add HIF/SDRAM mapping debugfs node MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since the available address mapping is about to be utilized for the erroneous SDRAM address decode, before adding such functionality it will be useful to have a way to get an info regarding the most complicated part of the address translation - HIF/SDRAM mapping table just in case something gets wrong in the implemented translation procedures. So add the DebugFS node which on read returns the HIF/SDRAM mapping table in the hexdump-like manner: first line contains the HIF address bit position units, first column contains the HIF address bit position tens, the line and column intersections have the SDRAM dimension (row/column/bank/etc) and a bit position used to encode the corresponding HIF address bit. Note DW uMCTL2 DDRC IP-core doesn't have a parameter to set the HIF address width. So the maximum value (60 bits) of the UMCTL2_A_ADDRW synthesize parameter [1] is utilized as the maximum HIF address width. That parameter defines the controller ports address bus width and in case if the DQ bus width equals to eight bytes defines the HIF address width too. So its upper constraints is fully applicable in this case. [1] DesignWare® Cores Enhanced Universal DDR Memory Controller (uMCTL2) Databook, Version 3.91a, October 2020, p.515 Signed-off-by: Serge Semin --- drivers/edac/synopsys_edac.c | 82 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index 15bf6b5f239a5..9436f9b63fe59 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -156,6 +156,7 @@ /* DDRC address mapping parameters */ #define DDR_ADDRMAP_NREGS 12 +#define DDR_MAX_HIF_WIDTH 60 #define DDR_MAX_ROW_WIDTH 18 #define DDR_MAX_COL_WIDTH 14 #define DDR_MAX_BANK_WIDTH 3 @@ -1350,6 +1351,84 @@ static int snps_ddrc_info_show(struct seq_file *s, void *data) DEFINE_SHOW_ATTRIBUTE(snps_ddrc_info); +static u8 snps_find_sdram_dim(struct snps_edac_priv *priv, u8 hif, char *dim) +{ + struct snps_hif_sdram_map *map = &priv->hif_sdram_map; + int i; + + for (i = 0; i < DDR_MAX_ROW_WIDTH; i++) { + if (map->row[i] == hif) { + *dim = 'r'; + return i; + } + } + + for (i = 0; i < DDR_MAX_COL_WIDTH; i++) { + if (map->col[i] == hif) { + *dim = 'c'; + return i; + } + } + + for (i = 0; i < DDR_MAX_BANK_WIDTH; i++) { + if (map->bank[i] == hif) { + *dim = 'b'; + return i; + } + } + + for (i = 0; i < DDR_MAX_BANKGRP_WIDTH; i++) { + if (map->bankgrp[i] == hif) { + *dim = 'g'; + return i; + } + } + + for (i = 0; i < DDR_MAX_RANK_WIDTH; i++) { + if (map->rank[i] == hif) { + *dim = 'a'; + return i; + } + } + + return DDR_ADDRMAP_UNUSED; +} + +static int snps_hif_sdram_map_show(struct seq_file *s, void *data) +{ + struct mem_ctl_info *mci = s->private; + struct snps_edac_priv *priv = mci->pvt_info; + char dim, buf[SNPS_DBGFS_BUF_LEN]; + const int line_len = 10; + u8 bit; + int i; + + seq_printf(s, "%3s", ""); + for (i = 0; i < line_len; i++) + seq_printf(s, " %02d ", i); + + for (i = 0; i < DDR_MAX_HIF_WIDTH; i++) { + if (i % line_len == 0) + seq_printf(s, "\n%02d ", i); + + bit = snps_find_sdram_dim(priv, i, &dim); + + if (bit != DDR_ADDRMAP_UNUSED) + scnprintf(buf, SNPS_DBGFS_BUF_LEN, "%c%hhu", dim, bit); + else + scnprintf(buf, SNPS_DBGFS_BUF_LEN, "--"); + + seq_printf(s, "%3s ", buf); + } + seq_putc(s, '\n'); + + seq_puts(s, "r - row, c - column, b - bank, g - bank group, a - rank\n"); + + return 0; +} + +DEFINE_SHOW_ATTRIBUTE(snps_hif_sdram_map); + /** * snps_data_poison_setup - Update poison registers. * @priv: DDR memory controller private instance data. @@ -1469,6 +1548,9 @@ static void snps_create_debugfs_nodes(struct mem_ctl_info *mci) edac_debugfs_create_file("ddrc_info", 0400, mci->debugfs, mci, &snps_ddrc_info_fops); + edac_debugfs_create_file("hif_sdram_map", 0400, mci->debugfs, mci, + &snps_hif_sdram_map_fops); + edac_debugfs_create_file("inject_data_error", 0600, mci->debugfs, mci, &snps_inject_data_error); -- cgit v1.2.3 From 395f7718efbd1f0601449f1b4d054d7475bab45c Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Tue, 2 Aug 2022 10:23:38 +0300 Subject: EDAC/synopsys: Add erroneous page-frame/offset reporting A full System/SDRAM address translation interface is now available. Use it to determine the system address causing the ECC faults: add the SDRAM->HIF->Application->System address translation procedures based on the DW uMCTL2 DDRC DQ-bus config and HIF/SDRAM mapping table retrieved on the device probe stage; for the sake of simplification convert the snps_ecc_error_info structure to containing the snps_sdram_addr structure instance, since the erroneous SDRAM address will now participate in the address translation chain; issue the SDRAM->System address translation before passing the later to the edac_mc_handle_error() method. Note the ECC address rank needs to be retrieved now too in order to determine a correct system address. But the rank won't be passed to the MCI core for now since the MCI device is registered with a single ranked layer 0. Also note the ZynqMP-specific HIF->Application->System address mapping procedure is an inverted version of the zynqmp_map_sys_to_hif() method. It implies moving the Application address up to the 32GB base address for the second SAR region (region with offset greater than 2GB). Signed-off-by: Serge Semin --- drivers/edac/synopsys_edac.c | 161 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 136 insertions(+), 25 deletions(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index 9436f9b63fe59..886192f2d28b1 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -346,20 +347,14 @@ struct snps_sdram_addr { /** * struct snps_ecc_error_info - ECC error log information. - * @row: Row number. - * @col: Column number. - * @bank: Bank number. - * @bankgrp: Bank group number. + * @sdram: SDRAM address. * @syndrome: Error syndrome. * @bitpos: Bit position. * @data: Data causing the error. * @ecc: Data ECC. */ struct snps_ecc_error_info { - u32 row; - u32 col; - u32 bank; - u32 bankgrp; + struct snps_sdram_addr sdram; u32 syndrome; u32 bitpos; u64 data; @@ -418,6 +413,20 @@ static void zynqmp_map_sys_to_hif(dma_addr_t sys, u64 *hif) *hif = sys >> 3; } + +/** + * zynqmp_map_hif_to_sys - Convert HIF addr to Sys addr on ZynqMP DDRC. + * @hif: HIF address (source). + * @sys: System address (destination). + */ +static void zynqmp_map_hif_to_sys(u64 hif, dma_addr_t *sys) +{ + *sys = hif << 3; + + /* Map Application address to System address */ + if (*sys >= SZ_2G) + *sys = *sys + SZ_32G - SZ_2G; +} #endif /** @@ -436,6 +445,21 @@ static void snps_map_app_to_hif(struct snps_edac_priv *priv, *hif = app >> priv->info.dq_width; } +/** + * snps_map_hif_to_app - Map HIF address to Application address. + * @priv: DDR memory controller private instance data. + * @hif: HIF address (source). + * @app: Application address (destination). + * + * Backward HIF-to-App translation is just the opposite DQ-width-based + * shift operation. + */ +static void snps_map_hif_to_app(struct snps_edac_priv *priv, + u64 hif, u64 *app) +{ + *app = hif << priv->info.dq_width; +} + /** * snps_map_hif_to_sdram - Map HIF address to SDRAM address. * @priv: DDR memory controller private instance data. @@ -487,6 +511,58 @@ static void snps_map_hif_to_sdram(struct snps_edac_priv *priv, } } +/** + * snps_map_sdram_to_hif - Map SDRAM address to HIF address. + * @priv: DDR memory controller private instance data. + * @sdram: SDRAM address (source). + * @hif: HIF address (destination). + * + * SDRAM-HIF address mapping is similar to the HIF-SDRAM mapping procedure, but + * we'll traverse each SDRAM rank/bank/column/row bit. + * + * Note the unmapped bits of the SDRAM address components will be just + * ignored. So make sure the source address is valid. + */ +static void snps_map_sdram_to_hif(struct snps_edac_priv *priv, + struct snps_sdram_addr *sdram, u64 *hif) +{ + struct snps_hif_sdram_map *map = &priv->hif_sdram_map; + unsigned long addr; + int i; + + *hif = 0; + + addr = sdram->row; + for_each_set_bit(i, &addr, DDR_MAX_ROW_WIDTH) { + if (map->row[i] != DDR_ADDRMAP_UNUSED) + *hif |= BIT_ULL(map->row[i]); + } + + addr = sdram->col; + for_each_set_bit(i, &addr, DDR_MAX_COL_WIDTH) { + if (map->col[i] != DDR_ADDRMAP_UNUSED) + *hif |= BIT_ULL(map->col[i]); + } + + addr = sdram->bank; + for_each_set_bit(i, &addr, DDR_MAX_BANK_WIDTH) { + if (map->bank[i] != DDR_ADDRMAP_UNUSED) + *hif |= BIT_ULL(map->bank[i]); + } + + addr = sdram->bankgrp; + for_each_set_bit(i, &addr, DDR_MAX_BANKGRP_WIDTH) { + if (map->bankgrp[i] != DDR_ADDRMAP_UNUSED) + *hif |= BIT_ULL(map->bankgrp[i]); + } + + addr = sdram->rank; + for_each_set_bit(i, &addr, DDR_MAX_RANK_WIDTH) { + if (map->rank[i] != DDR_ADDRMAP_UNUSED) + *hif |= BIT_ULL(map->rank[i]); + } +} + /** * snps_map_sys_to_sdram - Map System address to SDRAM address. * @priv: DDR memory controller private instance data. @@ -512,6 +588,31 @@ static void snps_map_sys_to_sdram(struct snps_edac_priv *priv, snps_map_hif_to_sdram(priv, hif, sdram); } +/** + * snps_map_sdram_to_sys - Map SDRAM address to System address. + * @priv: DDR memory controller private instance data. + * @sdram: SDRAM address (source). + * @sys: System address (destination). + * + * Perform a full mapping of the SDRAM address (row/column/bank/etc) to + * the System address specific to the controller system bus ports. + */ +static void snps_map_sdram_to_sys(struct snps_edac_priv *priv, + struct snps_sdram_addr *sdram, dma_addr_t *sys) +{ + u64 app, hif; + + snps_map_sdram_to_hif(priv, sdram, &hif); + + if (priv->info.caps & SNPS_CAP_ZYNQMP) { + zynqmp_map_hif_to_sys(hif, sys) + } else { + snps_map_hif_to_app(priv, hif, &app); + + *sys = app; + } +} + /** * snps_get_bitpos - Get DQ-bus corrected bit position. * @syndrome: Error syndrome. @@ -564,12 +665,13 @@ static int snps_get_error_info(struct snps_edac_priv *priv) p->ceinfo.bitpos = snps_get_bitpos(p->ceinfo.syndrome, priv->info.dq_width); regval = readl(base + ECC_CEADDR0_OFST); - p->ceinfo.row = FIELD_GET(ECC_CEADDR0_ROW_MASK, regval); + p->ceinfo.sdram.rank = FIELD_GET(ECC_CEADDR0_RANK_MASK, regval); + p->ceinfo.sdram.row = FIELD_GET(ECC_CEADDR0_ROW_MASK, regval); regval = readl(base + ECC_CEADDR1_OFST); - p->ceinfo.bank = FIELD_GET(ECC_CEADDR1_BANK_MASK, regval); - p->ceinfo.bankgrp = FIELD_GET(ECC_CEADDR1_BANKGRP_MASK, regval); - p->ceinfo.col = FIELD_GET(ECC_CEADDR1_COL_MASK, regval); + p->ceinfo.sdram.bankgrp = FIELD_GET(ECC_CEADDR1_BANKGRP_MASK, regval); + p->ceinfo.sdram.bank = FIELD_GET(ECC_CEADDR1_BANK_MASK, regval); + p->ceinfo.sdram.col = FIELD_GET(ECC_CEADDR1_COL_MASK, regval); p->ceinfo.data = readl(base + ECC_CSYND0_OFST); if (priv->info.dq_width == SNPS_DQ_64) @@ -582,12 +684,13 @@ ue_err: goto out; regval = readl(base + ECC_UEADDR0_OFST); - p->ueinfo.row = FIELD_GET(ECC_CEADDR0_ROW_MASK, regval); + p->ueinfo.sdram.rank = FIELD_GET(ECC_CEADDR0_RANK_MASK, regval); + p->ueinfo.sdram.row = FIELD_GET(ECC_CEADDR0_ROW_MASK, regval); regval = readl(base + ECC_UEADDR1_OFST); - p->ueinfo.bankgrp = FIELD_GET(ECC_CEADDR1_BANKGRP_MASK, regval); - p->ueinfo.bank = FIELD_GET(ECC_CEADDR1_BANK_MASK, regval); - p->ueinfo.col = FIELD_GET(ECC_CEADDR1_COL_MASK, regval); + p->ueinfo.sdram.bankgrp = FIELD_GET(ECC_CEADDR1_BANKGRP_MASK, regval); + p->ueinfo.sdram.bank = FIELD_GET(ECC_CEADDR1_BANK_MASK, regval); + p->ueinfo.sdram.col = FIELD_GET(ECC_CEADDR1_COL_MASK, regval); p->ueinfo.data = readl(base + ECC_UESYND0_OFST); if (priv->info.dq_width == SNPS_DQ_64) @@ -619,31 +722,39 @@ static void snps_handle_error(struct mem_ctl_info *mci, struct snps_ecc_status * { struct snps_edac_priv *priv = mci->pvt_info; struct snps_ecc_error_info *pinf; + dma_addr_t sys; if (p->ce_cnt) { pinf = &p->ceinfo; + snps_map_sdram_to_sys(priv, &pinf->sdram, &sys); + snprintf(priv->message, SNPS_EDAC_MSG_SIZE, - "Row %d Col %d Bank %d Bank Group %d Bit %d Data 0x%08llx:0x%02x", - pinf->row, pinf->col, pinf->bank, pinf->bankgrp, + "Row %hu Col %hu Bank %hhu Bank Group %hhu Rank %hhu Bit %d Data 0x%08llx:0x%02x", + pinf->sdram.row, pinf->sdram.col, pinf->sdram.bank, + pinf->sdram.bankgrp, pinf->sdram.rank, pinf->bitpos, pinf->data, pinf->ecc); - edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, - p->ce_cnt, 0, 0, pinf->syndrome, 0, 0, -1, + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, p->ce_cnt, + PHYS_PFN(sys), offset_in_page(sys), + pinf->syndrome, 0, 0, -1, priv->message, ""); } if (p->ue_cnt) { pinf = &p->ueinfo; + snps_map_sdram_to_sys(priv, &pinf->sdram, &sys); + snprintf(priv->message, SNPS_EDAC_MSG_SIZE, - "Row %d Col %d Bank %d Bank Group %d Data 0x%08llx:0x%02x", - pinf->row, pinf->col, pinf->bank, pinf->bankgrp, + "Row %hu Col %hu Bank %hhu Bank Group %hhu Rank %hhu Data 0x%08llx:0x%02x", + pinf->sdram.row, pinf->sdram.col, pinf->sdram.bank, + pinf->sdram.bankgrp, pinf->sdram.rank, pinf->data, pinf->ecc); - edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, - p->ue_cnt, 0, 0, 0, 0, 0, -1, - priv->message, ""); + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, p->ue_cnt, + PHYS_PFN(sys), offset_in_page(sys), + 0, 0, 0, -1, priv->message, ""); } memset(p, 0, sizeof(*p)); -- cgit v1.2.3 From f1aa06e8f78782290af05ecdc2f74f743c484e9e Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 3 Aug 2022 14:20:27 +0300 Subject: EDAC/synopsys: Add system address regions support Aside with the Application, HIF and SDRAM address spaces DW uMCTL2 DDR controller supports one more level of the address abstraction. It's the System Address Regions (SARs). By default SARs are disabled by means of the IP-core synthesize parameter UMCTL2_A_NSAR being set to zero. In that case the System and Application address spaces match. But if that parameter is set to a non-zero value (but less than or equal to 4), then it's possible to define up to 4 disjoint memory regions mapping to the SDRAM as consecutive addresses. So if the SARs are available on the particular DW uMCTL2 DDR controller, they need to be taken into account in order to get a correct Physical/DMA address by the SDRAM address and vice-versa. The SAR/Application address mapping support is implemented in the similar way as it has been done for the HIF/SDRAM address translation: read the mapping from the SARBASEn and SARSIZEn CSRs and save it in the System/Application address mapping table as the regions base address, size and offset; use the SARs mapping table to translate the system addresses to the application addresses and vice-versa in the framework of the sys-to-SDRAM and SDRAM-to-sys address translation chain. The described functionality is utilized in the code requiring the address translation: ECC errors detection and ECC data poisoning. Note aside with the number of SARs there is the aUMCTL2_SARMINSIZE IP-core parameter which indicates the SARs minimal block size. Alas it isn't auto-detectable, but it's critical to have a correct mapping table. So the suggested functionality expects it being specified for each particular controller otherwise the system address regions support will be forcibly disabled in the driver. Signed-off-by: Serge Semin --- drivers/edac/synopsys_edac.c | 191 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 188 insertions(+), 3 deletions(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index 886192f2d28b1..56565f0819a97 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -92,6 +93,10 @@ #define ECC_POISONPAT1_OFST 0x380 #define ECC_POISONPAT2_OFST 0x384 +/* DDR SAR Registers */ +#define DDR_SARBASE0_OFST 0xF04 +#define DDR_SARSIZE0_OFST 0xF08 + /* ZynqMP DDR QOS Registers */ #define ZYNQMP_DDR_QOS_IRQ_STAT_OFST 0x20200 #define ZYNQMP_DDR_QOS_IRQ_EN_OFST 0x20208 @@ -220,6 +225,10 @@ #define RANK_B0_BASE 6 #define RANK_B1_BASE 7 +/* DDRC System Address parameters */ +#define DDR_MAX_NSAR 4 +#define DDR_MIN_SARSIZE SZ_256M + /* ZynqMP DDR QOS Interrupt register definitions */ #define ZYNQMP_DDR_QOS_UE_MASK BIT(2) #define ZYNQMP_DDR_QOS_CE_MASK BIT(1) @@ -310,6 +319,24 @@ struct snps_ddrc_info { unsigned int ranks; }; +/** + * struct snps_sys_app_map - System/Application mapping table. + * @nsar: Number of SARs enabled on the controller (max 4). + * @minsize: Minimal block size (from 256MB to 32GB). + * @sar.base: SAR base address aligned to minsize. + * @sar.size: SAR size aligned to minsize. + * @sar.ofst: SAR address offset. + */ +struct snps_sys_app_map { + u8 nsar; + u64 minsize; + struct { + u64 base; + u64 size; + u64 ofst; + } sar[DDR_MAX_NSAR]; +}; + /** * struct snps_hif_sdram_map - HIF/SDRAM mapping table. * @row: HIF bit offsets used as row address bits. @@ -378,6 +405,7 @@ struct snps_ecc_status { /** * struct snps_edac_priv - DDR memory controller private data. * @info: DDR controller config info. + * @sys_app_map: Sys/App mapping table. * @hif_sdram_map: HIF/SDRAM mapping table. * @pdev: Platform device. * @baseaddr: Base address of the DDR controller. @@ -388,6 +416,7 @@ struct snps_ecc_status { */ struct snps_edac_priv { struct snps_ddrc_info info; + struct snps_sys_app_map sys_app_map; struct snps_hif_sdram_map hif_sdram_map; struct platform_device *pdev; void __iomem *baseaddr; @@ -429,6 +458,77 @@ static void zynqmp_map_hif_to_sys(u64 hif, dma_addr_t *sys) } #endif +/** + * snps_map_sys_to_app - Map System address to Application address. + * @priv: DDR memory controller private instance data. + * @sys: System address (source). + * @app: Application address (destination). + * + * System address space is used to define disjoint memory regions + * mapped then to the contiguous application memory space: + * + * System Address Space (SAR) <-> Application Address Space + * +------+ +------+ + * | SAR0 |----------------------->| Reg0 | + * +------+ -offset +------+ + * | ... | +----------->| Reg1 | + * +------+ | +------+ + * | SAR1 |-----------+ | ... | + * +------+ + * | ... | + * + * The translation is done by applying the corresponding SAR offset + * to the inbound system address. Note according to the hardware reference + * manual the same mapping is applied to the addresses up to the next + * SAR base address irrespective to the region size. + */ +static void snps_map_sys_to_app(struct snps_edac_priv *priv, + dma_addr_t sys, u64 *app) +{ + struct snps_sys_app_map *map = &priv->sys_app_map; + u64 ofst; + int i; + + ofst = 0; + for (i = 0; i < map->nsar; i++) { + if (sys < map->sar[i].base) + break; + + ofst = map->sar[i].ofst; + } + + *app = sys - ofst; +} + +/** + * snps_map_app_to_sys - Map Application address to System address. + * @priv: DDR memory controller private instance data. + * @app: Application address (source). + * @sys: System address (destination). + * + * Backward App-to-sys translation is easier because the application address + * space is contiguous. So we just need to add the offset corresponding + * to the region the passed address belongs to. Note the later offset is applied + * to all the addresses above the last available region. + */ +static void snps_map_app_to_sys(struct snps_edac_priv *priv, + u64 app, dma_addr_t *sys) +{ + struct snps_sys_app_map *map = &priv->sys_app_map; + u64 ofst, size; + int i; + + ofst = 0; + for (i = 0, size = 0; i < map->nsar; i++) { + ofst = map->sar[i].ofst; + size += map->sar[i].size; + if (app < size) + break; + } + + *sys = app + ofst; +} + /** * snps_map_app_to_hif - Map Application address to HIF address. * @priv: DDR memory controller private instance data. @@ -580,7 +680,7 @@ static void snps_map_sys_to_sdram(struct snps_edac_priv *priv, if (priv->info.caps & SNPS_CAP_ZYNQMP) { zynqmp_map_sys_to_hif(sys, &hif); } else { - app = sys; + snps_map_sys_to_app(priv, sys, &app); snps_map_app_to_hif(priv, app, &hif); } @@ -609,7 +709,7 @@ static void snps_map_sdram_to_sys(struct snps_edac_priv *priv, } else { snps_map_hif_to_app(priv, hif, &app); - *sys = app; + snps_map_app_to_sys(priv, app, sys); } } @@ -1020,6 +1120,52 @@ static int snps_get_ddrc_info(struct snps_edac_priv *priv) return init_plat ? init_plat(priv) : 0; } +/** + * snps_get_sys_app_map - Get System/Application address map. + * @priv: DDR memory controller private instance data. + * @sarregs: Array with SAR registers value. + * + * System address regions are defined by the SARBASEn and SARSIZEn registers. + * Controller reference manual requires the base addresses and sizes creating + * a set of ascending non-overlapped regions in order to have a linear + * application address space. Doing otherwise causes unpredictable results. + */ +static void snps_get_sys_app_map(struct snps_edac_priv *priv, u32 *sarregs) +{ + struct snps_sys_app_map *map = &priv->sys_app_map; + int i, ofst; + + /* + * SARs are supposed to be initialized in the ascending non-overlapped + * order: base[i - 1] < base[i] < etc. If that rule is broken for a SAR + * it's considered as no more SARs have been enabled, so the detection + * procedure will halt. Having the very first SAR with zero base + * address only makes sense if there is a consequent SAR. + */ + for (i = 0, ofst = 0; i < DDR_MAX_NSAR; i++) { + map->sar[i].base = sarregs[2 * i] * map->minsize; + if (map->sar[i].base) + map->nsar = i + 1; + else if (i && map->sar[i].base <= map->sar[i - 1].base) + break; + + map->sar[i].size = (sarregs[2 * i + 1] + 1) * map->minsize; + map->sar[i].ofst = map->sar[i].base - ofst; + ofst += map->sar[i].size; + } + + /* + * SAR block size isn't auto-detectable. If one isn't specified for the + * platform there is a good chance to have invalid mapping of the + * detected SARs. So proceed with 1:1 mapping then. + */ + if (!map->minsize && map->nsar) { + edac_printk(KERN_WARNING, EDAC_MC, + "No block size specified. Discard SARs mapping\n"); + map->nsar = 0; + } +} + /** * snps_get_hif_row_map - Get HIF/SDRAM-row address map. * @priv: DDR memory controller private instance data. @@ -1269,9 +1415,14 @@ static void snps_get_hif_rank_map(struct snps_edac_priv *priv, u32 *addrmap) */ static void snps_get_addr_map(struct snps_edac_priv *priv) { - u32 regval[DDR_ADDRMAP_NREGS]; + u32 regval[MAX(DDR_ADDRMAP_NREGS, 2 * DDR_MAX_NSAR)]; int i; + for (i = 0; i < 2 * DDR_MAX_NSAR; i++) + regval[i] = readl(priv->baseaddr + DDR_SARBASE0_OFST + i * 4); + + snps_get_sys_app_map(priv, regval); + for (i = 0; i < DDR_ADDRMAP_NREGS; i++) regval[i] = readl(priv->baseaddr + DDR_ADDRMAP0_OFST + i * 4); @@ -1462,6 +1613,37 @@ static int snps_ddrc_info_show(struct seq_file *s, void *data) DEFINE_SHOW_ATTRIBUTE(snps_ddrc_info); +static int snps_sys_app_map_show(struct seq_file *s, void *data) +{ + struct mem_ctl_info *mci = s->private; + struct snps_edac_priv *priv = mci->pvt_info; + struct snps_sys_app_map *map = &priv->sys_app_map; + u64 size; + int i; + + if (!map->nsar) { + seq_puts(s, "No SARs detected\n"); + return 0; + } + + seq_printf(s, "%9s %-37s %-18s %-37s\n", + "", "System address", "Offset", "App address"); + + for (i = 0, size = 0; i < map->nsar; i++) { + seq_printf(s, "Region %d: ", i); + seq_printf(s, "0x%016llx-0x%016llx ", map->sar[i].base, + map->sar[i].base + map->sar[i].size - 1); + seq_printf(s, "0x%016llx ", map->sar[i].ofst); + seq_printf(s, "0x%016llx-0x%016llx\n", size, + size + map->sar[i].size - 1); + size += map->sar[i].size; + } + + return 0; +} + +DEFINE_SHOW_ATTRIBUTE(snps_sys_app_map); + static u8 snps_find_sdram_dim(struct snps_edac_priv *priv, u8 hif, char *dim) { struct snps_hif_sdram_map *map = &priv->hif_sdram_map; @@ -1659,6 +1841,9 @@ static void snps_create_debugfs_nodes(struct mem_ctl_info *mci) edac_debugfs_create_file("ddrc_info", 0400, mci->debugfs, mci, &snps_ddrc_info_fops); + edac_debugfs_create_file("sys_app_map", 0400, mci->debugfs, mci, + &snps_sys_app_map_fops); + edac_debugfs_create_file("hif_sdram_map", 0400, mci->debugfs, mci, &snps_hif_sdram_map_fops); -- cgit v1.2.3 From 96baf3b91170aabf400069884cdaf24c230e6953 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Tue, 8 Oct 2024 13:10:16 +0300 Subject: EDAC/synopsys: Drop ZynqMP-specific Sys-HIF mapping methods Since the System address regions support has just been added there is no longer need to preserve the ZynqMP-specific Sys-HIF address translation methods in the driver. The generic implementation shall work for the Zynq UltraScale+ MPSoC DDR controller as well. The App-HIF address translation will work out of the box, since the ZynqMP-specific DQ bus width is know - it's 64-bits. The only problematic part is the Sys-App address mapping due to unknown SAR minimal block size. But it can be inferred from the info submitted in the log of commit 35e6dbfe1846 ("EDAC/synopsys: Fix error injection on Zynq UltraScale+"). In accordance with the commit log the very first System address region is placed at zero offset and is of 2GB size, meanwhile the second region resides at the 32GB offset (it' size is unknown). Thus the SAR minimal block size can be calculated by dividing the first region size (2GB) by the number of blocks initialized for it. The retrieved value will be used in the Sys-App mapping procedures. So let's the ZynqMP-specific Sys-HIF mapping methods and implement the SAR minimal block size autodetection procedure as described above. Signed-off-by: Serge Semin --- Note I don't any ZynqMP DDRC-based device. So testing the patch out would be very welcome. --- drivers/edac/synopsys_edac.c | 57 ++++++++++++-------------------------------- 1 file changed, 15 insertions(+), 42 deletions(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index 56565f0819a97..6f445d338ecea 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -428,36 +428,6 @@ struct snps_edac_priv { #endif }; -#ifdef CONFIG_EDAC_DEBUG -/** - * zynqmp_map_sys_to_hif - Convert Sys addr to HIF addr on ZynqMP DDRC. - * @sys: System address (source). - * @hif: HIF address (destination). - */ -static void zynqmp_map_sys_to_hif(dma_addr_t sys, u64 *hif) -{ - /* Map System address to Application address */ - if (sys >= SZ_32G) - sys = sys - SZ_32G + SZ_2G; - - *hif = sys >> 3; -} - -/** - * zynqmp_map_hif_to_sys - Convert HIF addr to Sys addr on ZynqMP DDRC. - * @hif: HIF address (source). - * @sys: System address (destination). - */ -static void zynqmp_map_hif_to_sys(u64 hif, dma_addr_t *sys) -{ - *sys = hif << 3; - - /* Map Application address to System address */ - if (*sys >= SZ_2G) - *sys = *sys + SZ_32G - SZ_2G; -} -#endif - /** * snps_map_sys_to_app - Map System address to Application address. * @priv: DDR memory controller private instance data. @@ -677,13 +647,9 @@ static void snps_map_sys_to_sdram(struct snps_edac_priv *priv, { u64 app, hif; - if (priv->info.caps & SNPS_CAP_ZYNQMP) { - zynqmp_map_sys_to_hif(sys, &hif); - } else { - snps_map_sys_to_app(priv, sys, &app); + snps_map_sys_to_app(priv, sys, &app); - snps_map_app_to_hif(priv, app, &hif); - } + snps_map_app_to_hif(priv, app, &hif); snps_map_hif_to_sdram(priv, hif, sdram); } @@ -704,13 +670,9 @@ static void snps_map_sdram_to_sys(struct snps_edac_priv *priv, snps_map_sdram_to_hif(priv, sdram, &hif); - if (priv->info.caps & SNPS_CAP_ZYNQMP) { - zynqmp_map_hif_to_sys(hif, sys) - } else { - snps_map_hif_to_app(priv, hif, &app); + snps_map_hif_to_app(priv, hif, &app); - snps_map_app_to_sys(priv, app, sys); - } + snps_map_app_to_sys(priv, app, sys); } /** @@ -972,6 +934,17 @@ static int zynqmp_init_plat(struct snps_edac_priv *priv) priv->info.caps |= SNPS_CAP_ZYNQMP; priv->info.dq_width = SNPS_DQ_64; + /* + * It's known that the Zynq UltraScale+ MPSoC DDR has a disjoint memory + * from 2GB to 32GB. It means that the first region has 0 base address + * and a size of 2GB. It can be utilized to calculate the block size + * based on the value written to the SARSIZE0 register. + * + * TODO convert this to a constant once the size is found. + */ + priv->sys_app_map.minsize = + SZ_2G / (readl(priv->baseaddr + DDR_SARSIZE0_OFST) + 1); + return 0; } -- cgit v1.2.3 From 40bb98d16f1fc9ce5a95b84f34f6af64def4fddf Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 4 Aug 2022 01:26:09 +0300 Subject: EDAC/synopsys: Add mapping-based memory size calculation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently the size of the memory attached to the controller is retrieved by means of the si_meminfo() method. It isn't quite correct because the system may have more than one memory controller. There is a better and more portable approach available to find out the attached memory size. Since the full HIF/SDRAM mapping table is available already in the device probe procedure and the DQ-bus width is detected at that stage too, that info can be used to calculate the total memory size accessible over the corresponding DW uMCTL2 DDR controller. It can be done since the controller databook demands that none two SDRAM bits are mapped to the same HIF bit [1] and that the unused SDRAM address bits mapping must be disabled [2]. Note the size calculation procedure takes the ranks mapping into account. That part will be removed after the multi-ranked MC registration is added. [1] DesignWare® Cores Enhanced Universal DDR Memory Controller (uMCTL2) Databook, Version 3.91a, October 2020, p.108 [2] DesignWare® Cores Enhanced Universal DDR Memory Controller (uMCTL2) Databook, Version 3.91a, October 2020, p.109 Signed-off-by: Serge Semin --- drivers/edac/synopsys_edac.c | 66 ++++++++++++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 17 deletions(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index 6f445d338ecea..185872f6d2ae1 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -976,20 +976,6 @@ static inline enum dev_type snps_get_dtype(u32 mstr) return DEV_UNKNOWN; } -/** - * snps_get_memsize - Read the size of the attached memory device. - * - * Return: the memory size in bytes. - */ -static u32 snps_get_memsize(void) -{ - struct sysinfo inf; - - si_meminfo(&inf); - - return inf.totalram * inf.mem_unit; -} - /** * snps_get_mtype - Returns controller memory type. * @mstr: Master CSR value. @@ -1410,6 +1396,51 @@ static void snps_get_addr_map(struct snps_edac_priv *priv) snps_get_hif_rank_map(priv, regval); } +/** + * snps_get_sdram_size - Calculate SDRAM size. + * @priv: DDR memory controller private data. + * + * The total size of the attached memory is calculated based on the HIF/SDRAM + * mapping table. It can be done since the hardware reference manual demands + * that none two SDRAM bits should be mapped to the same HIF bit and that the + * unused SDRAM address bits mapping must be disabled. + * + * Return: the memory size in bytes. + */ +static u64 snps_get_sdram_size(struct snps_edac_priv *priv) +{ + struct snps_hif_sdram_map *map = &priv->hif_sdram_map; + u64 size = 0; + int i; + + for (i = 0; i < DDR_MAX_ROW_WIDTH; i++) { + if (map->row[i] != DDR_ADDRMAP_UNUSED) + size++; + } + + for (i = 0; i < DDR_MAX_COL_WIDTH; i++) { + if (map->col[i] != DDR_ADDRMAP_UNUSED) + size++; + } + + for (i = 0; i < DDR_MAX_BANK_WIDTH; i++) { + if (map->bank[i] != DDR_ADDRMAP_UNUSED) + size++; + } + + for (i = 0; i < DDR_MAX_BANKGRP_WIDTH; i++) { + if (map->bankgrp[i] != DDR_ADDRMAP_UNUSED) + size++; + } + + for (i = 0; i < DDR_MAX_RANK_WIDTH; i++) { + if (map->rank[i] != DDR_ADDRMAP_UNUSED) + size++; + } + + return 1ULL << (size + priv->info.dq_width); +} + /** * snps_init_csrows - Initialize the csrow data. * @mci: EDAC memory controller instance. @@ -1422,7 +1453,8 @@ static void snps_init_csrows(struct mem_ctl_info *mci) struct snps_edac_priv *priv = mci->pvt_info; struct csrow_info *csi; struct dimm_info *dimm; - u32 size, row, width; + u32 row, width; + u64 size; int j; /* Actual SDRAM-word width for which ECC is calculated */ @@ -1430,13 +1462,13 @@ static void snps_init_csrows(struct mem_ctl_info *mci) for (row = 0; row < mci->nr_csrows; row++) { csi = mci->csrows[row]; - size = snps_get_memsize(); + size = snps_get_sdram_size(priv); for (j = 0; j < csi->nr_channels; j++) { dimm = csi->channels[j]->dimm; dimm->edac_mode = EDAC_SECDED; dimm->mtype = priv->info.sdram_mode; - dimm->nr_pages = (size >> PAGE_SHIFT) / csi->nr_channels; + dimm->nr_pages = PHYS_PFN(size) / csi->nr_channels; dimm->grain = width; dimm->dtype = priv->info.dev_cfg; } -- cgit v1.2.3 From 85cb94ed670ecf92668ed83ac86c3fd6d45c26cd Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 9 Sep 2022 14:41:05 +0300 Subject: dt-bindings: memory: snps: Convert the schema to being generic At the current state the DW uMCTL2 DDRC DT-schema can't be used as the common one for all the IP-core-based devices due to the compatible string property constraining the list of the supported device names. In order to fix that detach the common properties definition to the separate schema. The later will be used by the vendor-specific controller versions to preserve the DT-bindings convention defined for the DW uMCTL2 DDR controller. Thus the generic DW uMCTL2 DDRC DT-bindings will be left with the compatible property definition only and will just refer to the detached common DT-schema. Signed-off-by: Serge Semin Reviewed-by: Rob Herring --- Changelog v2: - This is a new patch created on v2 cycle of the patchset. (@Krzysztof) Changelog v3: - Create common DT-schema instead of using the generic device DT-bindings. (@Rob) --- .../memory-controllers/snps,dw-umctl2-common.yaml | 75 ++++++++++++++++++++++ .../memory-controllers/snps,dw-umctl2-ddrc.yaml | 57 ++-------------- 2 files changed, 81 insertions(+), 51 deletions(-) create mode 100644 Documentation/devicetree/bindings/memory-controllers/snps,dw-umctl2-common.yaml diff --git a/Documentation/devicetree/bindings/memory-controllers/snps,dw-umctl2-common.yaml b/Documentation/devicetree/bindings/memory-controllers/snps,dw-umctl2-common.yaml new file mode 100644 index 0000000000000..115fe5e8339a0 --- /dev/null +++ b/Documentation/devicetree/bindings/memory-controllers/snps,dw-umctl2-common.yaml @@ -0,0 +1,75 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/memory-controllers/snps,dw-umctl2-common.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Synopsys DesignWare Universal Multi-Protocol Memory Controller + +maintainers: + - Krzysztof Kozlowski + - Manish Narani + - Michal Simek + +description: + Synopsys DesignWare Enhanced uMCTL2 DDR Memory Controller is capable of + working with the memory devices supporting up to (LP)DDR4 protocol. It can + be equipped with SEC/DEC ECC feature if DRAM data bus width is either + 16-bits or 32-bits or 64-bits wide. + +select: false + +properties: + interrupts: + description: + DW uMCTL2 DDRC IP-core provides individual IRQ signal for each event":" + ECC Corrected Error, ECC Uncorrected Error, ECC Address Protection, + Scrubber-Done signal, DFI Parity/CRC Error. Some platforms may have the + signals merged before they reach the IRQ controller or have some of them + absent in case if the corresponding feature is unavailable/disabled. + minItems: 1 + maxItems: 5 + + interrupt-names: + minItems: 1 + maxItems: 5 + oneOf: + - description: Common ECC CE/UE/Scrubber/DFI Errors IRQ + items: + - const: ecc + - description: Individual ECC CE/UE/Scrubber/DFI Errors IRQs + items: + enum: [ ecc_ce, ecc_ue, ecc_ap, ecc_sbr, dfi_e ] + + reg: + maxItems: 1 + + clocks: + description: + A standard set of the clock sources contains CSRs bus clock, AXI-ports + reference clock, DDRC core clock, Scrubber standalone clock + (synchronous to the DDRC clock). + minItems: 1 + maxItems: 4 + + clock-names: + minItems: 1 + maxItems: 4 + items: + enum: [ pclk, aclk, core, sbr ] + + resets: + description: + Each clock domain can have separate reset signal. + minItems: 1 + maxItems: 4 + + reset-names: + minItems: 1 + maxItems: 4 + items: + enum: [ prst, arst, core, sbr ] + +additionalProperties: true + +... diff --git a/Documentation/devicetree/bindings/memory-controllers/snps,dw-umctl2-ddrc.yaml b/Documentation/devicetree/bindings/memory-controllers/snps,dw-umctl2-ddrc.yaml index 87ff9ee098f5e..80b25d2fa9746 100644 --- a/Documentation/devicetree/bindings/memory-controllers/snps,dw-umctl2-ddrc.yaml +++ b/Documentation/devicetree/bindings/memory-controllers/snps,dw-umctl2-ddrc.yaml @@ -20,6 +20,11 @@ description: | controller. It has an optional SEC/DEC ECC support in 64- and 32-bits bus width configurations. +allOf: + - $ref: /schemas/memory-controllers/snps,dw-umctl2-common.yaml# + +# Please create a separate DT-schema for your DW uMCTL2 DDR controller +# with more detailed properties definition. properties: compatible: oneOf: @@ -31,62 +36,12 @@ properties: - description: Xilinx ZynqMP DDR controller v2.40a const: xlnx,zynqmp-ddrc-2.40a - interrupts: - description: - DW uMCTL2 DDRC IP-core provides individual IRQ signal for each event":" - ECC Corrected Error, ECC Uncorrected Error, ECC Address Protection, - Scrubber-Done signal, DFI Parity/CRC Error. Some platforms may have the - signals merged before they reach the IRQ controller or have some of them - absent in case if the corresponding feature is unavailable/disabled. - minItems: 1 - maxItems: 5 - - interrupt-names: - minItems: 1 - maxItems: 5 - oneOf: - - description: Common ECC CE/UE/Scrubber/DFI Errors IRQ - items: - - const: ecc - - description: Individual ECC CE/UE/Scrubber/DFI Errors IRQs - items: - enum: [ ecc_ce, ecc_ue, ecc_ap, ecc_sbr, dfi_e ] - - reg: - maxItems: 1 - - clocks: - description: - A standard set of the clock sources contains CSRs bus clock, AXI-ports - reference clock, DDRC core clock, Scrubber standalone clock - (synchronous to the DDRC clock). - minItems: 1 - maxItems: 4 - - clock-names: - minItems: 1 - maxItems: 4 - items: - enum: [ pclk, aclk, core, sbr ] - - resets: - description: - Each clock domain can have separate reset signal. - minItems: 1 - maxItems: 4 - - reset-names: - minItems: 1 - maxItems: 4 - items: - enum: [ prst, arst, core, sbr ] - required: - compatible - reg - interrupts -additionalProperties: false +unevaluatedProperties: false examples: - | -- cgit v1.2.3 From e0000e12eb3f133e6a5ec10083eddee7b4019a26 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Tue, 9 Jun 2026 17:31:02 +0300 Subject: dt-bindings: memory: snps: Add PHYs phandle properties DW uMCTL2 controllers can be equipped with any vendor-specific PHY, but normally they are connected to the Synopsys DesignWare DDRx MultiPHYs via the Synopsys DDRx multiPHY Utility Block (PUB). The later comprise of the ready-to-use generic CSRs accessible over an APB interface and handling various DFI and PHY settings. In that case the PUB+PHY can be represented as a separate DT-node in the platform device-tree. Let's add the PUB+PHY node phandle to be referred in the memory controller DT-node to which the PHY is connected to so to have the device tree better describing the hardware. Signed-off-by: Serge Semin --- Changelog v5: - This is a new patch added on v5 of the series. --- .../bindings/memory-controllers/snps,dw-umctl2-common.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Documentation/devicetree/bindings/memory-controllers/snps,dw-umctl2-common.yaml b/Documentation/devicetree/bindings/memory-controllers/snps,dw-umctl2-common.yaml index 115fe5e8339a0..279568274a0ff 100644 --- a/Documentation/devicetree/bindings/memory-controllers/snps,dw-umctl2-common.yaml +++ b/Documentation/devicetree/bindings/memory-controllers/snps,dw-umctl2-common.yaml @@ -70,6 +70,16 @@ properties: items: enum: [ prst, arst, core, sbr ] + phys: + description: + Controller is equipped with PUB+PHY device to actually access SDRAM + chips. + maxItems: 1 + + phy-names: + items: + - const: pub + additionalProperties: true ... -- cgit v1.2.3 From d8f379deaf8465ff461c201e786c87edb06bf7ce Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 20 Jul 2022 11:19:33 +0300 Subject: dt-bindings: memory: Add BT1 DDRC DT-schema Baikal-T1 DDR controller is based on the DW uMCTL2 DDRC IP-core v2.51a with up to DDR3 protocol capability and 32-bit data bus + 8-bit ECC. There are individual IRQs for each ECC and DFI events. The dedicated scrubber clock source is absent since it's fully synchronous to the core clock. Signed-off-by: Serge Semin Reviewed-by: Rob Herring --- Changelog v2: - Keep the alphabetically ordered compatible strings list. (@Krzysztof) - Fix grammar nitpicks in the patch log. (@Krzysztof) - Drop the PHY CSR region. (@Rob) - Move the device bindings to the separate DT-schema. --- .../memory-controllers/baikal,bt1-ddrc.yaml | 94 ++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 Documentation/devicetree/bindings/memory-controllers/baikal,bt1-ddrc.yaml diff --git a/Documentation/devicetree/bindings/memory-controllers/baikal,bt1-ddrc.yaml b/Documentation/devicetree/bindings/memory-controllers/baikal,bt1-ddrc.yaml new file mode 100644 index 0000000000000..041acd768865a --- /dev/null +++ b/Documentation/devicetree/bindings/memory-controllers/baikal,bt1-ddrc.yaml @@ -0,0 +1,94 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/memory-controllers/baikal,bt1-ddrc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Baikal-T1 DDR Controller + +maintainers: + - Serge Semin + +description: + Baikal-T1 DDRC is based on the DW uMCTL2 DDRC IP-core v2.51a with DDR2 + and DDR3 protocol capability, 32-bit data bus + 8-bit ECC + up to 2 + SDRAM ranks. There are individual IRQs for each ECC and DFI events. + The dedicated scrubber clock source is absent since it's fully + synchronous to the core clock. + +allOf: + - $ref: /schemas/memory-controllers/snps,dw-umctl2-common.yaml# + +properties: + compatible: + const: baikal,bt1-ddrc + + reg: + maxItems: 1 + + interrupts: + maxItems: 4 + + interrupt-names: + items: + - const: dfi_e + - const: ecc_ce + - const: ecc_ue + - const: ecc_sbr + + clocks: + maxItems: 3 + + clock-names: + items: + - const: pclk + - const: aclk + - const: core + + resets: + maxItems: 2 + + reset-names: + items: + - const: arst + - const: core + +required: + - compatible + - reg + - clocks + - clock-names + - interrupts + - interrupt-names + +additionalProperties: false + +examples: + - | + #include + #include + #include + + memory-controller@1f042000 { + compatible = "baikal,bt1-ddrc"; + reg = <0x1f042000 0x1000>; + + interrupts = , + , + , + ; + interrupt-names = "dfi_e", "ecc_ce", "ecc_ue", "ecc_sbr"; + + clocks = <&ccu_sys CCU_SYS_APB_CLK>, + <&ccu_axi CCU_AXI_DDR_CLK>, + <&ccu_pll CCU_DDR_PLL>; + clock-names = "pclk", "aclk", "core"; + + resets = <&ccu_axi CCU_AXI_DDR_RST>, + <&ccu_sys CCU_SYS_DDR_INIT_RST>; + reset-names = "arst", "core"; + + phys = ; + phy-names = "pub"; + }; +... -- cgit v1.2.3 From 0ab49f7be1970b9004074a58dbfd5ea239919194 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sun, 31 Jul 2022 20:17:25 +0300 Subject: EDAC/synopsys: Add multi-ranked memory support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DW uMCTL2 DDRC supports multi-rank memory attached to the controller. If so the MSTR.active_ranks field will be set with the populated ranks bitfield. It is permitted to have one, two or four ranks activated at a time [1]. Since the driver now supports detecting the number of ranks use it for accordingly extending the MCI chip-select layer. In case of the ECC errors the affected rank will be read from the CE/UE address CSRs [2]. Note since the multi-rankness is abstracted out on the EDAC-core layer[0] level, drop the ranks from out of the total memory size calculation. [1] DesignWare® Cores Enhanced Universal DDR Memory Controller (uMCTL2) Databook, Version 3.91a, October 2020, p.739 [2] DesignWare® Cores Enhanced Universal DDR Memory Controller (uMCTL2) Databook, Version 3.91a, October 2020, p.821, p.832 Signed-off-by: Serge Semin --- drivers/edac/synopsys_edac.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index 185872f6d2ae1..ffb3eba369a15 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -23,9 +23,6 @@ #include "edac_module.h" -/* Number of cs_rows needed per memory controller */ -#define SNPS_EDAC_NR_CSROWS 1 - /* Number of channels per memory controller */ #define SNPS_EDAC_NR_CHANS 1 @@ -799,7 +796,7 @@ static void snps_handle_error(struct mem_ctl_info *mci, struct snps_ecc_status * edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, p->ce_cnt, PHYS_PFN(sys), offset_in_page(sys), - pinf->syndrome, 0, 0, -1, + pinf->syndrome, pinf->sdram.rank, 0, -1, priv->message, ""); } @@ -816,7 +813,8 @@ static void snps_handle_error(struct mem_ctl_info *mci, struct snps_ecc_status * edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, p->ue_cnt, PHYS_PFN(sys), offset_in_page(sys), - 0, 0, 0, -1, priv->message, ""); + 0, pinf->sdram.rank, 0, -1, + priv->message, ""); } memset(p, 0, sizeof(*p)); @@ -1433,10 +1431,7 @@ static u64 snps_get_sdram_size(struct snps_edac_priv *priv) size++; } - for (i = 0; i < DDR_MAX_RANK_WIDTH; i++) { - if (map->rank[i] != DDR_ADDRMAP_UNUSED) - size++; - } + /* Skip the ranks since the multi-rankness is determined by layer[0] */ return 1ULL << (size + priv->info.dq_width); } @@ -1490,7 +1485,7 @@ static struct mem_ctl_info *snps_mc_create(struct snps_edac_priv *priv) struct mem_ctl_info *mci; layers[0].type = EDAC_MC_LAYER_CHIP_SELECT; - layers[0].size = SNPS_EDAC_NR_CSROWS; + layers[0].size = priv->info.ranks; layers[0].is_virt_csrow = true; layers[1].type = EDAC_MC_LAYER_CHANNEL; layers[1].size = SNPS_EDAC_NR_CHANS; -- cgit v1.2.3 From 562c5fc42b8f6e87e4b9f13b3b75b5f22764e238 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sat, 6 Aug 2022 12:30:04 +0300 Subject: EDAC/synopsys: Add optional ECC Scrub support DW uMCTL2 DDRC ECC has a so called ECC Scrub feature in case if an single-bit error is detected. The scrub is executed as a new RMW operation to the location that resulted in a single-bit error thus fixing the ECC code preserved in the SDRAM. But that feature not only optional, but also runtime switchable. So there can be platforms with DW uMCTL2 DDRC not supporting hardware-base scrub. In those cases the single-bit errors will still be detected but won't be fixed until the next SDRAM write commands to the erroneous location. Since the ECC Scrub feature availability is detectable by means of the ECCCFG0.dis_scrub flag state use it to tune the MCI core up so one would automatically execute the platform-specific scrubbing to the affected SDRAM location. It's now possible to be done since the DW uMCTL2 DDRC driver supports the actual system address reported to the MCI core. The only thing left to do is to auto-detect the ECC Scrub feature availability and set the mem_ctl.info.scrub_mode mode with SCRUB_SW_SRC if the feature is unavailable. The rest will be done by the MCI core when the single-bit errors happen. Signed-off-by: Serge Semin --- drivers/edac/synopsys_edac.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index ffb3eba369a15..ff7697402367b 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -32,6 +32,7 @@ #define SNPS_EDAC_MOD_VER "1" /* DDR capabilities */ +#define SNPS_CAP_ECC_SCRUB BIT(0) #define SNPS_CAP_ZYNQMP BIT(31) /* Synopsys uMCTL2 DDR controller registers that are relevant to ECC */ @@ -119,6 +120,7 @@ #define DDR_MSTR_MEM_DDR2 0 /* ECC CFG0 register definitions */ +#define ECC_CFG0_DIS_SCRUB BIT(4) #define ECC_CFG0_MODE_MASK GENMASK(2, 0) /* ECC status register definitions */ @@ -1025,6 +1027,10 @@ static int snps_get_ddrc_info(struct snps_edac_priv *priv) return -ENODEV; } + /* Assume HW-src scrub is always available if it isn't disabled */ + if (!(regval & ECC_CFG0_DIS_SCRUB)) + priv->info.caps |= SNPS_CAP_ECC_SCRUB; + /* Auto-detect the basic HIF/SDRAM bus parameters */ regval = readl(priv->baseaddr + DDR_MSTR_OFST); @@ -1507,8 +1513,14 @@ static struct mem_ctl_info *snps_mc_create(struct snps_edac_priv *priv) MEM_FLAG_DDR3 | MEM_FLAG_LPDDR3 | MEM_FLAG_DDR4 | MEM_FLAG_LPDDR4; mci->edac_ctl_cap = EDAC_FLAG_NONE | EDAC_FLAG_SECDED; - mci->scrub_cap = SCRUB_FLAG_HW_SRC; - mci->scrub_mode = SCRUB_NONE; + + if (priv->info.caps & SNPS_CAP_ECC_SCRUB) { + mci->scrub_mode = SCRUB_HW_SRC; + mci->scrub_cap = SCRUB_FLAG_HW_SRC; + } else { + mci->scrub_mode = SCRUB_SW_SRC; + mci->scrub_cap = SCRUB_FLAG_SW_SRC; + } mci->edac_cap = EDAC_FLAG_SECDED; mci->ctl_name = "snps_umctl2_ddrc"; @@ -1601,6 +1613,8 @@ static int snps_ddrc_info_show(struct seq_file *s, void *data) seq_puts(s, "Caps:"); if (priv->info.caps) { + if (priv->info.caps & SNPS_CAP_ECC_SCRUB) + seq_puts(s, " +Scrub"); if (priv->info.caps & SNPS_CAP_ZYNQMP) seq_puts(s, " +ZynqMP"); } else { -- cgit v1.2.3 From aaebafc68f56edd0d301c94aee4f6919240166eb Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 4 Aug 2022 12:57:06 +0300 Subject: EDAC/synopsys: Drop ECC poison address from private data Since the driver now has the generic Sys/SDRAM address translation interface there is no need in preserving the poisonous address in the driver private data especially seeing it is used in the framework of the DebugFS node anyway. So drop the snps_edac_priv.poison_addr field and just perform Sys/SDRAM back and forth address translation right in place of the "inject_data_error" node accessors. It causes a bit more modifications than a simple field removal. Since the poisonous address is not preserved now there is no point in having the snps_data_poison_setup() method so its content can be moved right into the "inject_data_error" write operation. For the same reason there is no point in printing the ECCPOISONADDR{0,1} registers content in the "inject_data_error" read operation. Since the CSRs content is now parsed anyway print the SDRAM address instead. Signed-off-by: Serge Semin --- Changelog v4: - Fix inject_data_error string printing "Rank" word where "Col" is supposed to be. --- drivers/edac/synopsys_edac.c | 68 +++++++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 36 deletions(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index ff7697402367b..a568db1b621f4 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -411,7 +411,6 @@ struct snps_ecc_status { * @reglock: Concurrent CSRs access lock. * @message: Buffer for framing the event specific info. * @stat: ECC status information. - * @poison_addr: Data poison address. */ struct snps_edac_priv { struct snps_ddrc_info info; @@ -422,9 +421,6 @@ struct snps_edac_priv { spinlock_t reglock; char message[SNPS_EDAC_MSG_SIZE]; struct snps_ecc_status stat; -#ifdef CONFIG_EDAC_DEBUG - ulong poison_addr; -#endif }; /** @@ -1736,44 +1732,32 @@ static int snps_hif_sdram_map_show(struct seq_file *s, void *data) DEFINE_SHOW_ATTRIBUTE(snps_hif_sdram_map); -/** - * snps_data_poison_setup - Update poison registers. - * @priv: DDR memory controller private instance data. - * - * Update poison registers as per DDR mapping. - * Return: none. - */ -static void snps_data_poison_setup(struct snps_edac_priv *priv) -{ - struct snps_sdram_addr sdram; - u32 regval; - - snps_map_sys_to_sdram(priv, priv->poison_addr, &sdram); - - regval = FIELD_PREP(ECC_POISON0_RANK_MASK, sdram.rank) | - FIELD_PREP(ECC_POISON0_COL_MASK, sdram.col); - writel(regval, priv->baseaddr + ECC_POISON0_OFST); - - regval = FIELD_PREP(ECC_POISON1_BANKGRP_MASK, sdram.bankgrp) | - FIELD_PREP(ECC_POISON1_BANK_MASK, sdram.bank) | - FIELD_PREP(ECC_POISON1_ROW_MASK, sdram.row); - writel(regval, priv->baseaddr + ECC_POISON1_OFST); -} - static ssize_t snps_inject_data_error_read(struct file *filep, char __user *ubuf, size_t size, loff_t *offp) { struct mem_ctl_info *mci = filep->private_data; struct snps_edac_priv *priv = mci->pvt_info; + struct snps_sdram_addr sdram; char buf[SNPS_DBGFS_BUF_LEN]; + dma_addr_t sys; + u32 regval; int pos; - pos = scnprintf(buf, sizeof(buf), "Poison0 Addr: 0x%08x\n\r", - readl(priv->baseaddr + ECC_POISON0_OFST)); - pos += scnprintf(buf + pos, sizeof(buf) - pos, "Poison1 Addr: 0x%08x\n\r", - readl(priv->baseaddr + ECC_POISON1_OFST)); - pos += scnprintf(buf + pos, sizeof(buf) - pos, "Error injection Address: 0x%lx\n\r", - priv->poison_addr); + regval = readl(priv->baseaddr + ECC_POISON0_OFST); + sdram.rank = FIELD_GET(ECC_POISON0_RANK_MASK, regval); + sdram.col = FIELD_GET(ECC_POISON0_COL_MASK, regval); + + regval = readl(priv->baseaddr + ECC_POISON1_OFST); + sdram.bankgrp = FIELD_PREP(ECC_POISON1_BANKGRP_MASK, regval); + sdram.bank = FIELD_PREP(ECC_POISON1_BANK_MASK, regval); + sdram.row = FIELD_PREP(ECC_POISON1_ROW_MASK, regval); + + snps_map_sdram_to_sys(priv, &sdram, &sys); + + pos = scnprintf(buf, sizeof(buf), + "%pad: Row %hu Col %hu Bank %hhu Bank Group %hhu Rank %hhu\n", + &sys, sdram.row, sdram.col, sdram.bank, sdram.bankgrp, + sdram.rank); return simple_read_from_buffer(ubuf, size, offp, buf, pos); } @@ -1783,13 +1767,25 @@ static ssize_t snps_inject_data_error_write(struct file *filep, const char __use { struct mem_ctl_info *mci = filep->private_data; struct snps_edac_priv *priv = mci->pvt_info; + struct snps_sdram_addr sdram; + u32 regval; + u64 sys; int rc; - rc = kstrtoul_from_user(ubuf, size, 0, &priv->poison_addr); + rc = kstrtou64_from_user(ubuf, size, 0, &sys); if (rc) return rc; - snps_data_poison_setup(priv); + snps_map_sys_to_sdram(priv, sys, &sdram); + + regval = FIELD_PREP(ECC_POISON0_RANK_MASK, sdram.rank) | + FIELD_PREP(ECC_POISON0_COL_MASK, sdram.col); + writel(regval, priv->baseaddr + ECC_POISON0_OFST); + + regval = FIELD_PREP(ECC_POISON1_BANKGRP_MASK, sdram.bankgrp) | + FIELD_PREP(ECC_POISON1_BANK_MASK, sdram.bank) | + FIELD_PREP(ECC_POISON1_ROW_MASK, sdram.row); + writel(regval, priv->baseaddr + ECC_POISON1_OFST); return size; } -- cgit v1.2.3 From 532d00f10009cbc27b777f087624fc485558c5dd Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 4 Aug 2022 13:55:55 +0300 Subject: EDAC/synopsys: Add data poisoning disable support Even though being a pure-debug feature currently the data poison can't be disabled once it has been initialized and enabled. Irrespective to the way the feature has been implemented it doesn't seem right since the system may print false ECC errors in case if the poisoned address is accessed by the kernel or by the user-space applications. It's possible since the poisoned address isn't reserved in any kernel mm subsystems. Even though that doesn't seem right either at least it's tolerable since the ECC data poison is supposed to be utilized in the framework of the EDAC driver debugging, but having the feature non-switchable can't be justified that easy especially seeing it's not that hard to implement. So in order to have the ECC data poison switchable define three possible values acceptable by the "inject_data_poison" DebugFS node: 1. "CE" - emit correctable error (as before). 2. "UE" - emit uncorrectable error (used to be any non-"CE" value). 3. Any other value - disable data poison feature. Note the macros describing the data poison-related fields of the ECC_CFG0 register need to be redefined in a way so they would be used to separately switch the feature on/off and to select the type of the ECC error. As a result the suggest solution turns into a proper ECC_CFG0 CSRs fields setup based on the value written to the "inject_data_poison" DebugFS node. Signed-off-by: Serge Semin --- drivers/edac/synopsys_edac.c | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index a568db1b621f4..c0d1da61e08e1 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -123,6 +123,10 @@ #define ECC_CFG0_DIS_SCRUB BIT(4) #define ECC_CFG0_MODE_MASK GENMASK(2, 0) +/* ECC CFG1 register definitions */ +#define ECC_CFG1_POISON_BIT BIT(1) +#define ECC_CFG1_POISON_EN BIT(0) + /* ECC status register definitions */ #define ECC_STAT_UE_MASK GENMASK(23, 16) #define ECC_STAT_CE_MASK GENMASK(15, 8) @@ -154,10 +158,6 @@ #define ECC_POISON1_BANK_MASK GENMASK(26, 24) #define ECC_POISON1_ROW_MASK GENMASK(17, 0) -/* DDRC ECC CE & UE poison mask */ -#define ECC_CEPOISON_MASK GENMASK(1, 0) -#define ECC_UEPOISON_MASK BIT(0) - /* DDRC address mapping parameters */ #define DDR_ADDRMAP_NREGS 12 @@ -1804,10 +1804,14 @@ static ssize_t snps_inject_data_poison_read(struct file *filep, char __user *ubu int pos; regval = readl(priv->baseaddr + ECC_CFG1_OFST); - errstr = FIELD_GET(ECC_CEPOISON_MASK, regval) == ECC_CEPOISON_MASK ? - "Correctable Error" : "UnCorrectable Error"; + if (!(regval & ECC_CFG1_POISON_EN)) + errstr = "Off"; + else if (regval & ECC_CFG1_POISON_BIT) + errstr = "CE"; + else + errstr = "UE"; - pos = scnprintf(buf, sizeof(buf), "Data Poisoning: %s\n\r", errstr); + pos = scnprintf(buf, sizeof(buf), "%s\n", errstr); return simple_read_from_buffer(ubuf, size, offp, buf, pos); } @@ -1818,6 +1822,7 @@ static ssize_t snps_inject_data_poison_write(struct file *filep, const char __us struct mem_ctl_info *mci = filep->private_data; struct snps_edac_priv *priv = mci->pvt_info; char buf[SNPS_DBGFS_BUF_LEN]; + u32 regval; int rc; rc = simple_write_to_buffer(buf, sizeof(buf), offp, ubuf, size); @@ -1825,10 +1830,16 @@ static ssize_t snps_inject_data_poison_write(struct file *filep, const char __us return rc; writel(0, priv->baseaddr + DDR_SWCTL); + + regval = readl(priv->baseaddr + ECC_CFG1_OFST); if (strncmp(buf, "CE", 2) == 0) - writel(ECC_CEPOISON_MASK, priv->baseaddr + ECC_CFG1_OFST); + regval |= ECC_CFG1_POISON_BIT | ECC_CFG1_POISON_EN; + else if (strncmp(buf, "UE", 2) == 0) + regval = (regval & ~ECC_CFG1_POISON_BIT) | ECC_CFG1_POISON_EN; else - writel(ECC_UEPOISON_MASK, priv->baseaddr + ECC_CFG1_OFST); + regval &= ~ECC_CFG1_POISON_EN; + writel(regval, priv->baseaddr + ECC_CFG1_OFST); + writel(1, priv->baseaddr + DDR_SWCTL); return size; -- cgit v1.2.3 From ab6b4f047392368047b417ff51add4aebcd9b5fd Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 4 Aug 2022 16:36:32 +0300 Subject: EDAC/synopsys: Split up ECC UE/CE IRQs handler DW uMCTL2 DDRC IP-core doesn't have common IRQ line. Instead it provides individual IRQ output signals for each controller events like: corrected error, uncorrected error, DFI parity error, address protection, scrubber done, and so on. So the common IRQ handler implemented in the Synopsys EDAC driver isn't device-specific but is a particular platform specific. Obviously it won't be suitable for the generic devices which are added to the platforms with the original individual IRQs. So split up the common IRQ handler into two ones handling ECC corrected and uncorrected errors. It won't be that hard since both sub-methods it calls are already logically divided into two CE/UE parts. It just implies moving these parts into the dedicated methods and redefining the local variables a bit. The new methods will be simply called from the common IRQs handler if one is utilized on the particular platform. Otherwise each new IRQ handler will be called on particular interrupt request (the IRQ handlers registration will be added a bit later). Note the snps_ecc_status structure is no longer needed since the error data is collected and reported now within a single method. So drop it. Also note that since the ECC UE/CE IRQs can be reported via the individual lines their handlers can be run simultaneously, thus the message buffer can no longer be shared. So the former single message buffer has been replaced with two info-message buffers in the snps_edac_priv structure - each for each interrupts. Signed-off-by: Serge Semin --- drivers/edac/synopsys_edac.c | 279 ++++++++++++++++++++++--------------------- 1 file changed, 141 insertions(+), 138 deletions(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index c0d1da61e08e1..68241aacfa838 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -231,7 +231,6 @@ /* ZynqMP DDR QOS Interrupt register definitions */ #define ZYNQMP_DDR_QOS_UE_MASK BIT(2) #define ZYNQMP_DDR_QOS_CE_MASK BIT(1) -#define ZYNQMP_DDR_QOS_IRQ_MASK (ZYNQMP_DDR_QOS_UE_MASK | ZYNQMP_DDR_QOS_CE_MASK) /** * enum snps_dq_width - SDRAM DQ bus width (ECC capable). @@ -373,6 +372,8 @@ struct snps_sdram_addr { /** * struct snps_ecc_error_info - ECC error log information. + * @ecnt: Number of detected errors. + * @syndrome: Error syndrome. * @sdram: SDRAM address. * @syndrome: Error syndrome. * @bitpos: Bit position. @@ -380,6 +381,7 @@ struct snps_sdram_addr { * @ecc: Data ECC. */ struct snps_ecc_error_info { + u16 ecnt; struct snps_sdram_addr sdram; u32 syndrome; u32 bitpos; @@ -387,20 +389,6 @@ struct snps_ecc_error_info { u32 ecc; }; -/** - * struct snps_ecc_status - ECC status information to report. - * @ce_cnt: Correctable error count. - * @ue_cnt: Uncorrectable error count. - * @ceinfo: Correctable error log information. - * @ueinfo: Uncorrectable error log information. - */ -struct snps_ecc_status { - u32 ce_cnt; - u32 ue_cnt; - struct snps_ecc_error_info ceinfo; - struct snps_ecc_error_info ueinfo; -}; - /** * struct snps_edac_priv - DDR memory controller private data. * @info: DDR controller config info. @@ -409,8 +397,8 @@ struct snps_ecc_status { * @pdev: Platform device. * @baseaddr: Base address of the DDR controller. * @reglock: Concurrent CSRs access lock. - * @message: Buffer for framing the event specific info. - * @stat: ECC status information. + * @msg_ecc_ce: Buffer for framing the ECC CE specific info. + * @msg_ecc_ue: Buffer for framing the ECC UE specific info. */ struct snps_edac_priv { struct snps_ddrc_info info; @@ -419,8 +407,8 @@ struct snps_edac_priv { struct platform_device *pdev; void __iomem *baseaddr; spinlock_t reglock; - char message[SNPS_EDAC_MSG_SIZE]; - struct snps_ecc_status stat; + char msg_ecc_ce[SNPS_EDAC_MSG_SIZE]; + char msg_ecc_ue[SNPS_EDAC_MSG_SIZE]; }; /** @@ -692,130 +680,179 @@ static inline u32 snps_get_bitpos(u32 syndrome, enum snps_dq_width dq_width) } /** - * snps_get_error_info - Get the current ECC error info. - * @priv: DDR memory controller private instance data. + * snps_ce_irq_handler - Corrected error interrupt handler. + * @irq: IRQ number. + * @dev_id: Device ID. * - * Return: one if there is no error otherwise returns zero. + * Return: IRQ_NONE, if interrupt not set or IRQ_HANDLED otherwise. */ -static int snps_get_error_info(struct snps_edac_priv *priv) +static irqreturn_t snps_ce_irq_handler(int irq, void *dev_id) { - struct snps_ecc_status *p; - u32 regval, clearval; + struct mem_ctl_info *mci = dev_id; + struct snps_edac_priv *priv = mci->pvt_info; + struct snps_ecc_error_info einfo; unsigned long flags; - void __iomem *base; + u32 qosval, regval; + dma_addr_t sys; - base = priv->baseaddr; - p = &priv->stat; + /* Make sure IRQ is caused by a corrected ECC error */ + if (priv->info.caps & SNPS_CAP_ZYNQMP) { + qosval = readl(priv->baseaddr + ZYNQMP_DDR_QOS_IRQ_STAT_OFST); + if (!(qosval & ZYNQMP_DDR_QOS_CE_MASK)) + return IRQ_NONE; - regval = readl(base + ECC_STAT_OFST); - if (!regval) - return 1; + qosval &= ZYNQMP_DDR_QOS_CE_MASK; + } - p->ceinfo.syndrome = FIELD_GET(ECC_STAT_BITNUM_MASK, regval); + regval = readl(priv->baseaddr + ECC_STAT_OFST); + if (!FIELD_GET(ECC_STAT_CE_MASK, regval)) + return IRQ_NONE; - regval = readl(base + ECC_ERRCNT_OFST); - p->ce_cnt = FIELD_GET(ECC_ERRCNT_CECNT_MASK, regval); - p->ue_cnt = FIELD_GET(ECC_ERRCNT_UECNT_MASK, regval); - if (!p->ce_cnt) - goto ue_err; + /* Read error info like syndrome, bit position, SDRAM address, data */ + einfo.syndrome = FIELD_GET(ECC_STAT_BITNUM_MASK, regval); - p->ceinfo.bitpos = snps_get_bitpos(p->ceinfo.syndrome, priv->info.dq_width); + einfo.bitpos = snps_get_bitpos(einfo.syndrome, priv->info.dq_width); - regval = readl(base + ECC_CEADDR0_OFST); - p->ceinfo.sdram.rank = FIELD_GET(ECC_CEADDR0_RANK_MASK, regval); - p->ceinfo.sdram.row = FIELD_GET(ECC_CEADDR0_ROW_MASK, regval); + regval = readl(priv->baseaddr + ECC_ERRCNT_OFST); + einfo.ecnt = FIELD_GET(ECC_ERRCNT_CECNT_MASK, regval); - regval = readl(base + ECC_CEADDR1_OFST); - p->ceinfo.sdram.bankgrp = FIELD_GET(ECC_CEADDR1_BANKGRP_MASK, regval); - p->ceinfo.sdram.bank = FIELD_GET(ECC_CEADDR1_BANK_MASK, regval); - p->ceinfo.sdram.col = FIELD_GET(ECC_CEADDR1_COL_MASK, regval); + regval = readl(priv->baseaddr + ECC_CEADDR0_OFST); + einfo.sdram.rank = FIELD_GET(ECC_CEADDR0_RANK_MASK, regval); + einfo.sdram.row = FIELD_GET(ECC_CEADDR0_ROW_MASK, regval); - p->ceinfo.data = readl(base + ECC_CSYND0_OFST); - if (priv->info.dq_width == SNPS_DQ_64) - p->ceinfo.data |= (u64)readl(base + ECC_CSYND1_OFST) << 32; + regval = readl(priv->baseaddr + ECC_CEADDR1_OFST); + einfo.sdram.bankgrp = FIELD_GET(ECC_CEADDR1_BANKGRP_MASK, regval); + einfo.sdram.bank = FIELD_GET(ECC_CEADDR1_BANK_MASK, regval); + einfo.sdram.col = FIELD_GET(ECC_CEADDR1_COL_MASK, regval); - p->ceinfo.ecc = readl(base + ECC_CSYND2_OFST); - -ue_err: - if (!p->ue_cnt) - goto out; + einfo.data = readl(priv->baseaddr + ECC_CSYND0_OFST); + if (priv->info.dq_width == SNPS_DQ_64) + einfo.data |= (u64)readl(priv->baseaddr + ECC_CSYND1_OFST) << 32; - regval = readl(base + ECC_UEADDR0_OFST); - p->ueinfo.sdram.rank = FIELD_GET(ECC_CEADDR0_RANK_MASK, regval); - p->ueinfo.sdram.row = FIELD_GET(ECC_CEADDR0_ROW_MASK, regval); + einfo.ecc = readl(priv->baseaddr + ECC_CSYND2_OFST); - regval = readl(base + ECC_UEADDR1_OFST); - p->ueinfo.sdram.bankgrp = FIELD_GET(ECC_CEADDR1_BANKGRP_MASK, regval); - p->ueinfo.sdram.bank = FIELD_GET(ECC_CEADDR1_BANK_MASK, regval); - p->ueinfo.sdram.col = FIELD_GET(ECC_CEADDR1_COL_MASK, regval); + /* Report the detected errors with the corresponding sys address */ + snps_map_sdram_to_sys(priv, &einfo.sdram, &sys); - p->ueinfo.data = readl(base + ECC_UESYND0_OFST); - if (priv->info.dq_width == SNPS_DQ_64) - p->ueinfo.data |= (u64)readl(base + ECC_UESYND1_OFST) << 32; + snprintf(priv->msg_ecc_ce, SNPS_EDAC_MSG_SIZE, + "Row %hu Col %hu Bank %hhu Bank Group %hhu Rank %hhu Bit %d Data 0x%08llx:0x%02x", + einfo.sdram.row, einfo.sdram.col, einfo.sdram.bank, + einfo.sdram.bankgrp, einfo.sdram.rank, + einfo.bitpos, einfo.data, einfo.ecc); - p->ueinfo.ecc = readl(base + ECC_UESYND2_OFST); + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, einfo.ecnt, + PHYS_PFN(sys), offset_in_page(sys), + einfo.syndrome, einfo.sdram.rank, 0, -1, + priv->msg_ecc_ce, ""); -out: + /* Make sure the CE IRQ status is cleared */ spin_lock_irqsave(&priv->reglock, flags); - clearval = readl(base + ECC_CLR_OFST) | - ECC_CTRL_CLR_CE_ERR | ECC_CTRL_CLR_CE_ERRCNT | - ECC_CTRL_CLR_UE_ERR | ECC_CTRL_CLR_UE_ERRCNT; - writel(clearval, base + ECC_CLR_OFST); + regval = readl(priv->baseaddr + ECC_CLR_OFST) | + ECC_CTRL_CLR_CE_ERR | ECC_CTRL_CLR_CE_ERRCNT; + writel(regval, priv->baseaddr + ECC_CLR_OFST); spin_unlock_irqrestore(&priv->reglock, flags); - return 0; + if (priv->info.caps & SNPS_CAP_ZYNQMP) + writel(qosval, priv->baseaddr + ZYNQMP_DDR_QOS_IRQ_STAT_OFST); + + return IRQ_HANDLED; } /** - * snps_handle_error - Handle Correctable and Uncorrectable errors. - * @mci: EDAC memory controller instance. - * @p: Synopsys ECC status structure. + * snps_ue_irq_handler - Uncorrected error interrupt handler. + * @irq: IRQ number. + * @dev_id: Device ID. * - * Handles ECC correctable and uncorrectable errors. + * Return: IRQ_NONE, if interrupt not set or IRQ_HANDLED otherwise. */ -static void snps_handle_error(struct mem_ctl_info *mci, struct snps_ecc_status *p) +static irqreturn_t snps_ue_irq_handler(int irq, void *dev_id) { + struct mem_ctl_info *mci = dev_id; struct snps_edac_priv *priv = mci->pvt_info; - struct snps_ecc_error_info *pinf; + struct snps_ecc_error_info einfo; + unsigned long flags; + u32 qosval, regval; dma_addr_t sys; - if (p->ce_cnt) { - pinf = &p->ceinfo; + /* Make sure IRQ is caused by an uncorrected ECC error */ + if (priv->info.caps & SNPS_CAP_ZYNQMP) { + qosval = readl(priv->baseaddr + ZYNQMP_DDR_QOS_IRQ_STAT_OFST); + if (!(regval & ZYNQMP_DDR_QOS_UE_MASK)) + return IRQ_NONE; + + qosval &= ZYNQMP_DDR_QOS_UE_MASK; + } - snps_map_sdram_to_sys(priv, &pinf->sdram, &sys); + regval = readl(priv->baseaddr + ECC_STAT_OFST); + if (!FIELD_GET(ECC_STAT_UE_MASK, regval)) + return IRQ_NONE; - snprintf(priv->message, SNPS_EDAC_MSG_SIZE, - "Row %hu Col %hu Bank %hhu Bank Group %hhu Rank %hhu Bit %d Data 0x%08llx:0x%02x", - pinf->sdram.row, pinf->sdram.col, pinf->sdram.bank, - pinf->sdram.bankgrp, pinf->sdram.rank, - pinf->bitpos, pinf->data, pinf->ecc); + /* Read error info like SDRAM address, data and syndrome */ + regval = readl(priv->baseaddr + ECC_ERRCNT_OFST); + einfo.ecnt = FIELD_GET(ECC_ERRCNT_UECNT_MASK, regval); - edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, p->ce_cnt, - PHYS_PFN(sys), offset_in_page(sys), - pinf->syndrome, pinf->sdram.rank, 0, -1, - priv->message, ""); - } + regval = readl(priv->baseaddr + ECC_UEADDR0_OFST); + einfo.sdram.rank = FIELD_GET(ECC_CEADDR0_RANK_MASK, regval); + einfo.sdram.row = FIELD_GET(ECC_CEADDR0_ROW_MASK, regval); - if (p->ue_cnt) { - pinf = &p->ueinfo; + regval = readl(priv->baseaddr + ECC_UEADDR1_OFST); + einfo.sdram.bankgrp = FIELD_GET(ECC_CEADDR1_BANKGRP_MASK, regval); + einfo.sdram.bank = FIELD_GET(ECC_CEADDR1_BANK_MASK, regval); + einfo.sdram.col = FIELD_GET(ECC_CEADDR1_COL_MASK, regval); - snps_map_sdram_to_sys(priv, &pinf->sdram, &sys); + einfo.data = readl(priv->baseaddr + ECC_UESYND0_OFST); + if (priv->info.dq_width == SNPS_DQ_64) + einfo.data |= (u64)readl(priv->baseaddr + ECC_UESYND1_OFST) << 32; - snprintf(priv->message, SNPS_EDAC_MSG_SIZE, - "Row %hu Col %hu Bank %hhu Bank Group %hhu Rank %hhu Data 0x%08llx:0x%02x", - pinf->sdram.row, pinf->sdram.col, pinf->sdram.bank, - pinf->sdram.bankgrp, pinf->sdram.rank, - pinf->data, pinf->ecc); + einfo.ecc = readl(priv->baseaddr + ECC_UESYND2_OFST); - edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, p->ue_cnt, - PHYS_PFN(sys), offset_in_page(sys), - 0, pinf->sdram.rank, 0, -1, - priv->message, ""); - } + /* Report the detected errors with the corresponding sys address */ + snps_map_sdram_to_sys(priv, &einfo.sdram, &sys); + + snprintf(priv->msg_ecc_ue, SNPS_EDAC_MSG_SIZE, + "Row %hu Col %hu Bank %hhu Bank Group %hhu Rank %hhu Data 0x%08llx:0x%02x", + einfo.sdram.row, einfo.sdram.col, einfo.sdram.bank, + einfo.sdram.bankgrp, einfo.sdram.rank, + einfo.data, einfo.ecc); + + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, einfo.ecnt, + PHYS_PFN(sys), offset_in_page(sys), + 0, einfo.sdram.rank, 0, -1, + priv->msg_ecc_ue, ""); + + /* Make sure the UE IRQ status is cleared */ + spin_lock_irqsave(&priv->reglock, flags); - memset(p, 0, sizeof(*p)); + regval = readl(priv->baseaddr + ECC_CLR_OFST) | + ECC_CTRL_CLR_UE_ERR | ECC_CTRL_CLR_UE_ERRCNT; + writel(regval, priv->baseaddr + ECC_CLR_OFST); + + spin_unlock_irqrestore(&priv->reglock, flags); + + if (priv->info.caps & SNPS_CAP_ZYNQMP) + writel(qosval, priv->baseaddr + ZYNQMP_DDR_QOS_IRQ_STAT_OFST); + + return IRQ_HANDLED; +} + +/** + * snps_com_irq_handler - Interrupt IRQ signal handler. + * @irq: IRQ number. + * @dev_id: Device ID. + * + * Return: IRQ_NONE, if interrupts not set or IRQ_HANDLED otherwise. + */ +static irqreturn_t snps_com_irq_handler(int irq, void *dev_id) +{ + irqreturn_t rc = IRQ_NONE; + + rc |= snps_ce_irq_handler(irq, dev_id); + + rc |= snps_ue_irq_handler(irq, dev_id); + + return rc; } static void snps_enable_irq(struct snps_edac_priv *priv) @@ -861,40 +898,6 @@ static void snps_disable_irq(struct snps_edac_priv *priv) spin_unlock_irqrestore(&priv->reglock, flags); } -/** - * snps_irq_handler - Interrupt Handler for ECC interrupts. - * @irq: IRQ number. - * @dev_id: Device ID. - * - * Return: IRQ_NONE, if interrupt not set or IRQ_HANDLED otherwise. - */ -static irqreturn_t snps_irq_handler(int irq, void *dev_id) -{ - struct mem_ctl_info *mci = dev_id; - struct snps_edac_priv *priv; - int status, regval; - - priv = mci->pvt_info; - - if (priv->info.caps & SNPS_CAP_ZYNQMP) { - regval = readl(priv->baseaddr + ZYNQMP_DDR_QOS_IRQ_STAT_OFST); - regval &= (ZYNQMP_DDR_QOS_CE_MASK | ZYNQMP_DDR_QOS_UE_MASK); - if (!(regval & ZYNQMP_DDR_QOS_IRQ_MASK)) - return IRQ_NONE; - } - - status = snps_get_error_info(priv); - if (status) - return IRQ_NONE; - - snps_handle_error(mci, &priv->stat); - - if (priv->info.caps & SNPS_CAP_ZYNQMP) - writel(regval, priv->baseaddr + ZYNQMP_DDR_QOS_IRQ_STAT_OFST); - - return IRQ_HANDLED; -} - /** * snps_create_data - Create private data. * @pdev: platform device. @@ -1561,7 +1564,7 @@ static int snps_setup_irq(struct mem_ctl_info *mci) return irq; } - ret = devm_request_irq(&priv->pdev->dev, irq, snps_irq_handler, + ret = devm_request_irq(&priv->pdev->dev, irq, snps_com_irq_handler, 0, dev_name(&priv->pdev->dev), mci); if (ret < 0) { edac_printk(KERN_ERR, EDAC_MC, "Failed to request IRQ\n"); -- cgit v1.2.3 From 1327616f59cc67f261201cbc03b14f8f5db86b95 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 4 Aug 2022 18:32:06 +0300 Subject: EDAC/synopsys: Add individual named ECC IRQs support Currently the DW uMCTL2 DDRC EDAC driver supports a common unnamed IRQ only. It isn't suitable for the platforms which have the individual IRQ lines for each DDRC event (ECC UE, ECC CE, DFI parity error, Scrubber done, etc). Moreover the DW uMCTL2 DDRC IP-core doesn't have an option to be configured with a common interrupts output line. So in order to have the generic DW uMCTL2 DDR controller supported by the driver add the individual (per DDRC event) IRQs request support. There is not much to do really since the common IRQs handler has already been split up into the sub-handlers: first try to request the individual IRQs; if failed fallback to using the common IRQ. The IRQ names are used in accordance with the DW uMCTL2 DDRC DT-bindings. Signed-off-by: Serge Semin --- drivers/edac/synopsys_edac.c | 91 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 81 insertions(+), 10 deletions(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index 68241aacfa838..1e12413262c06 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -1552,25 +1552,96 @@ static void snps_mc_free(struct mem_ctl_info *mci) edac_mc_free(mci); } -static int snps_setup_irq(struct mem_ctl_info *mci) +/** + * snps_request_ind_irq - Request individual DDRC IRQs. + * @mci: EDAC memory controller instance. + * + * Return: 0 if the IRQs were successfully requested, 1 if the individual IRQs + * are unavailable, otherwise negative errno. + */ +static int snps_request_ind_irq(struct mem_ctl_info *mci) { struct snps_edac_priv *priv = mci->pvt_info; - int ret, irq; + struct device *dev = &priv->pdev->dev; + int rc, irq; - irq = platform_get_irq(priv->pdev, 0); - if (irq < 0) { - edac_printk(KERN_ERR, EDAC_MC, - "No IRQ %d in DT\n", irq); + irq = platform_get_irq_byname_optional(priv->pdev, "ecc_ce"); + if (irq == -ENXIO) + return 1; + if (irq < 0) + return irq; + + rc = devm_request_irq(dev, irq, snps_ce_irq_handler, 0, "ecc_ce", mci); + if (rc) { + edac_printk(KERN_ERR, EDAC_MC, "Failed to request ECC CE IRQ\n"); + return rc; + } + + irq = platform_get_irq_byname(priv->pdev, "ecc_ue"); + if (irq < 0) return irq; + + rc = devm_request_irq(dev, irq, snps_ue_irq_handler, 0, "ecc_ue", mci); + if (rc) { + edac_printk(KERN_ERR, EDAC_MC, "Failed to request ECC UE IRQ\n"); + return rc; + } + + return 0; +} + +/** + * snps_request_com_irq - Request common DDRC IRQ. + * @mci: EDAC memory controller instance. + * + * It first attempts to get the named IRQ. If failed the method fallbacks + * to first available one. + * + * Return: 0 if the IRQ was successfully requested otherwise negative errno. + */ +static int snps_request_com_irq(struct mem_ctl_info *mci) +{ + struct snps_edac_priv *priv = mci->pvt_info; + struct device *dev = &priv->pdev->dev; + int rc, irq; + + irq = platform_get_irq_byname_optional(priv->pdev, "ecc"); + if (irq < 0) { + irq = platform_get_irq(priv->pdev, 0); + if (irq < 0) + return irq; } - ret = devm_request_irq(&priv->pdev->dev, irq, snps_com_irq_handler, - 0, dev_name(&priv->pdev->dev), mci); - if (ret < 0) { + rc = devm_request_irq(dev, irq, snps_com_irq_handler, 0, "ecc", mci); + if (rc) { edac_printk(KERN_ERR, EDAC_MC, "Failed to request IRQ\n"); - return ret; + return rc; } + return 0; +} + +/** + * snps_setup_irq - Request and enable DDRC IRQs. + * @mci: EDAC memory controller instance. + * + * It first tries to get and request individual IRQs. If failed the method + * fallbacks to the common IRQ line case. The IRQs will be enabled only if + * some of these requests have been successful. + * + * Return: 0 if IRQs were successfully setup otherwise negative errno. + */ +static int snps_setup_irq(struct mem_ctl_info *mci) +{ + struct snps_edac_priv *priv = mci->pvt_info; + int rc; + + rc = snps_request_ind_irq(mci); + if (rc > 0) + rc = snps_request_com_irq(mci); + if (rc) + return rc; + snps_enable_irq(priv); return 0; -- cgit v1.2.3 From 1c8e6b463da765003dd5cd03e85fff5287914b5b Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 4 Aug 2022 22:05:57 +0300 Subject: EDAC/synopsys: Add DFI alert_n IRQ support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In accordance with [1] DW uMCTL2 DDR controller can generate an IRQ in case if an attached SDRAM detects a CRC/Parity error. That capability is mainly applicable for the DDR4 memory which has an additional signals PARITY/ALERT_n indicating the even SDRAM address/command parity signal and alert if the parity turns to be not even. But in accordance with [1] at least the SDRAM address/command parity is calculated irrespective of the memory protocol and then sent out by means of the dfi_parity_n signal further to the DDR PHY. So depending on the DDR protocol and the DDR PHY implementation the CRC/Parity error can be checked at some point independently from the DDR devices type and then signaled via the dfi_alert_n line. In anycase it would be very much useful to catch the event and at least warn the user about problems with the DFI/SDRAM signals integrity. So add the DFI CRC/Parity IRQs handling support in the next manner. First the IRQ line is requested by the name "dfi_e" (defined in the DT-bindings) and register its handler in case of the platform with the individual DW uMCTL2 DDRC IRQs. If individual IRQs are unavailable the common IRQ handler will call the DFI CRC/Parity event handler. Note the handler just checks the IRQ status, reads the number of errors, reports the fatal error to the MCI core and clears the IRQ status. Alas neither the erroneous SDRAM address nor the executed command are available in this case. Second the DFI CRC/Parity IRQ is enabled/disabled together with the ECC CE/UE interrupts in the controller probe procedure. Finally the CRC/Parity capability is advertised by the EDAC controller capabilities flags. [1] DesignWare® Cores Enhanced Universal DDR Memory Controller (uMCTL2) Databook, Version 3.91a, October 2020, p.131-132 Signed-off-by: Serge Semin --- drivers/edac/synopsys_edac.c | 80 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 2 deletions(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index 1e12413262c06..4b56c9866e102 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -80,6 +80,12 @@ #define ECC_POISON0_OFST 0xB8 #define ECC_POISON1_OFST 0xBC +/* DDR CRC/Parity Registers */ +#define DDR_CRCPARCTL0_OFST 0xC0 +#define DDR_CRCPARCTL1_OFST 0xC4 +#define DDR_CRCPARCTL2_OFST 0xC8 +#define DDR_CRCPARSTAT_OFST 0xCC + /* DDR Address Map Registers */ #define DDR_ADDRMAP0_OFST 0x200 @@ -151,6 +157,13 @@ #define ECC_CEADDR1_BANK_MASK GENMASK(23, 16) #define ECC_CEADDR1_COL_MASK GENMASK(11, 0) +/* DDR CRC/Parity register definitions */ +#define DDR_CRCPARCTL0_CLR_ALRT_ERRCNT BIT(2) +#define DDR_CRCPARCTL0_CLR_ALRT_ERR BIT(1) +#define DDR_CRCPARCTL0_EN_ALRT_IRQ BIT(0) +#define DDR_CRCPARSTAT_ALRT_ERR BIT(16) +#define DDR_CRCPARSTAT_ALRT_CNT_MASK GENMASK(15, 0) + /* ECC Poison register definitions */ #define ECC_POISON0_RANK_MASK GENMASK(27, 24) #define ECC_POISON0_COL_MASK GENMASK(11, 0) @@ -399,6 +412,7 @@ struct snps_ecc_error_info { * @reglock: Concurrent CSRs access lock. * @msg_ecc_ce: Buffer for framing the ECC CE specific info. * @msg_ecc_ue: Buffer for framing the ECC UE specific info. + * @msg_dfi_e: Buffer for framing the DFI E specific info. */ struct snps_edac_priv { struct snps_ddrc_info info; @@ -409,6 +423,7 @@ struct snps_edac_priv { spinlock_t reglock; char msg_ecc_ce[SNPS_EDAC_MSG_SIZE]; char msg_ecc_ue[SNPS_EDAC_MSG_SIZE]; + char msg_dfi_e[SNPS_EDAC_MSG_SIZE]; }; /** @@ -837,6 +852,48 @@ static irqreturn_t snps_ue_irq_handler(int irq, void *dev_id) return IRQ_HANDLED; } +/** + * snps_dfi_irq_handler - DFI CRC/Parity error interrupt handler. + * @irq: IRQ number. + * @dev_id: Device ID. + * + * Return: IRQ_NONE, if interrupt not set or IRQ_HANDLED otherwise. + */ +static irqreturn_t snps_dfi_irq_handler(int irq, void *dev_id) +{ + struct mem_ctl_info *mci = dev_id; + struct snps_edac_priv *priv = mci->pvt_info; + unsigned long flags; + u32 regval; + u16 ecnt; + + /* Make sure IRQ is caused by an DFI alert error */ + regval = readl(priv->baseaddr + DDR_CRCPARSTAT_OFST); + if (!(regval & DDR_CRCPARSTAT_ALRT_ERR)) + return IRQ_NONE; + + /* Just a number of CRC/Parity errors is available */ + ecnt = FIELD_GET(DDR_CRCPARSTAT_ALRT_CNT_MASK, regval); + + /* Report the detected errors with just the custom message */ + snprintf(priv->msg_dfi_e, SNPS_EDAC_MSG_SIZE, + "DFI CRC/Parity error detected on dfi_alert_n"); + + edac_mc_handle_error(HW_EVENT_ERR_FATAL, mci, ecnt, + 0, 0, 0, 0, 0, -1, priv->msg_dfi_e, ""); + + /* Make sure the DFI alert IRQ status is cleared */ + spin_lock_irqsave(&priv->reglock, flags); + + regval = readl(priv->baseaddr + DDR_CRCPARCTL0_OFST) | + DDR_CRCPARCTL0_CLR_ALRT_ERR | DDR_CRCPARCTL0_CLR_ALRT_ERRCNT; + writel(regval, priv->baseaddr + DDR_CRCPARCTL0_OFST); + + spin_unlock_irqrestore(&priv->reglock, flags); + + return IRQ_HANDLED; +} + /** * snps_com_irq_handler - Interrupt IRQ signal handler. * @irq: IRQ number. @@ -852,6 +909,8 @@ static irqreturn_t snps_com_irq_handler(int irq, void *dev_id) rc |= snps_ue_irq_handler(irq, dev_id); + rc |= snps_dfi_irq_handler(irq, dev_id); + return rc; } @@ -876,6 +935,13 @@ static void snps_enable_irq(struct snps_edac_priv *priv) writel(ECC_CTRL_EN_CE_IRQ | ECC_CTRL_EN_UE_IRQ, priv->baseaddr + ECC_CLR_OFST); + /* + * CRC/Parity interrupts control has been available since v2.10a. + * This is noop for the older controllers. + */ + writel(DDR_CRCPARCTL0_EN_ALRT_IRQ, + priv->baseaddr + DDR_CRCPARCTL0_OFST); + spin_unlock_irqrestore(&priv->reglock, flags); } @@ -894,6 +960,7 @@ static void snps_disable_irq(struct snps_edac_priv *priv) spin_lock_irqsave(&priv->reglock, flags); writel(0, priv->baseaddr + ECC_CLR_OFST); + writel(0, priv->baseaddr + DDR_CRCPARCTL0_OFST); spin_unlock_irqrestore(&priv->reglock, flags); } @@ -1511,7 +1578,8 @@ static struct mem_ctl_info *snps_mc_create(struct snps_edac_priv *priv) mci->mtype_cap = MEM_FLAG_LPDDR | MEM_FLAG_DDR2 | MEM_FLAG_LPDDR2 | MEM_FLAG_DDR3 | MEM_FLAG_LPDDR3 | MEM_FLAG_DDR4 | MEM_FLAG_LPDDR4; - mci->edac_ctl_cap = EDAC_FLAG_NONE | EDAC_FLAG_SECDED; + mci->edac_ctl_cap = EDAC_FLAG_NONE | EDAC_FLAG_SECDED | EDAC_FLAG_PARITY; + mci->edac_cap = mci->edac_ctl_cap; if (priv->info.caps & SNPS_CAP_ECC_SCRUB) { mci->scrub_mode = SCRUB_HW_SRC; @@ -1521,7 +1589,6 @@ static struct mem_ctl_info *snps_mc_create(struct snps_edac_priv *priv) mci->scrub_cap = SCRUB_FLAG_SW_SRC; } - mci->edac_cap = EDAC_FLAG_SECDED; mci->ctl_name = "snps_umctl2_ddrc"; mci->dev_name = SNPS_EDAC_MOD_STRING; mci->mod_name = SNPS_EDAC_MOD_VER; @@ -1587,6 +1654,15 @@ static int snps_request_ind_irq(struct mem_ctl_info *mci) return rc; } + irq = platform_get_irq_byname_optional(priv->pdev, "dfi_e"); + if (irq > 0) { + rc = devm_request_irq(dev, irq, snps_dfi_irq_handler, 0, "dfi_e", mci); + if (rc) { + edac_printk(KERN_ERR, EDAC_MC, "Failed to request DFI IRQ\n"); + return rc; + } + } + return 0; } -- cgit v1.2.3 From 0706d8505515ec8eabecb1944411238deac53544 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 5 Aug 2022 14:38:54 +0300 Subject: EDAC/synopsys: Add reference clocks support Currently the driver doesn't support any clock-related resources request and handling, fairly assuming that all of them are supposed to be enabled anyway in order for the system to work correctly. It's true for the Core and AXI Ports reference clocks, but the CSR (APB) and Scrubber clocks might still be disabled in case if the system firmware doesn't imply any other software touching the DDR controller internals. Since the DW uMCTL2 DDRC driver does access the controller registers at the very least the driver needs to make sure the APB clock is enabled. So add the reference clocks support then. First the driver will request all the clocks possibly defined for the controller (Core, AXI, APB and Scrubber). Second the APB clock will be enabled/disabled only since the Scrubber is currently unsupported by the driver. Since the Core and AXI clocks feed the critical system parts they left untouched to avoid a risk to de-stabilize the system memory. Please note the clocks connection IDs have been chosen in accordance with the DT-bindings. Signed-off-by: Serge Semin --- drivers/edac/synopsys_edac.c | 101 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 98 insertions(+), 3 deletions(-) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index 4b56c9866e102..5f094fc04fdb6 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -303,6 +304,25 @@ enum snps_ecc_mode { SNPS_ECC_ADVX4X8 = 5, }; +/** + * enum snps_ref_clk - DW uMCTL2 DDR controller clocks. + * @SNPS_CSR_CLK: CSR/APB interface clock. + * @SNPS_AXI_CLK: AXI (AHB) Port reference clock. + * @SNPS_CORE_CLK: DDR controller (including DFI) clock. SDRAM clock + * matches runs with this freq in 1:1 ratio mode and + * with twice of this freq in case of 1:2 ratio mode. + * @SNPS_SBR_CLK: Scrubber port reference clock (synchronous to + * the core clock). + * @SNPS_MAX_NCLK: Total number of clocks. + */ +enum snps_ref_clk { + SNPS_CSR_CLK, + SNPS_AXI_CLK, + SNPS_CORE_CLK, + SNPS_SBR_CLK, + SNPS_MAX_NCLK +}; + /** * struct snps_ddrc_info - DDR controller platform parameters. * @caps: DDR controller capabilities. @@ -410,6 +430,7 @@ struct snps_ecc_error_info { * @pdev: Platform device. * @baseaddr: Base address of the DDR controller. * @reglock: Concurrent CSRs access lock. + * @clks: Controller reference clocks. * @msg_ecc_ce: Buffer for framing the ECC CE specific info. * @msg_ecc_ue: Buffer for framing the ECC UE specific info. * @msg_dfi_e: Buffer for framing the DFI E specific info. @@ -421,6 +442,7 @@ struct snps_edac_priv { struct platform_device *pdev; void __iomem *baseaddr; spinlock_t reglock; + struct clk_bulk_data clks[SNPS_MAX_NCLK]; char msg_ecc_ce[SNPS_EDAC_MSG_SIZE]; char msg_ecc_ue[SNPS_EDAC_MSG_SIZE]; char msg_dfi_e[SNPS_EDAC_MSG_SIZE]; @@ -989,6 +1011,60 @@ static struct snps_edac_priv *snps_create_data(struct platform_device *pdev) return priv; } +/** + * snps_get_res - Get platform device resources. + * @priv: DDR memory controller private instance data. + * + * It's supposed to request all the controller resources available for the + * particular platform and enable all the required for the driver normal + * work. Note only the CSR and Scrubber clocks are supposed to be switched + * on/off by the driver. + * + * Return: negative errno if failed to get the resources, otherwise - zero. + */ +static int snps_get_res(struct snps_edac_priv *priv) +{ + const char * const ids[] = { + [SNPS_CSR_CLK] = "pclk", + [SNPS_AXI_CLK] = "aclk", + [SNPS_CORE_CLK] = "core", + [SNPS_SBR_CLK] = "sbr", + }; + int i, rc; + + for (i = 0; i < SNPS_MAX_NCLK; i++) + priv->clks[i].id = ids[i]; + + rc = devm_clk_bulk_get_optional(&priv->pdev->dev, SNPS_MAX_NCLK, + priv->clks); + if (rc) { + edac_printk(KERN_INFO, EDAC_MC, "Failed to get ref clocks\n"); + return rc; + } + + /* + * Don't touch the Core and AXI clocks since they are critical for the + * stable system functioning and are supposed to have been enabled + * anyway. + */ + rc = clk_prepare_enable(priv->clks[SNPS_CSR_CLK].clk); + if (rc) { + edac_printk(KERN_INFO, EDAC_MC, "Couldn't enable CSR clock\n"); + return rc; + } + + return 0; +} + +/** + * snps_put_res - Put platform device resources. + * @priv: DDR memory controller private instance data. + */ +static void snps_put_res(struct snps_edac_priv *priv) +{ + clk_disable_unprepare(priv->clks[SNPS_CSR_CLK].clk); +} + /* * zynqmp_init_plat - ZynqMP-specific platform initialization. * @priv: DDR memory controller private data. @@ -1739,9 +1815,17 @@ static int snps_ddrc_info_show(struct seq_file *s, void *data) { struct mem_ctl_info *mci = s->private; struct snps_edac_priv *priv = mci->pvt_info; + unsigned long rate; seq_printf(s, "SDRAM: %s\n", edac_mem_types[priv->info.sdram_mode]); + rate = clk_get_rate(priv->clks[SNPS_CORE_CLK].clk); + if (rate) { + rate = rate / HZ_PER_MHZ; + seq_printf(s, "Clock: Core %luMHz SDRAM %luMHz\n", + rate, priv->info.freq_ratio * rate); + } + seq_printf(s, "DQ bus: %u/%s\n", (BITS_PER_BYTE << priv->info.dq_width), priv->info.dq_mode == SNPS_DQ_FULL ? "Full" : priv->info.dq_mode == SNPS_DQ_HALF ? "Half" : @@ -2050,15 +2134,21 @@ static int snps_mc_probe(struct platform_device *pdev) if (IS_ERR(priv)) return PTR_ERR(priv); - rc = snps_get_ddrc_info(priv); + rc = snps_get_res(priv); if (rc) return rc; + rc = snps_get_ddrc_info(priv); + if (rc) + goto put_res; + snps_get_addr_map(priv); mci = snps_mc_create(priv); - if (IS_ERR(mci)) - return PTR_ERR(mci); + if (IS_ERR(mci)) { + rc = PTR_ERR(mci); + goto put_res; + } rc = snps_setup_irq(mci); if (rc) @@ -2078,6 +2168,9 @@ static int snps_mc_probe(struct platform_device *pdev) free_edac_mc: snps_mc_free(mci); +put_res: + snps_put_res(priv); + return rc; } @@ -2098,6 +2191,8 @@ static void snps_mc_remove(struct platform_device *pdev) edac_mc_del_mc(&pdev->dev); snps_mc_free(mci); + + snps_put_res(priv); } static const struct of_device_id snps_edac_match[] = { -- cgit v1.2.3 From dca347980b6576dc3b7d28f715a495a96350cfff Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sat, 6 Aug 2022 09:19:36 +0300 Subject: EDAC/synopsys: Add ECC Scrubber support DW uMCTL2 DDR controller IP-core can by synthesized with an embedded Scrubber engine. The ECC Scrubber (SBR) is a block which initiates periodic background burst read commands to the DDRC and further towards the DDR memory in an attempt to trigger Correctable or Uncorrectable errors. If a Correctable error is detected the ECC Scrub feature will execute the Read-Modify-Write (RMW) procedure in order to fix the ECC. In case of the Uncorrectable error it will be just reported as the corresponding IRQ event. So it's definitely very useful feature. Let's add it to the driver then especially seeing the MCI core already has some infrastructure for it. First of all the Scrubber clock needs to be enabled if one is supplied otherwise the engine won't work. Secondly the Scrubber engine support needs to be detected. Alas there is no any special CSR indicating whether the DW uMCTL2 DDRC IP-core has been synthesized with one embedded. Instead implement the detection procedure based on the Scrubber-specific CSRs writability. So if the SBRWDATA0 CSR is writable then the CSR exists, which means the Scrubber is available, otherwise the capability will be considered as absent. Thirdly the MCI core provides two callbacks utilized for the Scrubber tuning: set the Scrubber bandwidth in bytes, which can also be used to disable the periodic scrubbing; get the Scrubber bandwidth (zero if disabled). Both of them can be implemented by using the Scrubber CSRs the controller provides. In particular aside with the back-to-back periodic reads the Scrubber provides a way to delay the next read command for the predefined set of 512's Core/Scrubber clock cycles. It can be used to change the Scrubber bandwidth from the DDR maximal bandwidth (no delay) to up to (0x1FFF * 512) Core/Scrubber clock cycles (see the inline comments for details and utilized formulae). Note the Scrubber clock must be synchronous to the Core clock by the controller design so use the Core clock rate for the calculations. Pleas also note if no Core clock specified the Scrubber will still be supported, but the bandwidth will be used directly to calculate the Scrubber reads interval. The back-to-back reads mode in this case will be indicated by the INT_MAX bandwidth. Fourthly the back-to-back scrubbing most likely will cause the significant system performance drop. The manual says that it has been added to the controller for the initial SDRAM initialization and the fast SDRAM scrubbing after getting out of the low-power state. In anyway it is supposed to be enabled only for a single SDRAM pass. So preserve that semantic here to avoid the system lagging and disable the back-to-back scrubbing in the Scrubber Done IRQ handler after the Scrubbing work is done. Finally the denoted scrub-rate callbacks and the SCRUB_FLAG_HW_PROG and SCRUB_FLAG_HW_TUN flags will be set to the MCI descriptor based on the detected Scrubber capability. So no capability - no flags and no callbacks. Signed-off-by: Serge Semin --- Changelog v4: - Use div_u64() instead of do_div(). - Use FIELD_MAX() instead of open-coding the bitwise shift to find the max field value. --- drivers/edac/synopsys_edac.c | 299 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 299 insertions(+) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index 5f094fc04fdb6..8254465078915 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -19,6 +20,7 @@ #include #include #include +#include #include #include @@ -34,6 +36,7 @@ /* DDR capabilities */ #define SNPS_CAP_ECC_SCRUB BIT(0) +#define SNPS_CAP_ECC_SCRUBBER BIT(1) #define SNPS_CAP_ZYNQMP BIT(31) /* Synopsys uMCTL2 DDR controller registers that are relevant to ECC */ @@ -102,6 +105,12 @@ #define DDR_SARBASE0_OFST 0xF04 #define DDR_SARSIZE0_OFST 0xF08 +/* ECC Scrubber Registers */ +#define ECC_SBRCTL_OFST 0xF24 +#define ECC_SBRSTAT_OFST 0xF28 +#define ECC_SBRWDATA0_OFST 0xF2C +#define ECC_SBRWDATA1_OFST 0xF30 + /* ZynqMP DDR QOS Registers */ #define ZYNQMP_DDR_QOS_IRQ_STAT_OFST 0x20200 #define ZYNQMP_DDR_QOS_IRQ_EN_OFST 0x20208 @@ -242,6 +251,18 @@ #define DDR_MAX_NSAR 4 #define DDR_MIN_SARSIZE SZ_256M +/* ECC Scrubber registers definitions */ +#define ECC_SBRCTL_SCRUB_INTERVAL GENMASK(20, 8) +#define ECC_SBRCTL_INTERVAL_STEP 512 +#define ECC_SBRCTL_INTERVAL_MIN 0 +#define ECC_SBRCTL_INTERVAL_SAFE 1 +#define ECC_SBRCTL_INTERVAL_MAX FIELD_MAX(ECC_SBRCTL_SCRUB_INTERVAL) +#define ECC_SBRCTL_SCRUB_BURST GENMASK(6, 4) +#define ECC_SBRCTL_SCRUB_MODE_WR BIT(2) +#define ECC_SBRCTL_SCRUB_EN BIT(0) +#define ECC_SBRSTAT_SCRUB_DONE BIT(1) +#define ECC_SBRSTAT_SCRUB_BUSY BIT(0) + /* ZynqMP DDR QOS Interrupt register definitions */ #define ZYNQMP_DDR_QOS_UE_MASK BIT(2) #define ZYNQMP_DDR_QOS_CE_MASK BIT(1) @@ -916,6 +937,47 @@ static irqreturn_t snps_dfi_irq_handler(int irq, void *dev_id) return IRQ_HANDLED; } +/** + * snps_sbr_irq_handler - Scrubber Done interrupt handler. + * @irq: IRQ number. + * @dev_id: Device ID. + * + * It just checks whether the IRQ has been caused by the Scrubber Done event + * and disables the back-to-back scrubbing by falling back to the smallest + * delay between the Scrubber read commands. + * + * Return: IRQ_NONE, if interrupt not set or IRQ_HANDLED otherwise. + */ +static irqreturn_t snps_sbr_irq_handler(int irq, void *dev_id) +{ + struct mem_ctl_info *mci = dev_id; + struct snps_edac_priv *priv = mci->pvt_info; + unsigned long flags; + u32 regval, en; + + /* Make sure IRQ is caused by the Scrubber Done event */ + regval = readl(priv->baseaddr + ECC_SBRSTAT_OFST); + if (!(regval & ECC_SBRSTAT_SCRUB_DONE)) + return IRQ_NONE; + + spin_lock_irqsave(&priv->reglock, flags); + + regval = readl(priv->baseaddr + ECC_SBRCTL_OFST); + en = regval & ECC_SBRCTL_SCRUB_EN; + writel(regval & ~en, priv->baseaddr + ECC_SBRCTL_OFST); + + regval = FIELD_PREP(ECC_SBRCTL_SCRUB_INTERVAL, ECC_SBRCTL_INTERVAL_SAFE); + writel(regval, priv->baseaddr + ECC_SBRCTL_OFST); + + writel(regval | en, priv->baseaddr + ECC_SBRCTL_OFST); + + spin_unlock_irqrestore(&priv->reglock, flags); + + edac_mc_printk(mci, KERN_WARNING, "Back-to-back scrubbing disabled\n"); + + return IRQ_HANDLED; +} + /** * snps_com_irq_handler - Interrupt IRQ signal handler. * @irq: IRQ number. @@ -925,6 +987,8 @@ static irqreturn_t snps_dfi_irq_handler(int irq, void *dev_id) */ static irqreturn_t snps_com_irq_handler(int irq, void *dev_id) { + struct mem_ctl_info *mci = dev_id; + struct snps_edac_priv *priv = mci->pvt_info; irqreturn_t rc = IRQ_NONE; rc |= snps_ce_irq_handler(irq, dev_id); @@ -933,6 +997,9 @@ static irqreturn_t snps_com_irq_handler(int irq, void *dev_id) rc |= snps_dfi_irq_handler(irq, dev_id); + if (priv->info.caps & SNPS_CAP_ECC_SCRUBBER) + rc |= snps_sbr_irq_handler(irq, dev_id); + return rc; } @@ -987,6 +1054,200 @@ static void snps_disable_irq(struct snps_edac_priv *priv) spin_unlock_irqrestore(&priv->reglock, flags); } +/** + * snps_get_sdram_bw - Get SDRAM bandwidth. + * @priv: DDR memory controller private instance data. + * + * The SDRAM interface bandwidth is calculated based on the DDRC Core clock rate + * and the DW uMCTL2 IP-core parameters like DQ-bus width and mode and + * Core/SDRAM clocks frequency ratio. Note it returns the theoretical bandwidth + * which in reality is hardly possible to reach. + * + * Return: SDRAM bandwidth or zero if no Core clock specified. + */ +static u64 snps_get_sdram_bw(struct snps_edac_priv *priv) +{ + unsigned long rate; + + /* + * Depending on the ratio mode the SDRAM clock either matches the Core + * clock or runs with the twice its frequency. + */ + rate = clk_get_rate(priv->clks[SNPS_CORE_CLK].clk); + rate *= priv->info.freq_ratio; + + /* + * Scale up by 2 since it's DDR (Double Data Rate) and subtract the + * DQ-mode since in non-Full mode only a part of the DQ-bus is utilised + * on each SDRAM clock edge. + */ + return (2U << (priv->info.dq_width - priv->info.dq_mode)) * (u64)rate; +} + +/** + * snps_get_scrub_bw - Get Scrubber bandwidth. + * @priv: DDR memory controller private instance data. + * @interval: Scrub interval. + * + * DW uMCTL2 DDRC Scrubber performs periodical progressive burst reads (RMW if + * ECC CE is detected) commands from the whole memory space. The read commands + * can be delayed by means of the SBRCTL.scrub_interval field. The Scrubber + * cycles look as follows: + * + * |-HIF-burst-read-|-------delay-------|-HIF-burst-read-|------- etc + * + * Tb = Bl*[DQ]/Bw[RAM], Td = 512*interval/Fc - periods of the HIF-burst-read + * and delay stages, where + * Bl - HIF burst length, [DQ] - Full DQ-bus width, Bw[RAM] - SDRAM bandwidth, + * Fc - Core clock frequency (Scrubber and Core clocks are synchronous). + * + * After some simple calculations the expressions above can be used to get the + * next Scrubber bandwidth formulae: + * + * Bw[Sbr] = Bw[RAM] / (1 + F * interval), where + * F = 2 * 512 * Fr * Fc * [DQ]e - interval scale factor with + * Fr - HIF/SDRAM clock frequency ratio (1 or 2), [DQ]e - DQ-bus width mode. + * + * Return: Scrubber bandwidth or zero if no Core clock specified. + */ +static u64 snps_get_scrub_bw(struct snps_edac_priv *priv, u32 interval) +{ + unsigned long fac; + u64 bw_ram; + + fac = (2 * ECC_SBRCTL_INTERVAL_STEP * priv->info.freq_ratio) / + (priv->info.hif_burst_len * (1UL << priv->info.dq_mode)); + + bw_ram = snps_get_sdram_bw(priv); + + return div_u64(bw_ram, 1 + fac * interval); +} + +/** + * snps_get_scrub_interval - Get Scrubber delay interval. + * @priv: DDR memory controller private instance data. + * @bw: Scrubber bandwidth. + * + * Similarly to the Scrubber bandwidth the interval formulae can be inferred + * from the same expressions: + * + * interval = (Bw[RAM] - Bw[Sbr]) / (F * Bw[Sbr]) + * + * Return: Scrubber delay interval or zero if no Core clock specified. + */ +static u32 snps_get_scrub_interval(struct snps_edac_priv *priv, u32 bw) +{ + unsigned long fac; + u64 bw_ram; + + fac = (2 * priv->info.freq_ratio * ECC_SBRCTL_INTERVAL_STEP) / + (priv->info.hif_burst_len * (1UL << priv->info.dq_mode)); + + bw_ram = snps_get_sdram_bw(priv); + + /* Divide twice so not to cause the integer overflow in (fac * bw) */ + return div_u64(div_u64(bw_ram - bw, bw), fac); +} + +/** + * snps_set_sdram_scrub_rate - Set the Scrubber bandwidth. + * @mci: EDAC memory controller instance. + * @bw: Bandwidth. + * + * It calculates the delay between the Scrubber read commands based on the + * specified bandwidth and the Core clock rate. If the Core clock is unavailable + * the passed bandwidth will be directly used as the interval value. + * + * Note the method warns about the back-to-back scrubbing since it may + * significantly degrade the system performance. This mode is supposed to be + * used for a single SDRAM scrubbing pass only. So it will be turned off in the + * Scrubber Done IRQ handler. + * + * Return: Actually set bandwidth (interval-based approximated bandwidth if the + * Core clock is unavailable) or zero if the Scrubber was disabled. + */ +static int snps_set_sdram_scrub_rate(struct mem_ctl_info *mci, u32 bw) +{ + struct snps_edac_priv *priv = mci->pvt_info; + u32 regval, interval; + unsigned long flags; + u64 bw_min, bw_max; + + /* Don't bother with the calculations just disable and return. */ + if (!bw) { + spin_lock_irqsave(&priv->reglock, flags); + + regval = readl(priv->baseaddr + ECC_SBRCTL_OFST); + regval &= ~ECC_SBRCTL_SCRUB_EN; + writel(regval, priv->baseaddr + ECC_SBRCTL_OFST); + + spin_unlock_irqrestore(&priv->reglock, flags); + + return 0; + } + + /* If no Core clock specified fallback to the direct interval setup. */ + bw_max = snps_get_scrub_bw(priv, ECC_SBRCTL_INTERVAL_MIN); + if (bw_max) { + bw_min = snps_get_scrub_bw(priv, ECC_SBRCTL_INTERVAL_MAX); + bw = clamp_t(u64, bw, bw_min, bw_max); + + interval = snps_get_scrub_interval(priv, bw); + } else { + bw = clamp_val(bw, ECC_SBRCTL_INTERVAL_MIN, ECC_SBRCTL_INTERVAL_MAX); + + interval = ECC_SBRCTL_INTERVAL_MAX - bw; + } + + /* + * SBRCTL.scrub_en bitfield must be accessed separately from the other + * CSR bitfields. It means the flag must be set/cleared with no updates + * to the rest of the fields. + */ + spin_lock_irqsave(&priv->reglock, flags); + + regval = FIELD_PREP(ECC_SBRCTL_SCRUB_INTERVAL, interval); + writel(regval, priv->baseaddr + ECC_SBRCTL_OFST); + + writel(regval | ECC_SBRCTL_SCRUB_EN, priv->baseaddr + ECC_SBRCTL_OFST); + + spin_unlock_irqrestore(&priv->reglock, flags); + + if (!interval) + edac_mc_printk(mci, KERN_WARNING, "Back-to-back scrubbing enabled\n"); + + if (!bw_max) + return interval ? bw : INT_MAX; + + return snps_get_scrub_bw(priv, interval); +} + +/** + * snps_get_sdram_scrub_rate - Get the Scrubber bandwidth. + * @mci: EDAC memory controller instance. + * + * Return: Scrubber bandwidth (interval-based approximated bandwidth if the + * Core clock is unavailable) or zero if the Scrubber was disabled. + */ +static int snps_get_sdram_scrub_rate(struct mem_ctl_info *mci) +{ + struct snps_edac_priv *priv = mci->pvt_info; + u32 regval; + u64 bw; + + regval = readl(priv->baseaddr + ECC_SBRCTL_OFST); + if (!(regval & ECC_SBRCTL_SCRUB_EN)) + return 0; + + regval = FIELD_GET(ECC_SBRCTL_SCRUB_INTERVAL, regval); + + bw = snps_get_scrub_bw(priv, regval); + if (!bw) + return regval ? ECC_SBRCTL_INTERVAL_MAX - regval : INT_MAX; + + return bw; +} + /** * snps_create_data - Create private data. * @pdev: platform device. @@ -1053,7 +1314,18 @@ static int snps_get_res(struct snps_edac_priv *priv) return rc; } + rc = clk_prepare_enable(priv->clks[SNPS_SBR_CLK].clk); + if (rc) { + edac_printk(KERN_INFO, EDAC_MC, "Couldn't enable Scrubber clock\n"); + goto err_disable_pclk; + } + return 0; + +err_disable_pclk: + clk_disable_unprepare(priv->clks[SNPS_CSR_CLK].clk); + + return rc; } /** @@ -1062,6 +1334,8 @@ static int snps_get_res(struct snps_edac_priv *priv) */ static void snps_put_res(struct snps_edac_priv *priv) { + clk_disable_unprepare(priv->clks[SNPS_SBR_CLK].clk); + clk_disable_unprepare(priv->clks[SNPS_CSR_CLK].clk); } @@ -1173,6 +1447,14 @@ static int snps_get_ddrc_info(struct snps_edac_priv *priv) if (!(regval & ECC_CFG0_DIS_SCRUB)) priv->info.caps |= SNPS_CAP_ECC_SCRUB; + /* Auto-detect the scrubber by writing to the SBRWDATA0 CSR */ + regval = readl(priv->baseaddr + ECC_SBRWDATA0_OFST); + writel(~regval, priv->baseaddr + ECC_SBRWDATA0_OFST); + if (regval != readl(priv->baseaddr + ECC_SBRWDATA0_OFST)) { + priv->info.caps |= SNPS_CAP_ECC_SCRUBBER; + writel(regval, priv->baseaddr + ECC_SBRWDATA0_OFST); + } + /* Auto-detect the basic HIF/SDRAM bus parameters */ regval = readl(priv->baseaddr + DDR_MSTR_OFST); @@ -1665,6 +1947,12 @@ static struct mem_ctl_info *snps_mc_create(struct snps_edac_priv *priv) mci->scrub_cap = SCRUB_FLAG_SW_SRC; } + if (priv->info.caps & SNPS_CAP_ECC_SCRUBBER) { + mci->scrub_cap |= SCRUB_FLAG_HW_PROG | SCRUB_FLAG_HW_TUN; + mci->set_sdram_scrub_rate = snps_set_sdram_scrub_rate; + mci->get_sdram_scrub_rate = snps_get_sdram_scrub_rate; + } + mci->ctl_name = "snps_umctl2_ddrc"; mci->dev_name = SNPS_EDAC_MOD_STRING; mci->mod_name = SNPS_EDAC_MOD_VER; @@ -1739,6 +2027,15 @@ static int snps_request_ind_irq(struct mem_ctl_info *mci) } } + irq = platform_get_irq_byname_optional(priv->pdev, "ecc_sbr"); + if (irq > 0) { + rc = devm_request_irq(dev, irq, snps_sbr_irq_handler, 0, "ecc_sbr", mci); + if (rc) { + edac_printk(KERN_ERR, EDAC_MC, "Failed to request Sbr IRQ\n"); + return rc; + } + } + return 0; } @@ -1845,6 +2142,8 @@ static int snps_ddrc_info_show(struct seq_file *s, void *data) if (priv->info.caps) { if (priv->info.caps & SNPS_CAP_ECC_SCRUB) seq_puts(s, " +Scrub"); + if (priv->info.caps & SNPS_CAP_ECC_SCRUBBER) + seq_puts(s, " +Scrubber"); if (priv->info.caps & SNPS_CAP_ZYNQMP) seq_puts(s, " +ZynqMP"); } else { -- cgit v1.2.3 From d3e74343c89b36539a70c8c2efdd4d2d38007a16 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sun, 7 Aug 2022 15:59:34 +0300 Subject: EDAC/synopsys: Drop vendor-specific arch dependency DW uMCTL2 DDRC EDAC driver is no longer specific to particular DDRC versions. It's generic in the most of the aspects now. So permit setting its kernel config independently from the ZynqMP/IntelFPAG/MXC platforms. Signed-off-by: Serge Semin --- drivers/edac/Kconfig | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/edac/Kconfig b/drivers/edac/Kconfig index fb8cf7cdc3057..5cee7343683b7 100644 --- a/drivers/edac/Kconfig +++ b/drivers/edac/Kconfig @@ -471,7 +471,6 @@ config EDAC_ARMADA_XP config EDAC_SYNOPSYS tristate "Synopsys DDR Memory Controller" - depends on ARCH_ZYNQMP || ARCH_INTEL_SOCFPGA || ARCH_MXC help Support for error detection and correction on the Synopsys DDR memory controller. -- cgit v1.2.3 From 0cbe89083d496b16902fa9911273334bfba6ab21 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sat, 6 Aug 2022 20:41:22 +0300 Subject: EDAC/synopsys: Add BT1 DDRC support Baikal-T1 SoC is equipped with the DW uMCTl2 DDRC v2.61a with 32-bit DQ-bus accepting DDR2/DDR3 SDRAMs of up to 2 ranks, 1:2 HIF/SDRAM clocks rate ratio, HIF interface burst length of 8 Full DQ-bus words, 40-bit System/Application address width and 128-bits data width, 3 System address regions with block size 256MB. There is SEC/DED ECC capability with Scrub (RMW) and Scrubber features. Since the Baikal-T1 DDR controller supports Sideband ECC add the controller support to the DW uMCTL2 DDRC EDAC driver. The most of the parameters listed above will be autodetected except HIF burst length and SAR block size, which will be set by means of the Baikal-T1-specific initialization method. The controller compatible string "baikal,bt1-ddrc" will be used to attach the driver to the kernel device. It's chosen in accordance with the just updated DT-bindings. Signed-off-by: Serge Semin --- Changelog v4: - Explicitly set snps_ddrc_info.dq_width for Baikal-T1 DDRC for better maintainability. - Explicitly set sys_app_map.minsize to SZ_256M instead of using a helper macro DDR_MIN_SARSIZE. --- drivers/edac/synopsys_edac.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/edac/synopsys_edac.c b/drivers/edac/synopsys_edac.c index 8254465078915..6e98bd89eb3b6 100644 --- a/drivers/edac/synopsys_edac.c +++ b/drivers/edac/synopsys_edac.c @@ -1364,6 +1364,21 @@ static int zynqmp_init_plat(struct snps_edac_priv *priv) return 0; } +/* + * bt1_init_plat - Baikal-T1-specific platform initialization. + * @priv: DDR memory controller private data. + * + * Return: always zero. + */ +static int bt1_init_plat(struct snps_edac_priv *priv) +{ + priv->info.dq_width = SNPS_DQ_32; + priv->info.hif_burst_len = SNPS_DDR_BL8; + priv->sys_app_map.minsize = SZ_256M; + + return 0; +} + /** * snps_get_dtype - Return the DDR memory chips type. * @mstr: Master CSR value. @@ -2496,6 +2511,7 @@ static void snps_mc_remove(struct platform_device *pdev) static const struct of_device_id snps_edac_match[] = { { .compatible = "xlnx,zynqmp-ddrc-2.40a", .data = zynqmp_init_plat }, + { .compatible = "baikal,bt1-ddrc", .data = bt1_init_plat }, { .compatible = "snps,ddrc-3.80a" }, { } }; -- cgit v1.2.3 From 13457c62944164b94931ab9fc7604d9628216fd3 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sat, 6 Feb 2021 00:12:23 +0300 Subject: net: phy: realtek: Fix events detection failure in LPI mode It has been noticed that RTL8211E PHY stops detecting and reporting events (cable plugging in/out, link state changes, etc) when EEE is successfully advertised and "RXC stopping in LPI" is enabled. The freeze happens right after 3.0.10 bit (PC1R "Clock Stop Enable" register) is set. At the same time LED2 stops blinking as if EEE mode has been disabled, interrupt pin doesn't change its state (signal tested by oscilloscope). Notably the network traffic still flows through the PHY with no obvious problem as long as a network cable is connected. But if any MDIO read/write procedure is performed after the "RXC stop in LPI" mode is enabled (even from a non-existent device on the MDIO bus) the PHY gets to be unfrozen, LED2 starts blinking and PHY interrupts happens again (even which have happened during the freeze). The problem has been noticed on RTL8211E PHY working together with DW GMAC 3.73a MAC and reporting its event via a dedicated DW MAC GPIO-IRQ signal. (Obviously the problem has been unnoticed in the polling mode, since it gets naturally fixed by the periodic MDIO read procedure from the PHY status register - BMSR.) In order to fix that problem we suggest to locally re-implement the MMD write method for RTL8211E PHY and perform a dummy read right after the PC1R register is accessed to enable the RXC stopping in LPI mode. Fixes: ef3d90491a15 ("net: phy: realtek: add rtl8211e driver") Signed-off-by: Serge Semin Reviewed-by: Andrew Lunn --- Original patch: Link: https://lore.kernel.org/netdev/20210208140341.9271-2-Sergey.Semin@baikalelectronics.ru/ --- drivers/net/phy/realtek/realtek_main.c | 37 ++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c index 75565fbdbf6d4..3f2a54eacbbac 100644 --- a/drivers/net/phy/realtek/realtek_main.c +++ b/drivers/net/phy/realtek/realtek_main.c @@ -1465,6 +1465,42 @@ static int rtl822xb_get_rate_matching(struct phy_device *phydev, return RATE_MATCH_NONE; } +static int rtl8211e_write_mmd(struct phy_device *phydev, int devnum, u16 regnum, + u16 val) +{ + int ret; + + /* Write to the MMD registers by using the standard control/data pair. + * The only difference is that we need to perform a dummy read after + * the PC1R.CLKSTOP_EN bit is set. It's required to workaround an issue + * of a partial core freeze so LED2 stops blinking in EEE mode, PHY + * stops detecting the link change and raising IRQs until any read from + * its registers performed. That happens only if and right after the PHY + * is enabled to stop RXC in LPI mode. + */ + ret = __phy_write(phydev, MII_MMD_CTRL, devnum); + if (ret) + return ret; + + ret = __phy_write(phydev, MII_MMD_DATA, regnum); + if (ret) + return ret; + + ret = __phy_write(phydev, MII_MMD_CTRL, devnum | MII_MMD_CTRL_NOINCR); + if (ret) + return ret; + + ret = __phy_write(phydev, MII_MMD_DATA, val); + if (ret) + return ret; + + if (devnum == MDIO_MMD_PCS && regnum == MDIO_CTRL1 && + val & MDIO_PCS_CTRL1_CLKSTOP_EN) + ret = __phy_read(phydev, MII_MMD_DATA); + + return ret < 0 ? ret : 0; +} + static int rtl822x_get_features(struct phy_device *phydev) { int val; @@ -2209,6 +2245,7 @@ static struct phy_driver realtek_drvs[] = { .resume = genphy_resume, .read_page = rtl821x_read_page, .write_page = rtl821x_write_page, + .write_mmd = rtl8211e_write_mmd, .led_hw_is_supported = rtl8211x_led_hw_is_supported, .led_hw_control_get = rtl8211e_led_hw_control_get, .led_hw_control_set = rtl8211e_led_hw_control_set, -- cgit v1.2.3 From 5d330ba68c50409a53f3da5456397adb531a3743 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 5 Mar 2021 00:41:26 +0300 Subject: net: stmmac: Omit last desc flag for non-linear jumbos in chain-mode Indeed similar to the ring mode we need not to set the LS flag in a Tx DMA descriptor of the last chunk of the linear jumbo-SKB data if it's supposed to have additional fragments attached. That buffers will be used to initialized further Tx DMA descriptors later in the common stmmac_rx() code. The LS flag will be set for the last of them then. A similar fix has been introduced for the ring-mode in the commit 58f2ce6f6161 ("net: stmmac: fix jumbo frame sending with non-linear skbs"). But for some reason it hasn't been done for the chained descriptors. Fixes: 58f2ce6f6161 ("net: stmmac: fix jumbo frame sending with non-linear skbs") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/chain_mode.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/chain_mode.c b/drivers/net/ethernet/stmicro/stmmac/chain_mode.c index 37f9417c7c0e0..be206940bb159 100644 --- a/drivers/net/ethernet/stmicro/stmmac/chain_mode.c +++ b/drivers/net/ethernet/stmicro/stmmac/chain_mode.c @@ -74,7 +74,8 @@ static int jumbo_frm(struct stmmac_tx_queue *tx_q, struct sk_buff *skb, tx_q->tx_skbuff_dma[entry].len = len; /* last descriptor can be set now */ stmmac_prepare_tx_desc(priv, desc, 0, len, csum, - STMMAC_CHAIN_MODE, 1, true, skb->len); + STMMAC_CHAIN_MODE, 1, + !skb_is_nonlinear(skb), skb->len); len = 0; } } -- cgit v1.2.3 From f961852122f4285409c78bfacf5ccd7331ea46af Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 5 Mar 2021 01:01:34 +0300 Subject: net: stmmac: Fix extended descriptors usage for jumbos in chain-mode If a DW MAC NIC is synthesized to support the extended descriptors, then the driver uses dma_etx pointer to keep an array of once in the Tx DMA Queue structures. For some reason the specific to the chained-mode jumbo_frm() referred to the dma_tx pointer of DMA Tx Queue descriptor in any case, which of course was initialized with NULL for the DW MACs expecting extended descriptors being specified. So any attempt to send a Jumbo frame in chain-mode caused "Unable to handle kernel paging request at virtual address" kernel crash. Fix that by selecting a proper descriptor pointer depending on whether the NIC supports extended descriptors or not. Fixes: c24602ef8664 ("stmmac: support extend descriptors") Signed-off-by: Serge Semin --- Yeah, that Normal/Enhanced access pattern really annoying. Food for thoughts about a more thorough cleanup of the driver. --- drivers/net/ethernet/stmicro/stmmac/chain_mode.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/chain_mode.c b/drivers/net/ethernet/stmicro/stmmac/chain_mode.c index be206940bb159..d7ff4114a7eda 100644 --- a/drivers/net/ethernet/stmicro/stmmac/chain_mode.c +++ b/drivers/net/ethernet/stmicro/stmmac/chain_mode.c @@ -24,7 +24,10 @@ static int jumbo_frm(struct stmmac_tx_queue *tx_q, struct sk_buff *skb, unsigned int i = 1, len; struct dma_desc *desc; - desc = tx_q->dma_tx + entry; + if (priv->extend_desc) + desc = (struct dma_desc *)(tx_q->dma_etx + entry); + else + desc = tx_q->dma_tx + entry; if (priv->plat->enh_desc) bmax = BUF_SIZE_8KiB; @@ -48,7 +51,11 @@ static int jumbo_frm(struct stmmac_tx_queue *tx_q, struct sk_buff *skb, while (len != 0) { tx_q->tx_skbuff[entry] = NULL; entry = STMMAC_GET_ENTRY(entry, priv->dma_conf.dma_tx_size); - desc = tx_q->dma_tx + entry; + + if (priv->extend_desc) + desc = (struct dma_desc *)(tx_q->dma_etx + entry); + else + desc = tx_q->dma_tx + entry; if (len > bmax) { des2 = dma_map_single(priv->device, -- cgit v1.2.3 From 3acb2d99c9fb7355214151e708cee33fc91dbdc3 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 23 Jun 2021 16:40:11 +0300 Subject: net: stmmac: dwmac-sun8i: Don't modify chain-mode module parameter Doing so activates the chain-mode for any DW MAC-based NIC on the platform no matter with what parameter the module is loaded. Even if there is no any other network controller on the SoC it is logically incorrect. Fixes: 9f93ac8d4085 ("net-next: stmmac: Add dwmac-sun8i") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 13d3cac056bea..73f0894433753 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -7374,8 +7374,9 @@ static int stmmac_hw_init(struct stmmac_priv *priv) /* dwmac-sun8i only work in chain mode */ if (priv->plat->flags & STMMAC_FLAG_HAS_SUN8I) - chain_mode = 1; - priv->chain_mode = chain_mode; + priv->chain_mode = 1; + else + priv->chain_mode = chain_mode; /* Initialize HW Interface */ ret = stmmac_hwif_init(priv); -- cgit v1.2.3 From 786034d7780852fdc518024456f9843a5515e447 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sun, 28 Nov 2021 16:45:07 +0300 Subject: net: stmmac: Enable ATDS for chain-mode It wasn't stated why the Alternate Descriptor Size shouldn't have been changed for the chained DMA descriptors, while the original commit did introduce some chain_mode.c modifications. So the ATDS-related change in commit c24602ef8664 ("stmmac: support extend descriptors") seems contradicting. Anyway from the DW MAC/GMAC hardware point of view there is no such limitation - whether the chained descriptors can't be used together with the extended alternate descriptors. Moreover not setting the BUS_MODE.ATDS flag will cause the driver malfunction in the framework of the IPC Full Checksum and Advanced Timestamp feature, since the later features require the additional 4-7 dwords of the DMA descriptor to set some flags and a timestamp. So to speak in order to have all these features working correctly in the chained mode too let's make sure the ATDS flag is set irrespectively from the DMA descriptors mode. Fixes: c24602ef8664 ("stmmac: support extend descriptors") Signed-off-by: Serge Semin Reviewed-by: Piotr Raczynski --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 73f0894433753..6114fbd0ac2f3 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -3252,7 +3252,7 @@ static int stmmac_init_dma_engine(struct stmmac_priv *priv) return -EINVAL; } - if (priv->extend_desc && (priv->mode == STMMAC_RING_MODE)) + if (priv->extend_desc) priv->plat->dma_cfg->atds = 1; ret = stmmac_prereset_configure(priv); -- cgit v1.2.3 From 873eb364fe6eb55a03d309cefcf96deff5e80ea7 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 19 Jul 2021 13:40:27 +0300 Subject: net: stmmac: Free temporary Rx SKB on request In case if an incoming frame couldn't be finished in one stmmac_rx() method call an SKB used to collect data so far will be saved in the corresponding Rx-queue state buffer. If the network device is closed before the frame is completed the preserved SKB will be utilized on the next network interface link uprising cycle right on the first frame reception, which will cause having a confused set of SKB data. Let's free the allocated Rx SKB then when all Rx-buffers are requested to be freed. Fixes: ec222003bd94 ("net: stmmac: Prepare to add Split Header support") Signed-off-by: Serge Semin Reviewed-by: Piotr Raczynski --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 6114fbd0ac2f3..749a4c5a2d531 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -1720,6 +1720,10 @@ static void dma_free_rx_skbufs(struct stmmac_priv *priv, for (i = 0; i < dma_conf->dma_rx_size; i++) stmmac_free_rx_buffer(priv, rx_q, i); + + if (rx_q->state_saved) + dev_kfree_skb(rx_q->state.skb); + rx_q->state_saved = false; } static int stmmac_alloc_rx_buffers(struct stmmac_priv *priv, -- cgit v1.2.3 From d994a2248f6070c6bd704de8099394b1c45ee25e Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 24 Dec 2020 20:51:08 +0300 Subject: net: stmmac: Free Rx descs on Tx allocation failure Indeed in accordance with the alloc_dma_desc_resources() method logic the Rx descriptors will be left allocated if Tx descriptors allocation fails. Fix it by calling the free_dma_rx_desc_resources() in case if the alloc_dma_tx_desc_resources() method returns non-zero value. While at it refactor the method a bit. Just move the Rx descriptors allocation method invocation out of the local variables declaration block and discard a pointless comment from there. Fixes: 71fedb0198cb ("net: stmmac: break some functions into RX and TX scopes") Signed-off-by: Serge Semin Reviewed-by: Piotr Raczynski --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 749a4c5a2d531..6a006707565a3 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -2367,13 +2367,15 @@ err_dma: static int alloc_dma_desc_resources(struct stmmac_priv *priv, struct stmmac_dma_conf *dma_conf) { - /* RX Allocation */ - int ret = alloc_dma_rx_desc_resources(priv, dma_conf); + int ret; + ret = alloc_dma_rx_desc_resources(priv, dma_conf); if (ret) return ret; ret = alloc_dma_tx_desc_resources(priv, dma_conf); + if (ret) + free_dma_rx_desc_resources(priv, dma_conf); return ret; } -- cgit v1.2.3 From 69422cfa8dded9eafbb73671086be434b0f00731 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 19 Jul 2021 13:56:03 +0300 Subject: net: stmmac: Fix Rx IC bit setting procedure for Rx-mitigation The WDT-less Rx-traffic mitigation is simple: enable the Rx-descriptor Completion interrupt only when a particular number of descriptors handled by the NIC. Alas it's broken now because the data variable rx_count_frames is always set to zero due to commit 6fa9d691b91a ("net: stmmac: Prevent divide-by-zero") (++rx_count_frames + rx_coal_frames > rx_coal_frames always if no overflow happens). So Rx IC will be enabled for each descriptor as soon as rx_coal_frames is non-zero or there is no Rx WDT available, which basically disables the Rx-mitigation in the framework of the number of received frames part. Fix that by discarding the statement: rx_q->rx_count_frames += priv->rx_coal_frames. Fixes: 6fa9d691b91a ("net: stmmac: Prevent divide-by-zero") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 6a006707565a3..74071ed29f901 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -4984,7 +4984,6 @@ static inline void stmmac_rx_refill(struct stmmac_priv *priv, u32 queue) stmmac_refill_desc3(priv, rx_q, p); rx_q->rx_count_frames++; - rx_q->rx_count_frames += priv->rx_coal_frames[queue]; if (rx_q->rx_count_frames > priv->rx_coal_frames[queue]) rx_q->rx_count_frames = 0; @@ -5368,7 +5367,6 @@ static bool stmmac_rx_refill_zc(struct stmmac_priv *priv, u32 queue, u32 budget) stmmac_refill_desc3(priv, rx_q, rx_desc); rx_q->rx_count_frames++; - rx_q->rx_count_frames += priv->rx_coal_frames[queue]; if (rx_q->rx_count_frames > priv->rx_coal_frames[queue]) rx_q->rx_count_frames = 0; -- cgit v1.2.3 From af893ec60f9d9e9ca497ddf3ad32a9a7375af25b Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 7 Jul 2021 13:37:32 +0300 Subject: net: stmmac: Make buf_sz module parameter useful Since the first day of the driver been added to the kernel the Rx DMA-buffer size module parameter hasn't been utilized for something more-or-less useful. The STMMAC main code used to initialize the actual Rx DMA-buffer size (priv->dma_buf_sz) with it by default, but then overwrote it anyway in accordance with Network device MTU or with default DMA_BUFFER_SIZE/DEFAULT_BUFSIZE macro. Moreover in the both older and newer versions of the STMMAC driver the buf_sz module parameter was overwritten on each Net-dev open procedure with a value calculated in accordance with MTU and allowed maximum buffer size. Needless to say that even though it doesn't affect the driver functionality much, setting the generic parameter with a value specific to a private device is wrong, seeing each DW MAC may have different maximum buffer size on the same platform. At last the buf_sz parameter has been completely unused and basically dropped in removed in commit 072dd84b4c5a ("net: stmmac: mostly remove "buf_sz""). In order to finally make the buf_sz (Rx DMA buffer size) parameter really useful let's make it utilized to calculate the Rx DMA-buffer size instead of MTU if one is greater than the later. Thus we'll be able to tune the buffer size up when it's necessary. Note since the buf_sz parameter is now utilized get it back to the cmdline parser - stmmac_cmdline_opt(), thus partly reverting the commit 072dd84b4c5a ("net: stmmac: mostly remove "buf_sz""). Fixes: 47dd7a540b8a ("net: add support for STMicroelectronics Ethernet controllers.") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 74071ed29f901..f4cf140dc01d4 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -104,11 +104,10 @@ static int tc = TC_DEFAULT; module_param(tc, int, 0644); MODULE_PARM_DESC(tc, "DMA threshold control value"); -/* This is unused */ -#define DEFAULT_BUFSIZE 1536 -static int buf_sz = DEFAULT_BUFSIZE; -module_param(buf_sz, int, 0644); -MODULE_PARM_DESC(buf_sz, "DMA buffer size"); +#define DEFAULT_BUFSIZE 1536U +static unsigned int buf_sz = DEFAULT_BUFSIZE; +module_param(buf_sz, uint, 0644); +MODULE_PARM_DESC(buf_sz, "Rx DMA buffer size"); static const u32 default_msg_level = (NETIF_MSG_DRV | NETIF_MSG_PROBE | NETIF_MSG_LINK | NETIF_MSG_IFUP | @@ -254,6 +253,8 @@ static void stmmac_verify_args(void) { if (unlikely(watchdog < 0)) watchdog = TX_TIMEO; + if (unlikely((buf_sz < DEFAULT_BUFSIZE) || (buf_sz > BUF_SIZE_16KiB))) + buf_sz = DEFAULT_BUFSIZE; if (unlikely((pause < 0) || (pause > 0xffff))) pause = PAUSE_TIME; @@ -4032,7 +4033,7 @@ stmmac_setup_dma_desc(struct stmmac_priv *priv, unsigned int mtu) bfsize = 0; if (bfsize < BUF_SIZE_16KiB) - bfsize = stmmac_set_bfsize(mtu); + bfsize = stmmac_set_bfsize(max(buf_sz, mtu)); dma_conf->dma_buf_sz = bfsize; /* Chose the tx/rx size from the already defined one in the @@ -8339,6 +8340,9 @@ static int __init stmmac_cmdline_opt(char *str) } else if (!strncmp(opt, "phyaddr:", 8)) { if (kstrtoint(opt + 8, 0, &phyaddr)) goto err; + } else if (!strncmp(opt, "buf_sz:", 7)) { + if (kstrtoint(opt + 7, 0, &buf_sz)) + goto err; } else if (!strncmp(opt, "tc:", 3)) { if (kstrtoint(opt + 3, 0, &tc)) goto err; -- cgit v1.2.3 From ecdfa48a8129ab5d7167203899c8a45cae6bffc8 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 18 Jan 2021 23:01:58 +0300 Subject: net: stmmac: gmac4: Use dwmac410_disable_dma_irq for DW MAC v4.10 DMA From the very beginning of the DW GMAC v4.10 IP support the driver has used an invalid DMA IRQ disable method to switch the DMA IRQs off. Since commit 021bd5e36970 ("net: stmmac: Let TX and RX interrupts be independently enabled/disabled") a valid method has been added to the dwmac4_lib.c module, but the commit author forgot to initialize the corresponding field of the DW MAC DMA operations descriptor with it. That mistake hasn't caused any problem so far just because the RIE/TIE fields match in both 4.x and 4.10 IPs. Anyway fix the inconsistency in order to at least have a coherent driver code. Fixes: 021bd5e36970 ("net: stmmac: Let TX and RX interrupts be independently enabled/disabled") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c index 60b880cdd9da2..3b78febab07cb 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c @@ -583,7 +583,7 @@ const struct stmmac_dma_ops dwmac410_dma_ops = { .dma_rx_mode = dwmac4_dma_rx_chan_op_mode, .dma_tx_mode = dwmac4_dma_tx_chan_op_mode, .enable_dma_irq = dwmac410_enable_dma_irq, - .disable_dma_irq = dwmac4_disable_dma_irq, + .disable_dma_irq = dwmac410_disable_dma_irq, .start_tx = dwmac4_dma_start_tx, .stop_tx = dwmac4_dma_stop_tx, .start_rx = dwmac4_dma_start_rx, -- cgit v1.2.3 From 002737d34860062be908c94314fde3e916d4af8a Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Tue, 14 Mar 2023 00:24:42 +0300 Subject: net: dwmac-loongson: Perceive zero IRQ as invalid Linux kernel defines zero IRQ number as invalid in case if IRQ couldn't be mapped. Fix that for Loongson PCI MAC specific IRQs request procedure. Fixes: 30bba69d7db4 ("stmmac: pci: Add dwmac support for Loongson") Signed-off-by: Serge Semin Reviewed-by: Piotr Raczynski --- drivers/net/ethernet/stmicro/stmmac/dwmac-loongson.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-loongson.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-loongson.c index 8152132235834..27ab6efd86ed6 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-loongson.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-loongson.c @@ -429,14 +429,14 @@ static int loongson_dwmac_dt_config(struct pci_dev *pdev, plat->bus_id = ret; res->irq = of_irq_get_byname(np, "macirq"); - if (res->irq < 0) { + if (res->irq <= 0) { dev_err(&pdev->dev, "IRQ macirq not found\n"); ret = -ENODEV; goto err_put_node; } res->wol_irq = of_irq_get_byname(np, "eth_wake_irq"); - if (res->wol_irq < 0) { + if (res->wol_irq <= 0) { dev_info(&pdev->dev, "IRQ eth_wake_irq not found, using macirq\n"); res->wol_irq = res->irq; -- cgit v1.2.3 From ccb91397a5bd9a8885f3f66397b6eff31972918e Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 9 Aug 2021 00:51:14 +0300 Subject: net: stmmac: Put MDIO OF-node if MDIO bus data allocation fails The MDIO-bus data allocation procedure has stopped ignoring the memory allocation failures since commit 1a981c0586c0 ("net: stmmac: Make MDIO bus reset optional"). The same semantics was maintained in the framework of the commit f3c2caacee82 ("net: stmmac: don't create a MDIO bus if unnecessary"). In both cases we should have also reverted everything what was done before the failure happens in the stmmac_mdio_setup() (former stmmac_dt_phy()) method, but the patches lacked that change. That is the MDIO OF-node refcounter decrement must have been introduced in the framework of any of those patches. Make sure it's done then. Note if the MDIO DT-node hasn't been found then the mdio_node pointer will be set to NULL. It's ok to pass NULL to of_node_put() since it just skips the refcount decrement operation then. Thus there is no need in additional check of the passed pointer value here. Fixes: 1a981c0586c0 ("net: stmmac: Make MDIO bus reset optional") Fixes: f3c2caacee82 ("net: stmmac: don't create a MDIO bus if unnecessary") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c index 5c9fd91a1db9d..a4d777e0611a2 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c @@ -352,8 +352,10 @@ static int stmmac_mdio_setup(struct plat_stmmacenet_data *plat, plat->mdio_bus_data = devm_kzalloc(dev, sizeof(*plat->mdio_bus_data), GFP_KERNEL); - if (!plat->mdio_bus_data) + if (!plat->mdio_bus_data) { + of_node_put(plat->mdio_node); return -ENOMEM; + } plat->mdio_bus_data->needs_reset = true; } -- cgit v1.2.3 From 7d804efbcc49422bf308041dce4488e85370ae27 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 16 Jul 2021 23:05:22 +0300 Subject: net: stmmac: Make sure PM is resumed on remove pm_runtime_get_sync() method returns either the PM-runtime usage counter value or an error. In the later case the further operations can be kind of pointless since due to the error the device might get to be powered down. Let's fix that by calling the pm_runtime_resume_and_get() method instead of pm_runtime_get_sync() and stopping further function execution if it fails. Note the PM-runtime usage counter will be decremented by pm_runtime_resume_and_get() method itself. Fixes: 6449520391df ("net: stmmac: properly handle with runtime pm in stmmac_dvr_remove()") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index f4cf140dc01d4..4f2b164207342 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -8098,10 +8098,13 @@ void stmmac_dvr_remove(struct device *dev) { struct net_device *ndev = dev_get_drvdata(dev); struct stmmac_priv *priv = netdev_priv(ndev); + int ret; netdev_info(priv->dev, "%s: removing driver", __func__); - pm_runtime_get_sync(dev); + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return; unregister_netdev(ndev); -- cgit v1.2.3 From d636f031aa62e7d58fb5f2b3601a54458166c86f Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 16 Jul 2021 23:48:47 +0300 Subject: net: stmmac: Cleanup PM-runtime on driver probe error In case of any error detected in the STMMAC driver probe procedure the PM-runtime data of the device needs to be restored back to the default state. That part has been forgotten in commit 5ec55823438e ("net: stmmac: add clocks management for gmac driver"). Fix that by calling the pm_runtime_put_noidle() and pm_runtime_disable() pair in the cleanup-on-error block of the stmmac_dvr_probe() method. Fixes: 5ec55823438e ("net: stmmac: add clocks management for gmac driver") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 4f2b164207342..6fae27ea8e870 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -8050,6 +8050,8 @@ error_phy_setup: error_pcs_setup: stmmac_mdio_unregister(ndev); error_mdio_register: + pm_runtime_put_noidle(device); + pm_runtime_disable(device); stmmac_napi_del(ndev); error_hw_init: destroy_workqueue(priv->wq); -- cgit v1.2.3 From 133a2536028e81b716525999d0685b5ad94e2152 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 13 Aug 2021 13:31:59 +0300 Subject: net: stmmac: dwmac-rk: Use device-managed PHY-clock getting If PHY-node has been detected in the DW MAC RK3288 probe procedure, then the glue-driver will try to retrieve the clock provider from it by directly calling the of_clk_get() method. The clock descriptor returned from it will be de-allocated only in the rk_gmac_exit() method. The later is called on the device removal path and in case if __stmmac_dvr_probe() fails. So should any error happen between the rk_gmac_clk_init() and __stmmac_dvr_probe() methods execution (for instance in the rk_gmac_init()->rk_gmac_powerup() method) the PHY-clock object will never be released thus causing the memory leak. Let's fix that by using the device-managed clock getting procedure here to prevent the leak. Semantics of the devm_get_clk_from_child() method perfectly fits in this case. The naming is confusing a bit though. Fixes: de1e963ad064 ("net: stmmac: rk: put the PHY clock on remove") Fixes: fecd4d7eef8b ("net: stmmac: dwmac-rk: Add integrated PHY support") Signed-off-by: Serge Semin --- Please note I don't poses any device with Rockhip RK3288 on-board. So this change is untested. --- drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c index b0441a368cb13..36b5c6127bd5c 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c @@ -1140,7 +1140,8 @@ static int rk_gmac_clk_init(struct plat_stmmacenet_data *plat) } if (plat->phy_node && bsp_priv->integrated_phy) { - bsp_priv->clk_phy = of_clk_get(plat->phy_node, 0); + bsp_priv->clk_phy = + devm_get_clk_from_child(dev, plat->phy_node, NULL); ret = PTR_ERR_OR_ZERO(bsp_priv->clk_phy); if (ret) return dev_err_probe(dev, ret, "Cannot get PHY clock\n"); @@ -1559,9 +1560,6 @@ static void rk_gmac_exit(struct device *dev, void *bsp_priv_) rk_gmac_powerdown(bsp_priv); - if (priv->plat->phy_node && bsp_priv->integrated_phy) - clk_put(bsp_priv->clk_phy); - reset_control_put(bsp_priv->phy_reset); } -- cgit v1.2.3 From b8e9d01838bc93cb18828264d103e00f26241150 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 13 Aug 2021 14:07:12 +0300 Subject: net: stmmac: dwmac-rk: Add device-managed PHY-reset control release The DW MAC Rockhip RK3288 glue-driver acquires a PHY-reset control by calling the of_reset_control_get() method in case if an integrated PHY is detected on the platform. But the release is performed only in the framework of the rk_gmac_setup() method itself and way down on bsp_plat->exit() callback. From that perspective rk_gmac_setup() and the path after the __stmmac_dvr_probe() method invocation are consistent. But should any fail happens between these two function execution (for instance in rk_gmac_clk_init() or in the rk_gmac_init()->rk_gmac_powerup()) the phy_reset object won't be released thus causing the memory leak. Let's fix it by registering the reset_control_put()-based action for the phy_reset object in the device-managed subsystem. Thus in case of any error in the probe() path after the registration, the object will always be automatically released and no leak would happen. Fixes: 1c249fc1e19e ("net: stmmac: rk: fix missing reset_control_put()") Fixes: fecd4d7eef8b ("net: stmmac: dwmac-rk: Add integrated PHY support") Signed-off-by: Serge Semin --- Please note I don't poses any device with Rockhip RK3288 on-board. So this change is untested. --- drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c index 36b5c6127bd5c..382c02d25d8c6 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c @@ -1313,6 +1313,13 @@ static struct rk_priv_data *rk_gmac_setup(struct platform_device *pdev, dev_err(&pdev->dev, "No PHY reset control found.\n"); bsp_priv->phy_reset = NULL; } + + ret = devm_add_action_or_reset(dev, reset_control_put, + bsp_priv->phy_reset); + if (ret) { + dev_err_probe(dev, ret, "failed to add rst put action\n"); + return ERR_PTR(ret); + } } } dev_info(dev, "integrated PHY? (%s).\n", @@ -1335,7 +1342,6 @@ static struct rk_priv_data *rk_gmac_setup(struct platform_device *pdev, if (ops->init) { ret = ops->init(bsp_priv); if (ret) { - reset_control_put(bsp_priv->phy_reset); dev_err_probe(dev, ret, "failed to init BSP\n"); return ERR_PTR(ret); } @@ -1559,8 +1565,6 @@ static void rk_gmac_exit(struct device *dev, void *bsp_priv_) struct rk_priv_data *bsp_priv = bsp_priv_; rk_gmac_powerdown(bsp_priv); - - reset_control_put(bsp_priv->phy_reset); } static int rk_gmac_probe(struct platform_device *pdev) -- cgit v1.2.3 From 0f0ab33b2635e3b231c036b87df5c774fee5d008 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sun, 25 Jul 2021 11:19:21 +0300 Subject: net: stmmac: visconti: Discard the system clock disabling Since commit 8f269102baf7 ("net: stmmac: disable clocks in stmmac_remove_config_dt()") it is done in the stmmac_remove_config_dt() method. Thus there is no need in explicitly disabling the clock here in the glue driver since it is done by the denoted function called in the stmmac_pltfr_remove() method. Fixes: 8f269102baf7 ("net: stmmac: disable clocks in stmmac_remove_config_dt()") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/dwmac-visconti.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-visconti.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-visconti.c index 9497b13a57539..a68abd4d0ec35 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-visconti.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-visconti.c @@ -195,11 +195,8 @@ static int visconti_eth_clock_probe(struct platform_device *pdev, static void visconti_eth_clock_remove(struct platform_device *pdev) { struct visconti_eth *dwmac = get_stmmac_bsp_priv(&pdev->dev); - struct net_device *ndev = platform_get_drvdata(pdev); - struct stmmac_priv *priv = netdev_priv(ndev); clk_disable_unprepare(dwmac->phy_ref_clk); - clk_disable_unprepare(priv->plat->stmmac_clk); } static int visconti_eth_dwmac_probe(struct platform_device *pdev) -- cgit v1.2.3 From d31dc987f36990fb7f5328bd1750dac30935e4f2 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sat, 16 Jan 2021 01:28:27 +0300 Subject: net: stmmac: Assert reset control after MDIO de-registration Indeed it's unlikely but MDIO de-registration may still require an access to the core registers, which obviously won't be possible in case if the interface has been put into the reset state. So move the reset control assertion to be executed after the MDIO bus is de-registered. Fixes: e743471f8d9c ("stmmac: fix oops on rmmod after assigning ip addr") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 6fae27ea8e870..b365805613a0a 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -8116,13 +8116,15 @@ void stmmac_dvr_remove(struct device *dev) stmmac_unregister_devlink(priv); phylink_destroy(priv->phylink); - if (priv->plat->stmmac_rst) - reset_control_assert(priv->plat->stmmac_rst); - reset_control_assert(priv->plat->stmmac_ahb_rst); stmmac_pcs_clean(ndev); stmmac_mdio_unregister(ndev); + if (priv->plat->stmmac_rst) + reset_control_assert(priv->plat->stmmac_rst); + + reset_control_assert(priv->plat->stmmac_ahb_rst); + destroy_workqueue(priv->wq); mutex_destroy(&priv->lock); bitmap_free(priv->af_xdp_zc_qps); -- cgit v1.2.3 From 5247f6da5bccebdd0d1fffae9d069f3ceef72e19 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 6 Aug 2021 13:52:06 +0300 Subject: net: stmmac: Halt DT-config probe on AXI setup failure If the stmmac_axi_setup() fails to allocate a memory chunk for the AXI-config data it will return -ENOMEM pointer. Upon return that pointer will be assigned to the plat->axi field. Neither stmmac_config_probe_dt() nor further plat->axi usages expect it to have errno value but being either valid pointer or NULL. Thus if for some reason the memory couldn't be allocated, accessing errno-pointer for instance in the framework of the stmmac_axi() method will cause the kernel crash on AXI bus configuration. Let's fix that by checking the return value of the stmmac_axi_setup() and passing the error number further to the stmmac_probe_config_dt() method caller. Note we could avoid the method prototype modification, but since we are changing the way the function is called anyway it can be converted to be looking as another local functions like stmmac_mtl_setup() or stmmac_dt_phy() for unification. The same prototype will be used in the cleanup changes committed a bit later. Fixes: afea03656add ("stmmac: rework DMA bus setting and introduce new platform AXI structure") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c index a4d777e0611a2..7d77a79788f8f 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c @@ -87,11 +87,15 @@ static int dwmac1000_validate_ucast_entries(struct device *dev, /** * stmmac_axi_setup - parse DT parameters for programming the AXI register * @pdev: platform device + * @plat: enet data * Description: * if required, from device-tree the AXI internal register can be tuned * by using platform parameters. + * Return value: + * 0 on success and negative error otherwise. */ -static struct stmmac_axi *stmmac_axi_setup(struct platform_device *pdev) +static int stmmac_axi_setup(struct platform_device *pdev, + struct plat_stmmacenet_data *plat) { struct device_node *np; struct stmmac_axi *axi; @@ -99,12 +103,12 @@ static struct stmmac_axi *stmmac_axi_setup(struct platform_device *pdev) np = of_parse_phandle(pdev->dev.of_node, "snps,axi-config", 0); if (!np) - return NULL; + return 0; axi = devm_kzalloc(&pdev->dev, sizeof(*axi), GFP_KERNEL); if (!axi) { of_node_put(np); - return ERR_PTR(-ENOMEM); + return -ENOMEM; } axi->axi_lpi_en = of_property_read_bool(np, "snps,lpi_en"); @@ -122,7 +126,9 @@ static struct stmmac_axi *stmmac_axi_setup(struct platform_device *pdev) stmmac_axi_blen_to_mask(&axi->axi_blen_regval, axi_blen, AXI_BLEN); of_node_put(np); - return axi; + plat->axi = axi; + + return 0; } /** @@ -581,7 +587,9 @@ stmmac_probe_config_dt(struct platform_device *pdev, u8 *mac) of_property_read_u32(np, "snps,ps-speed", &plat->mac_port_sel_speed); - plat->axi = stmmac_axi_setup(pdev); + rc = stmmac_axi_setup(pdev, plat); + if (rc) + goto error_put_mdio; rc = stmmac_mtl_setup(pdev, plat); if (rc) { -- cgit v1.2.3 From 4f6c8880572e9f2292adf830e78b6f1516e36337 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 27 Oct 2021 22:47:33 +0300 Subject: net: stmmac: dwxgmac: Fix hash-table nullification on IFF_ALLMULTI It has been wrong to manually set the imperfect filter hash table entries to ones in case if IFF_ALLMULTI filter is requested in dwxgmac2_set_filter(), since later in the same method the dwxgmac2_set_mchash() function is called, which will nullify the table back. Instead we need to set the local mc_filter[8] array to ones. The array will be passed to the dwxgmac2_set_mchash() method thus initializing the hash table entries as implied. The same is done in the DW MAC v4.x version of the set_filter callback - dwmac4_set_filter(). Fixes: 0efedbf11f07 ("net: stmmac: xgmac: Fix XGMAC selftests") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c index 49893b9fb88c4..912dc2cefa3ad 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c @@ -501,7 +501,6 @@ static void dwxgmac2_set_filter(struct mac_device_info *hw, u32 value = readl(ioaddr + XGMAC_PACKET_FILTER); int mcbitslog2 = hw->mcast_bits_log2; u32 mc_filter[8]; - int i; value &= ~(XGMAC_FILTER_PR | XGMAC_FILTER_HMC | XGMAC_FILTER_PM); value |= XGMAC_FILTER_HPF; @@ -514,9 +513,7 @@ static void dwxgmac2_set_filter(struct mac_device_info *hw, } else if ((dev->flags & IFF_ALLMULTI) || (netdev_mc_count(dev) > hw->multicast_filter_bins)) { value |= XGMAC_FILTER_PM; - - for (i = 0; i < XGMAC_MAX_HASH_TABLE; i++) - writel(~0x0, ioaddr + XGMAC_HASH_TABLE(i)); + memset(mc_filter, 0xff, sizeof(mc_filter)); } else if (!netdev_mc_empty(dev) && (dev->flags & IFF_MULTICAST)) { struct netdev_hw_addr *ha; -- cgit v1.2.3 From 893a5b3b37175cfe9ab8e4989b82b07b9f4d5e71 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 27 Oct 2021 22:59:28 +0300 Subject: net: stmmac: gmac: Fix missing IFF_MULTICAST check in dwmac1000_set_filter The similar fix has been provided in the framework of commit 2f633d5820e4 ("net: stmmac: xgmac: fix missing IFF_MULTICAST checki in dwxgmac2_set_filter") and commit 2ba31cd93784 ("net: stmmac: fix missing IFF_MULTICAST check in dwmac4_set_filter") with the next jusitification: Without checking for IFF_MULTICAST flag, it is wrong to assume multicast filtering is always enabled. By checking against IFF_MULTICAST, now the driver behaves correctly when the multicast support is toggled by below command:- ip link set multicast off|on Let's add the same modification to the DW GMAC-specific set_filter callback in order to fix the problem for the DW GMAC controllers too. Fixes: 47dd7a540b8a ("net: add support for STMicroelectronics Ethernet controllers.") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c index af566636fad90..8488d4b2e7c70 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c @@ -162,7 +162,7 @@ static void dwmac1000_set_filter(struct mac_device_info *hw, } else if (!netdev_mc_empty(dev) && (mcbitslog2 == 0)) { /* Fall back to all multicast if we've no filter */ value = GMAC_FRAME_FILTER_PM; - } else if (!netdev_mc_empty(dev)) { + } else if (!netdev_mc_empty(dev) && dev->flags & IFF_MULTICAST) { struct netdev_hw_addr *ha; /* Hash filter for multicast */ -- cgit v1.2.3 From 6dff588908f1e03ce8dd51f2933b35f50a1827d1 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 5 Nov 2021 16:55:30 +0300 Subject: net: stmmac: Fix off-by-one ucast filter setup Each DW MAC can be equipped with a certain number of MAC-address slots, which can be used for incoming traffic address perfect filtering. The number of available slots depends on the IP-core synthesize parameter and can vary: DW GMAC v3.x/v4.x: 1 - N, where N: [1; 32] or 1 - 64 or 1 - 128. DW x(L)GMAC: 1 - N, where N: [1; 32]. See that is exactly what the mac->unicast_filter_entries, XGMAC_ADDR_MAX, GMAC_MAX_PERFECT_ADDRESSES driver parameters and macro represent. (unicast_filter_entries is initialized with 1 either by the glue-drivers or in the stmmac_probe_config_dt() method). The problem is that the value preserved in them includes the all the MAC-address slots including the very first one which is reserved by the driver for the network interface device address, and according to each DW MAC IP-specific set_filter() method it is excluded from the unicast filter setups (see the local reg variable initialized with 1). So in order to get the actual number of available slots for the unicast addresses we need to decrease the unicast_filter_entries/XGMAC_ADDR_MAX/GMAC_MAX_PERFECT_ADDRESSES parameters by one. If we don't do so the set_filter() method will end up initializing a non-existent MAC-address slot thus losing a network traffic destined to the requested unicast address. The problem denoted above is fixed by decrementing the unicast_filter_entries variable when it comes to checking whether there is enough additional MAC-address slots available for the netdev_uc_count() addresses. If there is, the perfect filtering will be setup otherwise the promiscuous mode will be enabled. While at it convert the while() loop of the leftover MAC-address slots nullification to be for() loop as more natural in this context. Fixes: 47dd7a540b8a ("net: add support for STMicroelectronics Ethernet controllers.") Fixes: 477286b53f55 ("stmmac: add GMAC4 core support") Fixes: 0efedbf11f07 ("net: stmmac: xgmac: Fix XGMAC selftests") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c | 6 ++---- drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c | 5 ++--- drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c | 2 +- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c index 8488d4b2e7c70..ba24be32220ed 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c @@ -146,7 +146,6 @@ static void dwmac1000_set_filter(struct mac_device_info *hw, { void __iomem *ioaddr = (void __iomem *)dev->base_addr; unsigned int value = 0; - unsigned int perfect_addr_number = hw->unicast_filter_entries; u32 mc_filter[8]; int mcbitslog2 = hw->mcast_bits_log2; @@ -189,7 +188,7 @@ static void dwmac1000_set_filter(struct mac_device_info *hw, dwmac1000_set_mchash(ioaddr, mc_filter, mcbitslog2); /* Handle multiple unicast addresses (perfect filtering) */ - if (netdev_uc_count(dev) > perfect_addr_number) + if (netdev_uc_count(dev) > hw->unicast_filter_entries - 1) /* Switch to promiscuous mode if more than unicast * addresses are requested than supported by hardware. */ @@ -205,10 +204,9 @@ static void dwmac1000_set_filter(struct mac_device_info *hw, reg++; } - while (reg < perfect_addr_number) { + for (; reg < hw->unicast_filter_entries; reg++) { writel(0, ioaddr + GMAC_ADDR_HIGH(reg)); writel(0, ioaddr + GMAC_ADDR_LOW(reg)); - reg++; } } diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c index 623868afe93d2..7665aa5c3d918 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c @@ -515,7 +515,7 @@ static void dwmac4_set_filter(struct mac_device_info *hw, value |= GMAC_PACKET_FILTER_HPF; /* Handle multiple unicast addresses */ - if (netdev_uc_count(dev) > hw->unicast_filter_entries) { + if (netdev_uc_count(dev) > hw->unicast_filter_entries - 1) { /* Switch to promiscuous mode if more than 128 addrs * are required */ @@ -529,10 +529,9 @@ static void dwmac4_set_filter(struct mac_device_info *hw, reg++; } - while (reg < GMAC_MAX_PERFECT_ADDRESSES) { + for (; reg < GMAC_MAX_PERFECT_ADDRESSES; reg++) { writel(0, ioaddr + GMAC_ADDR_HIGH(reg)); writel(0, ioaddr + GMAC_ADDR_LOW(reg)); - reg++; } } diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c index 912dc2cefa3ad..8d77f6e7e71bb 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c @@ -529,7 +529,7 @@ static void dwxgmac2_set_filter(struct mac_device_info *hw, dwxgmac2_set_mchash(ioaddr, mc_filter, mcbitslog2); /* Handle multiple unicast addresses */ - if (netdev_uc_count(dev) > hw->unicast_filter_entries) { + if (netdev_uc_count(dev) > hw->unicast_filter_entries - 1) { value |= XGMAC_FILTER_PR; } else { struct netdev_hw_addr *ha; -- cgit v1.2.3 From a68d8b3d8754e784b92577e8041189665ee27243 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 10 Nov 2021 10:19:02 +0300 Subject: net: stmmac: sun8i: Fix RX CSUM being always enabled The driver has supported the HW-based Rx traffic checksum calculation being switched on/off (setting/clearing the NETIF_F_RXCSUM feature flag) since commit d2afb5bdffde ("stmmac: fix the rx csum feature"). If that feature has been switched off by for instance ethtool the tool will report that status. But even though the Sun8i MAC supports Rx COE turning on and off it doesn't do that in accordance with the current rx_csum-flag-based logic implemented in the STMMAC driver. It simply always enables the feature by setting the EMAC_RX_DO_CRC flag in the Rx CTL0 register. So in case if ethtool disabled the feature and started reporting that it was disabled in fact HW will still calculate the packets checksum. (It hasn't caused any problem so far because Sun8i supports Rx COE v2 which doesn't affect the incoming packets length unlike Rx COE v1.) Let's fix that by mending the sun8i_dwmac_rx_ipc_enable() method of the Sun8i glue-driver so one would take the state of the rx_csum flag into account and set or cleared the RX_DO_CRC flag in accordance with it. The similar approach has been implemented in the DW GMAC, GMAC4, xGMAC HW-if submodules. Fixes: 9f93ac8d4085 ("net-next: stmmac: Add dwmac-sun8i") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c index c01b86fd64da1..f06cc70130221 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c @@ -669,7 +669,12 @@ static int sun8i_dwmac_rx_ipc_enable(struct mac_device_info *hw) u32 v; v = readl(ioaddr + EMAC_RX_CTL0); - v |= EMAC_RX_DO_CRC; + + if (hw->rx_csum) + v |= EMAC_RX_DO_CRC; + else + v &= ~EMAC_RX_DO_CRC; + writel(v, ioaddr + EMAC_RX_CTL0); return 1; -- cgit v1.2.3 From 9270185c5cd7502ece5936b9534a4a6ed677c264 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sun, 21 Nov 2021 18:53:03 +0300 Subject: net: stmmac: Fix SG-feature being user-changeable All DW MACs support the SG-feature by design in the framework of multiple DMA descriptors participating in the Tx/Rx procedure. It can't be disabled neither by any hardware flag nor in the driver software. Thus the NETIF_F_SG flag is supposed to be set in the net_device.features field as unchangeable by the users. Fixes: 5e982f3bfdd5 ("net: stmmac: convert to hw_features") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index b365805613a0a..142aed8a4280e 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -7871,8 +7871,7 @@ static int __stmmac_dvr_probe(struct device *device, ndev->xdp_metadata_ops = &stmmac_xdp_metadata_ops; ndev->xsk_tx_metadata_ops = &stmmac_xsk_tx_metadata_ops; - ndev->hw_features = NETIF_F_SG | NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM | - NETIF_F_RXCSUM; + ndev->hw_features = NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM | NETIF_F_RXCSUM; ndev->xdp_features = NETDEV_XDP_ACT_BASIC | NETDEV_XDP_ACT_REDIRECT | NETDEV_XDP_ACT_XSK_ZEROCOPY; @@ -7931,7 +7930,7 @@ static int __stmmac_dvr_probe(struct device *device, } } - ndev->features |= ndev->hw_features | NETIF_F_HIGHDMA; + ndev->features |= ndev->hw_features | NETIF_F_SG | NETIF_F_HIGHDMA; ndev->watchdog_timeo = msecs_to_jiffies(watchdog); #ifdef STMMAC_VLAN_TAG_USED /* Both mac100 and gmac support receive VLAN tag detection */ -- cgit v1.2.3 From 45b81aa6e87c670ae8c0609c72c81b7ea866d260 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Tue, 23 Nov 2021 09:40:15 +0300 Subject: net: stmmac: Don't advertise NETIF_IF_HW_TC if none filter/sched supported If a DW MAC IP-core has been synthesized with no traffic filtering or scheduling capability which are required by the STMMAC TC-implementation, the current TC-initialization procedure will still set the NETIF_IF_HW_TC flag. Moreover it will be expose as a user-changeable HW-accelerated TC-feature. That's obviously wrong. Note the problem only concerns the DW MAC IP-cores with possible QoS-modules support like DW Eth QoS and DW X(L)G-MAC. Let's fix this by setting the NETIF_IF_HW_TC feature flag only if the TC-initialization procedure was successful, there is at least one required HW-capability available and make sure the corresponding callback explicitly returns -EOPNOTSUPP if required HW-capability is unavailable. Fixes: c104891c4b1f ("net: stmmac: Do not return error code in TC Initialization") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 8 +++++--- drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c | 12 ++++++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 142aed8a4280e..9ce203f2081bb 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -7875,9 +7875,11 @@ static int __stmmac_dvr_probe(struct device *device, ndev->xdp_features = NETDEV_XDP_ACT_BASIC | NETDEV_XDP_ACT_REDIRECT | NETDEV_XDP_ACT_XSK_ZEROCOPY; - ret = stmmac_tc_init(priv, priv); - if (!ret) { - ndev->hw_features |= NETIF_F_HW_TC; + if (priv->dma_cap.frpsel || priv->dma_cap.av || priv->dma_cap.l3l4fnum || + priv->dma_cap.tbssel || priv->dma_cap.estsel) { + ret = stmmac_tc_init(priv, priv); + if (!ret) + ndev->hw_features |= NETIF_F_HW_TC; } if ((priv->plat->flags & STMMAC_FLAG_TSO_EN) && (priv->dma_cap.tsoen)) { diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c index d786527185999..e6c36cb19ddd1 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c @@ -219,6 +219,9 @@ static int tc_delete_knode(struct stmmac_priv *priv, static int tc_setup_cls_u32(struct stmmac_priv *priv, struct tc_cls_u32_offload *cls) { + if (!priv->dma_cap.frpsel) + return -EOPNOTSUPP; + switch (cls->command) { case TC_CLSU32_REPLACE_KNODE: tc_unfill_entry(priv, cls); @@ -340,11 +343,12 @@ static int tc_setup_cbs(struct stmmac_priv *priv, u32 ptr; int ret; + if (!priv->dma_cap.av) + return -EOPNOTSUPP; + /* Queue 0 is not AVB capable */ if (queue <= 0 || queue >= tx_queues_count) return -EINVAL; - if (!priv->dma_cap.av) - return -EOPNOTSUPP; port_transmit_rate_kbps = qopt->idleslope - qopt->sendslope; @@ -866,6 +870,9 @@ static int tc_setup_cls(struct stmmac_priv *priv, { int ret = 0; + if (!priv->dma_cap.l3l4fnum) + return -EOPNOTSUPP; + /* When RSS is enabled, the filtering will be bypassed */ if (priv->rss.enable) return -EBUSY; @@ -1160,6 +1167,7 @@ static int tc_setup_etf(struct stmmac_priv *priv, { if (!priv->dma_cap.tbssel) return -EOPNOTSUPP; + if (qopt->queue >= priv->plat->tx_queues_to_use) return -EINVAL; if (!(priv->dma_conf.tx_queue[qopt->queue].tbs & STMMAC_TBS_AVAIL)) -- cgit v1.2.3 From 0900e14d514cfb3a1543cb683add304d0599be99 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 6 Mar 2023 05:32:43 +0300 Subject: Revert "net: stmmac: Correctly take timestamp for PTPv2" This reverts commit 14f347334bf232074616e29e29103dd0c7c54dec. The change was introduced by Jose with the justification that PTP_V2_EVENT requires the timestamp snapshot being captured for all event packets. Indeed the MAC_Timestamp_Control.TSEVNTENA field description states that that flag must be set in order to have the timestamp snapshot being taken for event messages only (SYNC, Delay_Req, Pdelay_Req, or Pdelay_Resp). But that is only a general description meanwhile the set of event messages may change depending on the clock node type (TSCLKTYPE/SNAPTYPSEL field of the CSR) and on whether the messages are relevant to the master node (TSMSTRENA field of the CSR). That semantics is summarized in the next table: SNAPTYPSEL TSMSTRENA TSEVNTENA PTP_Messages 01 x 0 SYNC, Follow_Up, Delay_Req, Delay_Resp, Pdelay_Req, Pdelay_Resp, Pdelay_Resp_Follow_Up 01 0 1 SYNC, Pdelay_Req, Pdelay_Resp It's relevant for all PTP-capable IP-cores [1], [2] and [3]. (Though the Pdelay_* messages are omitted for DW GMAC v3.50a). Most likely Jose just missed that peculiarity and provided the fix based only on the CSR description. That's why first the commit f2fb6b6275eb ("net: stmmac: enable timestamp snapshot for required PTP packets in dwmac v5.10a") worked around the problem for the DW Eth QoS v5.10a and afterwards the commit 3cb958027cb8 ("net: stmmac: Fix E2E delay mechanism") did the same for DW Eth QoS newer than v4.10a. Thus let's revert the change completely as being invalid for any PTP-capable DW MAC device. [1] DesignWare Cores Ethernet MAC Universal Databook, Revision 3.50a, February 2009, p. 303. [2] DesignWare Cores Ethernet MAC Universal Databook, Revision 3.73a, October 2013, p. 462. [3] DesignWare Cores Ethernet Quality-of-Service Databook, Revision 5.10a, December 2017, p.256. Fixes: 14f347334bf2 ("net: stmmac: Correctly take timestamp for PTPv2") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 9ce203f2081bb..02116b44a9774 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -684,8 +684,7 @@ static int stmmac_hwtstamp_set(struct net_device *dev, config->rx_filter = HWTSTAMP_FILTER_PTP_V2_EVENT; ptp_v2 = PTP_TCR_TSVER2ENA; snap_type_sel = PTP_TCR_SNAPTYPSEL_1; - if (priv->synopsys_id < DWMAC_CORE_4_10) - ts_event_en = PTP_TCR_TSEVNTENA; + ptp_over_ipv4_udp = PTP_TCR_TSIPV4ENA; ptp_over_ipv6_udp = PTP_TCR_TSIPV6ENA; ptp_over_ethernet = PTP_TCR_TSIPENA; -- cgit v1.2.3 From a6acac16114042842525d557726948a24793c681 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 3 Aug 2023 12:31:28 +0300 Subject: net: stmmac: Fix freeing unused IRQ line if multi_msi_en is false In case if the DW *MAC platform has DMA per-channel IRQs available, specified and initialized in the STMMAC-resources structure but for some reason the platform driver doesn't wish to use them, the stmmac_free_irq() function will try to free those IRQs anyway. It will cause the kernel to print backtrace and warn: > WARNING: CPU: 1 PID: 1015 at kernel/irq/manage.c:1893 free_irq+0x104/0x468 > Trying to free already-free IRQ ... Fix the stmmac_free_irq() method to freeing the DMA per-channel IRQs only if the feature is explicitly enabled by means of the plat_stmmacenet_data.multi_msi_en flag. Fixes: 8532f613bc78 ("net: stmmac: introduce MSI Interrupt routines for mac, safety, RX & TX") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 02116b44a9774..a58fac36896c9 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -3745,7 +3745,7 @@ static void stmmac_free_irq(struct net_device *dev, switch (irq_err) { case REQ_IRQ_ERR_ALL: - irq_idx = priv->plat->tx_queues_to_use; + irq_idx = priv->plat->dma_cfg->multi_msi_en ? priv->plat->tx_queues_to_use : 0; fallthrough; case REQ_IRQ_ERR_TX: for (j = irq_idx - 1; j >= 0; j--) { @@ -3754,7 +3754,7 @@ static void stmmac_free_irq(struct net_device *dev, free_irq(priv->tx_irq[j], &priv->dma_conf.tx_queue[j]); } } - irq_idx = priv->plat->rx_queues_to_use; + irq_idx = priv->plat->dma_cfg->multi_msi_en ? priv->plat->rx_queues_to_use : 0; fallthrough; case REQ_IRQ_ERR_RX: for (j = irq_idx - 1; j >= 0; j--) { -- cgit v1.2.3 From 7138196a885982ed6a5d827d7a6cd086f923f7f3 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 12 Feb 2025 18:18:21 +0300 Subject: net: stmmac: Fix SPH feature broken for zero-copy case Commit ae0d49d7f019 ("net: stmmac: Switch to zero-copy in non-XDP RX path") all of the sadden added the NET_SKB_PAD offset for the allocated Page-based buffer. Because of that the subsequent DMA-buffer sync procedure executed by the Page Pool core stopped syncing the range below the offset meanwhile the DW XGMAC DMA engine delivered the frames payload starting from zero base address. Thus a lot of seemingly corrupted frames got to be detected by the network core. A subsequent commit cb6cc8ed7717 ("net: stmmac: Apply new page pool parameters when SPH is enabled") suggested to fix the problem by dropping the offset for the SPH case, which hasn't been good for the interface performance since the there is no longer NET_SKB_PAD headroom for the traffic fixups. Thus network subsystem had to realloca the data buffer. Let's fix the problem differently. Instead of regressing the interface performance the proper fix is to just define an additional Page Pool for the secondary buffers with no offset and SKB-overheads since there is only payload is going to be there. Fixes: cb6cc8ed7717 ("net: stmmac: Apply new page pool parameters when SPH is enabled") Fixes: ae0d49d7f019 ("net: stmmac: Switch to zero-copy in non-XDP RX path") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac.h | 1 + drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 27 ++++++++++++++++------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h index 33667a26708c8..bdfa3887bd997 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h @@ -122,6 +122,7 @@ struct stmmac_rx_queue { struct xdp_rxq_info xdp_rxq; struct xsk_buff_pool *xsk_pool; struct page_pool *page_pool; + struct page_pool *sec_page_pool; struct stmmac_rx_buffer *buf_pool; struct stmmac_priv *priv_data; struct dma_extended_desc *dma_erx; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index a58fac36896c9..ddc108b3f69a9 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -1617,7 +1617,7 @@ static int stmmac_init_rx_buffers(struct stmmac_priv *priv, } if (priv->sph_active && !buf->sec_page) { - buf->sec_page = page_pool_alloc_pages(rx_q->page_pool, gfp); + buf->sec_page = page_pool_alloc_pages(rx_q->sec_page_pool, gfp); if (!buf->sec_page) return -ENOMEM; @@ -1654,7 +1654,7 @@ static void stmmac_free_rx_buffer(struct stmmac_priv *priv, buf->page = NULL; if (buf->sec_page) - page_pool_put_full_page(rx_q->page_pool, buf->sec_page, false); + page_pool_put_full_page(rx_q->sec_page_pool, buf->sec_page, false); buf->sec_page = NULL; } @@ -2107,6 +2107,8 @@ static void __free_dma_rx_desc_resources(struct stmmac_priv *priv, kfree(rx_q->buf_pool); if (rx_q->page_pool) page_pool_destroy(rx_q->page_pool); + if (rx_q->sec_page_pool) + page_pool_destroy(rx_q->sec_page_pool); } static void free_dma_rx_desc_resources(struct stmmac_priv *priv, @@ -2206,11 +2208,6 @@ static int __alloc_dma_rx_desc_resources(struct stmmac_priv *priv, pp_params.offset = stmmac_rx_offset(priv); pp_params.max_len = dma_conf->dma_buf_sz; - if (priv->sph_active) { - pp_params.offset = 0; - pp_params.max_len += stmmac_rx_offset(priv); - } - rx_q->page_pool = page_pool_create(&pp_params); if (IS_ERR(rx_q->page_pool)) { ret = PTR_ERR(rx_q->page_pool); @@ -2218,6 +2215,20 @@ static int __alloc_dma_rx_desc_resources(struct stmmac_priv *priv, return ret; } + if (priv->sph_active) { + num_pages = DIV_ROUND_UP(dma_conf->dma_buf_sz, PAGE_SIZE); + pp_params.order = order_base_2(num_pages); + pp_params.offset = 0; + pp_params.max_len = dma_conf->dma_buf_sz; + + rx_q->sec_page_pool = page_pool_create(&pp_params); + if (IS_ERR(rx_q->sec_page_pool)) { + ret = PTR_ERR(rx_q->sec_page_pool); + rx_q->sec_page_pool = NULL; + return ret; + } + } + rx_q->buf_pool = kzalloc_objs(*rx_q->buf_pool, dma_conf->dma_rx_size); if (!rx_q->buf_pool) return -ENOMEM; @@ -4967,7 +4978,7 @@ static inline void stmmac_rx_refill(struct stmmac_priv *priv, u32 queue) } if (priv->sph_active && !buf->sec_page) { - buf->sec_page = page_pool_alloc_pages(rx_q->page_pool, gfp); + buf->sec_page = page_pool_alloc_pages(rx_q->sec_page_pool, gfp); if (!buf->sec_page) break; -- cgit v1.2.3 From 9d421dea92eb79ffd26beed8e8df2b4771b30660 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 5 Feb 2025 23:30:00 +0300 Subject: net: stmmac: Reduce SKB data size Since commit ae0d49d7f019 ("net: stmmac: Switch to zero-copy in non-XDP RX path") the size of the allocated buffer passed to the napi_build_skb() implies the entire buffer size retrieved from the Page Pool. It make the skb->truesize too big which significantly reduces the network interface performance. Let's switch to passing the actual Rx DMA-buffer size with an additional padding for the SKB shared data. This will improve the interface performance. Fixes: ae0d49d7f019 ("net: stmmac: Switch to zero-copy in non-XDP RX path") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index ddc108b3f69a9..0917a23f196c3 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -2191,13 +2191,12 @@ static int __alloc_dma_rx_desc_resources(struct stmmac_priv *priv, unsigned int napi_id; int ret; - dma_buf_sz_pad = stmmac_rx_offset(priv) + dma_conf->dma_buf_sz + - SKB_DATA_ALIGN(sizeof(struct skb_shared_info)); + dma_buf_sz_pad = SKB_HEAD_ALIGN(stmmac_rx_offset(priv) + dma_conf->dma_buf_sz); num_pages = DIV_ROUND_UP(dma_buf_sz_pad, PAGE_SIZE); rx_q->queue_index = queue; rx_q->priv_data = priv; - rx_q->napi_skb_frag_size = num_pages * PAGE_SIZE; + rx_q->napi_skb_frag_size = dma_buf_sz_pad; pp_params.flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV; pp_params.pool_size = dma_conf->dma_rx_size; @@ -5620,10 +5619,8 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue) struct sk_buff *skb = NULL; struct stmmac_xdp_buff ctx; int xdp_status = 0; - int bufsz; dma_dir = page_pool_get_dma_dir(rx_q->page_pool); - bufsz = DIV_ROUND_UP(priv->dma_conf.dma_buf_sz, PAGE_SIZE) * PAGE_SIZE; limit = min(priv->dma_conf.dma_rx_size - 1, (unsigned int)limit); if (netif_msg_rx_status(priv)) { @@ -5736,7 +5733,8 @@ read_again: net_prefetch(page_address(buf->page) + buf->page_offset); - xdp_init_buff(&ctx.xdp, bufsz, &rx_q->xdp_rxq); + xdp_init_buff(&ctx.xdp, rx_q->napi_skb_frag_size, + &rx_q->xdp_rxq); xdp_prepare_buff(&ctx.xdp, page_address(buf->page), buf->page_offset, buf1_len, true); -- cgit v1.2.3 From 454e0becc2757d2235e067c04e07cd6a57c1066e Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 5 Feb 2025 23:36:46 +0300 Subject: net: stmmac: Drop page recycling on SKB allocation failure There is no point in calling the page_pool_recycle_direct() method if an SKB allocation failed. The page will be just reduced next time the Rx DMA-descriptor is utilized. So drop the method invocation then. Fixes: ae0d49d7f019 ("net: stmmac: Switch to zero-copy in non-XDP RX path") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 0917a23f196c3..7551cb3d20369 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -5794,8 +5794,6 @@ read_again: skb = napi_build_skb(page_address(buf->page), rx_q->napi_skb_frag_size); if (!skb) { - page_pool_recycle_direct(rx_q->page_pool, - buf->page); rx_dropped++; count++; goto drain_data; -- cgit v1.2.3 From cc016217f0b09bf5aada1a35dd51b9c4658ea0c1 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 5 Feb 2025 23:50:48 +0300 Subject: net: stmmac: Get back the rx-copybreak functionality Since commit 2af6106ae949 ("net: stmmac: Introducing support for Page Pool") the mapping and unmapping has been replaced with Pages Pool usage. The ethtool-tunable config like Rx copy-break has no longer been used for SK-buffers setup. So the ethtool tunable callback since setting/getting the ETHTOOL_RX_COPYBREAK id hasn't really changed anything. The same concerned the "rx_zeroc_thresh" member of the device private data, but the main user had already been removed in commit 2af6106ae949 ("net: stmmac: Introducing support for Page Pool") and in commit d66e67bd4cc7 ("net: stmmac: Remove unused inline function stmmac_rx_threshold_count"). In anyway it might still be useful for the performance sake to copy small frames right to the SKB buffer and re-use the Rx DMA-buffer for a next communication. Get back the data copying and the rx-copybreak functionalities by partly reverting the commit 971150af717f ("net: stmmac: Drop useless code related to ethtool rx-copybreak") and joining it with what removed in the commit ae0d49d7f019 ("net: stmmac: Switch to zero-copy in non-XDP RX path"). Fixes: ae0d49d7f019 ("net: stmmac: Switch to zero-copy in non-XDP RX path") Fixes: 971150af717f ("net: stmmac: Drop useless code related to ethtool rx-copybreak") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac.h | 2 ++ .../net/ethernet/stmicro/stmmac/stmmac_ethtool.c | 39 ++++++++++++++++++++++ drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 26 +++++++++++---- 3 files changed, 60 insertions(+), 7 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h index bdfa3887bd997..ed271bfdc0679 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h @@ -257,6 +257,8 @@ struct stmmac_priv { bool sph_active; bool sph_capable; u32 sarc_type; + + unsigned int rx_copybreak; u32 rx_riwt[MTL_MAX_RX_QUEUES]; int hwts_rx_en; bool tsfupdt_coarse; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c index c1e26965d9b5d..cc04fc62e7017 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c @@ -1035,6 +1035,43 @@ static int stmmac_get_ts_info(struct net_device *dev, return ethtool_op_get_ts_info(dev, info); } +static int stmmac_get_tunable(struct net_device *dev, + const struct ethtool_tunable *tuna, void *data) +{ + struct stmmac_priv *priv = netdev_priv(dev); + int ret = 0; + + switch (tuna->id) { + case ETHTOOL_RX_COPYBREAK: + *(u32 *)data = priv->rx_copybreak; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int stmmac_set_tunable(struct net_device *dev, + const struct ethtool_tunable *tuna, + const void *data) +{ + struct stmmac_priv *priv = netdev_priv(dev); + int ret = 0; + + switch (tuna->id) { + case ETHTOOL_RX_COPYBREAK: + priv->rx_copybreak = *(u32 *)data; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + static int stmmac_get_mm(struct net_device *ndev, struct ethtool_mm_state *state) { @@ -1124,6 +1161,8 @@ static const struct ethtool_ops stmmac_ethtool_ops = { .set_per_queue_coalesce = stmmac_set_per_queue_coalesce, .get_channels = stmmac_get_channels, .set_channels = stmmac_set_channels, + .get_tunable = stmmac_get_tunable, + .set_tunable = stmmac_set_tunable, .get_link_ksettings = stmmac_ethtool_get_link_ksettings, .set_link_ksettings = stmmac_ethtool_set_link_ksettings, .get_mm = stmmac_get_mm, diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 7551cb3d20369..3865f392b0393 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -109,6 +109,8 @@ static unsigned int buf_sz = DEFAULT_BUFSIZE; module_param(buf_sz, uint, 0644); MODULE_PARM_DESC(buf_sz, "Rx DMA buffer size"); +#define STMMAC_RX_COPYBREAK 256 + static const u32 default_msg_level = (NETIF_MSG_DRV | NETIF_MSG_PROBE | NETIF_MSG_LINK | NETIF_MSG_IFUP | NETIF_MSG_IFDOWN | NETIF_MSG_TIMER); @@ -4105,6 +4107,8 @@ static int __stmmac_open(struct net_device *dev, u32 chan; int ret; + priv->rx_copybreak = STMMAC_RX_COPYBREAK; + for (int i = 0; i < MTL_MAX_TX_QUEUES; i++) if (priv->dma_conf.tx_queue[i].tbs & STMMAC_TBS_EN) dma_conf->tx_queue[i].tbs = priv->dma_conf.tx_queue[i].tbs; @@ -5786,13 +5790,14 @@ read_again: } if (!skb) { - unsigned int head_pad_len; - /* XDP program may expand or reduce tail */ buf1_len = ctx.xdp.data_end - ctx.xdp.data; - skb = napi_build_skb(page_address(buf->page), - rx_q->napi_skb_frag_size); + if (buf1_len < priv->rx_copybreak) + skb = napi_alloc_skb(&ch->rx_napi, buf1_len); + else + skb = napi_build_skb(page_address(buf->page), + rx_q->napi_skb_frag_size); if (!skb) { rx_dropped++; count++; @@ -5800,10 +5805,15 @@ read_again: } /* XDP program may adjust header */ - head_pad_len = ctx.xdp.data - ctx.xdp.data_hard_start; - skb_reserve(skb, head_pad_len); + if (buf1_len < priv->rx_copybreak) { + skb_copy_to_linear_data(skb, ctx.xdp.data, buf1_len); + page_pool_put_page(rx_q->page_pool, buf->page, buf1_len, true); + } else { + skb_reserve(skb, ctx.xdp.data - ctx.xdp.data_hard_start); + skb_mark_for_recycle(skb); + } + skb_put(skb, buf1_len); - skb_mark_for_recycle(skb); buf->page = NULL; } else if (buf1_len) { dma_sync_single_for_cpu(priv->device, buf->addr, @@ -5811,6 +5821,7 @@ read_again: skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags, buf->page, buf->page_offset, buf1_len, priv->dma_conf.dma_buf_sz); + skb_mark_for_recycle(skb); buf->page = NULL; } @@ -5820,6 +5831,7 @@ read_again: skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags, buf->sec_page, 0, buf2_len, priv->dma_conf.dma_buf_sz); + skb_mark_for_recycle(skb); buf->sec_page = NULL; } -- cgit v1.2.3 From ae11d41779ff0751a7ed151143469901a058e3d2 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 13 Feb 2025 17:58:48 +0300 Subject: net: stmmac: Add multiple frames-per-page support The driver currently implements a standard one-frame-per-page model for the Rx path. This is way not optimal seeing the normal MTU is ~1.5KB which can fit at least two times per system pages even with all the headrooms and padding. If the kernel is configured with 16KB page then up to 8 frames can be received by means of a single page. Moreover such an optimization normally leads to even greater interface performance due to reducing the amount of the system TLB misses and less DMA-sync procedures. So let's convert the STMMAC driver to implementing the multiple frames-per-page feature just by having the page_pool_alloc() method utilized. In addition the Page Pools must be created in a way so the DMA-sync would be performed for the entire page (except a very first offset). Note the change doesn't concern the XDP functionality. The driver shall allocated a single page for each Rx-frame as before. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac.h | 2 + drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 80 +++++++++++++++-------- 2 files changed, 54 insertions(+), 28 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h index ed271bfdc0679..dc2058d8620cb 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h @@ -89,11 +89,13 @@ struct stmmac_rx_buffer { struct page *page; dma_addr_t addr; __u32 page_offset; + __u32 frame_offset; }; struct xdp_buff *xdp; }; struct page *sec_page; dma_addr_t sec_addr; + __u32 sec_page_offset; }; struct stmmac_xdp_buff { diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 3865f392b0393..758df67a31c4c 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -1607,31 +1607,38 @@ static int stmmac_init_rx_buffers(struct stmmac_priv *priv, struct stmmac_rx_queue *rx_q = &dma_conf->rx_queue[queue]; struct stmmac_rx_buffer *buf = &rx_q->buf_pool[i]; gfp_t gfp = (GFP_ATOMIC | __GFP_NOWARN); + unsigned int size; if (priv->dma_cap.host_dma_width <= 32) gfp |= GFP_DMA32; if (!buf->page) { - buf->page = page_pool_alloc_pages(rx_q->page_pool, gfp); + size = rx_q->napi_skb_frag_size; + buf->page = page_pool_alloc(rx_q->page_pool, + &buf->page_offset, &size, gfp); if (!buf->page) return -ENOMEM; - buf->page_offset = stmmac_rx_offset(priv); + + buf->frame_offset = stmmac_rx_offset(priv); + buf->addr = page_pool_get_dma_addr(buf->page) + buf->page_offset + + buf->frame_offset; } if (priv->sph_active && !buf->sec_page) { - buf->sec_page = page_pool_alloc_pages(rx_q->sec_page_pool, gfp); + size = dma_conf->dma_buf_sz; + buf->sec_page = page_pool_alloc(rx_q->sec_page_pool, + &buf->sec_page_offset, &size, gfp); if (!buf->sec_page) return -ENOMEM; - buf->sec_addr = page_pool_get_dma_addr(buf->sec_page); + buf->sec_addr = page_pool_get_dma_addr(buf->sec_page) + + buf->sec_page_offset; stmmac_set_desc_sec_addr(priv, p, buf->sec_addr, true); } else { buf->sec_page = NULL; stmmac_set_desc_sec_addr(priv, p, buf->sec_addr, false); } - buf->addr = page_pool_get_dma_addr(buf->page) + buf->page_offset; - stmmac_set_desc_addr(priv, p, buf->addr); if (dma_conf->dma_buf_sz == BUF_SIZE_16KiB) stmmac_init_desc3(priv, p); @@ -2198,16 +2205,27 @@ static int __alloc_dma_rx_desc_resources(struct stmmac_priv *priv, rx_q->queue_index = queue; rx_q->priv_data = priv; - rx_q->napi_skb_frag_size = dma_buf_sz_pad; pp_params.flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV; pp_params.pool_size = dma_conf->dma_rx_size; pp_params.order = order_base_2(num_pages); pp_params.nid = dev_to_node(priv->device); pp_params.dev = priv->device; - pp_params.dma_dir = xdp_prog ? DMA_BIDIRECTIONAL : DMA_FROM_DEVICE; pp_params.offset = stmmac_rx_offset(priv); - pp_params.max_len = dma_conf->dma_buf_sz; + /* Allocate the entire page for a single frame in case of XDP and use + * a Headroom+DMA-buffer+Pad region per-frame otherwise to optimize the + * page memory usage. Note the later requires syncing the whole page + * after all the fragments released. + */ + if (xdp_prog) { + rx_q->napi_skb_frag_size = num_pages * PAGE_SIZE; + pp_params.dma_dir = DMA_BIDIRECTIONAL; + pp_params.max_len = dma_conf->dma_buf_sz; + } else { + rx_q->napi_skb_frag_size = dma_buf_sz_pad; + pp_params.dma_dir = DMA_FROM_DEVICE; + pp_params.max_len = num_pages * PAGE_SIZE - pp_params.offset; + } rx_q->page_pool = page_pool_create(&pp_params); if (IS_ERR(rx_q->page_pool)) { @@ -2220,7 +2238,7 @@ static int __alloc_dma_rx_desc_resources(struct stmmac_priv *priv, num_pages = DIV_ROUND_UP(dma_conf->dma_buf_sz, PAGE_SIZE); pp_params.order = order_base_2(num_pages); pp_params.offset = 0; - pp_params.max_len = dma_conf->dma_buf_sz; + pp_params.max_len = PAGE_SIZE << pp_params.order; rx_q->sec_page_pool = page_pool_create(&pp_params); if (IS_ERR(rx_q->sec_page_pool)) { @@ -4960,6 +4978,7 @@ static inline void stmmac_rx_refill(struct stmmac_priv *priv, u32 queue) int dirty = stmmac_rx_dirty(priv, queue); unsigned int entry = rx_q->dirty_rx; gfp_t gfp = (GFP_ATOMIC | __GFP_NOWARN); + unsigned int size; if (priv->dma_cap.host_dma_width <= 32) gfp |= GFP_DMA32; @@ -4975,21 +4994,28 @@ static inline void stmmac_rx_refill(struct stmmac_priv *priv, u32 queue) p = rx_q->dma_rx + entry; if (!buf->page) { - buf->page = page_pool_alloc_pages(rx_q->page_pool, gfp); + size = rx_q->napi_skb_frag_size; + buf->page = page_pool_alloc(rx_q->page_pool, + &buf->page_offset, &size, gfp); if (!buf->page) break; + + buf->frame_offset = stmmac_rx_offset(priv); + buf->addr = page_pool_get_dma_addr(buf->page) + + buf->page_offset + buf->frame_offset; } if (priv->sph_active && !buf->sec_page) { - buf->sec_page = page_pool_alloc_pages(rx_q->sec_page_pool, gfp); + size = priv->dma_conf.dma_buf_sz; + buf->sec_page = page_pool_alloc(rx_q->sec_page_pool, + &buf->sec_page_offset, &size, gfp); if (!buf->sec_page) break; - buf->sec_addr = page_pool_get_dma_addr(buf->sec_page); + buf->sec_addr = page_pool_get_dma_addr(buf->sec_page) + + buf->sec_page_offset; } - buf->addr = page_pool_get_dma_addr(buf->page) + buf->page_offset; - stmmac_set_desc_addr(priv, p, buf->addr); if (priv->sph_active) stmmac_set_desc_sec_addr(priv, p, buf->sec_addr, true); @@ -5735,15 +5761,15 @@ read_again: dma_sync_single_for_cpu(priv->device, buf->addr, buf1_len, dma_dir); net_prefetch(page_address(buf->page) + - buf->page_offset); + buf->page_offset + buf->frame_offset); xdp_init_buff(&ctx.xdp, rx_q->napi_skb_frag_size, &rx_q->xdp_rxq); - xdp_prepare_buff(&ctx.xdp, page_address(buf->page), - buf->page_offset, buf1_len, true); + xdp_prepare_buff(&ctx.xdp, page_address(buf->page) + buf->page_offset, + buf->frame_offset, buf1_len, true); pre_len = ctx.xdp.data_end - ctx.xdp.data_hard_start - - buf->page_offset; + buf->frame_offset; ctx.priv = priv; ctx.desc = p; @@ -5754,7 +5780,7 @@ read_again: * cover max len CPU touch */ sync_len = ctx.xdp.data_end - ctx.xdp.data_hard_start - - buf->page_offset; + buf->frame_offset; sync_len = max(sync_len, pre_len); /* For Not XDP_PASS verdict */ @@ -5762,9 +5788,7 @@ read_again: unsigned int xdp_res = -PTR_ERR(skb); if (xdp_res & STMMAC_XDP_CONSUMED) { - page_pool_put_page(rx_q->page_pool, - virt_to_head_page(ctx.xdp.data), - sync_len, true); + page_pool_put_full_page(rx_q->page_pool, buf->page, true); buf->page = NULL; rx_dropped++; @@ -5796,7 +5820,7 @@ read_again: if (buf1_len < priv->rx_copybreak) skb = napi_alloc_skb(&ch->rx_napi, buf1_len); else - skb = napi_build_skb(page_address(buf->page), + skb = napi_build_skb(page_address(buf->page) + buf->page_offset, rx_q->napi_skb_frag_size); if (!skb) { rx_dropped++; @@ -5807,7 +5831,7 @@ read_again: /* XDP program may adjust header */ if (buf1_len < priv->rx_copybreak) { skb_copy_to_linear_data(skb, ctx.xdp.data, buf1_len); - page_pool_put_page(rx_q->page_pool, buf->page, buf1_len, true); + page_pool_put_full_page(rx_q->page_pool, buf->page, true); } else { skb_reserve(skb, ctx.xdp.data - ctx.xdp.data_hard_start); skb_mark_for_recycle(skb); @@ -5819,8 +5843,8 @@ read_again: dma_sync_single_for_cpu(priv->device, buf->addr, buf1_len, dma_dir); skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags, - buf->page, buf->page_offset, buf1_len, - priv->dma_conf.dma_buf_sz); + buf->page, buf->page_offset + buf->frame_offset, + buf1_len, priv->dma_conf.dma_buf_sz); skb_mark_for_recycle(skb); buf->page = NULL; } @@ -5829,7 +5853,7 @@ read_again: dma_sync_single_for_cpu(priv->device, buf->sec_addr, buf2_len, dma_dir); skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags, - buf->sec_page, 0, buf2_len, + buf->sec_page, buf->sec_page_offset, buf2_len, priv->dma_conf.dma_buf_sz); skb_mark_for_recycle(skb); buf->sec_page = NULL; -- cgit v1.2.3 From fdd333321e3c5fa31fe2e3fb19b52b1df8974a32 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 5 Feb 2025 20:29:12 +0300 Subject: net: stmmac: Align frames with the IP-header base address Recent commit converted the Rx procedure to perform a pure zero-copy in non-XDP RX path by passing the Rx DMA-buffer to SKB instead of copying the data to the internal SKB sub-buffer. On the contrary the change caused a significant performance degradation on the platforms not supporting a size-unaligned data access. In particular the MIPS ip_fast_csum() method expect the properly aligned buffer passed. So do many other arches. The method is called in the framework of the napi_gro_receive() procedure. Fix the problem by aligning the buffer to the IP-header base address. It's done just by fixing the stmmac_rx_offset() method. Note a pure zero-copying had already been supported by the driver before the commit 2af6106ae949 ("net: stmmac: Introducing support for Page Pool") with the IP-aligned buffer offset. But the later commit dropped it in the framework of the Page Pool API adding to the driver. Then another commit a955318fe67e ("stmmac: align RX buffers") got the aligning back, which was reverted later by the commit 12d125b4574b ("stmmac: Revert "stmmac: align RX buffers"") with a justification that it broke an Nvidia Jetson TX2 system. There was no discovery why the NET_SKB_PAD offset broke the platform, but the reviewers proved that setting just the NET_IP_ALIGN offset didn't cause any regression. So let's add it then seeing it improves the performance on some platforms. Fixes: ae0d49d7f019 ("net: stmmac: Switch to zero-copy in non-XDP RX path") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac.h | 1 + drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 37 +++++++++++++++++++---- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h index dc2058d8620cb..70ef54d6c2be0 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h @@ -90,6 +90,7 @@ struct stmmac_rx_buffer { dma_addr_t addr; __u32 page_offset; __u32 frame_offset; + __u32 frag_offset; }; struct xdp_buff *xdp; }; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 758df67a31c4c..da291bd2184f5 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -1479,12 +1479,32 @@ static void stmmac_display_rings(struct stmmac_priv *priv, stmmac_display_tx_rings(priv, dma_conf); } +/** + * stmmac_fs_offset - find out platform-specific frame offset + * Description: retrieve Rx DMA-buffer offset to align up the incoming + * frames within the IP-header. + */ +static unsigned int stmmac_fs_offset(void) +{ +#ifdef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS + return 0; +#else + return NET_IP_ALIGN; +#endif +} + +/** + * stmmac_rx_offset - find out Rx DMA-buffer offset + * @priv: driver private structure + * Description: retrieve Rx DMA-buffer offset specific to the current + * interface mode: XDP-less and XDP. + */ static unsigned int stmmac_rx_offset(struct stmmac_priv *priv) { if (stmmac_xdp_is_enabled(priv)) - return XDP_PACKET_HEADROOM; + return XDP_PACKET_HEADROOM + stmmac_fs_offset(); - return NET_SKB_PAD; + return NET_SKB_PAD + stmmac_fs_offset(); } static int stmmac_set_bfsize(int mtu) @@ -1620,6 +1640,7 @@ static int stmmac_init_rx_buffers(struct stmmac_priv *priv, return -ENOMEM; buf->frame_offset = stmmac_rx_offset(priv); + buf->frag_offset = buf->frame_offset - stmmac_fs_offset(); buf->addr = page_pool_get_dma_addr(buf->page) + buf->page_offset + buf->frame_offset; } @@ -5001,6 +5022,7 @@ static inline void stmmac_rx_refill(struct stmmac_priv *priv, u32 queue) break; buf->frame_offset = stmmac_rx_offset(priv); + buf->frag_offset = buf->frame_offset - stmmac_fs_offset(); buf->addr = page_pool_get_dma_addr(buf->page) + buf->page_offset + buf->frame_offset; } @@ -5063,9 +5085,12 @@ static unsigned int stmmac_rx_buf1_len(struct stmmac_priv *priv, return hlen; } - /* First descriptor, not last descriptor and not split header */ - if (status & rx_not_ls) - return priv->dma_conf.dma_buf_sz; + /* Not last descriptor and not split header */ + if (status & rx_not_ls) { + /* First descriptor, buffer size reduced by offset */ + plen = priv->dma_conf.dma_buf_sz; + return len ? plen : plen - stmmac_fs_offset(); + } plen = stmmac_get_rx_frame_len(priv, p, coe); @@ -5843,7 +5868,7 @@ read_again: dma_sync_single_for_cpu(priv->device, buf->addr, buf1_len, dma_dir); skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags, - buf->page, buf->page_offset + buf->frame_offset, + buf->page, buf->page_offset + buf->frag_offset, buf1_len, priv->dma_conf.dma_buf_sz); skb_mark_for_recycle(skb); buf->page = NULL; -- cgit v1.2.3 From 7b0b87c91e709ca1106a4958a476b4454e394a2c Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 24 Apr 2025 22:55:10 +0300 Subject: net: stmmac: Stop inlining netdev_get_tx_queue() calls Currently almost all the netdev_queue pointer retrieval in the driver are made by directly calling the netdev_get_tx_queue() method even if it's done in the framework of the same code blocks/functions. It's not only looking very bulky and hard to read but also not that optimal since every call implies getting a pointer to the netdev_queue array item. As a preparation before adding the "xmit_more" feature support let's introduce the local pointers to the netdev_queue structure and initialize it with the netdevice queue pointer retrieved from the netdev_get_tx_queue() method. This shall make the code clearer. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 45 +++++++++++++---------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index da291bd2184f5..44d5183dddfe2 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -2854,8 +2854,11 @@ static int stmmac_tx_clean(struct stmmac_priv *priv, int budget, u32 queue, unsigned int bytes_compl = 0, pkts_compl = 0; unsigned int entry, xmits = 0, count = 0; u32 tx_packets = 0, tx_errors = 0; + struct netdev_queue *nq; + + nq = netdev_get_tx_queue(priv->dev, queue); - __netif_tx_lock_bh(netdev_get_tx_queue(priv->dev, queue)); + __netif_tx_lock_bh(nq); tx_q->xsk_frames_done = 0; @@ -2976,16 +2979,13 @@ static int stmmac_tx_clean(struct stmmac_priv *priv, int budget, u32 queue, } tx_q->dirty_tx = entry; - netdev_tx_completed_queue(netdev_get_tx_queue(priv->dev, queue), - pkts_compl, bytes_compl); + netdev_tx_completed_queue(nq, pkts_compl, bytes_compl); - if (unlikely(netif_tx_queue_stopped(netdev_get_tx_queue(priv->dev, - queue))) && + if (unlikely(netif_tx_queue_stopped(nq)) && stmmac_tx_avail(priv, queue) > STMMAC_TX_THRESH(priv)) { - netif_dbg(priv, tx_done, priv->dev, "%s: restart transmit\n", __func__); - netif_tx_wake_queue(netdev_get_tx_queue(priv->dev, queue)); + netif_tx_wake_queue(nq); } if (tx_q->xsk_pool) { @@ -3025,7 +3025,7 @@ static int stmmac_tx_clean(struct stmmac_priv *priv, int budget, u32 queue, priv->xstats.tx_errors += tx_errors; - __netif_tx_unlock_bh(netdev_get_tx_queue(priv->dev, queue)); + __netif_tx_unlock_bh(nq); /* Combine decisions from TX clean and XSK TX */ return max(count, xmits); @@ -3041,8 +3041,11 @@ static int stmmac_tx_clean(struct stmmac_priv *priv, int budget, u32 queue, static void stmmac_tx_err(struct stmmac_priv *priv, u32 chan) { struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[chan]; + struct netdev_queue *nq; + + nq = netdev_get_tx_queue(priv->dev, chan); - netif_tx_stop_queue(netdev_get_tx_queue(priv->dev, chan)); + netif_tx_stop_queue(nq); stmmac_stop_tx_dma(priv, chan); dma_free_tx_skbufs(priv, &priv->dma_conf, chan); @@ -3053,7 +3056,7 @@ static void stmmac_tx_err(struct stmmac_priv *priv, u32 chan) stmmac_start_tx_dma(priv, chan); priv->xstats.tx_errors++; - netif_tx_wake_queue(netdev_get_tx_queue(priv->dev, chan)); + netif_tx_wake_queue(nq); } /** @@ -4435,6 +4438,7 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev) struct stmmac_txq_stats *txq_stats; struct stmmac_tx_queue *tx_q; bool set_ic, is_last_segment; + struct netdev_queue *nq; u32 pay_len, mss, queue; int i, first_tx, nfrags; u8 proto_hdr_len, hdr; @@ -4456,6 +4460,7 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev) nfrags = skb_shinfo(skb)->nr_frags; queue = skb_get_queue_mapping(skb); + nq = netdev_get_tx_queue(dev, queue); tx_q = &priv->dma_conf.tx_queue[queue]; txq_stats = &priv->xstats.txq_stats[queue]; first_tx = tx_q->cur_tx; @@ -4472,9 +4477,8 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev) /* Desc availability based on threshold should be enough safe */ if (unlikely(stmmac_tx_avail(priv, queue) < (((skb->len - proto_hdr_len) / TSO_MAX_BUFF_SIZE + 1)))) { - if (!netif_tx_queue_stopped(netdev_get_tx_queue(dev, queue))) { - netif_tx_stop_queue(netdev_get_tx_queue(priv->dev, - queue)); + if (!netif_tx_queue_stopped(nq)) { + netif_tx_stop_queue(nq); /* This is a hard error, log it. */ netdev_err(priv->dev, "%s: Tx Ring full when queue awake\n", @@ -4605,7 +4609,7 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev) if (unlikely(stmmac_tx_avail(priv, queue) <= (MAX_SKB_FRAGS + 1))) { netif_dbg(priv, hw, priv->dev, "%s: stop transmitted packets\n", __func__); - netif_tx_stop_queue(netdev_get_tx_queue(priv->dev, queue)); + netif_tx_stop_queue(nq); } u64_stats_update_begin(&txq_stats->q_syncp); @@ -4656,7 +4660,7 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev) print_pkt(skb->data, skb_headlen(skb)); } - netdev_tx_sent_queue(netdev_get_tx_queue(dev, queue), skb->len); + netdev_tx_sent_queue(nq, skb->len); skb_tx_timestamp(skb); stmmac_flush_tx_descriptors(priv, queue); @@ -4715,10 +4719,12 @@ static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev) struct dma_desc *desc, *first; struct stmmac_tx_queue *tx_q; int i, csum_insertion = 0; + struct netdev_queue *nq; int entry, first_tx; dma_addr_t des; u32 sdu_len; + nq = netdev_get_tx_queue(dev, queue); tx_q = &priv->dma_conf.tx_queue[queue]; txq_stats = &priv->xstats.txq_stats[queue]; first_tx = tx_q->cur_tx; @@ -4748,9 +4754,8 @@ static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev) } if (unlikely(stmmac_tx_avail(priv, queue) < nfrags + 1)) { - if (!netif_tx_queue_stopped(netdev_get_tx_queue(dev, queue))) { - netif_tx_stop_queue(netdev_get_tx_queue(priv->dev, - queue)); + if (!netif_tx_queue_stopped(nq)) { + netif_tx_stop_queue(nq); /* This is a hard error, log it. */ netdev_err(priv->dev, "%s: Tx Ring full when queue awake\n", @@ -4896,7 +4901,7 @@ static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev) if (unlikely(stmmac_tx_avail(priv, queue) <= (MAX_SKB_FRAGS + 1))) { netif_dbg(priv, hw, priv->dev, "%s: stop transmitted packets\n", __func__); - netif_tx_stop_queue(netdev_get_tx_queue(priv->dev, queue)); + netif_tx_stop_queue(nq); } u64_stats_update_begin(&txq_stats->q_syncp); @@ -4951,7 +4956,7 @@ static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev) stmmac_set_tx_owner(priv, first); - netdev_tx_sent_queue(netdev_get_tx_queue(dev, queue), skb->len); + netdev_tx_sent_queue(nq, skb->len); stmmac_enable_dma_transmission(priv, priv->ioaddr, queue); skb_tx_timestamp(skb); -- cgit v1.2.3 From f2663e9c7c6c81972a1147d02c09489f4c975754 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 28 Apr 2025 21:24:17 +0300 Subject: net: stmmac: Add xmit_more support Currently the STMMAC driver flushes out the xmit frames as soon as at least one Tx DMA-descriptor is ready initialized and passed to DMA. It isn't optimal since the Tx procedure is quite resource consuming process due to the resource freeing handling. Instead it's better to pre-initialize a certain amount of the descriptor and then burst them out at once per the networking core request. It's done by means of the "xmit_more" networking feature. Let's add the xmit_more feature support to the STMMAC driver too then. It isn't that problematic. The xmit-procedures must just make sure that the xmit-engine is started only after the networking core allows that by means of the netdev_xmit_more() method. That's it. The change will significantly improve the pktgen procedure on a DMA-noncoherent platform. 1. Without this change and no burst activated: Result: OK: 2337930(c2337930+d0) usec, 418841 (1500byte,0frags) 179150pps 2149Mb/sec (2149800000bps) errors: 0 2. Without this change, but with burst 6: Result: OK: 4272049(c4272049+d0) usec, 1176576 (1500byte,0frags) 275412pps 3304Mb/sec (3304944000bps) errors: 0 3. With this change and with burst 6: Result: OK: 7759942(c7759942+d0) usec, 2575656 (1500byte,0frags) 331916pps 3982Mb/sec (3982992000bps) errors: 0 4. With this change and with burst 100: Result: OK: 2371571(c2371571+d0) usec, 988200 (1500byte,0frags) 416685pps 5000Mb/sec (5000220000bps) errors: 0 See, it gives at least 500Mbps pktgen performance rise! Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 116 ++++++++++++---------- 1 file changed, 63 insertions(+), 53 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 44d5183dddfe2..b29aab347d9d8 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -4434,10 +4434,11 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev) { struct dma_desc *desc, *first, *mss_desc = NULL; struct stmmac_priv *priv = netdev_priv(dev); + bool set_ic, is_last_segment, drop = false; unsigned int first_entry, tx_packets; struct stmmac_txq_stats *txq_stats; + netdev_tx_t ret = NETDEV_TX_OK; struct stmmac_tx_queue *tx_q; - bool set_ic, is_last_segment; struct netdev_queue *nq; u32 pay_len, mss, queue; int i, first_tx, nfrags; @@ -4477,14 +4478,9 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev) /* Desc availability based on threshold should be enough safe */ if (unlikely(stmmac_tx_avail(priv, queue) < (((skb->len - proto_hdr_len) / TSO_MAX_BUFF_SIZE + 1)))) { - if (!netif_tx_queue_stopped(nq)) { - netif_tx_stop_queue(nq); - /* This is a hard error, log it. */ - netdev_err(priv->dev, - "%s: Tx Ring full when queue awake\n", - __func__); - } - return NETDEV_TX_BUSY; + netif_tx_stop_queue(nq); + ret = NETDEV_TX_BUSY; + goto flush_ring; } pay_len = skb_headlen(skb) - proto_hdr_len; /* no frags */ @@ -4524,8 +4520,11 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev) /* first descriptor: fill Headers on Buf1 */ des = dma_map_single(priv->device, skb->data, skb_headlen(skb), DMA_TO_DEVICE); - if (dma_mapping_error(priv->device, des)) - goto dma_map_err; + if (dma_mapping_error(priv->device, des)) { + netdev_err(priv->dev, "First Tx DMA-desc map failed\n"); + drop = true; + goto flush_ring; + } stmmac_set_desc_addr(priv, first, des); stmmac_tso_allocator(priv, des + proto_hdr_len, pay_len, @@ -4555,8 +4554,11 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev) des = skb_frag_dma_map(priv->device, frag, 0, skb_frag_size(frag), DMA_TO_DEVICE); - if (dma_mapping_error(priv->device, des)) - goto dma_map_err; + if (dma_mapping_error(priv->device, des)) { + netdev_err(priv->dev, "Frag Tx DMA-desc map failed\n"); + drop = true; + goto flush_ring; + } stmmac_tso_allocator(priv, des, skb_frag_size(frag), (i == nfrags - 1), queue); @@ -4660,19 +4662,20 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev) print_pkt(skb->data, skb_headlen(skb)); } - netdev_tx_sent_queue(nq, skb->len); skb_tx_timestamp(skb); - stmmac_flush_tx_descriptors(priv, queue); - stmmac_tx_timer_arm(priv, queue); - - return NETDEV_TX_OK; + /* Push out the packets after the particular amount of SKBs handled */ + if (__netdev_tx_sent_queue(nq, skb->len, netdev_xmit_more())) { +flush_ring: + if (drop) { + dev_kfree_skb(skb); + priv->xstats.tx_dropped++; + } + stmmac_flush_tx_descriptors(priv, queue); + stmmac_tx_timer_arm(priv, queue); + } -dma_map_err: - dev_err(priv->device, "Tx dma map failed\n"); - dev_kfree_skb(skb); - priv->xstats.tx_dropped++; - return NETDEV_TX_OK; + return ret; } /** @@ -4707,7 +4710,7 @@ static bool stmmac_has_ip_ethertype(struct sk_buff *skb) */ static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev) { - bool enh_desc, has_vlan, set_ic, is_jumbo = false; + bool enh_desc, has_vlan, set_ic, drop = false, is_jumbo = false; struct stmmac_priv *priv = netdev_priv(dev); unsigned int nopaged_len = skb_headlen(skb); u32 queue = skb_get_queue_mapping(skb); @@ -4716,6 +4719,7 @@ static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev) int gso = skb_shinfo(skb)->gso_type; struct stmmac_txq_stats *txq_stats; struct dma_edesc *tbs_desc = NULL; + netdev_tx_t ret = NETDEV_TX_OK; struct dma_desc *desc, *first; struct stmmac_tx_queue *tx_q; int i, csum_insertion = 0; @@ -4749,19 +4753,15 @@ static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev) sdu_len += VLAN_HLEN; if (sdu_len > priv->est->max_sdu[queue]) { priv->xstats.max_sdu_txq_drop[queue]++; - goto max_sdu_err; + drop = true; + goto flush_ring; } } if (unlikely(stmmac_tx_avail(priv, queue) < nfrags + 1)) { - if (!netif_tx_queue_stopped(nq)) { - netif_tx_stop_queue(nq); - /* This is a hard error, log it. */ - netdev_err(priv->dev, - "%s: Tx Ring full when queue awake\n", - __func__); - } - return NETDEV_TX_BUSY; + netif_tx_stop_queue(nq); + ret = NETDEV_TX_BUSY; + goto flush_ring; } /* Check if VLAN can be inserted by HW */ @@ -4782,8 +4782,10 @@ static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev) if (csum_insertion && (priv->plat->tx_queues_cfg[queue].coe_unsupported || !stmmac_has_ip_ethertype(skb))) { - if (unlikely(skb_checksum_help(skb))) - goto dma_map_err; + if (unlikely(skb_checksum_help(skb))) { + drop = true; + goto flush_ring; + } csum_insertion = !csum_insertion; } @@ -4806,8 +4808,10 @@ static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev) if (unlikely(is_jumbo)) { entry = stmmac_jumbo_frm(priv, tx_q, skb, csum_insertion); - if (unlikely(entry < 0) && (entry != -EINVAL)) - goto dma_map_err; + if (unlikely(entry < 0) && (entry != -EINVAL)) { + drop = true; + goto flush_ring; + } } for (i = 0; i < nfrags; i++) { @@ -4827,8 +4831,11 @@ static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev) des = skb_frag_dma_map(priv->device, frag, 0, len, DMA_TO_DEVICE); - if (dma_mapping_error(priv->device, des)) - goto dma_map_err; /* should reuse desc w/o issues */ + if (dma_mapping_error(priv->device, des)) { + netdev_err(priv->dev, "Frag Tx DMA map failed\n"); + drop = true; + goto flush_ring; /* should reuse desc w/o issues */ + } tx_q->tx_skbuff_dma[entry].buf = des; @@ -4922,8 +4929,11 @@ static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev) des = dma_map_single(priv->device, skb->data, nopaged_len, DMA_TO_DEVICE); - if (dma_mapping_error(priv->device, des)) - goto dma_map_err; + if (dma_mapping_error(priv->device, des)) { + netdev_err(priv->dev, "First Tx DMA map failed\n"); + drop = true; + goto flush_ring; + } tx_q->tx_skbuff_dma[first_entry].buf = des; tx_q->tx_skbuff_dma[first_entry].buf_type = STMMAC_TXBUF_T_SKB; @@ -4956,21 +4966,21 @@ static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev) stmmac_set_tx_owner(priv, first); - netdev_tx_sent_queue(nq, skb->len); - - stmmac_enable_dma_transmission(priv, priv->ioaddr, queue); skb_tx_timestamp(skb); - stmmac_flush_tx_descriptors(priv, queue); - stmmac_tx_timer_arm(priv, queue); - return NETDEV_TX_OK; + /* Push out the packets after the particular amount of SKBs handled */ + if (__netdev_tx_sent_queue(nq, skb->len, netdev_xmit_more())) { +flush_ring: + if (drop) { + dev_kfree_skb(skb); + priv->xstats.tx_dropped++; + } + stmmac_enable_dma_transmission(priv, priv->ioaddr, queue); + stmmac_flush_tx_descriptors(priv, queue); + stmmac_tx_timer_arm(priv, queue); + } -dma_map_err: - netdev_err(priv->dev, "Tx DMA map failed\n"); -max_sdu_err: - dev_kfree_skb(skb); - priv->xstats.tx_dropped++; - return NETDEV_TX_OK; + return ret; } static void stmmac_rx_vlan(struct net_device *dev, struct sk_buff *skb) -- cgit v1.2.3 From 329c0f5a3a19daf394a54b60cd1c69559c1a6b64 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Tue, 22 Apr 2025 22:42:57 +0300 Subject: net: stmmac: Set TSO frames max size The maximum size of the TSO-frames is determined by the respective DMA-descriptor field width. It's of 18 bits on both TSO-capable devices - DW QoS Ether and DW XGMAC. But the networking core currently passes GSO-frames with up to 64K size, which isn't optimal for the DW MAC devices. Let's setup the network device to extend the default 64K max TSO frames size to 256K then. This shall increase the interface performance if the gso_max_size is respectively tuned up. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index b29aab347d9d8..5b3fe4b7ccfeb 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -62,6 +62,7 @@ #define STMMAC_HWTS_ACTIVE (PTP_TCR_TSENA | PTP_TCR_TSCTRLSSR) #define STMMAC_ALIGN(x) ALIGN(ALIGN(x, SMP_CACHE_BYTES), 16) +#define TSO_MAX_LOAD_SIZE SZ_256K #define TSO_MAX_BUFF_SIZE (SZ_16K - 1) /* Module parameters */ @@ -7969,6 +7970,7 @@ static int __stmmac_dvr_probe(struct device *device, if (priv->plat->core_type == DWMAC_CORE_GMAC4) ndev->hw_features |= NETIF_F_GSO_UDP_L4; priv->tso = true; + netif_set_tso_max_size(ndev, TSO_MAX_LOAD_SIZE); dev_info(priv->device, "TSO feature enabled\n"); } -- cgit v1.2.3 From b9530a099129115aeef6e36c7936d3fddfa7a844 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 22 Aug 2025 17:26:27 +0300 Subject: net: stmmac: Link NAPI instances to queues and IRQs Make STMMAC driver compatible with the recently added netlink queue GET APIs. For now it doesn't provide much benefits, but hopefully at some point it will give an auto RPS CPU affiliation... Link: https://lore.kernel.org/r/170147336340.5260.6773000274196548907.stgit@anambiarhost.jf.intel.com Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 28 +++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 5b3fe4b7ccfeb..92fe1f39eac8f 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -7596,8 +7596,10 @@ static void stmmac_napi_add(struct net_device *dev) { struct stmmac_priv *priv = netdev_priv(dev); u32 queue, maxq; + bool perch; maxq = max(priv->plat->rx_queues_to_use, priv->plat->tx_queues_to_use); + perch = priv->plat->dma_cfg->multi_msi_en; for (queue = 0; queue < maxq; queue++) { struct stmmac_channel *ch = &priv->channel[queue]; @@ -7606,17 +7608,29 @@ static void stmmac_napi_add(struct net_device *dev) ch->index = queue; spin_lock_init(&ch->lock); + /* Non-XDP multi-queue setup for common and per-channel IRQs */ if (queue < priv->plat->rx_queues_to_use) { netif_napi_add(dev, &ch->rx_napi, stmmac_napi_poll_rx); + netif_napi_set_irq(&ch->rx_napi, + perch ? priv->rx_irq[queue] : dev->irq); + netif_queue_set_napi(dev, queue, NETDEV_QUEUE_TYPE_RX, + &ch->rx_napi); } if (queue < priv->plat->tx_queues_to_use) { - netif_napi_add_tx(dev, &ch->tx_napi, - stmmac_napi_poll_tx); + netif_napi_add_tx(dev, &ch->tx_napi, stmmac_napi_poll_tx); + netif_napi_set_irq(&ch->tx_napi, + perch ? priv->tx_irq[queue] : dev->irq); + netif_queue_set_napi(dev, queue, NETDEV_QUEUE_TYPE_TX, + &ch->tx_napi); } + + /* XDP multi-queue setup. Set Rx-IRQ as most probable NAPI starter */ if (queue < priv->plat->rx_queues_to_use && queue < priv->plat->tx_queues_to_use) { netif_napi_add(dev, &ch->rxtx_napi, stmmac_napi_poll_rxtx); + netif_napi_set_irq(&ch->rxtx_napi, + perch ? priv->rx_irq[queue] : dev->irq); } } } @@ -7631,10 +7645,16 @@ static void stmmac_napi_del(struct net_device *dev) for (queue = 0; queue < maxq; queue++) { struct stmmac_channel *ch = &priv->channel[queue]; - if (queue < priv->plat->rx_queues_to_use) + if (queue < priv->plat->rx_queues_to_use) { + netif_queue_set_napi(dev, queue, NETDEV_QUEUE_TYPE_RX, + NULL); netif_napi_del(&ch->rx_napi); - if (queue < priv->plat->tx_queues_to_use) + } + if (queue < priv->plat->tx_queues_to_use) { + netif_queue_set_napi(dev, queue, NETDEV_QUEUE_TYPE_TX, + NULL); netif_napi_del(&ch->tx_napi); + } if (queue < priv->plat->rx_queues_to_use && queue < priv->plat->tx_queues_to_use) { netif_napi_del(&ch->rxtx_napi); -- cgit v1.2.3 From 2c4959472a54df70539806e0c92e99ad036f132b Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Tue, 2 Sep 2025 19:47:04 +0300 Subject: net: stmmac: Convert SPH length calc to being optional Currently the Split Packet Header length calculation code is always called in the Buffer1 length getter. First of all it's pointless for the controllers not supporting SPH feature. Moreover since the code is executed in the Rx fast path it might slow down the Rx procedure a bit. Secondly even if the controller supports SPH feature it might be disable. Thus getting the Rx header length will worsen the execution performance even more since each stmmac_get_rx_header_len() ends up touching the Rx DMA-descriptor fields. The descriptors reside in the DMA-coherent memory which on some platforms is uncacheable thus very slow. Let's fix that by convert the SPH header length calculation procedure to being executed only if the SPH-feature actually enabled. Thus the Rx path will speed up +150-200Mbps on DMA-noncoherent platforms. Fixes: 67afd6d1cfdf ("net: stmmac: Add Split Header support and enable it in XGMAC cores") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 92fe1f39eac8f..9b92924732277 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -5087,18 +5087,23 @@ static unsigned int stmmac_rx_buf1_len(struct stmmac_priv *priv, struct dma_desc *p, int status, unsigned int len) { - unsigned int plen = 0, hlen = 0; int coe = priv->hw->rx_csum; + unsigned int plen = 0; - /* Not first descriptor, buffer is always zero */ - if (priv->sph_active && len) - return 0; + /* Split header enabled */ + if (priv->sph_active) { + unsigned int hlen = 0; - /* First descriptor, get split header length */ - stmmac_get_rx_header_len(priv, p, &hlen); - if (priv->sph_active && hlen) { - priv->xstats.rx_split_hdr_pkt_n++; - return hlen; + /* Not first descriptor, primary buffer unused in any case */ + if (len) + return 0; + + /* First descriptor, get split header length */ + stmmac_get_rx_header_len(priv, p, &hlen); + if (hlen) { + priv->xstats.rx_split_hdr_pkt_n++; + return hlen; + } } /* Not last descriptor and not split header */ -- cgit v1.2.3 From 9df1a79a4974eee302ba14261b3a1ae4619a0878 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Tue, 12 Aug 2025 02:44:32 +0300 Subject: net: stmmac: Add AXI ACE attributes setup If the DW QoS Ether/XGMAC/XLGMAC/etc are synthesized into the system with the AXI4 master bus it's possible to specify the cacheability attributes of the memory accesses. In the meantime if the controller is defined in the system as DMA-cache-coherent then any cacheable attributes can be utilized. This shall significantly speed up the controller IO performance. In our case the iperf3 got x4 faster on a single thread test reaching the practical maximum of the DW XGMAC controller! Let's provide a new fine-tuning platform AXI cache coherency parameter then. So if it's initialized with true then the driver considers that the device is DMA-coherent and enables the "Write-back No-allocate" memory access proto with Outer Shareable domain. Thus an Rx-es will be written to the cache and Tx-es will be read from the cache if there are respective entries are. Otherwise the data will be read directly from the memory. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/common.h | 10 ++++++ drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c | 24 ++++++++++++++ drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.h | 38 ++++++++++++++++++---- drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h | 23 +++++++++++++ drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c | 25 ++++++++++++++ .../net/ethernet/stmicro/stmmac/stmmac_platform.c | 7 ++++ include/linux/stmmac.h | 1 + 7 files changed, 121 insertions(+), 7 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/common.h b/drivers/net/ethernet/stmicro/stmmac/common.h index d26e8a0630224..9277141990ec2 100644 --- a/drivers/net/ethernet/stmicro/stmmac/common.h +++ b/drivers/net/ethernet/stmicro/stmmac/common.h @@ -581,6 +581,16 @@ void stmmac_axi_blen_to_mask(u32 *regval, const u32 *blen, size_t len); #define STMMAC_VLAN_INSERT 0x2 #define STMMAC_VLAN_REPLACE 0x3 +/* AXI ACE */ +#define STMMAC_AXI_ACE_AR_WbNa 0xb +#define STMMAC_AXI_ACE_AW_WbNa 0x7 +#define STMMAC_AXI_ACE_D_NONS 0x0 +#define STMMAC_AXI_ACE_D_INNS 0x1 +#define STMMAC_AXI_ACE_D_OUTS 0x2 +#define STMMAC_AXI_ACE_D_SYS 0x3 +#define STMMAC_AXI_ACE(_dev, _reg, _val) \ + (FIELD_PREP(_dev ## _AXI_ ## _reg, STMMAC_AXI_ACE_ ## _val)) + struct mac_device_info; struct mac_link { diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c index 3b78febab07cb..1868683f0f02b 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c @@ -40,6 +40,30 @@ static void dwmac4_dma_axi(void __iomem *ioaddr, struct stmmac_axi *axi) value = (value & ~DMA_AXI_BLEN_MASK) | axi->axi_blen_regval; writel(value, ioaddr + DMA_SYS_BUS_MODE); + + if (axi->axi_cc) { + writel(STMMAC_AXI_ACE(DMA, TX_AR_THD, D_OUTS) | + STMMAC_AXI_ACE(DMA, TX_AR_THC, AR_WbNa) | + STMMAC_AXI_ACE(DMA, TX_AR_TED, D_OUTS) | + STMMAC_AXI_ACE(DMA, TX_AR_TEC, AR_WbNa) | + STMMAC_AXI_ACE(DMA, TX_AR_TDRD, D_OUTS) | + STMMAC_AXI_ACE(DMA, TX_AR_TDRC, AR_WbNa), + ioaddr + DMA_AXI_TX_AR_ACE_CTRL); + writel(STMMAC_AXI_ACE(DMA, RX_AW_RDD, D_OUTS) | + STMMAC_AXI_ACE(DMA, RX_AW_RDC, AW_WbNa) | + STMMAC_AXI_ACE(DMA, RX_AW_RHD, D_OUTS) | + STMMAC_AXI_ACE(DMA, RX_AW_RHC, AW_WbNa) | + STMMAC_AXI_ACE(DMA, RX_AW_RPD, D_OUTS) | + STMMAC_AXI_ACE(DMA, RX_AW_RPC, AW_WbNa) | + STMMAC_AXI_ACE(DMA, RX_AW_RDWD, D_OUTS) | + STMMAC_AXI_ACE(DMA, RX_AW_RDWC, AW_WbNa), + ioaddr + DMA_AXI_RX_AW_ACE_CTRL); + writel(STMMAC_AXI_ACE(DMA, TXRX_AWAR_RDRD, D_OUTS) | + STMMAC_AXI_ACE(DMA, TXRX_AWAR_RDRC, AR_WbNa) | + STMMAC_AXI_ACE(DMA, TXRX_AWAR_TDWD, D_OUTS) | + STMMAC_AXI_ACE(DMA, TXRX_AWAR_TDWC, AW_WbNa), + ioaddr + DMA_AXI_TXRX_AWAR_ACE_CTRL); + } } static void dwmac4_dma_init_rx_chan(struct stmmac_priv *priv, diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.h b/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.h index 9d9077a4ac9f1..f7105a2b786f9 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.h @@ -24,13 +24,6 @@ #define DMA_SYS_BUS_MODE 0x00001004 -#define DMA_BUS_MODE_MB BIT(14) -#define DMA_BUS_MODE_FB BIT(0) - -#define DMA_STATUS 0x00001008 - -#define DMA_AXI_BUS_MODE 0x00001028 - #define DMA_AXI_EN_LPI BIT(31) #define DMA_AXI_LPI_XIT_FRM BIT(30) #define DMA_AXI_WR_OSR_LMT GENMASK(27, 24) @@ -41,6 +34,37 @@ #define DMA_SYS_BUS_EAME BIT(11) #define DMA_SYS_BUS_FB BIT(0) +#define DMA_STATUS 0x00001008 + +#define DMA_AXI_TX_AR_ACE_CTRL 0x00001020 + +#define DMA_AXI_TX_AR_THD GENMASK(21, 20) +#define DMA_AXI_TX_AR_THC GENMASK(19, 16) +#define DMA_AXI_TX_AR_TED GENMASK(13, 12) +#define DMA_AXI_TX_AR_TEC GENMASK(11, 8) +#define DMA_AXI_TX_AR_TDRD GENMASK(5, 4) +#define DMA_AXI_TX_AR_TDRC GENMASK(3, 0) + +#define DMA_AXI_RX_AW_ACE_CTRL 0x00001024 + +#define DMA_AXI_RX_AW_RDD GENMASK(29, 28) +#define DMA_AXI_RX_AW_RDC GENMASK(27, 24) +#define DMA_AXI_RX_AW_RHD GENMASK(21, 20) +#define DMA_AXI_RX_AW_RHC GENMASK(19, 16) +#define DMA_AXI_RX_AW_RPD GENMASK(13, 12) +#define DMA_AXI_RX_AW_RPC GENMASK(11, 8) +#define DMA_AXI_RX_AW_RDWD GENMASK(5, 4) +#define DMA_AXI_RX_AW_RDWC GENMASK(3, 0) + +#define DMA_AXI_TXRX_AWAR_ACE_CTRL 0x00001028 + +#define DMA_AXI_TXRX_AWAR_WRP GENMASK(22, 20) +#define DMA_AXI_TXRX_AWAR_RDP GENMASK(18, 16) +#define DMA_AXI_TXRX_AWAR_RDRD GENMASK(13, 12) +#define DMA_AXI_TXRX_AWAR_RDRC GENMASK(11, 8) +#define DMA_AXI_TXRX_AWAR_TDWD GENMASK(5, 4) +#define DMA_AXI_TXRX_AWAR_TDWC GENMASK(3, 0) + #define DMA_TBS_CTRL 0x00001050 #define DMA_TBS_FTOS GENMASK(31, 8) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h index 51943705a2b03..a1327b19fce5d 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h @@ -329,6 +329,29 @@ #define XGMAC_EAME BIT(11) /* XGMAC_BLEN* are now defined as DMA_AXI_BLEN* in common.h */ #define XGMAC_UNDEF BIT(0) +#define XGMAC_AXI_TX_AR_ACE_CTRL 0x00003010 +#define XGMAC_AXI_TX_AR_THD GENMASK(21, 20) +#define XGMAC_AXI_TX_AR_THC GENMASK(19, 16) +#define XGMAC_AXI_TX_AR_TED GENMASK(13, 12) +#define XGMAC_AXI_TX_AR_TEC GENMASK(11, 8) +#define XGMAC_AXI_TX_AR_TDRD GENMASK(5, 4) +#define XGMAC_AXI_TX_AR_TDRC GENMASK(3, 0) +#define XGMAC_AXI_RX_AW_ACE_CTRL 0x00003018 +#define XGMAC_AXI_RX_AW_RDD GENMASK(29, 28) +#define XGMAC_AXI_RX_AW_RDC GENMASK(27, 24) +#define XGMAC_AXI_RX_AW_RHD GENMASK(21, 20) +#define XGMAC_AXI_RX_AW_RHC GENMASK(19, 16) +#define XGMAC_AXI_RX_AW_RPD GENMASK(13, 12) +#define XGMAC_AXI_RX_AW_RPC GENMASK(11, 8) +#define XGMAC_AXI_RX_AW_RDWD GENMASK(5, 4) +#define XGMAC_AXI_RX_AW_RDWC GENMASK(3, 0) +#define XGMAC_AXI_TXRX_AWAR_ACE_CTRL 0x0000301c +#define XGMAC_AXI_TXRX_AWAR_WRP GENMASK(22, 20) +#define XGMAC_AXI_TXRX_AWAR_RDP GENMASK(18, 16) +#define XGMAC_AXI_TXRX_AWAR_RDRD GENMASK(13, 12) +#define XGMAC_AXI_TXRX_AWAR_RDRC GENMASK(11, 8) +#define XGMAC_AXI_TXRX_AWAR_TDWD GENMASK(5, 4) +#define XGMAC_AXI_TXRX_AWAR_TDWC GENMASK(3, 0) #define XGMAC_TX_EDMA_CTRL 0x00003040 #define XGMAC_TDPS GENMASK(29, 0) #define XGMAC_RX_EDMA_CTRL 0x00003044 diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c index 03437f1cf3df3..1770751917eb5 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c @@ -101,6 +101,31 @@ static void dwxgmac2_dma_axi(void __iomem *ioaddr, struct stmmac_axi *axi) value = (value & ~DMA_AXI_BLEN_MASK) | axi->axi_blen_regval; writel(value, ioaddr + XGMAC_DMA_SYSBUS_MODE); + + if (axi->axi_cc) { + writel(STMMAC_AXI_ACE(XGMAC, TX_AR_THD, D_OUTS) | + STMMAC_AXI_ACE(XGMAC, TX_AR_THC, AR_WbNa) | + STMMAC_AXI_ACE(XGMAC, TX_AR_TED, D_OUTS) | + STMMAC_AXI_ACE(XGMAC, TX_AR_TEC, AR_WbNa) | + STMMAC_AXI_ACE(XGMAC, TX_AR_TDRD, D_OUTS) | + STMMAC_AXI_ACE(XGMAC, TX_AR_TDRC, AR_WbNa), + ioaddr + XGMAC_AXI_TX_AR_ACE_CTRL); + writel(STMMAC_AXI_ACE(XGMAC, RX_AW_RDD, D_OUTS) | + STMMAC_AXI_ACE(XGMAC, RX_AW_RDC, AW_WbNa) | + STMMAC_AXI_ACE(XGMAC, RX_AW_RHD, D_OUTS) | + STMMAC_AXI_ACE(XGMAC, RX_AW_RHC, AW_WbNa) | + STMMAC_AXI_ACE(XGMAC, RX_AW_RPD, D_OUTS) | + STMMAC_AXI_ACE(XGMAC, RX_AW_RPC, AW_WbNa) | + STMMAC_AXI_ACE(XGMAC, RX_AW_RDWD, D_OUTS) | + STMMAC_AXI_ACE(XGMAC, RX_AW_RDWC, AW_WbNa), + ioaddr + XGMAC_AXI_RX_AW_ACE_CTRL); + writel(STMMAC_AXI_ACE(XGMAC, TXRX_AWAR_RDRD, D_OUTS) | + STMMAC_AXI_ACE(XGMAC, TXRX_AWAR_RDRC, AR_WbNa) | + STMMAC_AXI_ACE(XGMAC, TXRX_AWAR_TDWD, D_OUTS) | + STMMAC_AXI_ACE(XGMAC, TXRX_AWAR_TDWC, AW_WbNa), + ioaddr + XGMAC_AXI_TXRX_AWAR_ACE_CTRL); + } + writel(XGMAC_TDPS, ioaddr + XGMAC_TX_EDMA_CTRL); writel(XGMAC_RDPS, ioaddr + XGMAC_RX_EDMA_CTRL); } diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c index 7d77a79788f8f..617fedff7ef9c 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c @@ -100,6 +100,7 @@ static int stmmac_axi_setup(struct platform_device *pdev, struct device_node *np; struct stmmac_axi *axi; u32 axi_blen[AXI_BLEN]; + enum dev_dma_attr attr; np = of_parse_phandle(pdev->dev.of_node, "snps,axi-config", 0); if (!np) @@ -126,6 +127,12 @@ static int stmmac_axi_setup(struct platform_device *pdev, stmmac_axi_blen_to_mask(&axi->axi_blen_regval, axi_blen, AXI_BLEN); of_node_put(np); + attr = device_get_dma_attr(&pdev->dev); + if (attr == DEV_DMA_NOT_SUPPORTED) + return -ENODEV; + + axi->axi_cc = (attr == DEV_DMA_COHERENT); + plat->axi = axi; return 0; diff --git a/include/linux/stmmac.h b/include/linux/stmmac.h index 32352a216567e..b96bcb3905a7e 100644 --- a/include/linux/stmmac.h +++ b/include/linux/stmmac.h @@ -117,6 +117,7 @@ struct stmmac_axi { bool axi_fb; bool axi_mb; bool axi_rb; + bool axi_cc; }; struct stmmac_rxq_cfg { -- cgit v1.2.3 From c62abedbeb5c41f0ddb3f03ebb7dcf022da25b8c Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Tue, 5 Jan 2021 11:58:54 +0300 Subject: net: stmmac: Discard STMMAC_RESETING flag That flag is totally useless. It's set inside a non-reentrant mutex-protected section. For the same reason the test-and-set loop is also pointless. The flag is also unused anywhere else in the driver. So just drop it. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac.h | 1 - drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 3 --- 2 files changed, 4 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h index 70ef54d6c2be0..c01e00a59a222 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h @@ -383,7 +383,6 @@ struct stmmac_priv { enum stmmac_state { STMMAC_DOWN, STMMAC_RESET_REQUESTED, - STMMAC_RESETING, STMMAC_SERVICE_SCHED, }; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 9b92924732277..7b21dbe6f7d28 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -7399,14 +7399,11 @@ static void stmmac_reset_subtask(struct stmmac_priv *priv) rtnl_lock(); netif_trans_update(priv->dev); - while (test_and_set_bit(STMMAC_RESETING, &priv->state)) - usleep_range(1000, 2000); set_bit(STMMAC_DOWN, &priv->state); dev_close(priv->dev); dev_open(priv->dev, NULL); clear_bit(STMMAC_DOWN, &priv->state); - clear_bit(STMMAC_RESETING, &priv->state); rtnl_unlock(); } -- cgit v1.2.3 From 6d6df6b2cde7e7fe810d369579c4ba73c8fccf96 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 1 Aug 2025 17:25:25 +0300 Subject: net: stmmac: Discard STMMAC_DOWN flag It's absolutely redundant. The STMMAC_DOWN flag is set only when the device is being forcibly reset. But netdev_state_t::__LINK_STATE_START is cleared in that case too right at the head of the dev_open() method. Thus the generic netif_running() will be semantically equivalent to call instead of checking the STMMAC_DOWN flag set. Besides it will be more correct to call netif_running() besides to the reasioning above since it's set/cleared on any device open/close cycles thus preventing the IRQ-handlers execution in the more appropriate cases. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac.h | 1 - drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 26 +++++++++-------------- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h index c01e00a59a222..82bb1119f34ca 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h @@ -381,7 +381,6 @@ struct stmmac_priv { }; enum stmmac_state { - STMMAC_DOWN, STMMAC_RESET_REQUESTED, STMMAC_SERVICE_SCHED, }; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 7b21dbe6f7d28..44edf523f68ea 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -6299,8 +6299,7 @@ static irqreturn_t stmmac_interrupt(int irq, void *dev_id) struct net_device *dev = (struct net_device *)dev_id; struct stmmac_priv *priv = netdev_priv(dev); - /* Check if adapter is up */ - if (test_bit(STMMAC_DOWN, &priv->state)) + if (!netif_running(dev)) return IRQ_HANDLED; /* Check ASP error if it isn't delivered via an individual IRQ */ @@ -6321,8 +6320,7 @@ static irqreturn_t stmmac_mac_interrupt(int irq, void *dev_id) struct net_device *dev = (struct net_device *)dev_id; struct stmmac_priv *priv = netdev_priv(dev); - /* Check if adapter is up */ - if (test_bit(STMMAC_DOWN, &priv->state)) + if (!netif_running(dev)) return IRQ_HANDLED; /* To handle Common interrupts */ @@ -6336,8 +6334,7 @@ static irqreturn_t stmmac_safety_interrupt(int irq, void *dev_id) struct net_device *dev = (struct net_device *)dev_id; struct stmmac_priv *priv = netdev_priv(dev); - /* Check if adapter is up */ - if (test_bit(STMMAC_DOWN, &priv->state)) + if (!netif_running(dev)) return IRQ_HANDLED; /* Check if a fatal error happened */ @@ -6357,8 +6354,7 @@ static irqreturn_t stmmac_msi_intr_tx(int irq, void *data) dma_conf = container_of(tx_q, struct stmmac_dma_conf, tx_queue[chan]); priv = container_of(dma_conf, struct stmmac_priv, dma_conf); - /* Check if adapter is up */ - if (test_bit(STMMAC_DOWN, &priv->state)) + if (!netif_running(priv->dev)) return IRQ_HANDLED; status = stmmac_napi_check(priv, chan, DMA_DIR_TX); @@ -6383,8 +6379,7 @@ static irqreturn_t stmmac_msi_intr_rx(int irq, void *data) dma_conf = container_of(rx_q, struct stmmac_dma_conf, rx_queue[chan]); priv = container_of(dma_conf, struct stmmac_priv, dma_conf); - /* Check if adapter is up */ - if (test_bit(STMMAC_DOWN, &priv->state)) + if (!netif_running(priv->dev)) return IRQ_HANDLED; stmmac_napi_check(priv, chan, DMA_DIR_RX); @@ -6987,7 +6982,7 @@ static int stmmac_xdp_xmit(struct net_device *dev, int num_frames, int i, nxmit = 0; int queue; - if (unlikely(test_bit(STMMAC_DOWN, &priv->state))) + if (!netif_running(dev)) return -ENETDOWN; if (unlikely(flags & ~XDP_XMIT_FLAGS_MASK)) @@ -7283,8 +7278,7 @@ int stmmac_xsk_wakeup(struct net_device *dev, u32 queue, u32 flags) struct stmmac_tx_queue *tx_q; struct stmmac_channel *ch; - if (test_bit(STMMAC_DOWN, &priv->state) || - !netif_carrier_ok(priv->dev)) + if (!netif_carrier_ok(dev) || !netif_running(dev)) return -ENETDOWN; if (!stmmac_xdp_is_enabled(priv)) @@ -7392,7 +7386,9 @@ static void stmmac_reset_subtask(struct stmmac_priv *priv) { if (!test_and_clear_bit(STMMAC_RESET_REQUESTED, &priv->state)) return; - if (test_bit(STMMAC_DOWN, &priv->state)) + + /* Controller is in inactive state. Reset will be done on open */ + if (!netif_running(priv->dev)) return; netdev_err(priv->dev, "Reset adapter.\n"); @@ -7400,10 +7396,8 @@ static void stmmac_reset_subtask(struct stmmac_priv *priv) rtnl_lock(); netif_trans_update(priv->dev); - set_bit(STMMAC_DOWN, &priv->state); dev_close(priv->dev); dev_open(priv->dev, NULL); - clear_bit(STMMAC_DOWN, &priv->state); rtnl_unlock(); } -- cgit v1.2.3 From a0ff0388f24cce67eed99306780e40011f918c62 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sat, 9 Jan 2021 00:25:40 +0300 Subject: net: stmmac: Discard conditional service task execution Indeed CMWQ guaranties that each particular work item is non-reenatrant, while using the atomic bitmask operation statement may cause a requested event being missed if for instance some event happens while the service task is being executed (see the STMMAC_SERVICE_SCHED flag semantic). Similarly the service task can be requested for being executed while the STMMAC core is in the down state. (Though for now there is no such sub-task defined in the driver). So to speak just drop the conditional service task execution and queue the corresponding work anytime it's requested, while the service sub-tasks shall determine whether they really need to be performed in particular situations. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac.h | 1 - drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 5 +---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h index 82bb1119f34ca..bb11a614c36c6 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h @@ -382,7 +382,6 @@ struct stmmac_priv { enum stmmac_state { STMMAC_RESET_REQUESTED, - STMMAC_SERVICE_SCHED, }; extern const struct dev_pm_ops stmmac_simple_pm_ops; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 44edf523f68ea..e70d64a0de952 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -339,9 +339,7 @@ static void stmmac_enable_all_queues(struct stmmac_priv *priv) static void stmmac_service_event_schedule(struct stmmac_priv *priv) { - if (!test_bit(STMMAC_DOWN, &priv->state) && - !test_and_set_bit(STMMAC_SERVICE_SCHED, &priv->state)) - queue_work(priv->wq, &priv->service_task); + queue_work(priv->wq, &priv->service_task); } static void stmmac_global_err(struct stmmac_priv *priv) @@ -7407,7 +7405,6 @@ static void stmmac_service_task(struct work_struct *work) service_task); stmmac_reset_subtask(priv); - clear_bit(STMMAC_SERVICE_SCHED, &priv->state); } static void stmmac_print_actphyif(struct stmmac_priv *priv) -- cgit v1.2.3 From 670b547e7769afd5e16ff0c46641c1cee2ea04df Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 27 Jan 2021 15:47:43 +0300 Subject: net: stmmac: Add 'cause' arg to the service task executioner In order to have a more descriptive and coherent service task interface let's add the cause argument to the stmmac_service_event_schedule() method. It will be used to test-and-set the corresponding flag in the private device state variable, and execute the service handler if the flag hasn't been set. By doing so we'll be able to activate the service sub-task just by calling the stmmac_service_event_schedule() method. Note currently there is only a single user of the service tasks interface. It's used to handle a case of the critical device errors to cause the interface reset. The changes provided here will also prevent the global error handler from being called twice if the service task has already being executed while reset sub-task still isn't started. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index e70d64a0de952..813cbf93e674c 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -337,16 +337,18 @@ static void stmmac_enable_all_queues(struct stmmac_priv *priv) } } -static void stmmac_service_event_schedule(struct stmmac_priv *priv) +static void stmmac_service_event_schedule(struct stmmac_priv *priv, + unsigned long cause) { - queue_work(priv->wq, &priv->service_task); + if (!test_and_set_bit(cause, &priv->state)) + queue_work(priv->wq, &priv->service_task); } static void stmmac_global_err(struct stmmac_priv *priv) { netif_carrier_off(priv->dev); - set_bit(STMMAC_RESET_REQUESTED, &priv->state); - stmmac_service_event_schedule(priv); + + stmmac_service_event_schedule(priv, STMMAC_RESET_REQUESTED); } static void print_pkt(unsigned char *buf, int len) @@ -7382,9 +7384,6 @@ static const struct net_device_ops stmmac_netdev_ops = { static void stmmac_reset_subtask(struct stmmac_priv *priv) { - if (!test_and_clear_bit(STMMAC_RESET_REQUESTED, &priv->state)) - return; - /* Controller is in inactive state. Reset will be done on open */ if (!netif_running(priv->dev)) return; @@ -7404,7 +7403,8 @@ static void stmmac_service_task(struct work_struct *work) struct stmmac_priv *priv = container_of(work, struct stmmac_priv, service_task); - stmmac_reset_subtask(priv); + if (test_and_clear_bit(STMMAC_RESET_REQUESTED, &priv->state)) + stmmac_reset_subtask(priv); } static void stmmac_print_actphyif(struct stmmac_priv *priv) -- cgit v1.2.3 From c5360f0685d37bed7cef240cc2cfb79f395a668e Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 18 Nov 2021 10:54:14 +0300 Subject: net: stmmac: Introduce centralized state change interface The STMMAC driver has already been equipped with a state-change infrastructure (added in commit 34877a15f787 ("net: stmmac: Rework and fix TX Timeout code")), but it has been unintuitive and hard-to-read directly using the atomic bit set/clear/test operations. The infrastructure been utilized for the device hard-resetting so far thus lacking less severe errors handling. As a preparation before adding a comprehensive errors handling code let's convert it to being more structured. First of all the stmmac_state structure will describe the flags bit number of the corresponding requests (currently there is only one: reset). Secondly three new accessors are introduced (just macros converted to the atomic bit operations) to get/set/clear a particular state flag of the entire device and a particular DMA channel. A fourth and fifth accessors are responsible for setting the new device state flag and running deferred task handling it. It enables the state-change request only if it hasn't been and executes a bottom half work in such case thus preventing the particular state-change handler repeated execution. Finally the state interface init and clear procedures have been moved to dedicated methods so to have less bulky probe procedures and collect the states handling methods in a single place of the driver. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac.h | 49 ++++++++++--- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 85 ++++++++++++++++------- 2 files changed, 99 insertions(+), 35 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h index bb11a614c36c6..a7187259d4602 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h @@ -11,6 +11,7 @@ #define STMMAC_RESOURCE_NAME "stmmaceth" +#include #include #include #include @@ -21,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -39,6 +41,38 @@ struct stmmac_resources { int tx_irq[MTL_MAX_TX_QUEUES]; }; +enum stmmac_state { + STMMAC_STATE_RESET, + STMMAC_STATE_COUNT +}; + +#define stmmac_set_state(_priv, _state) \ + test_and_set_bit(STMMAC_STATE_ ## _state, (_priv)->state) + +#define stmmac_get_state(_priv, _state) \ + test_bit(STMMAC_STATE_ ## _state, (_priv)->state) + +#define stmmac_clr_state(_priv, _state) \ + test_and_clear_bit(STMMAC_STATE_ ## _state, (_priv)->state) + +#define stmmac_srv_state(_priv, _state) \ + ({ \ + if (!stmmac_set_state(_priv, _state)) \ + queue_work((_priv)->state_wq, &(_priv)->state_work); \ + }) + +#define stmmac_sch_state(_priv, _ch, _state) \ + ({ \ + if (!stmmac_set_state(&(_priv)->channel[(_ch)], _state)) \ + queue_work((_priv)->state_wq, &(_priv)->state_work); \ + }) + +#define stmmac_fls_state(_priv, _state) \ + ({ \ + if (stmmac_clr_state(_priv, _state)) \ + flush_work(&(_priv)->state_work); \ + }) + enum stmmac_txbuf_type { STMMAC_TXBUF_T_SKB, STMMAC_TXBUF_T_XDP_TX, @@ -145,6 +179,7 @@ struct stmmac_rx_queue { }; struct stmmac_channel { + unsigned long state[BITS_TO_LONGS(STMMAC_STATE_COUNT)]; struct napi_struct rx_napi ____cacheline_aligned_in_smp; struct napi_struct tx_napi ____cacheline_aligned_in_smp; struct napi_struct rxtx_napi ____cacheline_aligned_in_smp; @@ -250,6 +285,12 @@ struct stmmac_est { struct stmmac_priv { /* Frequently used values are kept adjacent for cache effect */ + + /* Device state-flags and work-task handling them */ + unsigned long state[BITS_TO_LONGS(STMMAC_STATE_COUNT)]; + struct workqueue_struct *state_wq; + struct work_struct state_work; + u32 tx_coal_frames[MTL_MAX_TX_QUEUES]; u32 tx_coal_timer[MTL_MAX_TX_QUEUES]; u32 rx_coal_frames[MTL_MAX_RX_QUEUES]; @@ -349,10 +390,6 @@ struct stmmac_priv { struct dentry *dbgfs_dir; #endif - unsigned long state; - struct workqueue_struct *wq; - struct work_struct service_task; - /* Frame Preemption feature (FPE) */ struct stmmac_fpe_cfg fpe_cfg; @@ -380,10 +417,6 @@ struct stmmac_priv { struct devlink *devlink; }; -enum stmmac_state { - STMMAC_RESET_REQUESTED, -}; - extern const struct dev_pm_ops stmmac_simple_pm_ops; int stmmac_mdio_unregister(struct net_device *ndev); diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 813cbf93e674c..ae40ff8293f0f 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -337,18 +337,11 @@ static void stmmac_enable_all_queues(struct stmmac_priv *priv) } } -static void stmmac_service_event_schedule(struct stmmac_priv *priv, - unsigned long cause) -{ - if (!test_and_set_bit(cause, &priv->state)) - queue_work(priv->wq, &priv->service_task); -} - static void stmmac_global_err(struct stmmac_priv *priv) { netif_carrier_off(priv->dev); - stmmac_service_event_schedule(priv, STMMAC_RESET_REQUESTED); + stmmac_srv_state(priv, RESET); } static void print_pkt(unsigned char *buf, int len) @@ -7382,29 +7375,70 @@ static const struct net_device_ops stmmac_netdev_ops = { .ndo_hwtstamp_set = stmmac_hwtstamp_set, }; +/** + * stmmac_reset_subtask - Controller reset subtask + * @priv: driver private structure + * Description: + * Perform full controller reset due to some unrecoverable error. Normally + * it's caused by some out-side system problem: Tx timeout detected by the + * network core, fatal DMA-bus errors, etc. + */ static void stmmac_reset_subtask(struct stmmac_priv *priv) { /* Controller is in inactive state. Reset will be done on open */ if (!netif_running(priv->dev)) return; - netdev_err(priv->dev, "Reset adapter.\n"); + netdev_err(priv->dev, "Reset adapter\n"); - rtnl_lock(); netif_trans_update(priv->dev); dev_close(priv->dev); dev_open(priv->dev, NULL); - rtnl_unlock(); } -static void stmmac_service_task(struct work_struct *work) +/** + * stmmac_state_work - MAC state-change handler + * @work: worker structure + * Description: Execute the state-change tasks based on the state flags set. + */ +static void stmmac_state_work(struct work_struct *work) { struct stmmac_priv *priv = container_of(work, struct stmmac_priv, - service_task); + state_work); + + /* Synchronize with possible on-going ndos */ + rtnl_lock(); - if (test_and_clear_bit(STMMAC_RESET_REQUESTED, &priv->state)) + if (stmmac_clr_state(priv, RESET)) stmmac_reset_subtask(priv); + + rtnl_unlock(); +} + +/** + * stmmac_state_init - Init the MAC custom state interface + * @priv: driver private structure + * Description: initialize the dynamic MAC state interface. For now + * it just concerns the device-specific workqueue allocation and the states + * change handling work initialization. + */ +static int stmmac_state_init(struct stmmac_priv *priv) +{ + priv->state_wq = create_singlethread_workqueue("stmmac-wq"); + if (!priv->state_wq) { + dev_err(priv->device, "failed to create workqueue\n"); + return -ENOMEM; + } + + INIT_WORK(&priv->state_work, stmmac_state_work); + + return 0; +} + +static void stmmac_state_clear(struct stmmac_priv *priv) +{ + destroy_workqueue(priv->state_wq); } static void stmmac_print_actphyif(struct stmmac_priv *priv) @@ -7914,16 +7948,6 @@ static int __stmmac_dvr_probe(struct device *device, if (!priv->af_xdp_zc_qps) return -ENOMEM; - /* Allocate workqueue */ - priv->wq = create_singlethread_workqueue("stmmac_wq"); - if (!priv->wq) { - dev_err(priv->device, "failed to create workqueue\n"); - ret = -ENOMEM; - goto error_wq_init; - } - - INIT_WORK(&priv->service_task, stmmac_service_task); - timer_setup(&priv->eee_ctrl_timer, stmmac_eee_ctrl_timer, 0); /* Override with kernel parameters if supplied XXX CRS XXX @@ -7950,6 +7974,11 @@ static int __stmmac_dvr_probe(struct device *device, /* Wait a bit for the reset to take effect */ udelay(10); + /* Init basic device/driver features state */ + ret = stmmac_state_init(priv); + if (ret) + goto error_state_init; + /* Init MAC and get the capabilities */ ret = stmmac_hw_init(priv); if (ret) @@ -8152,8 +8181,9 @@ error_mdio_register: pm_runtime_disable(device); stmmac_napi_del(ndev); error_hw_init: - destroy_workqueue(priv->wq); -error_wq_init: + stmmac_state_clear(priv); + +error_state_init: bitmap_free(priv->af_xdp_zc_qps); return ret; @@ -8218,12 +8248,13 @@ void stmmac_dvr_remove(struct device *dev) stmmac_pcs_clean(ndev); stmmac_mdio_unregister(ndev); + stmmac_state_clear(priv); + if (priv->plat->stmmac_rst) reset_control_assert(priv->plat->stmmac_rst); reset_control_assert(priv->plat->stmmac_ahb_rst); - destroy_workqueue(priv->wq); mutex_destroy(&priv->lock); bitmap_free(priv->af_xdp_zc_qps); -- cgit v1.2.3 From 6a6993faf9e5044468e6befb5cf7dd0d044f8835 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 30 Jul 2025 15:06:36 +0300 Subject: net: stmmac: dwmac1000: Fix incorrect descs state after cleanup The stmmac_clear_tx_descriptors() method is supposed to perform the Tx DMA-descriptors initialization as if from scratch. That is it must make sure that the descriptors are in the usable from the rest of the Tx-related code state after the call. That's what happens on DW QoS Ether and DW XGMAC/XLGMAC. But in case of DW GMAC the stmmac_desc_ops::init_tx_desc() function just sets required flag as if from scratch - as if descriptors memory has just been z-allocated. But that's not true in case of a DMA-error handling performed in stmmac_tx_err() which calls the stmmac_clear_tx_descriptors() on the already used Tx DMA-descriptors. As a result the descriptors won't be properly cleaned which in its turn causes unpredictable errors on further transfers due to the garbage left in the descriptors fields. Let's fix the problem by zeroing out the DESC0 and DESC1 fields in the Tx DMA-descriptors as it's done in the stmmac_desc_ops::release_tx_desc() method. The rest of the DWORDS will be overwritten in one way or another. Note the problem has been here since the initial driver commit Fixes: 47dd7a540b8a ("net: add support for STMicroelectronics Ethernet controllers.") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/enh_desc.c | 2 +- drivers/net/ethernet/stmicro/stmmac/norm_desc.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/enh_desc.c b/drivers/net/ethernet/stmicro/stmmac/enh_desc.c index 8f6993c8bcae5..36dc1e8682cff 100644 --- a/drivers/net/ethernet/stmicro/stmmac/enh_desc.c +++ b/drivers/net/ethernet/stmicro/stmmac/enh_desc.c @@ -270,7 +270,7 @@ static void enh_desc_init_rx_desc(struct dma_desc *p, int disable_rx_ic, static void enh_desc_init_tx_desc(struct dma_desc *p, int mode, int end) { - p->des0 &= cpu_to_le32(~ETDES0_OWN); + memset(p, 0, offsetof(struct dma_desc, des2)); if (mode == STMMAC_CHAIN_MODE) enh_desc_end_tx_desc_on_chain(p); else diff --git a/drivers/net/ethernet/stmicro/stmmac/norm_desc.c b/drivers/net/ethernet/stmicro/stmmac/norm_desc.c index 859cb9242a523..4ee8bda644c0c 100644 --- a/drivers/net/ethernet/stmicro/stmmac/norm_desc.c +++ b/drivers/net/ethernet/stmicro/stmmac/norm_desc.c @@ -134,7 +134,7 @@ static void ndesc_init_rx_desc(struct dma_desc *p, int disable_rx_ic, int mode, static void ndesc_init_tx_desc(struct dma_desc *p, int mode, int end) { - p->des0 &= cpu_to_le32(~TDES0_OWN); + memset(p, 0, offsetof(struct dma_desc, des2)); if (mode == STMMAC_CHAIN_MODE) ndesc_tx_set_on_chain(p); else -- cgit v1.2.3 From ceb00465c9339ebb55476d24e3ac264cd1ccc6f1 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 1 Aug 2025 19:19:30 +0300 Subject: net: stmmac: Fix missing error status It's quite possible to have both Normal and Abnormal IRQs detected but any supported DW MAC IP-core. In that case the stmmac_dma_ops::dma_interrupt() method may return several flags set. In case if one of which is dma_irq_status::tx_hard_error it won't be perceived by the hard-IRQ handlers due to the strict equality and if-else-if pattern used in the respective conditional statement. Thus the error status will be forever missed with no error-handler executed. Let's fix that by using the bitwise AND operator checking the tx_hard_error flag state in the DMA IRQ status and detaching the checking to the separate if-statement. Fixes: 9125cdd1be11 ("stmmac: add the initial tx coalesce schema") Fixes: aec7ff278145 ("stmmac: move the dma out from the main") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index ae40ff8293f0f..2d7f2677b8082 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -3163,9 +3163,9 @@ static void stmmac_dma_interrupt(struct stmmac_priv *priv) if (unlikely(status[chan] & tx_hard_error_bump_tc)) { /* Try to bump up the dma threshold on this failure */ stmmac_bump_dma_threshold(priv, chan); - } else if (unlikely(status[chan] == tx_hard_error)) { - stmmac_tx_err(priv, chan); } + if (unlikely(status[chan] & tx_hard_error)) + stmmac_tx_err(priv, chan); } } @@ -6355,9 +6355,9 @@ static irqreturn_t stmmac_msi_intr_tx(int irq, void *data) if (unlikely(status & tx_hard_error_bump_tc)) { /* Try to bump up the dma threshold on this failure */ stmmac_bump_dma_threshold(priv, chan); - } else if (unlikely(status == tx_hard_error)) { - stmmac_tx_err(priv, chan); } + if (unlikely(status & tx_hard_error)) + stmmac_tx_err(priv, chan); return IRQ_HANDLED; } -- cgit v1.2.3 From a17db5c817fbd62f065a23652d134a6fd2427520 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 1 Aug 2025 22:24:51 +0300 Subject: net: stmmac: Fix racy Tx queue error handler The Tx DMA fatal errors are currently handled by the stmmac_tx_err() method. Its main goal is to re-initialize the Tx DMA-channel so one could be re-started from scratch. On a way doing so it stops the Tx netif queue by calling netif_tx_stop_queue(), makes sure DMA-engine is stopped, frees SKBs and unmaps buffers, cleanup all the descriptors, re-initializes Tx DMA-descriptors and then kicks up DMA-engine to run again. Seeing no locking is done to guard the described actions, all of these steps get to be extremely racy to what is done in the SKBs xmit procedures and the ndos implying the Tx queue various setups. First of all the Tx netif queue can be as easily re-enabled in the stmmac_xmit*() methods thus permitting new frames to be submitted for further xfer. Secondly the xmit procedure implies various DMA-descriptors ring operations which won't be correctly performed shall the respective data is changed behind the xmit-method back. Not to say that stmmac_disable_all_queues() and stmmac_enable_all_queues() are called with the IRQ enabled which potentially may cause the used-after-free bugs or even random crashes. Let's fix all the problems above by deferring the Tx queue error handle to the specific workqueue task in the same way as it's done in case of the fatal error handling. This is the only applicable option seeing the xmit-locking is supposed to be done no higher than in the bottom-half context. So since the sub-task execution is protected by the RTNL-lock it shall be safe to call concurrently with the device open/close procedure and the rest of the related ndos. Besides the __netif_tx_lock()/__netif_tx_unlock() utilization shall prevent the race condition around the Tx descriptors concurrent usage. Fixes: 47dd7a540b8a ("net: add support for STMicroelectronics Ethernet controllers.") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac.h | 1 + drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 102 +++++++++++++++------- 2 files changed, 73 insertions(+), 30 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h index a7187259d4602..9216cb7c1b689 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h @@ -43,6 +43,7 @@ struct stmmac_resources { enum stmmac_state { STMMAC_STATE_RESET, + STMMAC_STATE_RESET_TX, STMMAC_STATE_COUNT }; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 2d7f2677b8082..94318c7fd1824 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -344,6 +344,19 @@ static void stmmac_global_err(struct stmmac_priv *priv) stmmac_srv_state(priv, RESET); } +/** + * stmmac_handle_tx_err - Handle Tx queue error + * @priv: driver private structure + * @queue: TX queue index + * Description: + * Handle recoverable Tx queue error by updating the channel state and + * invoking the service task. + */ +static void stmmac_handle_tx_err(struct stmmac_priv *priv, u32 queue) +{ + stmmac_sch_state(priv, queue, RESET_TX); +} + static void print_pkt(unsigned char *buf, int len) { pr_debug("len = %d byte, buf addr: 0x%p\n", len, buf); @@ -3025,34 +3038,6 @@ static int stmmac_tx_clean(struct stmmac_priv *priv, int budget, u32 queue, return max(count, xmits); } -/** - * stmmac_tx_err - to manage the tx error - * @priv: driver private structure - * @chan: channel index - * Description: it cleans the descriptors and restarts the transmission - * in case of transmission errors. - */ -static void stmmac_tx_err(struct stmmac_priv *priv, u32 chan) -{ - struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[chan]; - struct netdev_queue *nq; - - nq = netdev_get_tx_queue(priv->dev, chan); - - netif_tx_stop_queue(nq); - - stmmac_stop_tx_dma(priv, chan); - dma_free_tx_skbufs(priv, &priv->dma_conf, chan); - stmmac_clear_tx_descriptors(priv, &priv->dma_conf, chan); - stmmac_reset_tx_queue(priv, chan); - stmmac_init_tx_chan(priv, priv->ioaddr, priv->plat->dma_cfg, - tx_q->dma_tx_phy, chan); - stmmac_start_tx_dma(priv, chan); - - priv->xstats.tx_errors++; - netif_tx_wake_queue(nq); -} - /** * stmmac_set_dma_operation_mode - Set DMA operation mode by channel * @priv: driver private structure @@ -3165,7 +3150,7 @@ static void stmmac_dma_interrupt(struct stmmac_priv *priv) stmmac_bump_dma_threshold(priv, chan); } if (unlikely(status[chan] & tx_hard_error)) - stmmac_tx_err(priv, chan); + stmmac_handle_tx_err(priv, chan); } } @@ -6357,7 +6342,7 @@ static irqreturn_t stmmac_msi_intr_tx(int irq, void *data) stmmac_bump_dma_threshold(priv, chan); } if (unlikely(status & tx_hard_error)) - stmmac_tx_err(priv, chan); + stmmac_handle_tx_err(priv, chan); return IRQ_HANDLED; } @@ -7397,6 +7382,54 @@ static void stmmac_reset_subtask(struct stmmac_priv *priv) dev_open(priv->dev, NULL); } +/** + * stmmac_reset_tx_subtask - Tx DMA-engine reset subtask + * @priv: driver private structure + * @queue: TX queue index + * Description: + * Perform Tx-queue reset procedure. Normally it's caused by an upstream + * DMA-bus error response. In anyway the DMA is implied to be stopped + * due to that and the affected DMA-descriptor is closed with an error status. + */ +static void stmmac_reset_tx_subtask(struct stmmac_priv *priv, u32 queue) +{ + struct stmmac_tx_queue *tx_q; + struct netdev_queue *nq; + + /* Skip if controller is inactive (queue will be reset on open anyway) */ + if (!netif_running(priv->dev)) + return; + + netdev_err(priv->dev, "Reset Tx queue %u\n", queue); + + tx_q = &priv->dma_conf.tx_queue[queue]; + + /* Make sure no transfer along the way */ + nq = netdev_get_tx_queue(priv->dev, queue); + __netif_tx_lock_bh(nq); + + /* Tx DMA must be stopped for sure to proceed */ + stmmac_stop_tx_dma(priv, queue); + + /* Free pending Tx SKBs and mapped buffers */ + dma_free_tx_skbufs(priv, &priv->dma_conf, queue); + + /* Clean up all the Tx descriptor for further transfers */ + stmmac_clear_tx_descriptors(priv, &priv->dma_conf, queue); + + /* Reset the desc ring head and tail pointers */ + stmmac_reset_tx_queue(priv, queue); + stmmac_init_tx_chan(priv, priv->ioaddr, priv->plat->dma_cfg, + tx_q->dma_tx_phy, queue); + + /* Start the Tx DMA back */ + stmmac_start_tx_dma(priv, queue); + + priv->xstats.tx_errors++; + + __netif_tx_unlock_bh(nq); +} + /** * stmmac_state_work - MAC state-change handler * @work: worker structure @@ -7406,10 +7439,19 @@ static void stmmac_state_work(struct work_struct *work) { struct stmmac_priv *priv = container_of(work, struct stmmac_priv, state_work); + u32 maxq, queue; /* Synchronize with possible on-going ndos */ rtnl_lock(); + maxq = max(priv->plat->rx_queues_to_use, priv->plat->tx_queues_to_use); + for (queue = 0; queue < maxq; queue++) { + struct stmmac_channel *ch = &priv->channel[queue]; + + if (stmmac_clr_state(ch, RESET_TX)) + stmmac_reset_tx_subtask(priv, queue); + } + if (stmmac_clr_state(priv, RESET)) stmmac_reset_subtask(priv); -- cgit v1.2.3 From 6e83abed42b91d13ab82daf9b25df210e1041361 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 4 Aug 2025 17:02:54 +0300 Subject: net: stmmac: dwmac1000: Fix fatal bus error handling Currently the Fatal Bus Error (FBE DMA status bit) is handled almost completely incorrect. First of all DW GMAC databooks starting at least from v3.20a define it as fatal non-recoverable error [1]: "For any data transfer initiated by a DMA channel, if the slave replies with an error response, that DMA stops all operations and updates the error bits and the Fatal Bus Error bit in the Status register (DMA Register5). That DMA controller can resume operation only after soft resetting or hard resetting the core and re-initializing the DMA." But instead of soft-resetting the controller as the databooks says the handler just re-initializes the Tx DMA-channel. In practice if the fatal error happened on the Tx DMA interface the implemented procedure works sometimes, but in some cases (at least what we found out) the controller just refuses to reset the current DMA-descriptor pointer so only the soft-reset gets to do that. Secondly the Fatal Bus Error may happen on any Tx and _Rx_ DMA interfaces. But currently it's handled by the common IRQ and individual Tx IRQ handlers. Thus it will be missing for the cases when the Rx DMA IRQ is delivered via a separate signal (like on Loongson GMACs). Let's fix all the problems above by assigning a new flag (io_fatal_error) to the Fatal Bus Errors detected on the DW GMACs (and its derivatives). If such status is returned from the IRQ handler then the full controller reset will be performed by means of the stmmac_handle_fatal_error() method (former stmmac_global_err()). While at it change the DMA IRQ status definitions enumerating the error statuses post the normal IRQ statuses since the later one will likely persistent on any DW MAC IP-core meanwhile the error statuses may be added besides to what is currently supported by the driver. Note this problem doesn't concern the most modern IP-cores like DW QoS Ether and DW XGMAC/XLGMAC. Their databooks suggest to solve the fatal bus errors by indeed re-initializing the respective DMA-interface. [1] DesignWare Cores Ethernet MAC Universal Databook, Revision 3.20a, August 2006, p. 53. Fixes: 47dd7a540b8a ("net: add support for STMicroelectronics Ethernet controllers.") Fixes: 803fc61df261 ("net: stmmac: dwmac-loongson: Add Loongson Multi-channels GMAC support") Fixes: 8532f613bc78 ("net: stmmac: introduce MSI Interrupt routines for mac, safety, RX & TX") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/common.h | 9 +++--- .../net/ethernet/stmicro/stmmac/dwmac-loongson.c | 2 +- drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c | 2 +- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 32 ++++++++++++++++------ 4 files changed, 30 insertions(+), 15 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/common.h b/drivers/net/ethernet/stmicro/stmmac/common.h index 9277141990ec2..16c311d4c5879 100644 --- a/drivers/net/ethernet/stmicro/stmmac/common.h +++ b/drivers/net/ethernet/stmicro/stmmac/common.h @@ -375,10 +375,11 @@ enum tx_frame_status { }; enum dma_irq_status { - tx_hard_error = 0x1, - tx_hard_error_bump_tc = 0x2, - handle_rx = 0x4, - handle_tx = 0x8, + handle_rx = 0x1, + handle_tx = 0x2, + io_fatal_error = 0x4, + tx_hard_error = 0x8, + tx_hard_error_bump_tc = 0x10, }; enum dma_irq_dir { diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-loongson.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-loongson.c index 27ab6efd86ed6..8dd45b1134141 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-loongson.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-loongson.c @@ -267,7 +267,7 @@ static int loongson_dwmac_dma_interrupt(struct stmmac_priv *priv, } if (unlikely(fb_intr_status)) { x->fatal_bus_error_irq++; - ret = tx_hard_error; + ret = io_fatal_error; } } /* TX/RX NORMAL interrupts */ diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c b/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c index a0383f9486c2d..4153546bf9fd8 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c @@ -204,7 +204,7 @@ int dwmac_dma_interrupt(struct stmmac_priv *priv, void __iomem *ioaddr, } if (unlikely(intr_status & DMA_STATUS_FBI)) { x->fatal_bus_error_irq++; - ret = tx_hard_error; + ret = io_fatal_error; } } /* TX/RX NORMAL interrupts */ diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 94318c7fd1824..f710dae188cca 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -337,7 +337,15 @@ static void stmmac_enable_all_queues(struct stmmac_priv *priv) } } -static void stmmac_global_err(struct stmmac_priv *priv) +/** + * stmmac_handle_fatal_err - Handle controller fatal error + * @priv: driver private structure + * @queue: TX queue index + * Description: + * Handle unrecoverable error by submitting the controller reset and + * invoking the service task. + */ +static void stmmac_handle_fatal_err(struct stmmac_priv *priv) { netif_carrier_off(priv->dev); @@ -3078,7 +3086,7 @@ static bool stmmac_safety_feat_interrupt(struct stmmac_priv *priv) ret = stmmac_safety_feat_irq_status(priv, priv->dev, priv->ioaddr, priv->dma_cap.asp, &priv->sstats); if (ret && (ret != -EINVAL)) { - stmmac_global_err(priv); + stmmac_handle_fatal_err(priv); return true; } @@ -3131,12 +3139,12 @@ static void stmmac_dma_interrupt(struct stmmac_priv *priv) { u32 tx_channel_count = priv->plat->tx_queues_to_use; u32 rx_channel_count = priv->plat->rx_queues_to_use; - u32 channels_to_check = tx_channel_count > rx_channel_count ? - tx_channel_count : rx_channel_count; - u32 chan; int status[MAX_T(u32, MTL_MAX_TX_QUEUES, MTL_MAX_RX_QUEUES)]; + u32 channels_to_check; + u32 chan; /* Make sure we never check beyond our status buffer. */ + channels_to_check = max(tx_channel_count, rx_channel_count); if (WARN_ON_ONCE(channels_to_check > ARRAY_SIZE(status))) channels_to_check = ARRAY_SIZE(status); @@ -3144,7 +3152,9 @@ static void stmmac_dma_interrupt(struct stmmac_priv *priv) status[chan] = stmmac_napi_check(priv, chan, DMA_DIR_RXTX); - for (chan = 0; chan < tx_channel_count; chan++) { + for (chan = 0; chan < channels_to_check; chan++) { + if (unlikely(status[chan] & io_fatal_error)) + stmmac_handle_fatal_err(priv); if (unlikely(status[chan] & tx_hard_error_bump_tc)) { /* Try to bump up the dma threshold on this failure */ stmmac_bump_dma_threshold(priv, chan); @@ -6068,7 +6078,7 @@ static void stmmac_tx_timeout(struct net_device *dev, unsigned int txqueue) { struct stmmac_priv *priv = netdev_priv(dev); - stmmac_global_err(priv); + stmmac_handle_fatal_err(priv); } /** @@ -6336,7 +6346,8 @@ static irqreturn_t stmmac_msi_intr_tx(int irq, void *data) return IRQ_HANDLED; status = stmmac_napi_check(priv, chan, DMA_DIR_TX); - + if (unlikely(status & io_fatal_error)) + stmmac_handle_fatal_err(priv); if (unlikely(status & tx_hard_error_bump_tc)) { /* Try to bump up the dma threshold on this failure */ stmmac_bump_dma_threshold(priv, chan); @@ -6353,6 +6364,7 @@ static irqreturn_t stmmac_msi_intr_rx(int irq, void *data) struct stmmac_dma_conf *dma_conf; int chan = rx_q->queue_index; struct stmmac_priv *priv; + int status; dma_conf = container_of(rx_q, struct stmmac_dma_conf, rx_queue[chan]); priv = container_of(dma_conf, struct stmmac_priv, dma_conf); @@ -6360,7 +6372,9 @@ static irqreturn_t stmmac_msi_intr_rx(int irq, void *data) if (!netif_running(priv->dev)) return IRQ_HANDLED; - stmmac_napi_check(priv, chan, DMA_DIR_RX); + status = stmmac_napi_check(priv, chan, DMA_DIR_RX); + if (unlikely(status & io_fatal_error)) + stmmac_handle_fatal_err(priv); return IRQ_HANDLED; } -- cgit v1.2.3 From 7e8d67961858044b3ae8ebafc7517b95125fcb95 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 13 Mar 2023 14:08:17 +0300 Subject: dt-bindings: mtd: Add "rom" nodename Aside of "flash" and "s?ram" it's possible to have just RO-memory accessed as the Memory Technology Device. In this case the most suitable nodename would be just "rom". Signed-off-by: Serge Semin --- Documentation/devicetree/bindings/mtd/mtd.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/mtd/mtd.yaml b/Documentation/devicetree/bindings/mtd/mtd.yaml index 5a2d06c96c0d1..ac10efe309e4b 100644 --- a/Documentation/devicetree/bindings/mtd/mtd.yaml +++ b/Documentation/devicetree/bindings/mtd/mtd.yaml @@ -14,7 +14,7 @@ select: false properties: $nodename: - pattern: "^(flash|.*sram|nand)(@.*)?$" + pattern: "^(flash|rom|.*sram|nand)(@.*)?$" label: description: -- cgit v1.2.3 From a79f4598f2f6bafa623e6abba9a7b4cac8475d9e Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 13 Mar 2023 14:14:21 +0300 Subject: dt-bindings: mtd: Add "sram-controller" nodename In accordance with the DT-specification [1] SRAM-related node name is supposed to be "sram-controller". Update the generic MTD bindings to permitting the nodes with such names thus to be consistent with the specification. [1] Devicetree Specification Release v0.3, 13 February 2020, p.10 Signed-off-by: Serge Semin --- Documentation/devicetree/bindings/mtd/mtd.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/mtd/mtd.yaml b/Documentation/devicetree/bindings/mtd/mtd.yaml index ac10efe309e4b..e8170a88f3d04 100644 --- a/Documentation/devicetree/bindings/mtd/mtd.yaml +++ b/Documentation/devicetree/bindings/mtd/mtd.yaml @@ -14,7 +14,7 @@ select: false properties: $nodename: - pattern: "^(flash|rom|.*sram|nand)(@.*)?$" + pattern: "^(flash|rom|sram-controller|.*sram|nand)(@.*)?$" label: description: -- cgit v1.2.3 From 39da390b82ea0af738c68b9647bcaaea64a4b0ee Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 6 Aug 2026 03:42:34 +0300 Subject: dt-bindings: sram: Add "sram-controller" nodename In accordance with the DT-specification [1] SRAM-related node name is supposed to be "sram-controller". Update the generic SRAM bindings to permitting the nodes with such names thus to be consistent with the specification. [1] Devicetree Specification Release v0.3, 13 February 2020, p.10 Signed-off-by: Serge Semin --- Documentation/devicetree/bindings/sram/sram.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/sram/sram.yaml b/Documentation/devicetree/bindings/sram/sram.yaml index c451140962c86..e9faffb644638 100644 --- a/Documentation/devicetree/bindings/sram/sram.yaml +++ b/Documentation/devicetree/bindings/sram/sram.yaml @@ -22,7 +22,7 @@ description: |+ properties: $nodename: - pattern: "^sram(@.*)?" + pattern: "^sram(-controller)?(@.*)?" compatible: contains: -- cgit v1.2.3 From 2f1b58de051761c333ca3771d7db986dd2aa5e4f Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sun, 24 Apr 2022 22:06:40 +0300 Subject: dt-bindings: mtd-physmap: Add Baikal-T1 internal ROM bindings Rom embedded into the Baikal-T1 SoC has been supported by the MTD Physmap ROM driver since commit b3e79e7682e0 ("mtd: physmap: Add Baikal-T1 physically mapped ROM support"). Let's make sure that the corresponding DT node is evaluated by the 'mtd-rom'-compatible DT schema. Note the subschema is defined as having two compatibles: 'baikal,bt1-int-rom' and 'mtd-rom'. The former string is listed in the enumeration for the sake of maintainability in case if some other vendor-specific MTD Physmap ROM is added. Signed-off-by: Serge Semin --- Documentation/devicetree/bindings/mtd/mtd-physmap.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/devicetree/bindings/mtd/mtd-physmap.yaml b/Documentation/devicetree/bindings/mtd/mtd-physmap.yaml index a9ec3ca002c7d..b1e75623f5a96 100644 --- a/Documentation/devicetree/bindings/mtd/mtd-physmap.yaml +++ b/Documentation/devicetree/bindings/mtd/mtd-physmap.yaml @@ -56,6 +56,10 @@ properties: - cypress,cy7c1019dv33-10zsxi - arm,vexpress-psram - const: mtd-ram + - items: + - enum: + - baikal,bt1-int-rom + - const: mtd-rom - enum: - cfi-flash - jedec-flash -- cgit v1.2.3 From 676412c0e863d3f4ae0c394e4bc6184659452734 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sat, 10 Oct 2020 16:11:36 +0300 Subject: dt-bindings: sram: Add Baikal-T1 Boot SRAM binding Baikal-T1 is equipped with embedded SRAM of 64KB being accessed over the AXI interconnect. It's normally used at the SoC startup procedure to execute the system bootloader before the normal memory is initialized. Alternatively the internal SRAM can be used to automatically pre-load a bootloader code from external SPI-flash if corresponding Baikal-T1 Boot mode is enabled, But functionally the later feature isn't that useful, since SRAM is too small while the initial software can be XIP-ed right from the SPI-flash and then download the system software from any place it wants. Baikal-T1 SRAM is clocked with a dedicated AXI-clock source and can be reset by toggling the corresponding reset line. Due to the ability to serve as a target of the system startup code pre-loading SRAM is connected to the Baikal-T1 Boot Controller, which needs to be tuned in order to get a direct access to the memory. The later is implemented in the framework of the mux-controller functionality. Signed-off-by: Serge Semin --- Documentation/devicetree/bindings/sram/sram.yaml | 57 +++++++++++++++++------- 1 file changed, 42 insertions(+), 15 deletions(-) diff --git a/Documentation/devicetree/bindings/sram/sram.yaml b/Documentation/devicetree/bindings/sram/sram.yaml index e9faffb644638..72bfd4cd78df9 100644 --- a/Documentation/devicetree/bindings/sram/sram.yaml +++ b/Documentation/devicetree/bindings/sram/sram.yaml @@ -37,6 +37,7 @@ properties: - qcom,kaanapali-imem - qcom,rpm-msg-ram - rockchip,rk3288-pmu-sram + - baikal,bt1-sram reg: maxItems: 1 @@ -47,6 +48,15 @@ properties: A list of phandle and clock specifier pair that controls the single SRAM clock. + clock-names: true + + resets: + description: + A list of phandle and reset specifier pair that controls the SRAM + state reset. + + reset-names: true + "#address-cells": const: 1 @@ -90,6 +100,7 @@ patternProperties: - arm,juno-scp-shmem - arm,scmi-shmem - arm,scp-shmem + - baikal,bt1-boot-sram - qcom,pil-reloc-info - renesas,smp-sram - rockchip,rk3066-smp-sram @@ -144,21 +155,37 @@ required: - compatible - reg -if: - not: - properties: - compatible: - contains: - enum: - - qcom,rpm-msg-ram - - rockchip,rk3288-pmu-sram -then: - required: - - "#address-cells" - - "#size-cells" - - ranges - -additionalProperties: false +allOf: + - $ref: /schemas/mux/mux-consumer.yaml# + - if: + properties: + compatible: + contains: + const: baikal,bt1-sram + then: + properties: + mux-controls: + maxItems: 1 + required: + - mux-controls + else: + properties: + mux-controls: false + - if: + properties: + compatible: + not: + contains: + enum: + - qcom,rpm-msg-ram + - rockchip,rk3288-pmu-sram + then: + required: + - "#address-cells" + - "#size-cells" + - ranges + +unevaluatedProperties: false examples: - | -- cgit v1.2.3 From 494ea6402a6509dced83d4a22d44acdc1e3d26f4 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 5 Aug 2026 15:19:41 +0300 Subject: clk: baikal-t1: Rename directory to bt1 Platform-specific code and drivers are all being placed either at directories with the "bt1" names or just named with "bt1" being part of it. The same is applicable to the Platform-specific bootloader code. The only exception is the Linux kernel clock driver. Let's align the naming here to be unified with the rest of the Baikal-T1 code. Signed-off-by: Serge Semin --- drivers/clk/Kconfig | 2 +- drivers/clk/Makefile | 2 +- drivers/clk/baikal-t1/Kconfig | 52 --- drivers/clk/baikal-t1/Makefile | 4 - drivers/clk/baikal-t1/ccu-div.c | 653 ------------------------------------ drivers/clk/baikal-t1/ccu-div.h | 121 ------- drivers/clk/baikal-t1/ccu-pll.c | 560 ------------------------------- drivers/clk/baikal-t1/ccu-pll.h | 72 ---- drivers/clk/baikal-t1/ccu-rst.c | 217 ------------ drivers/clk/baikal-t1/ccu-rst.h | 67 ---- drivers/clk/baikal-t1/clk-ccu-div.c | 520 ---------------------------- drivers/clk/baikal-t1/clk-ccu-pll.c | 277 --------------- drivers/clk/bt1/Kconfig | 52 +++ drivers/clk/bt1/Makefile | 4 + drivers/clk/bt1/ccu-div.c | 653 ++++++++++++++++++++++++++++++++++++ drivers/clk/bt1/ccu-div.h | 121 +++++++ drivers/clk/bt1/ccu-pll.c | 560 +++++++++++++++++++++++++++++++ drivers/clk/bt1/ccu-pll.h | 72 ++++ drivers/clk/bt1/ccu-rst.c | 217 ++++++++++++ drivers/clk/bt1/ccu-rst.h | 67 ++++ drivers/clk/bt1/clk-ccu-div.c | 520 ++++++++++++++++++++++++++++ drivers/clk/bt1/clk-ccu-pll.c | 277 +++++++++++++++ 22 files changed, 2545 insertions(+), 2545 deletions(-) delete mode 100644 drivers/clk/baikal-t1/Kconfig delete mode 100644 drivers/clk/baikal-t1/Makefile delete mode 100644 drivers/clk/baikal-t1/ccu-div.c delete mode 100644 drivers/clk/baikal-t1/ccu-div.h delete mode 100644 drivers/clk/baikal-t1/ccu-pll.c delete mode 100644 drivers/clk/baikal-t1/ccu-pll.h delete mode 100644 drivers/clk/baikal-t1/ccu-rst.c delete mode 100644 drivers/clk/baikal-t1/ccu-rst.h delete mode 100644 drivers/clk/baikal-t1/clk-ccu-div.c delete mode 100644 drivers/clk/baikal-t1/clk-ccu-pll.c create mode 100644 drivers/clk/bt1/Kconfig create mode 100644 drivers/clk/bt1/Makefile create mode 100644 drivers/clk/bt1/ccu-div.c create mode 100644 drivers/clk/bt1/ccu-div.h create mode 100644 drivers/clk/bt1/ccu-pll.c create mode 100644 drivers/clk/bt1/ccu-pll.h create mode 100644 drivers/clk/bt1/ccu-rst.c create mode 100644 drivers/clk/bt1/ccu-rst.h create mode 100644 drivers/clk/bt1/clk-ccu-div.c create mode 100644 drivers/clk/bt1/clk-ccu-pll.c diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index 3d803b4cf5c13..b2e963f2225a5 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -502,7 +502,7 @@ config COMMON_CLK_RPMI source "drivers/clk/actions/Kconfig" source "drivers/clk/analogbits/Kconfig" source "drivers/clk/aspeed/Kconfig" -source "drivers/clk/baikal-t1/Kconfig" +source "drivers/clk/bt1/Kconfig" source "drivers/clk/bcm/Kconfig" source "drivers/clk/hisilicon/Kconfig" source "drivers/clk/imgtec/Kconfig" diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index f7bce3951a30d..641cb17e23e24 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -116,7 +116,7 @@ obj-y += aspeed/ obj-$(CONFIG_COMMON_CLK_AT91) += at91/ obj-$(CONFIG_ARCH_ARTPEC) += axis/ obj-$(CONFIG_ARC_PLAT_AXS10X) += axs10x/ -obj-$(CONFIG_CLK_BAIKAL_T1) += baikal-t1/ +obj-$(CONFIG_CLK_BAIKAL_T1) += bt1/ obj-y += bcm/ obj-$(CONFIG_ARCH_BERLIN) += berlin/ obj-$(CONFIG_ARCH_DAVINCI) += davinci/ diff --git a/drivers/clk/baikal-t1/Kconfig b/drivers/clk/baikal-t1/Kconfig deleted file mode 100644 index f0b1868303243..0000000000000 --- a/drivers/clk/baikal-t1/Kconfig +++ /dev/null @@ -1,52 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -config CLK_BAIKAL_T1 - bool "Baikal-T1 Clocks Control Unit interface" - depends on (MIPS_BAIKAL_T1 && OF) || COMPILE_TEST - default MIPS_BAIKAL_T1 - help - Clocks Control Unit is the core of Baikal-T1 SoC System Controller - responsible for the chip subsystems clocking and resetting. It - consists of multiple global clock domains, which can be reset by - means of the CCU control registers. These domains and devices placed - in them are fed with clocks generated by a hierarchy of PLLs, - configurable and fixed clock dividers. Enable this option to be able - to select Baikal-T1 CCU PLLs and Dividers drivers. - -if CLK_BAIKAL_T1 - -config CLK_BT1_CCU_PLL - bool "Baikal-T1 CCU PLLs support" - select MFD_SYSCON - default MIPS_BAIKAL_T1 - help - Enable this to support the PLLs embedded into the Baikal-T1 SoC - System Controller. These are five PLLs placed at the root of the - clocks hierarchy, right after an external reference oscillator - (normally of 25MHz). They are used to generate high frequency - signals, which are either directly wired to the consumers (like - CPUs, DDR, etc.) or passed over the clock dividers to be only - then used as an individual reference clock of a target device. - -config CLK_BT1_CCU_DIV - bool "Baikal-T1 CCU Dividers support" - select MFD_SYSCON - default MIPS_BAIKAL_T1 - help - Enable this to support the CCU dividers used to distribute clocks - between AXI-bus and system devices coming from CCU PLLs of Baikal-T1 - SoC. CCU dividers can be either configurable or with fixed divider, - either gateable or ungateable. Some of the CCU dividers can be as well - used to reset the domains they're supplying clock to. - -config CLK_BT1_CCU_RST - bool "Baikal-T1 CCU Resets support" - select RESET_CONTROLLER - select MFD_SYSCON - default MIPS_BAIKAL_T1 - help - Enable this to support the CCU reset blocks responsible for the - AXI-bus and some subsystems reset. These are mainly the - self-deasserted reset controls but there are several lines which - can be directly asserted/de-asserted (PCIe and DDR sub-domains). - -endif diff --git a/drivers/clk/baikal-t1/Makefile b/drivers/clk/baikal-t1/Makefile deleted file mode 100644 index 9c3637de94070..0000000000000 --- a/drivers/clk/baikal-t1/Makefile +++ /dev/null @@ -1,4 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -obj-$(CONFIG_CLK_BT1_CCU_PLL) += ccu-pll.o clk-ccu-pll.o -obj-$(CONFIG_CLK_BT1_CCU_DIV) += ccu-div.o clk-ccu-div.o -obj-$(CONFIG_CLK_BT1_CCU_RST) += ccu-rst.o diff --git a/drivers/clk/baikal-t1/ccu-div.c b/drivers/clk/baikal-t1/ccu-div.c deleted file mode 100644 index cc48e580e1599..0000000000000 --- a/drivers/clk/baikal-t1/ccu-div.c +++ /dev/null @@ -1,653 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC - * - * Authors: - * Serge Semin - * Dmitry Dunaev - * - * Baikal-T1 CCU Dividers interface driver - */ - -#define pr_fmt(fmt) "bt1-ccu-div: " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ccu-div.h" - -#define CCU_DIV_CTL 0x00 -#define CCU_DIV_CTL_EN BIT(0) -#define CCU_DIV_CTL_RST BIT(1) -#define CCU_DIV_CTL_SET_CLKDIV BIT(2) -#define CCU_DIV_CTL_CLKDIV_FLD 4 -#define CCU_DIV_CTL_CLKDIV_MASK(_width) \ - GENMASK((_width) + CCU_DIV_CTL_CLKDIV_FLD - 1, CCU_DIV_CTL_CLKDIV_FLD) -#define CCU_DIV_CTL_LOCK_SHIFTED BIT(27) -#define CCU_DIV_CTL_GATE_REF_BUF BIT(28) -#define CCU_DIV_CTL_LOCK_NORMAL BIT(31) - -#define CCU_DIV_LOCK_CHECK_RETRIES 50 - -#define CCU_DIV_CLKDIV_MIN 0 -#define CCU_DIV_CLKDIV_MAX(_mask) \ - ((_mask) >> CCU_DIV_CTL_CLKDIV_FLD) - -/* - * Use the next two methods until there are generic field setter and - * getter available with non-constant mask support. - */ -static inline u32 ccu_div_get(u32 mask, u32 val) -{ - return (val & mask) >> CCU_DIV_CTL_CLKDIV_FLD; -} - -static inline u32 ccu_div_prep(u32 mask, u32 val) -{ - return (val << CCU_DIV_CTL_CLKDIV_FLD) & mask; -} - -static inline unsigned long ccu_div_lock_delay_ns(unsigned long ref_clk, - unsigned long div) -{ - u64 ns = 4ULL * (div ?: 1) * NSEC_PER_SEC; - - do_div(ns, ref_clk); - - return ns; -} - -static inline unsigned long ccu_div_calc_freq(unsigned long ref_clk, - unsigned long div) -{ - return ref_clk / (div ?: 1); -} - -static int ccu_div_var_update_clkdiv(struct ccu_div *div, - unsigned long parent_rate, - unsigned long divider) -{ - unsigned long nd; - u32 val = 0; - u32 lock; - int count; - - nd = ccu_div_lock_delay_ns(parent_rate, divider); - - if (div->features & CCU_DIV_LOCK_SHIFTED) - lock = CCU_DIV_CTL_LOCK_SHIFTED; - else - lock = CCU_DIV_CTL_LOCK_NORMAL; - - regmap_update_bits(div->sys_regs, div->reg_ctl, - CCU_DIV_CTL_SET_CLKDIV, CCU_DIV_CTL_SET_CLKDIV); - - /* - * Until there is nsec-version of readl_poll_timeout() is available - * we have to implement the next polling loop. - */ - count = CCU_DIV_LOCK_CHECK_RETRIES; - do { - ndelay(nd); - regmap_read(div->sys_regs, div->reg_ctl, &val); - if (val & lock) - return 0; - } while (--count); - - return -ETIMEDOUT; -} - -static int ccu_div_var_enable(struct clk_hw *hw) -{ - struct clk_hw *parent_hw = clk_hw_get_parent(hw); - struct ccu_div *div = to_ccu_div(hw); - unsigned long flags; - u32 val = 0; - int ret; - - if (!parent_hw) { - pr_err("Can't enable '%s' with no parent", clk_hw_get_name(hw)); - return -EINVAL; - } - - regmap_read(div->sys_regs, div->reg_ctl, &val); - if (val & CCU_DIV_CTL_EN) - return 0; - - spin_lock_irqsave(&div->lock, flags); - ret = ccu_div_var_update_clkdiv(div, clk_hw_get_rate(parent_hw), - ccu_div_get(div->mask, val)); - if (!ret) - regmap_update_bits(div->sys_regs, div->reg_ctl, - CCU_DIV_CTL_EN, CCU_DIV_CTL_EN); - spin_unlock_irqrestore(&div->lock, flags); - if (ret) - pr_err("Divider '%s' lock timed out\n", clk_hw_get_name(hw)); - - return ret; -} - -static int ccu_div_gate_enable(struct clk_hw *hw) -{ - struct ccu_div *div = to_ccu_div(hw); - unsigned long flags; - - spin_lock_irqsave(&div->lock, flags); - regmap_update_bits(div->sys_regs, div->reg_ctl, - CCU_DIV_CTL_EN, CCU_DIV_CTL_EN); - spin_unlock_irqrestore(&div->lock, flags); - - return 0; -} - -static void ccu_div_gate_disable(struct clk_hw *hw) -{ - struct ccu_div *div = to_ccu_div(hw); - unsigned long flags; - - spin_lock_irqsave(&div->lock, flags); - regmap_update_bits(div->sys_regs, div->reg_ctl, CCU_DIV_CTL_EN, 0); - spin_unlock_irqrestore(&div->lock, flags); -} - -static int ccu_div_gate_is_enabled(struct clk_hw *hw) -{ - struct ccu_div *div = to_ccu_div(hw); - u32 val = 0; - - regmap_read(div->sys_regs, div->reg_ctl, &val); - - return !!(val & CCU_DIV_CTL_EN); -} - -static int ccu_div_buf_enable(struct clk_hw *hw) -{ - struct ccu_div *div = to_ccu_div(hw); - unsigned long flags; - - spin_lock_irqsave(&div->lock, flags); - regmap_update_bits(div->sys_regs, div->reg_ctl, - CCU_DIV_CTL_GATE_REF_BUF, 0); - spin_unlock_irqrestore(&div->lock, flags); - - return 0; -} - -static void ccu_div_buf_disable(struct clk_hw *hw) -{ - struct ccu_div *div = to_ccu_div(hw); - unsigned long flags; - - spin_lock_irqsave(&div->lock, flags); - regmap_update_bits(div->sys_regs, div->reg_ctl, - CCU_DIV_CTL_GATE_REF_BUF, CCU_DIV_CTL_GATE_REF_BUF); - spin_unlock_irqrestore(&div->lock, flags); -} - -static int ccu_div_buf_is_enabled(struct clk_hw *hw) -{ - struct ccu_div *div = to_ccu_div(hw); - u32 val = 0; - - regmap_read(div->sys_regs, div->reg_ctl, &val); - - return !(val & CCU_DIV_CTL_GATE_REF_BUF); -} - -static unsigned long ccu_div_var_recalc_rate(struct clk_hw *hw, - unsigned long parent_rate) -{ - struct ccu_div *div = to_ccu_div(hw); - unsigned long divider; - u32 val = 0; - - regmap_read(div->sys_regs, div->reg_ctl, &val); - divider = ccu_div_get(div->mask, val); - - return ccu_div_calc_freq(parent_rate, divider); -} - -static inline unsigned long ccu_div_var_calc_divider(unsigned long rate, - unsigned long parent_rate, - unsigned int mask) -{ - unsigned long divider; - - divider = parent_rate / rate; - return clamp_t(unsigned long, divider, CCU_DIV_CLKDIV_MIN, - CCU_DIV_CLKDIV_MAX(mask)); -} - -static int ccu_div_var_determine_rate(struct clk_hw *hw, - struct clk_rate_request *req) -{ - struct ccu_div *div = to_ccu_div(hw); - unsigned long divider; - - divider = ccu_div_var_calc_divider(req->rate, req->best_parent_rate, - div->mask); - - req->rate = ccu_div_calc_freq(req->best_parent_rate, divider); - - return 0; -} - -/* - * This method is used for the clock divider blocks, which support the - * on-the-fly rate change. So due to lacking the EN bit functionality - * they can't be gated before the rate adjustment. - */ -static int ccu_div_var_set_rate_slow(struct clk_hw *hw, unsigned long rate, - unsigned long parent_rate) -{ - struct ccu_div *div = to_ccu_div(hw); - unsigned long flags, divider; - u32 val; - int ret; - - divider = ccu_div_var_calc_divider(rate, parent_rate, div->mask); - if (divider == 1 && div->features & CCU_DIV_SKIP_ONE) { - divider = 0; - } else if (div->features & CCU_DIV_SKIP_ONE_TO_THREE) { - if (divider == 1 || divider == 2) - divider = 0; - else if (divider == 3) - divider = 4; - } - - val = ccu_div_prep(div->mask, divider); - - spin_lock_irqsave(&div->lock, flags); - regmap_update_bits(div->sys_regs, div->reg_ctl, div->mask, val); - ret = ccu_div_var_update_clkdiv(div, parent_rate, divider); - spin_unlock_irqrestore(&div->lock, flags); - if (ret) - pr_err("Divider '%s' lock timed out\n", clk_hw_get_name(hw)); - - return ret; -} - -/* - * This method is used for the clock divider blocks, which don't support - * the on-the-fly rate change. - */ -static int ccu_div_var_set_rate_fast(struct clk_hw *hw, unsigned long rate, - unsigned long parent_rate) -{ - struct ccu_div *div = to_ccu_div(hw); - unsigned long flags, divider; - u32 val; - - divider = ccu_div_var_calc_divider(rate, parent_rate, div->mask); - val = ccu_div_prep(div->mask, divider); - - /* - * Also disable the clock divider block if it was enabled by default - * or by the bootloader. - */ - spin_lock_irqsave(&div->lock, flags); - regmap_update_bits(div->sys_regs, div->reg_ctl, - div->mask | CCU_DIV_CTL_EN, val); - spin_unlock_irqrestore(&div->lock, flags); - - return 0; -} - -static unsigned long ccu_div_fixed_recalc_rate(struct clk_hw *hw, - unsigned long parent_rate) -{ - struct ccu_div *div = to_ccu_div(hw); - - return ccu_div_calc_freq(parent_rate, div->divider); -} - -static int ccu_div_fixed_determine_rate(struct clk_hw *hw, - struct clk_rate_request *req) -{ - struct ccu_div *div = to_ccu_div(hw); - - req->rate = ccu_div_calc_freq(req->best_parent_rate, div->divider); - - return 0; -} - -static int ccu_div_fixed_set_rate(struct clk_hw *hw, unsigned long rate, - unsigned long parent_rate) -{ - return 0; -} - -#ifdef CONFIG_DEBUG_FS - -struct ccu_div_dbgfs_bit { - struct ccu_div *div; - const char *name; - u32 mask; -}; - -#define CCU_DIV_DBGFS_BIT_ATTR(_name, _mask) { \ - .name = _name, \ - .mask = _mask \ - } - -static const struct ccu_div_dbgfs_bit ccu_div_bits[] = { - CCU_DIV_DBGFS_BIT_ATTR("div_en", CCU_DIV_CTL_EN), - CCU_DIV_DBGFS_BIT_ATTR("div_rst", CCU_DIV_CTL_RST), - CCU_DIV_DBGFS_BIT_ATTR("div_bypass", CCU_DIV_CTL_SET_CLKDIV), - CCU_DIV_DBGFS_BIT_ATTR("div_buf", CCU_DIV_CTL_GATE_REF_BUF), - CCU_DIV_DBGFS_BIT_ATTR("div_lock", CCU_DIV_CTL_LOCK_NORMAL) -}; - -#define CCU_DIV_DBGFS_BIT_NUM ARRAY_SIZE(ccu_div_bits) - -/* - * It can be dangerous to change the Divider settings behind clock framework - * back, therefore we don't provide any kernel config based compile time option - * for this feature to enable. - */ -#undef CCU_DIV_ALLOW_WRITE_DEBUGFS -#ifdef CCU_DIV_ALLOW_WRITE_DEBUGFS - -static int ccu_div_dbgfs_bit_set(void *priv, u64 val) -{ - const struct ccu_div_dbgfs_bit *bit = priv; - struct ccu_div *div = bit->div; - unsigned long flags; - - spin_lock_irqsave(&div->lock, flags); - regmap_update_bits(div->sys_regs, div->reg_ctl, - bit->mask, val ? bit->mask : 0); - spin_unlock_irqrestore(&div->lock, flags); - - return 0; -} - -static int ccu_div_dbgfs_var_clkdiv_set(void *priv, u64 val) -{ - struct ccu_div *div = priv; - unsigned long flags; - u32 data; - - val = clamp_t(u64, val, CCU_DIV_CLKDIV_MIN, - CCU_DIV_CLKDIV_MAX(div->mask)); - data = ccu_div_prep(div->mask, val); - - spin_lock_irqsave(&div->lock, flags); - regmap_update_bits(div->sys_regs, div->reg_ctl, div->mask, data); - spin_unlock_irqrestore(&div->lock, flags); - - return 0; -} - -#define ccu_div_dbgfs_mode 0644 - -#else /* !CCU_DIV_ALLOW_WRITE_DEBUGFS */ - -#define ccu_div_dbgfs_bit_set NULL -#define ccu_div_dbgfs_var_clkdiv_set NULL -#define ccu_div_dbgfs_mode 0444 - -#endif /* !CCU_DIV_ALLOW_WRITE_DEBUGFS */ - -static int ccu_div_dbgfs_bit_get(void *priv, u64 *val) -{ - const struct ccu_div_dbgfs_bit *bit = priv; - struct ccu_div *div = bit->div; - u32 data = 0; - - regmap_read(div->sys_regs, div->reg_ctl, &data); - *val = !!(data & bit->mask); - - return 0; -} -DEFINE_DEBUGFS_ATTRIBUTE(ccu_div_dbgfs_bit_fops, - ccu_div_dbgfs_bit_get, ccu_div_dbgfs_bit_set, "%llu\n"); - -static int ccu_div_dbgfs_var_clkdiv_get(void *priv, u64 *val) -{ - struct ccu_div *div = priv; - u32 data = 0; - - regmap_read(div->sys_regs, div->reg_ctl, &data); - *val = ccu_div_get(div->mask, data); - - return 0; -} -DEFINE_DEBUGFS_ATTRIBUTE(ccu_div_dbgfs_var_clkdiv_fops, - ccu_div_dbgfs_var_clkdiv_get, ccu_div_dbgfs_var_clkdiv_set, "%llu\n"); - -static int ccu_div_dbgfs_fixed_clkdiv_get(void *priv, u64 *val) -{ - struct ccu_div *div = priv; - - *val = div->divider; - - return 0; -} -DEFINE_DEBUGFS_ATTRIBUTE(ccu_div_dbgfs_fixed_clkdiv_fops, - ccu_div_dbgfs_fixed_clkdiv_get, NULL, "%llu\n"); - -static void ccu_div_var_debug_init(struct clk_hw *hw, struct dentry *dentry) -{ - struct ccu_div *div = to_ccu_div(hw); - struct ccu_div_dbgfs_bit *bits; - int didx, bidx, num = 2; - const char *name; - - num += !!(div->flags & CLK_SET_RATE_GATE) + - !!(div->features & CCU_DIV_RESET_DOMAIN); - - bits = kzalloc_objs(*bits, num); - if (!bits) - return; - - for (didx = 0, bidx = 0; bidx < CCU_DIV_DBGFS_BIT_NUM; ++bidx) { - name = ccu_div_bits[bidx].name; - if (!(div->flags & CLK_SET_RATE_GATE) && - !strcmp("div_en", name)) { - continue; - } - - if (!(div->features & CCU_DIV_RESET_DOMAIN) && - !strcmp("div_rst", name)) { - continue; - } - - if (!strcmp("div_buf", name)) - continue; - - bits[didx] = ccu_div_bits[bidx]; - bits[didx].div = div; - - if (div->features & CCU_DIV_LOCK_SHIFTED && - !strcmp("div_lock", name)) { - bits[didx].mask = CCU_DIV_CTL_LOCK_SHIFTED; - } - - debugfs_create_file_unsafe(bits[didx].name, ccu_div_dbgfs_mode, - dentry, &bits[didx], - &ccu_div_dbgfs_bit_fops); - ++didx; - } - - debugfs_create_file_unsafe("div_clkdiv", ccu_div_dbgfs_mode, dentry, - div, &ccu_div_dbgfs_var_clkdiv_fops); -} - -static void ccu_div_gate_debug_init(struct clk_hw *hw, struct dentry *dentry) -{ - struct ccu_div *div = to_ccu_div(hw); - struct ccu_div_dbgfs_bit *bit; - - bit = kmalloc_obj(*bit); - if (!bit) - return; - - *bit = ccu_div_bits[0]; - bit->div = div; - debugfs_create_file_unsafe(bit->name, ccu_div_dbgfs_mode, dentry, bit, - &ccu_div_dbgfs_bit_fops); - - debugfs_create_file_unsafe("div_clkdiv", 0400, dentry, div, - &ccu_div_dbgfs_fixed_clkdiv_fops); -} - -static void ccu_div_buf_debug_init(struct clk_hw *hw, struct dentry *dentry) -{ - struct ccu_div *div = to_ccu_div(hw); - struct ccu_div_dbgfs_bit *bit; - - bit = kmalloc_obj(*bit); - if (!bit) - return; - - *bit = ccu_div_bits[3]; - bit->div = div; - debugfs_create_file_unsafe(bit->name, ccu_div_dbgfs_mode, dentry, bit, - &ccu_div_dbgfs_bit_fops); -} - -static void ccu_div_fixed_debug_init(struct clk_hw *hw, struct dentry *dentry) -{ - struct ccu_div *div = to_ccu_div(hw); - - debugfs_create_file_unsafe("div_clkdiv", 0400, dentry, div, - &ccu_div_dbgfs_fixed_clkdiv_fops); -} - -#else /* !CONFIG_DEBUG_FS */ - -#define ccu_div_var_debug_init NULL -#define ccu_div_gate_debug_init NULL -#define ccu_div_buf_debug_init NULL -#define ccu_div_fixed_debug_init NULL - -#endif /* !CONFIG_DEBUG_FS */ - -static const struct clk_ops ccu_div_var_gate_to_set_ops = { - .enable = ccu_div_var_enable, - .disable = ccu_div_gate_disable, - .is_enabled = ccu_div_gate_is_enabled, - .recalc_rate = ccu_div_var_recalc_rate, - .determine_rate = ccu_div_var_determine_rate, - .set_rate = ccu_div_var_set_rate_fast, - .debug_init = ccu_div_var_debug_init -}; - -static const struct clk_ops ccu_div_var_nogate_ops = { - .recalc_rate = ccu_div_var_recalc_rate, - .determine_rate = ccu_div_var_determine_rate, - .set_rate = ccu_div_var_set_rate_slow, - .debug_init = ccu_div_var_debug_init -}; - -static const struct clk_ops ccu_div_gate_ops = { - .enable = ccu_div_gate_enable, - .disable = ccu_div_gate_disable, - .is_enabled = ccu_div_gate_is_enabled, - .recalc_rate = ccu_div_fixed_recalc_rate, - .determine_rate = ccu_div_fixed_determine_rate, - .set_rate = ccu_div_fixed_set_rate, - .debug_init = ccu_div_gate_debug_init -}; - -static const struct clk_ops ccu_div_buf_ops = { - .enable = ccu_div_buf_enable, - .disable = ccu_div_buf_disable, - .is_enabled = ccu_div_buf_is_enabled, - .debug_init = ccu_div_buf_debug_init -}; - -static const struct clk_ops ccu_div_fixed_ops = { - .recalc_rate = ccu_div_fixed_recalc_rate, - .determine_rate = ccu_div_fixed_determine_rate, - .set_rate = ccu_div_fixed_set_rate, - .debug_init = ccu_div_fixed_debug_init -}; - -struct ccu_div *ccu_div_hw_register(const struct ccu_div_init_data *div_init) -{ - struct clk_parent_data parent_data = { }; - struct clk_init_data hw_init = { }; - struct ccu_div *div; - int ret; - - if (!div_init) - return ERR_PTR(-EINVAL); - - div = kzalloc_obj(*div); - if (!div) - return ERR_PTR(-ENOMEM); - - /* - * Note since Baikal-T1 System Controller registers are MMIO-backed - * we won't check the regmap IO operations return status, because it - * must be zero anyway. - */ - div->hw.init = &hw_init; - div->id = div_init->id; - div->reg_ctl = div_init->base + CCU_DIV_CTL; - div->sys_regs = div_init->sys_regs; - div->flags = div_init->flags; - div->features = div_init->features; - spin_lock_init(&div->lock); - - hw_init.name = div_init->name; - hw_init.flags = div_init->flags; - - if (div_init->type == CCU_DIV_VAR) { - if (hw_init.flags & CLK_SET_RATE_GATE) - hw_init.ops = &ccu_div_var_gate_to_set_ops; - else - hw_init.ops = &ccu_div_var_nogate_ops; - div->mask = CCU_DIV_CTL_CLKDIV_MASK(div_init->width); - } else if (div_init->type == CCU_DIV_GATE) { - hw_init.ops = &ccu_div_gate_ops; - div->divider = div_init->divider; - } else if (div_init->type == CCU_DIV_BUF) { - hw_init.ops = &ccu_div_buf_ops; - } else if (div_init->type == CCU_DIV_FIXED) { - hw_init.ops = &ccu_div_fixed_ops; - div->divider = div_init->divider; - } else { - ret = -EINVAL; - goto err_free_div; - } - - if (!div_init->parent_name) { - ret = -EINVAL; - goto err_free_div; - } - parent_data.fw_name = div_init->parent_name; - parent_data.name = div_init->parent_name; - hw_init.parent_data = &parent_data; - hw_init.num_parents = 1; - - ret = of_clk_hw_register(div_init->np, &div->hw); - if (ret) - goto err_free_div; - - return div; - -err_free_div: - kfree(div); - - return ERR_PTR(ret); -} - -void ccu_div_hw_unregister(struct ccu_div *div) -{ - clk_hw_unregister(&div->hw); - - kfree(div); -} diff --git a/drivers/clk/baikal-t1/ccu-div.h b/drivers/clk/baikal-t1/ccu-div.h deleted file mode 100644 index 76d8ee44d4157..0000000000000 --- a/drivers/clk/baikal-t1/ccu-div.h +++ /dev/null @@ -1,121 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC - * - * Baikal-T1 CCU Dividers interface driver - */ -#ifndef __CLK_BT1_CCU_DIV_H__ -#define __CLK_BT1_CCU_DIV_H__ - -#include -#include -#include -#include -#include - -/* - * CCU Divider private clock IDs - * @CCU_SYS_SATA_CLK: CCU SATA internal clock - * @CCU_SYS_XGMAC_CLK: CCU XGMAC internal clock - */ -#define CCU_SYS_SATA_CLK -1 -#define CCU_SYS_XGMAC_CLK -2 - -/* - * CCU Divider private flags - * @CCU_DIV_BASIC: Basic divider clock required by the kernel as early as - * possible. - * @CCU_DIV_SKIP_ONE: Due to some reason divider can't be set to 1. - * It can be 0 though, which is functionally the same. - * @CCU_DIV_SKIP_ONE_TO_THREE: For some reason divider can't be within [1,3]. - * It can be either 0 or greater than 3. - * @CCU_DIV_LOCK_SHIFTED: Find lock-bit at non-standard position. - * @CCU_DIV_RESET_DOMAIN: There is a clock domain reset handle. - */ -#define CCU_DIV_BASIC BIT(0) -#define CCU_DIV_SKIP_ONE BIT(1) -#define CCU_DIV_SKIP_ONE_TO_THREE BIT(2) -#define CCU_DIV_LOCK_SHIFTED BIT(3) -#define CCU_DIV_RESET_DOMAIN BIT(4) - -/* - * enum ccu_div_type - CCU Divider types - * @CCU_DIV_VAR: Clocks gate with variable divider. - * @CCU_DIV_GATE: Clocks gate with fixed divider. - * @CCU_DIV_BUF: Clock gate with no divider. - * @CCU_DIV_FIXED: Ungateable clock with fixed divider. - */ -enum ccu_div_type { - CCU_DIV_VAR, - CCU_DIV_GATE, - CCU_DIV_BUF, - CCU_DIV_FIXED -}; - -/* - * struct ccu_div_init_data - CCU Divider initialization data - * @id: Clocks private identifier. - * @name: Clocks name. - * @parent_name: Parent clocks name in a fw node. - * @base: Divider register base address with respect to the sys_regs base. - * @sys_regs: Baikal-T1 System Controller registers map. - * @np: Pointer to the node describing the CCU Dividers. - * @type: CCU divider type (variable, fixed with and without gate). - * @width: Divider width if it's variable. - * @divider: Divider fixed value. - * @flags: CCU Divider clock flags. - * @features: CCU Divider private features. - */ -struct ccu_div_init_data { - unsigned int id; - const char *name; - const char *parent_name; - unsigned int base; - struct regmap *sys_regs; - struct device_node *np; - enum ccu_div_type type; - union { - unsigned int width; - unsigned int divider; - }; - unsigned long flags; - unsigned long features; -}; - -/* - * struct ccu_div - CCU Divider descriptor - * @hw: clk_hw of the divider. - * @id: Clock private identifier. - * @reg_ctl: Divider control register base address. - * @sys_regs: Baikal-T1 System Controller registers map. - * @lock: Divider state change spin-lock. - * @mask: Divider field mask. - * @divider: Divider fixed value. - * @flags: Divider clock flags. - * @features: CCU Divider private features. - */ -struct ccu_div { - struct clk_hw hw; - unsigned int id; - unsigned int reg_ctl; - struct regmap *sys_regs; - spinlock_t lock; - union { - u32 mask; - unsigned int divider; - }; - unsigned long flags; - unsigned long features; -}; -#define to_ccu_div(_hw) container_of(_hw, struct ccu_div, hw) - -static inline struct clk_hw *ccu_div_get_clk_hw(struct ccu_div *div) -{ - return div ? &div->hw : NULL; -} - -struct ccu_div *ccu_div_hw_register(const struct ccu_div_init_data *init); - -void ccu_div_hw_unregister(struct ccu_div *div); - -#endif /* __CLK_BT1_CCU_DIV_H__ */ diff --git a/drivers/clk/baikal-t1/ccu-pll.c b/drivers/clk/baikal-t1/ccu-pll.c deleted file mode 100644 index da7fbebb39abc..0000000000000 --- a/drivers/clk/baikal-t1/ccu-pll.c +++ /dev/null @@ -1,560 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC - * - * Authors: - * Serge Semin - * Dmitry Dunaev - * - * Baikal-T1 CCU PLL interface driver - */ - -#define pr_fmt(fmt) "bt1-ccu-pll: " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ccu-pll.h" - -#define CCU_PLL_CTL 0x000 -#define CCU_PLL_CTL_EN BIT(0) -#define CCU_PLL_CTL_RST BIT(1) -#define CCU_PLL_CTL_CLKR_FLD 2 -#define CCU_PLL_CTL_CLKR_MASK GENMASK(7, CCU_PLL_CTL_CLKR_FLD) -#define CCU_PLL_CTL_CLKF_FLD 8 -#define CCU_PLL_CTL_CLKF_MASK GENMASK(20, CCU_PLL_CTL_CLKF_FLD) -#define CCU_PLL_CTL_CLKOD_FLD 21 -#define CCU_PLL_CTL_CLKOD_MASK GENMASK(24, CCU_PLL_CTL_CLKOD_FLD) -#define CCU_PLL_CTL_BYPASS BIT(30) -#define CCU_PLL_CTL_LOCK BIT(31) -#define CCU_PLL_CTL1 0x004 -#define CCU_PLL_CTL1_BWADJ_FLD 3 -#define CCU_PLL_CTL1_BWADJ_MASK GENMASK(14, CCU_PLL_CTL1_BWADJ_FLD) - -#define CCU_PLL_LOCK_CHECK_RETRIES 50 - -#define CCU_PLL_NR_MAX \ - ((CCU_PLL_CTL_CLKR_MASK >> CCU_PLL_CTL_CLKR_FLD) + 1) -#define CCU_PLL_NF_MAX \ - ((CCU_PLL_CTL_CLKF_MASK >> (CCU_PLL_CTL_CLKF_FLD + 1)) + 1) -#define CCU_PLL_OD_MAX \ - ((CCU_PLL_CTL_CLKOD_MASK >> CCU_PLL_CTL_CLKOD_FLD) + 1) -#define CCU_PLL_NB_MAX \ - ((CCU_PLL_CTL1_BWADJ_MASK >> CCU_PLL_CTL1_BWADJ_FLD) + 1) -#define CCU_PLL_FDIV_MIN 427000UL -#define CCU_PLL_FDIV_MAX 3500000000UL -#define CCU_PLL_FOUT_MIN 200000000UL -#define CCU_PLL_FOUT_MAX 2500000000UL -#define CCU_PLL_FVCO_MIN 700000000UL -#define CCU_PLL_FVCO_MAX 3500000000UL -#define CCU_PLL_CLKOD_FACTOR 2 - -static inline unsigned long ccu_pll_lock_delay_us(unsigned long ref_clk, - unsigned long nr) -{ - u64 us = 500ULL * nr * USEC_PER_SEC; - - do_div(us, ref_clk); - - return us; -} - -static inline unsigned long ccu_pll_calc_freq(unsigned long ref_clk, - unsigned long nr, - unsigned long nf, - unsigned long od) -{ - u64 tmp = ref_clk; - - do_div(tmp, nr); - tmp *= nf; - do_div(tmp, od); - - return tmp; -} - -static int ccu_pll_reset(struct ccu_pll *pll, unsigned long ref_clk, - unsigned long nr) -{ - unsigned long ud, ut; - u32 val; - - ud = ccu_pll_lock_delay_us(ref_clk, nr); - ut = ud * CCU_PLL_LOCK_CHECK_RETRIES; - - regmap_update_bits(pll->sys_regs, pll->reg_ctl, - CCU_PLL_CTL_RST, CCU_PLL_CTL_RST); - - return regmap_read_poll_timeout_atomic(pll->sys_regs, pll->reg_ctl, val, - val & CCU_PLL_CTL_LOCK, ud, ut); -} - -static int ccu_pll_enable(struct clk_hw *hw) -{ - struct clk_hw *parent_hw = clk_hw_get_parent(hw); - struct ccu_pll *pll = to_ccu_pll(hw); - unsigned long flags; - u32 val = 0; - int ret; - - if (!parent_hw) { - pr_err("Can't enable '%s' with no parent", clk_hw_get_name(hw)); - return -EINVAL; - } - - regmap_read(pll->sys_regs, pll->reg_ctl, &val); - if (val & CCU_PLL_CTL_EN) - return 0; - - spin_lock_irqsave(&pll->lock, flags); - regmap_write(pll->sys_regs, pll->reg_ctl, val | CCU_PLL_CTL_EN); - ret = ccu_pll_reset(pll, clk_hw_get_rate(parent_hw), - FIELD_GET(CCU_PLL_CTL_CLKR_MASK, val) + 1); - spin_unlock_irqrestore(&pll->lock, flags); - if (ret) - pr_err("PLL '%s' reset timed out\n", clk_hw_get_name(hw)); - - return ret; -} - -static void ccu_pll_disable(struct clk_hw *hw) -{ - struct ccu_pll *pll = to_ccu_pll(hw); - unsigned long flags; - - spin_lock_irqsave(&pll->lock, flags); - regmap_update_bits(pll->sys_regs, pll->reg_ctl, CCU_PLL_CTL_EN, 0); - spin_unlock_irqrestore(&pll->lock, flags); -} - -static int ccu_pll_is_enabled(struct clk_hw *hw) -{ - struct ccu_pll *pll = to_ccu_pll(hw); - u32 val = 0; - - regmap_read(pll->sys_regs, pll->reg_ctl, &val); - - return !!(val & CCU_PLL_CTL_EN); -} - -static unsigned long ccu_pll_recalc_rate(struct clk_hw *hw, - unsigned long parent_rate) -{ - struct ccu_pll *pll = to_ccu_pll(hw); - unsigned long nr, nf, od; - u32 val = 0; - - regmap_read(pll->sys_regs, pll->reg_ctl, &val); - nr = FIELD_GET(CCU_PLL_CTL_CLKR_MASK, val) + 1; - nf = FIELD_GET(CCU_PLL_CTL_CLKF_MASK, val) + 1; - od = FIELD_GET(CCU_PLL_CTL_CLKOD_MASK, val) + 1; - - return ccu_pll_calc_freq(parent_rate, nr, nf, od); -} - -static void ccu_pll_calc_factors(unsigned long rate, unsigned long parent_rate, - unsigned long *nr, unsigned long *nf, - unsigned long *od) -{ - unsigned long err, freq, min_err = ULONG_MAX; - unsigned long num, denom, n1, d1, nri; - unsigned long nr_max, nf_max, od_max; - - /* - * Make sure PLL is working with valid input signal (Fdiv). If - * you want to speed the function up just reduce CCU_PLL_NR_MAX. - * This will cause a worse approximation though. - */ - nri = (parent_rate / CCU_PLL_FDIV_MAX) + 1; - nr_max = min(parent_rate / CCU_PLL_FDIV_MIN, CCU_PLL_NR_MAX); - - /* - * Find a closest [nr;nf;od] vector taking into account the - * limitations like: 1) 700MHz <= Fvco <= 3.5GHz, 2) PLL Od is - * either 1 or even number within the acceptable range (alas 1s - * is also excluded by the next loop). - */ - for (; nri <= nr_max; ++nri) { - /* Use Od factor to fulfill the limitation 2). */ - num = CCU_PLL_CLKOD_FACTOR * rate; - denom = parent_rate / nri; - - /* - * Make sure Fvco is within the acceptable range to fulfill - * the condition 1). Note due to the CCU_PLL_CLKOD_FACTOR value - * the actual upper limit is also divided by that factor. - * It's not big problem for us since practically there is no - * need in clocks with that high frequency. - */ - nf_max = min(CCU_PLL_FVCO_MAX / denom, CCU_PLL_NF_MAX); - od_max = CCU_PLL_OD_MAX / CCU_PLL_CLKOD_FACTOR; - - /* - * Bypass the out-of-bound values, which can't be properly - * handled by the rational fraction approximation algorithm. - */ - if (num / denom >= nf_max) { - n1 = nf_max; - d1 = 1; - } else if (denom / num >= od_max) { - n1 = 1; - d1 = od_max; - } else { - rational_best_approximation(num, denom, nf_max, od_max, - &n1, &d1); - } - - /* Select the best approximation of the target rate. */ - freq = ccu_pll_calc_freq(parent_rate, nri, n1, d1); - err = abs((int64_t)freq - num); - if (err < min_err) { - min_err = err; - *nr = nri; - *nf = n1; - *od = CCU_PLL_CLKOD_FACTOR * d1; - } - } -} - -static int ccu_pll_determine_rate(struct clk_hw *hw, - struct clk_rate_request *req) -{ - unsigned long nr = 1, nf = 1, od = 1; - - ccu_pll_calc_factors(req->rate, req->best_parent_rate, &nr, &nf, &od); - - req->rate = ccu_pll_calc_freq(req->best_parent_rate, nr, nf, od); - - return 0; -} - -/* - * This method is used for PLLs, which support the on-the-fly dividers - * adjustment. So there is no need in gating such clocks. - */ -static int ccu_pll_set_rate_reset(struct clk_hw *hw, unsigned long rate, - unsigned long parent_rate) -{ - struct ccu_pll *pll = to_ccu_pll(hw); - unsigned long nr, nf, od; - unsigned long flags; - u32 mask, val; - int ret; - - ccu_pll_calc_factors(rate, parent_rate, &nr, &nf, &od); - - mask = CCU_PLL_CTL_CLKR_MASK | CCU_PLL_CTL_CLKF_MASK | - CCU_PLL_CTL_CLKOD_MASK; - val = FIELD_PREP(CCU_PLL_CTL_CLKR_MASK, nr - 1) | - FIELD_PREP(CCU_PLL_CTL_CLKF_MASK, nf - 1) | - FIELD_PREP(CCU_PLL_CTL_CLKOD_MASK, od - 1); - - spin_lock_irqsave(&pll->lock, flags); - regmap_update_bits(pll->sys_regs, pll->reg_ctl, mask, val); - ret = ccu_pll_reset(pll, parent_rate, nr); - spin_unlock_irqrestore(&pll->lock, flags); - if (ret) - pr_err("PLL '%s' reset timed out\n", clk_hw_get_name(hw)); - - return ret; -} - -/* - * This method is used for PLLs, which don't support the on-the-fly dividers - * adjustment. So the corresponding clocks are supposed to be gated first. - */ -static int ccu_pll_set_rate_norst(struct clk_hw *hw, unsigned long rate, - unsigned long parent_rate) -{ - struct ccu_pll *pll = to_ccu_pll(hw); - unsigned long nr, nf, od; - unsigned long flags; - u32 mask, val; - - ccu_pll_calc_factors(rate, parent_rate, &nr, &nf, &od); - - /* - * Disable PLL if it was enabled by default or left enabled by the - * system bootloader. - */ - mask = CCU_PLL_CTL_CLKR_MASK | CCU_PLL_CTL_CLKF_MASK | - CCU_PLL_CTL_CLKOD_MASK | CCU_PLL_CTL_EN; - val = FIELD_PREP(CCU_PLL_CTL_CLKR_MASK, nr - 1) | - FIELD_PREP(CCU_PLL_CTL_CLKF_MASK, nf - 1) | - FIELD_PREP(CCU_PLL_CTL_CLKOD_MASK, od - 1); - - spin_lock_irqsave(&pll->lock, flags); - regmap_update_bits(pll->sys_regs, pll->reg_ctl, mask, val); - spin_unlock_irqrestore(&pll->lock, flags); - - return 0; -} - -#ifdef CONFIG_DEBUG_FS - -struct ccu_pll_dbgfs_bit { - struct ccu_pll *pll; - const char *name; - unsigned int reg; - u32 mask; -}; - -struct ccu_pll_dbgfs_fld { - struct ccu_pll *pll; - const char *name; - unsigned int reg; - unsigned int lsb; - u32 mask; - u32 min; - u32 max; -}; - -#define CCU_PLL_DBGFS_BIT_ATTR(_name, _reg, _mask) \ - { \ - .name = _name, \ - .reg = _reg, \ - .mask = _mask \ - } - -#define CCU_PLL_DBGFS_FLD_ATTR(_name, _reg, _lsb, _mask, _min, _max) \ - { \ - .name = _name, \ - .reg = _reg, \ - .lsb = _lsb, \ - .mask = _mask, \ - .min = _min, \ - .max = _max \ - } - -static const struct ccu_pll_dbgfs_bit ccu_pll_bits[] = { - CCU_PLL_DBGFS_BIT_ATTR("pll_en", CCU_PLL_CTL, CCU_PLL_CTL_EN), - CCU_PLL_DBGFS_BIT_ATTR("pll_rst", CCU_PLL_CTL, CCU_PLL_CTL_RST), - CCU_PLL_DBGFS_BIT_ATTR("pll_bypass", CCU_PLL_CTL, CCU_PLL_CTL_BYPASS), - CCU_PLL_DBGFS_BIT_ATTR("pll_lock", CCU_PLL_CTL, CCU_PLL_CTL_LOCK) -}; - -#define CCU_PLL_DBGFS_BIT_NUM ARRAY_SIZE(ccu_pll_bits) - -static const struct ccu_pll_dbgfs_fld ccu_pll_flds[] = { - CCU_PLL_DBGFS_FLD_ATTR("pll_nr", CCU_PLL_CTL, CCU_PLL_CTL_CLKR_FLD, - CCU_PLL_CTL_CLKR_MASK, 1, CCU_PLL_NR_MAX), - CCU_PLL_DBGFS_FLD_ATTR("pll_nf", CCU_PLL_CTL, CCU_PLL_CTL_CLKF_FLD, - CCU_PLL_CTL_CLKF_MASK, 1, CCU_PLL_NF_MAX), - CCU_PLL_DBGFS_FLD_ATTR("pll_od", CCU_PLL_CTL, CCU_PLL_CTL_CLKOD_FLD, - CCU_PLL_CTL_CLKOD_MASK, 1, CCU_PLL_OD_MAX), - CCU_PLL_DBGFS_FLD_ATTR("pll_nb", CCU_PLL_CTL1, CCU_PLL_CTL1_BWADJ_FLD, - CCU_PLL_CTL1_BWADJ_MASK, 1, CCU_PLL_NB_MAX) -}; - -#define CCU_PLL_DBGFS_FLD_NUM ARRAY_SIZE(ccu_pll_flds) - -/* - * It can be dangerous to change the PLL settings behind clock framework back, - * therefore we don't provide any kernel config based compile time option for - * this feature to enable. - */ -#undef CCU_PLL_ALLOW_WRITE_DEBUGFS -#ifdef CCU_PLL_ALLOW_WRITE_DEBUGFS - -static int ccu_pll_dbgfs_bit_set(void *priv, u64 val) -{ - const struct ccu_pll_dbgfs_bit *bit = priv; - struct ccu_pll *pll = bit->pll; - unsigned long flags; - - spin_lock_irqsave(&pll->lock, flags); - regmap_update_bits(pll->sys_regs, pll->reg_ctl + bit->reg, - bit->mask, val ? bit->mask : 0); - spin_unlock_irqrestore(&pll->lock, flags); - - return 0; -} - -static int ccu_pll_dbgfs_fld_set(void *priv, u64 val) -{ - struct ccu_pll_dbgfs_fld *fld = priv; - struct ccu_pll *pll = fld->pll; - unsigned long flags; - u32 data; - - val = clamp_t(u64, val, fld->min, fld->max); - data = ((val - 1) << fld->lsb) & fld->mask; - - spin_lock_irqsave(&pll->lock, flags); - regmap_update_bits(pll->sys_regs, pll->reg_ctl + fld->reg, fld->mask, - data); - spin_unlock_irqrestore(&pll->lock, flags); - - return 0; -} - -#define ccu_pll_dbgfs_mode 0644 - -#else /* !CCU_PLL_ALLOW_WRITE_DEBUGFS */ - -#define ccu_pll_dbgfs_bit_set NULL -#define ccu_pll_dbgfs_fld_set NULL -#define ccu_pll_dbgfs_mode 0444 - -#endif /* !CCU_PLL_ALLOW_WRITE_DEBUGFS */ - -static int ccu_pll_dbgfs_bit_get(void *priv, u64 *val) -{ - struct ccu_pll_dbgfs_bit *bit = priv; - struct ccu_pll *pll = bit->pll; - u32 data = 0; - - regmap_read(pll->sys_regs, pll->reg_ctl + bit->reg, &data); - *val = !!(data & bit->mask); - - return 0; -} -DEFINE_DEBUGFS_ATTRIBUTE(ccu_pll_dbgfs_bit_fops, - ccu_pll_dbgfs_bit_get, ccu_pll_dbgfs_bit_set, "%llu\n"); - -static int ccu_pll_dbgfs_fld_get(void *priv, u64 *val) -{ - struct ccu_pll_dbgfs_fld *fld = priv; - struct ccu_pll *pll = fld->pll; - u32 data = 0; - - regmap_read(pll->sys_regs, pll->reg_ctl + fld->reg, &data); - *val = ((data & fld->mask) >> fld->lsb) + 1; - - return 0; -} -DEFINE_DEBUGFS_ATTRIBUTE(ccu_pll_dbgfs_fld_fops, - ccu_pll_dbgfs_fld_get, ccu_pll_dbgfs_fld_set, "%llu\n"); - -static void ccu_pll_debug_init(struct clk_hw *hw, struct dentry *dentry) -{ - struct ccu_pll *pll = to_ccu_pll(hw); - struct ccu_pll_dbgfs_bit *bits; - struct ccu_pll_dbgfs_fld *flds; - int idx; - - bits = kzalloc_objs(*bits, CCU_PLL_DBGFS_BIT_NUM); - if (!bits) - return; - - for (idx = 0; idx < CCU_PLL_DBGFS_BIT_NUM; ++idx) { - bits[idx] = ccu_pll_bits[idx]; - bits[idx].pll = pll; - - debugfs_create_file_unsafe(bits[idx].name, ccu_pll_dbgfs_mode, - dentry, &bits[idx], - &ccu_pll_dbgfs_bit_fops); - } - - flds = kzalloc_objs(*flds, CCU_PLL_DBGFS_FLD_NUM); - if (!flds) - return; - - for (idx = 0; idx < CCU_PLL_DBGFS_FLD_NUM; ++idx) { - flds[idx] = ccu_pll_flds[idx]; - flds[idx].pll = pll; - - debugfs_create_file_unsafe(flds[idx].name, ccu_pll_dbgfs_mode, - dentry, &flds[idx], - &ccu_pll_dbgfs_fld_fops); - } -} - -#else /* !CONFIG_DEBUG_FS */ - -#define ccu_pll_debug_init NULL - -#endif /* !CONFIG_DEBUG_FS */ - -static const struct clk_ops ccu_pll_gate_to_set_ops = { - .enable = ccu_pll_enable, - .disable = ccu_pll_disable, - .is_enabled = ccu_pll_is_enabled, - .recalc_rate = ccu_pll_recalc_rate, - .determine_rate = ccu_pll_determine_rate, - .set_rate = ccu_pll_set_rate_norst, - .debug_init = ccu_pll_debug_init -}; - -static const struct clk_ops ccu_pll_straight_set_ops = { - .enable = ccu_pll_enable, - .disable = ccu_pll_disable, - .is_enabled = ccu_pll_is_enabled, - .recalc_rate = ccu_pll_recalc_rate, - .determine_rate = ccu_pll_determine_rate, - .set_rate = ccu_pll_set_rate_reset, - .debug_init = ccu_pll_debug_init -}; - -struct ccu_pll *ccu_pll_hw_register(const struct ccu_pll_init_data *pll_init) -{ - struct clk_parent_data parent_data = { }; - struct clk_init_data hw_init = { }; - struct ccu_pll *pll; - int ret; - - if (!pll_init) - return ERR_PTR(-EINVAL); - - pll = kzalloc_obj(*pll); - if (!pll) - return ERR_PTR(-ENOMEM); - - /* - * Note since Baikal-T1 System Controller registers are MMIO-backed - * we won't check the regmap IO operations return status, because it - * must be zero anyway. - */ - pll->hw.init = &hw_init; - pll->reg_ctl = pll_init->base + CCU_PLL_CTL; - pll->reg_ctl1 = pll_init->base + CCU_PLL_CTL1; - pll->sys_regs = pll_init->sys_regs; - pll->id = pll_init->id; - spin_lock_init(&pll->lock); - - hw_init.name = pll_init->name; - hw_init.flags = pll_init->flags; - - if (hw_init.flags & CLK_SET_RATE_GATE) - hw_init.ops = &ccu_pll_gate_to_set_ops; - else - hw_init.ops = &ccu_pll_straight_set_ops; - - if (!pll_init->parent_name) { - ret = -EINVAL; - goto err_free_pll; - } - parent_data.fw_name = pll_init->parent_name; - hw_init.parent_data = &parent_data; - hw_init.num_parents = 1; - - ret = of_clk_hw_register(pll_init->np, &pll->hw); - if (ret) - goto err_free_pll; - - return pll; - -err_free_pll: - kfree(pll); - - return ERR_PTR(ret); -} - -void ccu_pll_hw_unregister(struct ccu_pll *pll) -{ - clk_hw_unregister(&pll->hw); - - kfree(pll); -} diff --git a/drivers/clk/baikal-t1/ccu-pll.h b/drivers/clk/baikal-t1/ccu-pll.h deleted file mode 100644 index a71bfd7b90ecc..0000000000000 --- a/drivers/clk/baikal-t1/ccu-pll.h +++ /dev/null @@ -1,72 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC - * - * Baikal-T1 CCU PLL interface driver - */ -#ifndef __CLK_BT1_CCU_PLL_H__ -#define __CLK_BT1_CCU_PLL_H__ - -#include -#include -#include -#include -#include - -/* - * CCU PLL private flags - * @CCU_PLL_BASIC: Basic PLL required by the kernel as early as possible. - */ -#define CCU_PLL_BASIC BIT(0) - -/* - * struct ccu_pll_init_data - CCU PLL initialization data - * @id: Clock private identifier. - * @name: Clocks name. - * @parent_name: Clocks parent name in a fw node. - * @base: PLL registers base address with respect to the sys_regs base. - * @sys_regs: Baikal-T1 System Controller registers map. - * @np: Pointer to the node describing the CCU PLLs. - * @flags: PLL clock flags. - * @features: PLL private features. - */ -struct ccu_pll_init_data { - unsigned int id; - const char *name; - const char *parent_name; - unsigned int base; - struct regmap *sys_regs; - struct device_node *np; - unsigned long flags; - unsigned long features; -}; - -/* - * struct ccu_pll - CCU PLL descriptor - * @hw: clk_hw of the PLL. - * @id: Clock private identifier. - * @reg_ctl: PLL control register base. - * @reg_ctl1: PLL control1 register base. - * @sys_regs: Baikal-T1 System Controller registers map. - * @lock: PLL state change spin-lock. - */ -struct ccu_pll { - struct clk_hw hw; - unsigned int id; - unsigned int reg_ctl; - unsigned int reg_ctl1; - struct regmap *sys_regs; - spinlock_t lock; -}; -#define to_ccu_pll(_hw) container_of(_hw, struct ccu_pll, hw) - -static inline struct clk_hw *ccu_pll_get_clk_hw(struct ccu_pll *pll) -{ - return pll ? &pll->hw : NULL; -} - -struct ccu_pll *ccu_pll_hw_register(const struct ccu_pll_init_data *init); - -void ccu_pll_hw_unregister(struct ccu_pll *pll); - -#endif /* __CLK_BT1_CCU_PLL_H__ */ diff --git a/drivers/clk/baikal-t1/ccu-rst.c b/drivers/clk/baikal-t1/ccu-rst.c deleted file mode 100644 index 969e5de381a8e..0000000000000 --- a/drivers/clk/baikal-t1/ccu-rst.c +++ /dev/null @@ -1,217 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (C) 2021 BAIKAL ELECTRONICS, JSC - * - * Authors: - * Serge Semin - * - * Baikal-T1 CCU Resets interface driver - */ - -#define pr_fmt(fmt) "bt1-ccu-rst: " fmt - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "ccu-rst.h" - -#define CCU_AXI_MAIN_BASE 0x030 -#define CCU_AXI_DDR_BASE 0x034 -#define CCU_AXI_SATA_BASE 0x038 -#define CCU_AXI_GMAC0_BASE 0x03C -#define CCU_AXI_GMAC1_BASE 0x040 -#define CCU_AXI_XGMAC_BASE 0x044 -#define CCU_AXI_PCIE_M_BASE 0x048 -#define CCU_AXI_PCIE_S_BASE 0x04C -#define CCU_AXI_USB_BASE 0x050 -#define CCU_AXI_HWA_BASE 0x054 -#define CCU_AXI_SRAM_BASE 0x058 - -#define CCU_SYS_DDR_BASE 0x02c -#define CCU_SYS_SATA_REF_BASE 0x060 -#define CCU_SYS_APB_BASE 0x064 -#define CCU_SYS_PCIE_BASE 0x144 - -#define CCU_RST_DELAY_US 1 - -#define CCU_RST_TRIG(_base, _ofs) \ - { \ - .type = CCU_RST_TRIG, \ - .base = _base, \ - .mask = BIT(_ofs), \ - } - -#define CCU_RST_DIR(_base, _ofs) \ - { \ - .type = CCU_RST_DIR, \ - .base = _base, \ - .mask = BIT(_ofs), \ - } - -struct ccu_rst_info { - enum ccu_rst_type type; - unsigned int base; - unsigned int mask; -}; - -/* - * Each AXI-bus clock divider is equipped with the corresponding clock-consumer - * domain reset (it's self-deasserted reset control). - */ -static const struct ccu_rst_info axi_rst_info[] = { - [CCU_AXI_MAIN_RST] = CCU_RST_TRIG(CCU_AXI_MAIN_BASE, 1), - [CCU_AXI_DDR_RST] = CCU_RST_TRIG(CCU_AXI_DDR_BASE, 1), - [CCU_AXI_SATA_RST] = CCU_RST_TRIG(CCU_AXI_SATA_BASE, 1), - [CCU_AXI_GMAC0_RST] = CCU_RST_TRIG(CCU_AXI_GMAC0_BASE, 1), - [CCU_AXI_GMAC1_RST] = CCU_RST_TRIG(CCU_AXI_GMAC1_BASE, 1), - [CCU_AXI_XGMAC_RST] = CCU_RST_TRIG(CCU_AXI_XGMAC_BASE, 1), - [CCU_AXI_PCIE_M_RST] = CCU_RST_TRIG(CCU_AXI_PCIE_M_BASE, 1), - [CCU_AXI_PCIE_S_RST] = CCU_RST_TRIG(CCU_AXI_PCIE_S_BASE, 1), - [CCU_AXI_USB_RST] = CCU_RST_TRIG(CCU_AXI_USB_BASE, 1), - [CCU_AXI_HWA_RST] = CCU_RST_TRIG(CCU_AXI_HWA_BASE, 1), - [CCU_AXI_SRAM_RST] = CCU_RST_TRIG(CCU_AXI_SRAM_BASE, 1), -}; - -/* - * SATA reference clock domain and APB-bus domain are connected with the - * sefl-deasserted reset control, which can be activated via the corresponding - * clock divider register. DDR and PCIe sub-domains can be reset with directly - * controlled reset signals. Resetting the DDR controller though won't end up - * well while the Linux kernel is working. - */ -static const struct ccu_rst_info sys_rst_info[] = { - [CCU_SYS_SATA_REF_RST] = CCU_RST_TRIG(CCU_SYS_SATA_REF_BASE, 1), - [CCU_SYS_APB_RST] = CCU_RST_TRIG(CCU_SYS_APB_BASE, 1), - [CCU_SYS_DDR_FULL_RST] = CCU_RST_DIR(CCU_SYS_DDR_BASE, 1), - [CCU_SYS_DDR_INIT_RST] = CCU_RST_DIR(CCU_SYS_DDR_BASE, 2), - [CCU_SYS_PCIE_PCS_PHY_RST] = CCU_RST_DIR(CCU_SYS_PCIE_BASE, 0), - [CCU_SYS_PCIE_PIPE0_RST] = CCU_RST_DIR(CCU_SYS_PCIE_BASE, 4), - [CCU_SYS_PCIE_CORE_RST] = CCU_RST_DIR(CCU_SYS_PCIE_BASE, 8), - [CCU_SYS_PCIE_PWR_RST] = CCU_RST_DIR(CCU_SYS_PCIE_BASE, 9), - [CCU_SYS_PCIE_STICKY_RST] = CCU_RST_DIR(CCU_SYS_PCIE_BASE, 10), - [CCU_SYS_PCIE_NSTICKY_RST] = CCU_RST_DIR(CCU_SYS_PCIE_BASE, 11), - [CCU_SYS_PCIE_HOT_RST] = CCU_RST_DIR(CCU_SYS_PCIE_BASE, 12), -}; - -static int ccu_rst_reset(struct reset_controller_dev *rcdev, unsigned long idx) -{ - struct ccu_rst *rst = to_ccu_rst(rcdev); - const struct ccu_rst_info *info = &rst->rsts_info[idx]; - - if (info->type != CCU_RST_TRIG) - return -EOPNOTSUPP; - - regmap_update_bits(rst->sys_regs, info->base, info->mask, info->mask); - - /* The next delay must be enough to cover all the resets. */ - udelay(CCU_RST_DELAY_US); - - return 0; -} - -static int ccu_rst_set(struct reset_controller_dev *rcdev, - unsigned long idx, bool high) -{ - struct ccu_rst *rst = to_ccu_rst(rcdev); - const struct ccu_rst_info *info = &rst->rsts_info[idx]; - - if (info->type != CCU_RST_DIR) - return high ? -EOPNOTSUPP : 0; - - return regmap_update_bits(rst->sys_regs, info->base, - info->mask, high ? info->mask : 0); -} - -static int ccu_rst_assert(struct reset_controller_dev *rcdev, - unsigned long idx) -{ - return ccu_rst_set(rcdev, idx, true); -} - -static int ccu_rst_deassert(struct reset_controller_dev *rcdev, - unsigned long idx) -{ - return ccu_rst_set(rcdev, idx, false); -} - -static int ccu_rst_status(struct reset_controller_dev *rcdev, - unsigned long idx) -{ - struct ccu_rst *rst = to_ccu_rst(rcdev); - const struct ccu_rst_info *info = &rst->rsts_info[idx]; - u32 val; - - if (info->type != CCU_RST_DIR) - return -EOPNOTSUPP; - - regmap_read(rst->sys_regs, info->base, &val); - - return !!(val & info->mask); -} - -static const struct reset_control_ops ccu_rst_ops = { - .reset = ccu_rst_reset, - .assert = ccu_rst_assert, - .deassert = ccu_rst_deassert, - .status = ccu_rst_status, -}; - -struct ccu_rst *ccu_rst_hw_register(const struct ccu_rst_init_data *rst_init) -{ - struct ccu_rst *rst; - int ret; - - if (!rst_init) - return ERR_PTR(-EINVAL); - - rst = kzalloc_obj(*rst); - if (!rst) - return ERR_PTR(-ENOMEM); - - rst->sys_regs = rst_init->sys_regs; - if (of_device_is_compatible(rst_init->np, "baikal,bt1-ccu-axi")) { - rst->rcdev.nr_resets = ARRAY_SIZE(axi_rst_info); - rst->rsts_info = axi_rst_info; - } else if (of_device_is_compatible(rst_init->np, "baikal,bt1-ccu-sys")) { - rst->rcdev.nr_resets = ARRAY_SIZE(sys_rst_info); - rst->rsts_info = sys_rst_info; - } else { - pr_err("Incompatible DT node '%s' specified\n", - of_node_full_name(rst_init->np)); - ret = -EINVAL; - goto err_kfree_rst; - } - - rst->rcdev.owner = THIS_MODULE; - rst->rcdev.ops = &ccu_rst_ops; - rst->rcdev.of_node = rst_init->np; - - ret = reset_controller_register(&rst->rcdev); - if (ret) { - pr_err("Couldn't register '%s' reset controller\n", - of_node_full_name(rst_init->np)); - goto err_kfree_rst; - } - - return rst; - -err_kfree_rst: - kfree(rst); - - return ERR_PTR(ret); -} - -void ccu_rst_hw_unregister(struct ccu_rst *rst) -{ - reset_controller_unregister(&rst->rcdev); - - kfree(rst); -} diff --git a/drivers/clk/baikal-t1/ccu-rst.h b/drivers/clk/baikal-t1/ccu-rst.h deleted file mode 100644 index d6e8b2f671f4d..0000000000000 --- a/drivers/clk/baikal-t1/ccu-rst.h +++ /dev/null @@ -1,67 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Copyright (C) 2021 BAIKAL ELECTRONICS, JSC - * - * Baikal-T1 CCU Resets interface driver - */ -#ifndef __CLK_BT1_CCU_RST_H__ -#define __CLK_BT1_CCU_RST_H__ - -#include -#include -#include - -struct ccu_rst_info; - -/* - * enum ccu_rst_type - CCU Reset types - * @CCU_RST_TRIG: Self-deasserted reset signal. - * @CCU_RST_DIR: Directly controlled reset signal. - */ -enum ccu_rst_type { - CCU_RST_TRIG, - CCU_RST_DIR, -}; - -/* - * struct ccu_rst_init_data - CCU Resets initialization data - * @sys_regs: Baikal-T1 System Controller registers map. - * @np: Pointer to the node with the System CCU block. - */ -struct ccu_rst_init_data { - struct regmap *sys_regs; - struct device_node *np; -}; - -/* - * struct ccu_rst - CCU Reset descriptor - * @rcdev: Reset controller descriptor. - * @sys_regs: Baikal-T1 System Controller registers map. - * @rsts_info: Reset flag info (base address and mask). - */ -struct ccu_rst { - struct reset_controller_dev rcdev; - struct regmap *sys_regs; - const struct ccu_rst_info *rsts_info; -}; -#define to_ccu_rst(_rcdev) container_of(_rcdev, struct ccu_rst, rcdev) - -#ifdef CONFIG_CLK_BT1_CCU_RST - -struct ccu_rst *ccu_rst_hw_register(const struct ccu_rst_init_data *init); - -void ccu_rst_hw_unregister(struct ccu_rst *rst); - -#else - -static inline -struct ccu_rst *ccu_rst_hw_register(const struct ccu_rst_init_data *init) -{ - return NULL; -} - -static inline void ccu_rst_hw_unregister(struct ccu_rst *rst) {} - -#endif - -#endif /* __CLK_BT1_CCU_RST_H__ */ diff --git a/drivers/clk/baikal-t1/clk-ccu-div.c b/drivers/clk/baikal-t1/clk-ccu-div.c deleted file mode 100644 index d32072e4dd495..0000000000000 --- a/drivers/clk/baikal-t1/clk-ccu-div.c +++ /dev/null @@ -1,520 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC - * - * Authors: - * Serge Semin - * Dmitry Dunaev - * - * Baikal-T1 CCU Dividers clock driver - */ - -#define pr_fmt(fmt) "bt1-ccu-div: " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "ccu-div.h" -#include "ccu-rst.h" - -#define CCU_AXI_MAIN_BASE 0x030 -#define CCU_AXI_DDR_BASE 0x034 -#define CCU_AXI_SATA_BASE 0x038 -#define CCU_AXI_GMAC0_BASE 0x03C -#define CCU_AXI_GMAC1_BASE 0x040 -#define CCU_AXI_XGMAC_BASE 0x044 -#define CCU_AXI_PCIE_M_BASE 0x048 -#define CCU_AXI_PCIE_S_BASE 0x04C -#define CCU_AXI_USB_BASE 0x050 -#define CCU_AXI_HWA_BASE 0x054 -#define CCU_AXI_SRAM_BASE 0x058 - -#define CCU_SYS_SATA_REF_BASE 0x060 -#define CCU_SYS_APB_BASE 0x064 -#define CCU_SYS_GMAC0_BASE 0x068 -#define CCU_SYS_GMAC1_BASE 0x06C -#define CCU_SYS_XGMAC_BASE 0x070 -#define CCU_SYS_USB_BASE 0x074 -#define CCU_SYS_PVT_BASE 0x078 -#define CCU_SYS_HWA_BASE 0x07C -#define CCU_SYS_UART_BASE 0x084 -#define CCU_SYS_TIMER0_BASE 0x088 -#define CCU_SYS_TIMER1_BASE 0x08C -#define CCU_SYS_TIMER2_BASE 0x090 -#define CCU_SYS_WDT_BASE 0x150 - -#define CCU_DIV_VAR_INFO(_id, _name, _pname, _base, _width, _flags, _features) \ - { \ - .id = _id, \ - .name = _name, \ - .parent_name = _pname, \ - .base = _base, \ - .type = CCU_DIV_VAR, \ - .width = _width, \ - .flags = _flags, \ - .features = _features \ - } - -#define CCU_DIV_GATE_INFO(_id, _name, _pname, _base, _divider) \ - { \ - .id = _id, \ - .name = _name, \ - .parent_name = _pname, \ - .base = _base, \ - .type = CCU_DIV_GATE, \ - .divider = _divider \ - } - -#define CCU_DIV_BUF_INFO(_id, _name, _pname, _base, _flags) \ - { \ - .id = _id, \ - .name = _name, \ - .parent_name = _pname, \ - .base = _base, \ - .type = CCU_DIV_BUF, \ - .flags = _flags \ - } - -#define CCU_DIV_FIXED_INFO(_id, _name, _pname, _divider) \ - { \ - .id = _id, \ - .name = _name, \ - .parent_name = _pname, \ - .type = CCU_DIV_FIXED, \ - .divider = _divider \ - } - -struct ccu_div_info { - unsigned int id; - const char *name; - const char *parent_name; - unsigned int base; - enum ccu_div_type type; - union { - unsigned int width; - unsigned int divider; - }; - unsigned long flags; - unsigned long features; -}; - -struct ccu_div_data { - struct device_node *np; - struct regmap *sys_regs; - - unsigned int divs_num; - const struct ccu_div_info *divs_info; - struct ccu_div **divs; - - struct ccu_rst *rsts; -}; - -/* - * AXI Main Interconnect (axi_main_clk) and DDR AXI-bus (axi_ddr_clk) clocks - * must be left enabled in any case, since former one is responsible for - * clocking a bus between CPU cores and the rest of the SoC components, while - * the later is clocking the AXI-bus between DDR controller and the Main - * Interconnect. So should any of these clocks get to be disabled, the system - * will literally stop working. That's why we marked them as critical. - */ -static const struct ccu_div_info axi_info[] = { - CCU_DIV_VAR_INFO(CCU_AXI_MAIN_CLK, "axi_main_clk", "pcie_clk", - CCU_AXI_MAIN_BASE, 4, - CLK_IS_CRITICAL, CCU_DIV_RESET_DOMAIN), - CCU_DIV_VAR_INFO(CCU_AXI_DDR_CLK, "axi_ddr_clk", "sata_clk", - CCU_AXI_DDR_BASE, 4, - CLK_IS_CRITICAL | CLK_SET_RATE_GATE, - CCU_DIV_RESET_DOMAIN), - CCU_DIV_VAR_INFO(CCU_AXI_SATA_CLK, "axi_sata_clk", "sata_clk", - CCU_AXI_SATA_BASE, 4, - CLK_SET_RATE_GATE, CCU_DIV_RESET_DOMAIN), - CCU_DIV_VAR_INFO(CCU_AXI_GMAC0_CLK, "axi_gmac0_clk", "eth_clk", - CCU_AXI_GMAC0_BASE, 4, - CLK_SET_RATE_GATE, CCU_DIV_RESET_DOMAIN), - CCU_DIV_VAR_INFO(CCU_AXI_GMAC1_CLK, "axi_gmac1_clk", "eth_clk", - CCU_AXI_GMAC1_BASE, 4, - CLK_SET_RATE_GATE, CCU_DIV_RESET_DOMAIN), - CCU_DIV_VAR_INFO(CCU_AXI_XGMAC_CLK, "axi_xgmac_clk", "eth_clk", - CCU_AXI_XGMAC_BASE, 4, - CLK_SET_RATE_GATE, CCU_DIV_RESET_DOMAIN), - CCU_DIV_VAR_INFO(CCU_AXI_PCIE_M_CLK, "axi_pcie_m_clk", "pcie_clk", - CCU_AXI_PCIE_M_BASE, 4, - CLK_SET_RATE_GATE, CCU_DIV_RESET_DOMAIN), - CCU_DIV_VAR_INFO(CCU_AXI_PCIE_S_CLK, "axi_pcie_s_clk", "pcie_clk", - CCU_AXI_PCIE_S_BASE, 4, - CLK_SET_RATE_GATE, CCU_DIV_RESET_DOMAIN), - CCU_DIV_VAR_INFO(CCU_AXI_USB_CLK, "axi_usb_clk", "sata_clk", - CCU_AXI_USB_BASE, 4, - CLK_SET_RATE_GATE, CCU_DIV_RESET_DOMAIN), - CCU_DIV_VAR_INFO(CCU_AXI_HWA_CLK, "axi_hwa_clk", "sata_clk", - CCU_AXI_HWA_BASE, 4, - CLK_SET_RATE_GATE, CCU_DIV_RESET_DOMAIN), - CCU_DIV_VAR_INFO(CCU_AXI_SRAM_CLK, "axi_sram_clk", "eth_clk", - CCU_AXI_SRAM_BASE, 4, - CLK_SET_RATE_GATE, CCU_DIV_RESET_DOMAIN) -}; - -/* - * APB-bus clock is marked as critical since it's a main communication bus - * for the SoC devices registers IO-operations. - */ -static const struct ccu_div_info sys_info[] = { - CCU_DIV_VAR_INFO(CCU_SYS_SATA_CLK, "sys_sata_clk", - "sata_clk", CCU_SYS_SATA_REF_BASE, 4, - CLK_SET_RATE_GATE, - CCU_DIV_SKIP_ONE | CCU_DIV_LOCK_SHIFTED | - CCU_DIV_RESET_DOMAIN), - CCU_DIV_BUF_INFO(CCU_SYS_SATA_REF_CLK, "sys_sata_ref_clk", - "sys_sata_clk", CCU_SYS_SATA_REF_BASE, - CLK_SET_RATE_PARENT), - CCU_DIV_VAR_INFO(CCU_SYS_APB_CLK, "sys_apb_clk", - "pcie_clk", CCU_SYS_APB_BASE, 5, - CLK_IS_CRITICAL, CCU_DIV_BASIC | CCU_DIV_RESET_DOMAIN), - CCU_DIV_GATE_INFO(CCU_SYS_GMAC0_TX_CLK, "sys_gmac0_tx_clk", - "eth_clk", CCU_SYS_GMAC0_BASE, 5), - CCU_DIV_FIXED_INFO(CCU_SYS_GMAC0_PTP_CLK, "sys_gmac0_ptp_clk", - "eth_clk", 10), - CCU_DIV_GATE_INFO(CCU_SYS_GMAC1_TX_CLK, "sys_gmac1_tx_clk", - "eth_clk", CCU_SYS_GMAC1_BASE, 5), - CCU_DIV_FIXED_INFO(CCU_SYS_GMAC1_PTP_CLK, "sys_gmac1_ptp_clk", - "eth_clk", 10), - CCU_DIV_GATE_INFO(CCU_SYS_XGMAC_CLK, "sys_xgmac_clk", - "eth_clk", CCU_SYS_XGMAC_BASE, 1), - CCU_DIV_FIXED_INFO(CCU_SYS_XGMAC_REF_CLK, "sys_xgmac_ref_clk", - "sys_xgmac_clk", 8), - CCU_DIV_FIXED_INFO(CCU_SYS_XGMAC_PTP_CLK, "sys_xgmac_ptp_clk", - "sys_xgmac_clk", 8), - CCU_DIV_GATE_INFO(CCU_SYS_USB_CLK, "sys_usb_clk", - "eth_clk", CCU_SYS_USB_BASE, 10), - CCU_DIV_VAR_INFO(CCU_SYS_PVT_CLK, "sys_pvt_clk", - "ref_clk", CCU_SYS_PVT_BASE, 5, - CLK_SET_RATE_GATE, 0), - CCU_DIV_VAR_INFO(CCU_SYS_HWA_CLK, "sys_hwa_clk", - "sata_clk", CCU_SYS_HWA_BASE, 4, - CLK_SET_RATE_GATE, 0), - CCU_DIV_VAR_INFO(CCU_SYS_UART_CLK, "sys_uart_clk", - "eth_clk", CCU_SYS_UART_BASE, 17, - CLK_SET_RATE_GATE, 0), - CCU_DIV_FIXED_INFO(CCU_SYS_I2C1_CLK, "sys_i2c1_clk", - "eth_clk", 10), - CCU_DIV_FIXED_INFO(CCU_SYS_I2C2_CLK, "sys_i2c2_clk", - "eth_clk", 10), - CCU_DIV_FIXED_INFO(CCU_SYS_GPIO_CLK, "sys_gpio_clk", - "ref_clk", 25), - CCU_DIV_VAR_INFO(CCU_SYS_TIMER0_CLK, "sys_timer0_clk", - "ref_clk", CCU_SYS_TIMER0_BASE, 17, - CLK_SET_RATE_GATE, CCU_DIV_BASIC), - CCU_DIV_VAR_INFO(CCU_SYS_TIMER1_CLK, "sys_timer1_clk", - "ref_clk", CCU_SYS_TIMER1_BASE, 17, - CLK_SET_RATE_GATE, CCU_DIV_BASIC), - CCU_DIV_VAR_INFO(CCU_SYS_TIMER2_CLK, "sys_timer2_clk", - "ref_clk", CCU_SYS_TIMER2_BASE, 17, - CLK_SET_RATE_GATE, CCU_DIV_BASIC), - CCU_DIV_VAR_INFO(CCU_SYS_WDT_CLK, "sys_wdt_clk", - "eth_clk", CCU_SYS_WDT_BASE, 17, - CLK_SET_RATE_GATE, CCU_DIV_SKIP_ONE_TO_THREE) -}; - -static struct ccu_div_data *axi_data; -static struct ccu_div_data *sys_data; - -static void ccu_div_set_data(struct ccu_div_data *data) -{ - struct device_node *np = data->np; - - if (of_device_is_compatible(np, "baikal,bt1-ccu-axi")) - axi_data = data; - else if (of_device_is_compatible(np, "baikal,bt1-ccu-sys")) - sys_data = data; - else - pr_err("Invalid DT node '%s' specified\n", of_node_full_name(np)); -} - -static struct ccu_div_data *ccu_div_get_data(struct device_node *np) -{ - if (of_device_is_compatible(np, "baikal,bt1-ccu-axi")) - return axi_data; - else if (of_device_is_compatible(np, "baikal,bt1-ccu-sys")) - return sys_data; - - pr_err("Invalid DT node '%s' specified\n", of_node_full_name(np)); - - return NULL; -} - -static struct ccu_div *ccu_div_find_desc(struct ccu_div_data *data, - unsigned int clk_id) -{ - int idx; - - for (idx = 0; idx < data->divs_num; ++idx) { - if (data->divs_info[idx].id == clk_id) - return data->divs[idx]; - } - - return ERR_PTR(-EINVAL); -} - -static struct ccu_div_data *ccu_div_create_data(struct device_node *np) -{ - struct ccu_div_data *data; - int ret; - - data = kzalloc_obj(*data); - if (!data) - return ERR_PTR(-ENOMEM); - - data->np = np; - if (of_device_is_compatible(np, "baikal,bt1-ccu-axi")) { - data->divs_num = ARRAY_SIZE(axi_info); - data->divs_info = axi_info; - } else if (of_device_is_compatible(np, "baikal,bt1-ccu-sys")) { - data->divs_num = ARRAY_SIZE(sys_info); - data->divs_info = sys_info; - } else { - pr_err("Incompatible DT node '%s' specified\n", - of_node_full_name(np)); - ret = -EINVAL; - goto err_kfree_data; - } - - data->divs = kzalloc_objs(*data->divs, data->divs_num); - if (!data->divs) { - ret = -ENOMEM; - goto err_kfree_data; - } - - return data; - -err_kfree_data: - kfree(data); - - return ERR_PTR(ret); -} - -static void ccu_div_free_data(struct ccu_div_data *data) -{ - kfree(data->divs); - - kfree(data); -} - -static int ccu_div_find_sys_regs(struct ccu_div_data *data) -{ - data->sys_regs = syscon_node_to_regmap(data->np->parent); - if (IS_ERR(data->sys_regs)) { - pr_err("Failed to find syscon regs for '%s'\n", - of_node_full_name(data->np)); - return PTR_ERR(data->sys_regs); - } - - return 0; -} - -static struct clk_hw *ccu_div_of_clk_hw_get(struct of_phandle_args *clkspec, - void *priv) -{ - struct ccu_div_data *data = priv; - struct ccu_div *div; - unsigned int clk_id; - - clk_id = clkspec->args[0]; - div = ccu_div_find_desc(data, clk_id); - if (IS_ERR(div)) { - if (div != ERR_PTR(-EPROBE_DEFER)) - pr_info("Invalid clock ID %d specified\n", clk_id); - - return ERR_CAST(div); - } - - return ccu_div_get_clk_hw(div); -} - -static int ccu_div_clk_register(struct ccu_div_data *data, bool defer) -{ - int idx, ret; - - for (idx = 0; idx < data->divs_num; ++idx) { - const struct ccu_div_info *info = &data->divs_info[idx]; - struct ccu_div_init_data init = {0}; - - if (!!(info->features & CCU_DIV_BASIC) ^ defer) { - if (!data->divs[idx]) - data->divs[idx] = ERR_PTR(-EPROBE_DEFER); - - continue; - } - - init.id = info->id; - init.name = info->name; - init.parent_name = info->parent_name; - init.np = data->np; - init.type = info->type; - init.flags = info->flags; - init.features = info->features; - - if (init.type == CCU_DIV_VAR) { - init.base = info->base; - init.sys_regs = data->sys_regs; - init.width = info->width; - } else if (init.type == CCU_DIV_GATE) { - init.base = info->base; - init.sys_regs = data->sys_regs; - init.divider = info->divider; - } else if (init.type == CCU_DIV_BUF) { - init.base = info->base; - init.sys_regs = data->sys_regs; - } else { - init.divider = info->divider; - } - - data->divs[idx] = ccu_div_hw_register(&init); - if (IS_ERR(data->divs[idx])) { - ret = PTR_ERR(data->divs[idx]); - pr_err("Couldn't register divider '%s' hw\n", - init.name); - goto err_hw_unregister; - } - } - - return 0; - -err_hw_unregister: - for (--idx; idx >= 0; --idx) { - if (!!(data->divs_info[idx].features & CCU_DIV_BASIC) ^ defer) - continue; - - ccu_div_hw_unregister(data->divs[idx]); - } - - return ret; -} - -static void ccu_div_clk_unregister(struct ccu_div_data *data, bool defer) -{ - int idx; - - /* Uninstall only the clocks registered on the specified stage */ - for (idx = 0; idx < data->divs_num; ++idx) { - if (!!(data->divs_info[idx].features & CCU_DIV_BASIC) ^ defer) - continue; - - ccu_div_hw_unregister(data->divs[idx]); - } -} - -static int ccu_div_of_register(struct ccu_div_data *data) -{ - int ret; - - ret = of_clk_add_hw_provider(data->np, ccu_div_of_clk_hw_get, data); - if (ret) { - pr_err("Couldn't register dividers '%s' clock provider\n", - of_node_full_name(data->np)); - } - - return ret; -} - -static int ccu_div_rst_register(struct ccu_div_data *data) -{ - struct ccu_rst_init_data init = {0}; - - init.sys_regs = data->sys_regs; - init.np = data->np; - - data->rsts = ccu_rst_hw_register(&init); - if (IS_ERR(data->rsts)) { - pr_err("Couldn't register divider '%s' reset controller\n", - of_node_full_name(data->np)); - return PTR_ERR(data->rsts); - } - - return 0; -} - -static int ccu_div_probe(struct platform_device *pdev) -{ - struct ccu_div_data *data; - int ret; - - data = ccu_div_get_data(dev_of_node(&pdev->dev)); - if (!data) - return -EINVAL; - - ret = ccu_div_clk_register(data, false); - if (ret) - return ret; - - ret = ccu_div_rst_register(data); - if (ret) - goto err_clk_unregister; - - return 0; - -err_clk_unregister: - ccu_div_clk_unregister(data, false); - - return ret; -} - -static const struct of_device_id ccu_div_of_match[] = { - { .compatible = "baikal,bt1-ccu-axi" }, - { .compatible = "baikal,bt1-ccu-sys" }, - { } -}; - -static struct platform_driver ccu_div_driver = { - .probe = ccu_div_probe, - .driver = { - .name = "clk-ccu-div", - .of_match_table = ccu_div_of_match, - .suppress_bind_attrs = true, - }, -}; -builtin_platform_driver(ccu_div_driver); - -static __init void ccu_div_init(struct device_node *np) -{ - struct ccu_div_data *data; - int ret; - - data = ccu_div_create_data(np); - if (IS_ERR(data)) - return; - - ret = ccu_div_find_sys_regs(data); - if (ret) - goto err_free_data; - - ret = ccu_div_clk_register(data, true); - if (ret) - goto err_free_data; - - ret = ccu_div_of_register(data); - if (ret) - goto err_clk_unregister; - - ccu_div_set_data(data); - - return; - -err_clk_unregister: - ccu_div_clk_unregister(data, true); - -err_free_data: - ccu_div_free_data(data); -} -CLK_OF_DECLARE_DRIVER(ccu_axi, "baikal,bt1-ccu-axi", ccu_div_init); -CLK_OF_DECLARE_DRIVER(ccu_sys, "baikal,bt1-ccu-sys", ccu_div_init); diff --git a/drivers/clk/baikal-t1/clk-ccu-pll.c b/drivers/clk/baikal-t1/clk-ccu-pll.c deleted file mode 100644 index e5e4a6ea6f782..0000000000000 --- a/drivers/clk/baikal-t1/clk-ccu-pll.c +++ /dev/null @@ -1,277 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC - * - * Authors: - * Serge Semin - * Dmitry Dunaev - * - * Baikal-T1 CCU PLL clocks driver - */ - -#define pr_fmt(fmt) "bt1-ccu-pll: " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "ccu-pll.h" - -#define CCU_CPU_PLL_BASE 0x000 -#define CCU_SATA_PLL_BASE 0x008 -#define CCU_DDR_PLL_BASE 0x010 -#define CCU_PCIE_PLL_BASE 0x018 -#define CCU_ETH_PLL_BASE 0x020 - -#define CCU_PLL_INFO(_id, _name, _pname, _base, _flags, _features) \ - { \ - .id = _id, \ - .name = _name, \ - .parent_name = _pname, \ - .base = _base, \ - .flags = _flags, \ - .features = _features, \ - } - -#define CCU_PLL_NUM ARRAY_SIZE(pll_info) - -struct ccu_pll_info { - unsigned int id; - const char *name; - const char *parent_name; - unsigned int base; - unsigned long flags; - unsigned long features; -}; - -/* - * Alas we have to mark all PLLs as critical. CPU and DDR PLLs are sources of - * CPU cores and DDR controller reference clocks, due to which they obviously - * shouldn't be ever gated. SATA and PCIe PLLs are the parents of APB-bus and - * DDR controller AXI-bus clocks. If they are gated the system will be - * unusable. Moreover disabling SATA and Ethernet PLLs causes automatic reset - * of the corresponding subsystems. So until we aren't ready to re-initialize - * all the devices consuming those PLLs, they will be marked as critical too. - */ -static const struct ccu_pll_info pll_info[] = { - CCU_PLL_INFO(CCU_CPU_PLL, "cpu_pll", "ref_clk", CCU_CPU_PLL_BASE, - CLK_IS_CRITICAL, CCU_PLL_BASIC), - CCU_PLL_INFO(CCU_SATA_PLL, "sata_pll", "ref_clk", CCU_SATA_PLL_BASE, - CLK_IS_CRITICAL | CLK_SET_RATE_GATE, 0), - CCU_PLL_INFO(CCU_DDR_PLL, "ddr_pll", "ref_clk", CCU_DDR_PLL_BASE, - CLK_IS_CRITICAL | CLK_SET_RATE_GATE, 0), - CCU_PLL_INFO(CCU_PCIE_PLL, "pcie_pll", "ref_clk", CCU_PCIE_PLL_BASE, - CLK_IS_CRITICAL, CCU_PLL_BASIC), - CCU_PLL_INFO(CCU_ETH_PLL, "eth_pll", "ref_clk", CCU_ETH_PLL_BASE, - CLK_IS_CRITICAL | CLK_SET_RATE_GATE, 0) -}; - -struct ccu_pll_data { - struct device_node *np; - struct regmap *sys_regs; - struct ccu_pll *plls[CCU_PLL_NUM]; -}; - -static struct ccu_pll_data *pll_data; - -static struct ccu_pll *ccu_pll_find_desc(struct ccu_pll_data *data, - unsigned int clk_id) -{ - int idx; - - for (idx = 0; idx < CCU_PLL_NUM; ++idx) { - if (pll_info[idx].id == clk_id) - return data->plls[idx]; - } - - return ERR_PTR(-EINVAL); -} - -static struct ccu_pll_data *ccu_pll_create_data(struct device_node *np) -{ - struct ccu_pll_data *data; - - data = kzalloc_obj(*data); - if (!data) - return ERR_PTR(-ENOMEM); - - data->np = np; - - return data; -} - -static void ccu_pll_free_data(struct ccu_pll_data *data) -{ - kfree(data); -} - -static int ccu_pll_find_sys_regs(struct ccu_pll_data *data) -{ - data->sys_regs = syscon_node_to_regmap(data->np->parent); - if (IS_ERR(data->sys_regs)) { - pr_err("Failed to find syscon regs for '%s'\n", - of_node_full_name(data->np)); - return PTR_ERR(data->sys_regs); - } - - return 0; -} - -static struct clk_hw *ccu_pll_of_clk_hw_get(struct of_phandle_args *clkspec, - void *priv) -{ - struct ccu_pll_data *data = priv; - struct ccu_pll *pll; - unsigned int clk_id; - - clk_id = clkspec->args[0]; - pll = ccu_pll_find_desc(data, clk_id); - if (IS_ERR(pll)) { - if (pll != ERR_PTR(-EPROBE_DEFER)) - pr_info("Invalid PLL clock ID %d specified\n", clk_id); - - return ERR_CAST(pll); - } - - return ccu_pll_get_clk_hw(pll); -} - -static int ccu_pll_clk_register(struct ccu_pll_data *data, bool defer) -{ - int idx, ret; - - for (idx = 0; idx < CCU_PLL_NUM; ++idx) { - const struct ccu_pll_info *info = &pll_info[idx]; - struct ccu_pll_init_data init = {0}; - - /* Defer non-basic PLLs allocation for the probe stage */ - if (!!(info->features & CCU_PLL_BASIC) ^ defer) { - if (!data->plls[idx]) - data->plls[idx] = ERR_PTR(-EPROBE_DEFER); - - continue; - } - - init.id = info->id; - init.name = info->name; - init.parent_name = info->parent_name; - init.base = info->base; - init.sys_regs = data->sys_regs; - init.np = data->np; - init.flags = info->flags; - init.features = info->features; - - data->plls[idx] = ccu_pll_hw_register(&init); - if (IS_ERR(data->plls[idx])) { - ret = PTR_ERR(data->plls[idx]); - pr_err("Couldn't register PLL hw '%s'\n", - init.name); - goto err_hw_unregister; - } - } - - return 0; - -err_hw_unregister: - for (--idx; idx >= 0; --idx) { - if (!!(pll_info[idx].features & CCU_PLL_BASIC) ^ defer) - continue; - - ccu_pll_hw_unregister(data->plls[idx]); - } - - return ret; -} - -static void ccu_pll_clk_unregister(struct ccu_pll_data *data, bool defer) -{ - int idx; - - /* Uninstall only the clocks registered on the specified stage */ - for (idx = 0; idx < CCU_PLL_NUM; ++idx) { - if (!!(pll_info[idx].features & CCU_PLL_BASIC) ^ defer) - continue; - - ccu_pll_hw_unregister(data->plls[idx]); - } -} - -static int ccu_pll_of_register(struct ccu_pll_data *data) -{ - int ret; - - ret = of_clk_add_hw_provider(data->np, ccu_pll_of_clk_hw_get, data); - if (ret) { - pr_err("Couldn't register PLL provider of '%s'\n", - of_node_full_name(data->np)); - } - - return ret; -} - -static int ccu_pll_probe(struct platform_device *pdev) -{ - struct ccu_pll_data *data = pll_data; - - if (!data) - return -EINVAL; - - return ccu_pll_clk_register(data, false); -} - -static const struct of_device_id ccu_pll_of_match[] = { - { .compatible = "baikal,bt1-ccu-pll" }, - { } -}; - -static struct platform_driver ccu_pll_driver = { - .probe = ccu_pll_probe, - .driver = { - .name = "clk-ccu-pll", - .of_match_table = ccu_pll_of_match, - .suppress_bind_attrs = true, - }, -}; -builtin_platform_driver(ccu_pll_driver); - -static __init void ccu_pll_init(struct device_node *np) -{ - struct ccu_pll_data *data; - int ret; - - data = ccu_pll_create_data(np); - if (IS_ERR(data)) - return; - - ret = ccu_pll_find_sys_regs(data); - if (ret) - goto err_free_data; - - ret = ccu_pll_clk_register(data, true); - if (ret) - goto err_free_data; - - ret = ccu_pll_of_register(data); - if (ret) - goto err_clk_unregister; - - pll_data = data; - - return; - -err_clk_unregister: - ccu_pll_clk_unregister(data, true); - -err_free_data: - ccu_pll_free_data(data); -} -CLK_OF_DECLARE_DRIVER(ccu_pll, "baikal,bt1-ccu-pll", ccu_pll_init); diff --git a/drivers/clk/bt1/Kconfig b/drivers/clk/bt1/Kconfig new file mode 100644 index 0000000000000..f0b1868303243 --- /dev/null +++ b/drivers/clk/bt1/Kconfig @@ -0,0 +1,52 @@ +# SPDX-License-Identifier: GPL-2.0-only +config CLK_BAIKAL_T1 + bool "Baikal-T1 Clocks Control Unit interface" + depends on (MIPS_BAIKAL_T1 && OF) || COMPILE_TEST + default MIPS_BAIKAL_T1 + help + Clocks Control Unit is the core of Baikal-T1 SoC System Controller + responsible for the chip subsystems clocking and resetting. It + consists of multiple global clock domains, which can be reset by + means of the CCU control registers. These domains and devices placed + in them are fed with clocks generated by a hierarchy of PLLs, + configurable and fixed clock dividers. Enable this option to be able + to select Baikal-T1 CCU PLLs and Dividers drivers. + +if CLK_BAIKAL_T1 + +config CLK_BT1_CCU_PLL + bool "Baikal-T1 CCU PLLs support" + select MFD_SYSCON + default MIPS_BAIKAL_T1 + help + Enable this to support the PLLs embedded into the Baikal-T1 SoC + System Controller. These are five PLLs placed at the root of the + clocks hierarchy, right after an external reference oscillator + (normally of 25MHz). They are used to generate high frequency + signals, which are either directly wired to the consumers (like + CPUs, DDR, etc.) or passed over the clock dividers to be only + then used as an individual reference clock of a target device. + +config CLK_BT1_CCU_DIV + bool "Baikal-T1 CCU Dividers support" + select MFD_SYSCON + default MIPS_BAIKAL_T1 + help + Enable this to support the CCU dividers used to distribute clocks + between AXI-bus and system devices coming from CCU PLLs of Baikal-T1 + SoC. CCU dividers can be either configurable or with fixed divider, + either gateable or ungateable. Some of the CCU dividers can be as well + used to reset the domains they're supplying clock to. + +config CLK_BT1_CCU_RST + bool "Baikal-T1 CCU Resets support" + select RESET_CONTROLLER + select MFD_SYSCON + default MIPS_BAIKAL_T1 + help + Enable this to support the CCU reset blocks responsible for the + AXI-bus and some subsystems reset. These are mainly the + self-deasserted reset controls but there are several lines which + can be directly asserted/de-asserted (PCIe and DDR sub-domains). + +endif diff --git a/drivers/clk/bt1/Makefile b/drivers/clk/bt1/Makefile new file mode 100644 index 0000000000000..9c3637de94070 --- /dev/null +++ b/drivers/clk/bt1/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_CLK_BT1_CCU_PLL) += ccu-pll.o clk-ccu-pll.o +obj-$(CONFIG_CLK_BT1_CCU_DIV) += ccu-div.o clk-ccu-div.o +obj-$(CONFIG_CLK_BT1_CCU_RST) += ccu-rst.o diff --git a/drivers/clk/bt1/ccu-div.c b/drivers/clk/bt1/ccu-div.c new file mode 100644 index 0000000000000..cc48e580e1599 --- /dev/null +++ b/drivers/clk/bt1/ccu-div.c @@ -0,0 +1,653 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC + * + * Authors: + * Serge Semin + * Dmitry Dunaev + * + * Baikal-T1 CCU Dividers interface driver + */ + +#define pr_fmt(fmt) "bt1-ccu-div: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ccu-div.h" + +#define CCU_DIV_CTL 0x00 +#define CCU_DIV_CTL_EN BIT(0) +#define CCU_DIV_CTL_RST BIT(1) +#define CCU_DIV_CTL_SET_CLKDIV BIT(2) +#define CCU_DIV_CTL_CLKDIV_FLD 4 +#define CCU_DIV_CTL_CLKDIV_MASK(_width) \ + GENMASK((_width) + CCU_DIV_CTL_CLKDIV_FLD - 1, CCU_DIV_CTL_CLKDIV_FLD) +#define CCU_DIV_CTL_LOCK_SHIFTED BIT(27) +#define CCU_DIV_CTL_GATE_REF_BUF BIT(28) +#define CCU_DIV_CTL_LOCK_NORMAL BIT(31) + +#define CCU_DIV_LOCK_CHECK_RETRIES 50 + +#define CCU_DIV_CLKDIV_MIN 0 +#define CCU_DIV_CLKDIV_MAX(_mask) \ + ((_mask) >> CCU_DIV_CTL_CLKDIV_FLD) + +/* + * Use the next two methods until there are generic field setter and + * getter available with non-constant mask support. + */ +static inline u32 ccu_div_get(u32 mask, u32 val) +{ + return (val & mask) >> CCU_DIV_CTL_CLKDIV_FLD; +} + +static inline u32 ccu_div_prep(u32 mask, u32 val) +{ + return (val << CCU_DIV_CTL_CLKDIV_FLD) & mask; +} + +static inline unsigned long ccu_div_lock_delay_ns(unsigned long ref_clk, + unsigned long div) +{ + u64 ns = 4ULL * (div ?: 1) * NSEC_PER_SEC; + + do_div(ns, ref_clk); + + return ns; +} + +static inline unsigned long ccu_div_calc_freq(unsigned long ref_clk, + unsigned long div) +{ + return ref_clk / (div ?: 1); +} + +static int ccu_div_var_update_clkdiv(struct ccu_div *div, + unsigned long parent_rate, + unsigned long divider) +{ + unsigned long nd; + u32 val = 0; + u32 lock; + int count; + + nd = ccu_div_lock_delay_ns(parent_rate, divider); + + if (div->features & CCU_DIV_LOCK_SHIFTED) + lock = CCU_DIV_CTL_LOCK_SHIFTED; + else + lock = CCU_DIV_CTL_LOCK_NORMAL; + + regmap_update_bits(div->sys_regs, div->reg_ctl, + CCU_DIV_CTL_SET_CLKDIV, CCU_DIV_CTL_SET_CLKDIV); + + /* + * Until there is nsec-version of readl_poll_timeout() is available + * we have to implement the next polling loop. + */ + count = CCU_DIV_LOCK_CHECK_RETRIES; + do { + ndelay(nd); + regmap_read(div->sys_regs, div->reg_ctl, &val); + if (val & lock) + return 0; + } while (--count); + + return -ETIMEDOUT; +} + +static int ccu_div_var_enable(struct clk_hw *hw) +{ + struct clk_hw *parent_hw = clk_hw_get_parent(hw); + struct ccu_div *div = to_ccu_div(hw); + unsigned long flags; + u32 val = 0; + int ret; + + if (!parent_hw) { + pr_err("Can't enable '%s' with no parent", clk_hw_get_name(hw)); + return -EINVAL; + } + + regmap_read(div->sys_regs, div->reg_ctl, &val); + if (val & CCU_DIV_CTL_EN) + return 0; + + spin_lock_irqsave(&div->lock, flags); + ret = ccu_div_var_update_clkdiv(div, clk_hw_get_rate(parent_hw), + ccu_div_get(div->mask, val)); + if (!ret) + regmap_update_bits(div->sys_regs, div->reg_ctl, + CCU_DIV_CTL_EN, CCU_DIV_CTL_EN); + spin_unlock_irqrestore(&div->lock, flags); + if (ret) + pr_err("Divider '%s' lock timed out\n", clk_hw_get_name(hw)); + + return ret; +} + +static int ccu_div_gate_enable(struct clk_hw *hw) +{ + struct ccu_div *div = to_ccu_div(hw); + unsigned long flags; + + spin_lock_irqsave(&div->lock, flags); + regmap_update_bits(div->sys_regs, div->reg_ctl, + CCU_DIV_CTL_EN, CCU_DIV_CTL_EN); + spin_unlock_irqrestore(&div->lock, flags); + + return 0; +} + +static void ccu_div_gate_disable(struct clk_hw *hw) +{ + struct ccu_div *div = to_ccu_div(hw); + unsigned long flags; + + spin_lock_irqsave(&div->lock, flags); + regmap_update_bits(div->sys_regs, div->reg_ctl, CCU_DIV_CTL_EN, 0); + spin_unlock_irqrestore(&div->lock, flags); +} + +static int ccu_div_gate_is_enabled(struct clk_hw *hw) +{ + struct ccu_div *div = to_ccu_div(hw); + u32 val = 0; + + regmap_read(div->sys_regs, div->reg_ctl, &val); + + return !!(val & CCU_DIV_CTL_EN); +} + +static int ccu_div_buf_enable(struct clk_hw *hw) +{ + struct ccu_div *div = to_ccu_div(hw); + unsigned long flags; + + spin_lock_irqsave(&div->lock, flags); + regmap_update_bits(div->sys_regs, div->reg_ctl, + CCU_DIV_CTL_GATE_REF_BUF, 0); + spin_unlock_irqrestore(&div->lock, flags); + + return 0; +} + +static void ccu_div_buf_disable(struct clk_hw *hw) +{ + struct ccu_div *div = to_ccu_div(hw); + unsigned long flags; + + spin_lock_irqsave(&div->lock, flags); + regmap_update_bits(div->sys_regs, div->reg_ctl, + CCU_DIV_CTL_GATE_REF_BUF, CCU_DIV_CTL_GATE_REF_BUF); + spin_unlock_irqrestore(&div->lock, flags); +} + +static int ccu_div_buf_is_enabled(struct clk_hw *hw) +{ + struct ccu_div *div = to_ccu_div(hw); + u32 val = 0; + + regmap_read(div->sys_regs, div->reg_ctl, &val); + + return !(val & CCU_DIV_CTL_GATE_REF_BUF); +} + +static unsigned long ccu_div_var_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct ccu_div *div = to_ccu_div(hw); + unsigned long divider; + u32 val = 0; + + regmap_read(div->sys_regs, div->reg_ctl, &val); + divider = ccu_div_get(div->mask, val); + + return ccu_div_calc_freq(parent_rate, divider); +} + +static inline unsigned long ccu_div_var_calc_divider(unsigned long rate, + unsigned long parent_rate, + unsigned int mask) +{ + unsigned long divider; + + divider = parent_rate / rate; + return clamp_t(unsigned long, divider, CCU_DIV_CLKDIV_MIN, + CCU_DIV_CLKDIV_MAX(mask)); +} + +static int ccu_div_var_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct ccu_div *div = to_ccu_div(hw); + unsigned long divider; + + divider = ccu_div_var_calc_divider(req->rate, req->best_parent_rate, + div->mask); + + req->rate = ccu_div_calc_freq(req->best_parent_rate, divider); + + return 0; +} + +/* + * This method is used for the clock divider blocks, which support the + * on-the-fly rate change. So due to lacking the EN bit functionality + * they can't be gated before the rate adjustment. + */ +static int ccu_div_var_set_rate_slow(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct ccu_div *div = to_ccu_div(hw); + unsigned long flags, divider; + u32 val; + int ret; + + divider = ccu_div_var_calc_divider(rate, parent_rate, div->mask); + if (divider == 1 && div->features & CCU_DIV_SKIP_ONE) { + divider = 0; + } else if (div->features & CCU_DIV_SKIP_ONE_TO_THREE) { + if (divider == 1 || divider == 2) + divider = 0; + else if (divider == 3) + divider = 4; + } + + val = ccu_div_prep(div->mask, divider); + + spin_lock_irqsave(&div->lock, flags); + regmap_update_bits(div->sys_regs, div->reg_ctl, div->mask, val); + ret = ccu_div_var_update_clkdiv(div, parent_rate, divider); + spin_unlock_irqrestore(&div->lock, flags); + if (ret) + pr_err("Divider '%s' lock timed out\n", clk_hw_get_name(hw)); + + return ret; +} + +/* + * This method is used for the clock divider blocks, which don't support + * the on-the-fly rate change. + */ +static int ccu_div_var_set_rate_fast(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct ccu_div *div = to_ccu_div(hw); + unsigned long flags, divider; + u32 val; + + divider = ccu_div_var_calc_divider(rate, parent_rate, div->mask); + val = ccu_div_prep(div->mask, divider); + + /* + * Also disable the clock divider block if it was enabled by default + * or by the bootloader. + */ + spin_lock_irqsave(&div->lock, flags); + regmap_update_bits(div->sys_regs, div->reg_ctl, + div->mask | CCU_DIV_CTL_EN, val); + spin_unlock_irqrestore(&div->lock, flags); + + return 0; +} + +static unsigned long ccu_div_fixed_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct ccu_div *div = to_ccu_div(hw); + + return ccu_div_calc_freq(parent_rate, div->divider); +} + +static int ccu_div_fixed_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct ccu_div *div = to_ccu_div(hw); + + req->rate = ccu_div_calc_freq(req->best_parent_rate, div->divider); + + return 0; +} + +static int ccu_div_fixed_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + return 0; +} + +#ifdef CONFIG_DEBUG_FS + +struct ccu_div_dbgfs_bit { + struct ccu_div *div; + const char *name; + u32 mask; +}; + +#define CCU_DIV_DBGFS_BIT_ATTR(_name, _mask) { \ + .name = _name, \ + .mask = _mask \ + } + +static const struct ccu_div_dbgfs_bit ccu_div_bits[] = { + CCU_DIV_DBGFS_BIT_ATTR("div_en", CCU_DIV_CTL_EN), + CCU_DIV_DBGFS_BIT_ATTR("div_rst", CCU_DIV_CTL_RST), + CCU_DIV_DBGFS_BIT_ATTR("div_bypass", CCU_DIV_CTL_SET_CLKDIV), + CCU_DIV_DBGFS_BIT_ATTR("div_buf", CCU_DIV_CTL_GATE_REF_BUF), + CCU_DIV_DBGFS_BIT_ATTR("div_lock", CCU_DIV_CTL_LOCK_NORMAL) +}; + +#define CCU_DIV_DBGFS_BIT_NUM ARRAY_SIZE(ccu_div_bits) + +/* + * It can be dangerous to change the Divider settings behind clock framework + * back, therefore we don't provide any kernel config based compile time option + * for this feature to enable. + */ +#undef CCU_DIV_ALLOW_WRITE_DEBUGFS +#ifdef CCU_DIV_ALLOW_WRITE_DEBUGFS + +static int ccu_div_dbgfs_bit_set(void *priv, u64 val) +{ + const struct ccu_div_dbgfs_bit *bit = priv; + struct ccu_div *div = bit->div; + unsigned long flags; + + spin_lock_irqsave(&div->lock, flags); + regmap_update_bits(div->sys_regs, div->reg_ctl, + bit->mask, val ? bit->mask : 0); + spin_unlock_irqrestore(&div->lock, flags); + + return 0; +} + +static int ccu_div_dbgfs_var_clkdiv_set(void *priv, u64 val) +{ + struct ccu_div *div = priv; + unsigned long flags; + u32 data; + + val = clamp_t(u64, val, CCU_DIV_CLKDIV_MIN, + CCU_DIV_CLKDIV_MAX(div->mask)); + data = ccu_div_prep(div->mask, val); + + spin_lock_irqsave(&div->lock, flags); + regmap_update_bits(div->sys_regs, div->reg_ctl, div->mask, data); + spin_unlock_irqrestore(&div->lock, flags); + + return 0; +} + +#define ccu_div_dbgfs_mode 0644 + +#else /* !CCU_DIV_ALLOW_WRITE_DEBUGFS */ + +#define ccu_div_dbgfs_bit_set NULL +#define ccu_div_dbgfs_var_clkdiv_set NULL +#define ccu_div_dbgfs_mode 0444 + +#endif /* !CCU_DIV_ALLOW_WRITE_DEBUGFS */ + +static int ccu_div_dbgfs_bit_get(void *priv, u64 *val) +{ + const struct ccu_div_dbgfs_bit *bit = priv; + struct ccu_div *div = bit->div; + u32 data = 0; + + regmap_read(div->sys_regs, div->reg_ctl, &data); + *val = !!(data & bit->mask); + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(ccu_div_dbgfs_bit_fops, + ccu_div_dbgfs_bit_get, ccu_div_dbgfs_bit_set, "%llu\n"); + +static int ccu_div_dbgfs_var_clkdiv_get(void *priv, u64 *val) +{ + struct ccu_div *div = priv; + u32 data = 0; + + regmap_read(div->sys_regs, div->reg_ctl, &data); + *val = ccu_div_get(div->mask, data); + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(ccu_div_dbgfs_var_clkdiv_fops, + ccu_div_dbgfs_var_clkdiv_get, ccu_div_dbgfs_var_clkdiv_set, "%llu\n"); + +static int ccu_div_dbgfs_fixed_clkdiv_get(void *priv, u64 *val) +{ + struct ccu_div *div = priv; + + *val = div->divider; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(ccu_div_dbgfs_fixed_clkdiv_fops, + ccu_div_dbgfs_fixed_clkdiv_get, NULL, "%llu\n"); + +static void ccu_div_var_debug_init(struct clk_hw *hw, struct dentry *dentry) +{ + struct ccu_div *div = to_ccu_div(hw); + struct ccu_div_dbgfs_bit *bits; + int didx, bidx, num = 2; + const char *name; + + num += !!(div->flags & CLK_SET_RATE_GATE) + + !!(div->features & CCU_DIV_RESET_DOMAIN); + + bits = kzalloc_objs(*bits, num); + if (!bits) + return; + + for (didx = 0, bidx = 0; bidx < CCU_DIV_DBGFS_BIT_NUM; ++bidx) { + name = ccu_div_bits[bidx].name; + if (!(div->flags & CLK_SET_RATE_GATE) && + !strcmp("div_en", name)) { + continue; + } + + if (!(div->features & CCU_DIV_RESET_DOMAIN) && + !strcmp("div_rst", name)) { + continue; + } + + if (!strcmp("div_buf", name)) + continue; + + bits[didx] = ccu_div_bits[bidx]; + bits[didx].div = div; + + if (div->features & CCU_DIV_LOCK_SHIFTED && + !strcmp("div_lock", name)) { + bits[didx].mask = CCU_DIV_CTL_LOCK_SHIFTED; + } + + debugfs_create_file_unsafe(bits[didx].name, ccu_div_dbgfs_mode, + dentry, &bits[didx], + &ccu_div_dbgfs_bit_fops); + ++didx; + } + + debugfs_create_file_unsafe("div_clkdiv", ccu_div_dbgfs_mode, dentry, + div, &ccu_div_dbgfs_var_clkdiv_fops); +} + +static void ccu_div_gate_debug_init(struct clk_hw *hw, struct dentry *dentry) +{ + struct ccu_div *div = to_ccu_div(hw); + struct ccu_div_dbgfs_bit *bit; + + bit = kmalloc_obj(*bit); + if (!bit) + return; + + *bit = ccu_div_bits[0]; + bit->div = div; + debugfs_create_file_unsafe(bit->name, ccu_div_dbgfs_mode, dentry, bit, + &ccu_div_dbgfs_bit_fops); + + debugfs_create_file_unsafe("div_clkdiv", 0400, dentry, div, + &ccu_div_dbgfs_fixed_clkdiv_fops); +} + +static void ccu_div_buf_debug_init(struct clk_hw *hw, struct dentry *dentry) +{ + struct ccu_div *div = to_ccu_div(hw); + struct ccu_div_dbgfs_bit *bit; + + bit = kmalloc_obj(*bit); + if (!bit) + return; + + *bit = ccu_div_bits[3]; + bit->div = div; + debugfs_create_file_unsafe(bit->name, ccu_div_dbgfs_mode, dentry, bit, + &ccu_div_dbgfs_bit_fops); +} + +static void ccu_div_fixed_debug_init(struct clk_hw *hw, struct dentry *dentry) +{ + struct ccu_div *div = to_ccu_div(hw); + + debugfs_create_file_unsafe("div_clkdiv", 0400, dentry, div, + &ccu_div_dbgfs_fixed_clkdiv_fops); +} + +#else /* !CONFIG_DEBUG_FS */ + +#define ccu_div_var_debug_init NULL +#define ccu_div_gate_debug_init NULL +#define ccu_div_buf_debug_init NULL +#define ccu_div_fixed_debug_init NULL + +#endif /* !CONFIG_DEBUG_FS */ + +static const struct clk_ops ccu_div_var_gate_to_set_ops = { + .enable = ccu_div_var_enable, + .disable = ccu_div_gate_disable, + .is_enabled = ccu_div_gate_is_enabled, + .recalc_rate = ccu_div_var_recalc_rate, + .determine_rate = ccu_div_var_determine_rate, + .set_rate = ccu_div_var_set_rate_fast, + .debug_init = ccu_div_var_debug_init +}; + +static const struct clk_ops ccu_div_var_nogate_ops = { + .recalc_rate = ccu_div_var_recalc_rate, + .determine_rate = ccu_div_var_determine_rate, + .set_rate = ccu_div_var_set_rate_slow, + .debug_init = ccu_div_var_debug_init +}; + +static const struct clk_ops ccu_div_gate_ops = { + .enable = ccu_div_gate_enable, + .disable = ccu_div_gate_disable, + .is_enabled = ccu_div_gate_is_enabled, + .recalc_rate = ccu_div_fixed_recalc_rate, + .determine_rate = ccu_div_fixed_determine_rate, + .set_rate = ccu_div_fixed_set_rate, + .debug_init = ccu_div_gate_debug_init +}; + +static const struct clk_ops ccu_div_buf_ops = { + .enable = ccu_div_buf_enable, + .disable = ccu_div_buf_disable, + .is_enabled = ccu_div_buf_is_enabled, + .debug_init = ccu_div_buf_debug_init +}; + +static const struct clk_ops ccu_div_fixed_ops = { + .recalc_rate = ccu_div_fixed_recalc_rate, + .determine_rate = ccu_div_fixed_determine_rate, + .set_rate = ccu_div_fixed_set_rate, + .debug_init = ccu_div_fixed_debug_init +}; + +struct ccu_div *ccu_div_hw_register(const struct ccu_div_init_data *div_init) +{ + struct clk_parent_data parent_data = { }; + struct clk_init_data hw_init = { }; + struct ccu_div *div; + int ret; + + if (!div_init) + return ERR_PTR(-EINVAL); + + div = kzalloc_obj(*div); + if (!div) + return ERR_PTR(-ENOMEM); + + /* + * Note since Baikal-T1 System Controller registers are MMIO-backed + * we won't check the regmap IO operations return status, because it + * must be zero anyway. + */ + div->hw.init = &hw_init; + div->id = div_init->id; + div->reg_ctl = div_init->base + CCU_DIV_CTL; + div->sys_regs = div_init->sys_regs; + div->flags = div_init->flags; + div->features = div_init->features; + spin_lock_init(&div->lock); + + hw_init.name = div_init->name; + hw_init.flags = div_init->flags; + + if (div_init->type == CCU_DIV_VAR) { + if (hw_init.flags & CLK_SET_RATE_GATE) + hw_init.ops = &ccu_div_var_gate_to_set_ops; + else + hw_init.ops = &ccu_div_var_nogate_ops; + div->mask = CCU_DIV_CTL_CLKDIV_MASK(div_init->width); + } else if (div_init->type == CCU_DIV_GATE) { + hw_init.ops = &ccu_div_gate_ops; + div->divider = div_init->divider; + } else if (div_init->type == CCU_DIV_BUF) { + hw_init.ops = &ccu_div_buf_ops; + } else if (div_init->type == CCU_DIV_FIXED) { + hw_init.ops = &ccu_div_fixed_ops; + div->divider = div_init->divider; + } else { + ret = -EINVAL; + goto err_free_div; + } + + if (!div_init->parent_name) { + ret = -EINVAL; + goto err_free_div; + } + parent_data.fw_name = div_init->parent_name; + parent_data.name = div_init->parent_name; + hw_init.parent_data = &parent_data; + hw_init.num_parents = 1; + + ret = of_clk_hw_register(div_init->np, &div->hw); + if (ret) + goto err_free_div; + + return div; + +err_free_div: + kfree(div); + + return ERR_PTR(ret); +} + +void ccu_div_hw_unregister(struct ccu_div *div) +{ + clk_hw_unregister(&div->hw); + + kfree(div); +} diff --git a/drivers/clk/bt1/ccu-div.h b/drivers/clk/bt1/ccu-div.h new file mode 100644 index 0000000000000..76d8ee44d4157 --- /dev/null +++ b/drivers/clk/bt1/ccu-div.h @@ -0,0 +1,121 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC + * + * Baikal-T1 CCU Dividers interface driver + */ +#ifndef __CLK_BT1_CCU_DIV_H__ +#define __CLK_BT1_CCU_DIV_H__ + +#include +#include +#include +#include +#include + +/* + * CCU Divider private clock IDs + * @CCU_SYS_SATA_CLK: CCU SATA internal clock + * @CCU_SYS_XGMAC_CLK: CCU XGMAC internal clock + */ +#define CCU_SYS_SATA_CLK -1 +#define CCU_SYS_XGMAC_CLK -2 + +/* + * CCU Divider private flags + * @CCU_DIV_BASIC: Basic divider clock required by the kernel as early as + * possible. + * @CCU_DIV_SKIP_ONE: Due to some reason divider can't be set to 1. + * It can be 0 though, which is functionally the same. + * @CCU_DIV_SKIP_ONE_TO_THREE: For some reason divider can't be within [1,3]. + * It can be either 0 or greater than 3. + * @CCU_DIV_LOCK_SHIFTED: Find lock-bit at non-standard position. + * @CCU_DIV_RESET_DOMAIN: There is a clock domain reset handle. + */ +#define CCU_DIV_BASIC BIT(0) +#define CCU_DIV_SKIP_ONE BIT(1) +#define CCU_DIV_SKIP_ONE_TO_THREE BIT(2) +#define CCU_DIV_LOCK_SHIFTED BIT(3) +#define CCU_DIV_RESET_DOMAIN BIT(4) + +/* + * enum ccu_div_type - CCU Divider types + * @CCU_DIV_VAR: Clocks gate with variable divider. + * @CCU_DIV_GATE: Clocks gate with fixed divider. + * @CCU_DIV_BUF: Clock gate with no divider. + * @CCU_DIV_FIXED: Ungateable clock with fixed divider. + */ +enum ccu_div_type { + CCU_DIV_VAR, + CCU_DIV_GATE, + CCU_DIV_BUF, + CCU_DIV_FIXED +}; + +/* + * struct ccu_div_init_data - CCU Divider initialization data + * @id: Clocks private identifier. + * @name: Clocks name. + * @parent_name: Parent clocks name in a fw node. + * @base: Divider register base address with respect to the sys_regs base. + * @sys_regs: Baikal-T1 System Controller registers map. + * @np: Pointer to the node describing the CCU Dividers. + * @type: CCU divider type (variable, fixed with and without gate). + * @width: Divider width if it's variable. + * @divider: Divider fixed value. + * @flags: CCU Divider clock flags. + * @features: CCU Divider private features. + */ +struct ccu_div_init_data { + unsigned int id; + const char *name; + const char *parent_name; + unsigned int base; + struct regmap *sys_regs; + struct device_node *np; + enum ccu_div_type type; + union { + unsigned int width; + unsigned int divider; + }; + unsigned long flags; + unsigned long features; +}; + +/* + * struct ccu_div - CCU Divider descriptor + * @hw: clk_hw of the divider. + * @id: Clock private identifier. + * @reg_ctl: Divider control register base address. + * @sys_regs: Baikal-T1 System Controller registers map. + * @lock: Divider state change spin-lock. + * @mask: Divider field mask. + * @divider: Divider fixed value. + * @flags: Divider clock flags. + * @features: CCU Divider private features. + */ +struct ccu_div { + struct clk_hw hw; + unsigned int id; + unsigned int reg_ctl; + struct regmap *sys_regs; + spinlock_t lock; + union { + u32 mask; + unsigned int divider; + }; + unsigned long flags; + unsigned long features; +}; +#define to_ccu_div(_hw) container_of(_hw, struct ccu_div, hw) + +static inline struct clk_hw *ccu_div_get_clk_hw(struct ccu_div *div) +{ + return div ? &div->hw : NULL; +} + +struct ccu_div *ccu_div_hw_register(const struct ccu_div_init_data *init); + +void ccu_div_hw_unregister(struct ccu_div *div); + +#endif /* __CLK_BT1_CCU_DIV_H__ */ diff --git a/drivers/clk/bt1/ccu-pll.c b/drivers/clk/bt1/ccu-pll.c new file mode 100644 index 0000000000000..da7fbebb39abc --- /dev/null +++ b/drivers/clk/bt1/ccu-pll.c @@ -0,0 +1,560 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC + * + * Authors: + * Serge Semin + * Dmitry Dunaev + * + * Baikal-T1 CCU PLL interface driver + */ + +#define pr_fmt(fmt) "bt1-ccu-pll: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ccu-pll.h" + +#define CCU_PLL_CTL 0x000 +#define CCU_PLL_CTL_EN BIT(0) +#define CCU_PLL_CTL_RST BIT(1) +#define CCU_PLL_CTL_CLKR_FLD 2 +#define CCU_PLL_CTL_CLKR_MASK GENMASK(7, CCU_PLL_CTL_CLKR_FLD) +#define CCU_PLL_CTL_CLKF_FLD 8 +#define CCU_PLL_CTL_CLKF_MASK GENMASK(20, CCU_PLL_CTL_CLKF_FLD) +#define CCU_PLL_CTL_CLKOD_FLD 21 +#define CCU_PLL_CTL_CLKOD_MASK GENMASK(24, CCU_PLL_CTL_CLKOD_FLD) +#define CCU_PLL_CTL_BYPASS BIT(30) +#define CCU_PLL_CTL_LOCK BIT(31) +#define CCU_PLL_CTL1 0x004 +#define CCU_PLL_CTL1_BWADJ_FLD 3 +#define CCU_PLL_CTL1_BWADJ_MASK GENMASK(14, CCU_PLL_CTL1_BWADJ_FLD) + +#define CCU_PLL_LOCK_CHECK_RETRIES 50 + +#define CCU_PLL_NR_MAX \ + ((CCU_PLL_CTL_CLKR_MASK >> CCU_PLL_CTL_CLKR_FLD) + 1) +#define CCU_PLL_NF_MAX \ + ((CCU_PLL_CTL_CLKF_MASK >> (CCU_PLL_CTL_CLKF_FLD + 1)) + 1) +#define CCU_PLL_OD_MAX \ + ((CCU_PLL_CTL_CLKOD_MASK >> CCU_PLL_CTL_CLKOD_FLD) + 1) +#define CCU_PLL_NB_MAX \ + ((CCU_PLL_CTL1_BWADJ_MASK >> CCU_PLL_CTL1_BWADJ_FLD) + 1) +#define CCU_PLL_FDIV_MIN 427000UL +#define CCU_PLL_FDIV_MAX 3500000000UL +#define CCU_PLL_FOUT_MIN 200000000UL +#define CCU_PLL_FOUT_MAX 2500000000UL +#define CCU_PLL_FVCO_MIN 700000000UL +#define CCU_PLL_FVCO_MAX 3500000000UL +#define CCU_PLL_CLKOD_FACTOR 2 + +static inline unsigned long ccu_pll_lock_delay_us(unsigned long ref_clk, + unsigned long nr) +{ + u64 us = 500ULL * nr * USEC_PER_SEC; + + do_div(us, ref_clk); + + return us; +} + +static inline unsigned long ccu_pll_calc_freq(unsigned long ref_clk, + unsigned long nr, + unsigned long nf, + unsigned long od) +{ + u64 tmp = ref_clk; + + do_div(tmp, nr); + tmp *= nf; + do_div(tmp, od); + + return tmp; +} + +static int ccu_pll_reset(struct ccu_pll *pll, unsigned long ref_clk, + unsigned long nr) +{ + unsigned long ud, ut; + u32 val; + + ud = ccu_pll_lock_delay_us(ref_clk, nr); + ut = ud * CCU_PLL_LOCK_CHECK_RETRIES; + + regmap_update_bits(pll->sys_regs, pll->reg_ctl, + CCU_PLL_CTL_RST, CCU_PLL_CTL_RST); + + return regmap_read_poll_timeout_atomic(pll->sys_regs, pll->reg_ctl, val, + val & CCU_PLL_CTL_LOCK, ud, ut); +} + +static int ccu_pll_enable(struct clk_hw *hw) +{ + struct clk_hw *parent_hw = clk_hw_get_parent(hw); + struct ccu_pll *pll = to_ccu_pll(hw); + unsigned long flags; + u32 val = 0; + int ret; + + if (!parent_hw) { + pr_err("Can't enable '%s' with no parent", clk_hw_get_name(hw)); + return -EINVAL; + } + + regmap_read(pll->sys_regs, pll->reg_ctl, &val); + if (val & CCU_PLL_CTL_EN) + return 0; + + spin_lock_irqsave(&pll->lock, flags); + regmap_write(pll->sys_regs, pll->reg_ctl, val | CCU_PLL_CTL_EN); + ret = ccu_pll_reset(pll, clk_hw_get_rate(parent_hw), + FIELD_GET(CCU_PLL_CTL_CLKR_MASK, val) + 1); + spin_unlock_irqrestore(&pll->lock, flags); + if (ret) + pr_err("PLL '%s' reset timed out\n", clk_hw_get_name(hw)); + + return ret; +} + +static void ccu_pll_disable(struct clk_hw *hw) +{ + struct ccu_pll *pll = to_ccu_pll(hw); + unsigned long flags; + + spin_lock_irqsave(&pll->lock, flags); + regmap_update_bits(pll->sys_regs, pll->reg_ctl, CCU_PLL_CTL_EN, 0); + spin_unlock_irqrestore(&pll->lock, flags); +} + +static int ccu_pll_is_enabled(struct clk_hw *hw) +{ + struct ccu_pll *pll = to_ccu_pll(hw); + u32 val = 0; + + regmap_read(pll->sys_regs, pll->reg_ctl, &val); + + return !!(val & CCU_PLL_CTL_EN); +} + +static unsigned long ccu_pll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct ccu_pll *pll = to_ccu_pll(hw); + unsigned long nr, nf, od; + u32 val = 0; + + regmap_read(pll->sys_regs, pll->reg_ctl, &val); + nr = FIELD_GET(CCU_PLL_CTL_CLKR_MASK, val) + 1; + nf = FIELD_GET(CCU_PLL_CTL_CLKF_MASK, val) + 1; + od = FIELD_GET(CCU_PLL_CTL_CLKOD_MASK, val) + 1; + + return ccu_pll_calc_freq(parent_rate, nr, nf, od); +} + +static void ccu_pll_calc_factors(unsigned long rate, unsigned long parent_rate, + unsigned long *nr, unsigned long *nf, + unsigned long *od) +{ + unsigned long err, freq, min_err = ULONG_MAX; + unsigned long num, denom, n1, d1, nri; + unsigned long nr_max, nf_max, od_max; + + /* + * Make sure PLL is working with valid input signal (Fdiv). If + * you want to speed the function up just reduce CCU_PLL_NR_MAX. + * This will cause a worse approximation though. + */ + nri = (parent_rate / CCU_PLL_FDIV_MAX) + 1; + nr_max = min(parent_rate / CCU_PLL_FDIV_MIN, CCU_PLL_NR_MAX); + + /* + * Find a closest [nr;nf;od] vector taking into account the + * limitations like: 1) 700MHz <= Fvco <= 3.5GHz, 2) PLL Od is + * either 1 or even number within the acceptable range (alas 1s + * is also excluded by the next loop). + */ + for (; nri <= nr_max; ++nri) { + /* Use Od factor to fulfill the limitation 2). */ + num = CCU_PLL_CLKOD_FACTOR * rate; + denom = parent_rate / nri; + + /* + * Make sure Fvco is within the acceptable range to fulfill + * the condition 1). Note due to the CCU_PLL_CLKOD_FACTOR value + * the actual upper limit is also divided by that factor. + * It's not big problem for us since practically there is no + * need in clocks with that high frequency. + */ + nf_max = min(CCU_PLL_FVCO_MAX / denom, CCU_PLL_NF_MAX); + od_max = CCU_PLL_OD_MAX / CCU_PLL_CLKOD_FACTOR; + + /* + * Bypass the out-of-bound values, which can't be properly + * handled by the rational fraction approximation algorithm. + */ + if (num / denom >= nf_max) { + n1 = nf_max; + d1 = 1; + } else if (denom / num >= od_max) { + n1 = 1; + d1 = od_max; + } else { + rational_best_approximation(num, denom, nf_max, od_max, + &n1, &d1); + } + + /* Select the best approximation of the target rate. */ + freq = ccu_pll_calc_freq(parent_rate, nri, n1, d1); + err = abs((int64_t)freq - num); + if (err < min_err) { + min_err = err; + *nr = nri; + *nf = n1; + *od = CCU_PLL_CLKOD_FACTOR * d1; + } + } +} + +static int ccu_pll_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + unsigned long nr = 1, nf = 1, od = 1; + + ccu_pll_calc_factors(req->rate, req->best_parent_rate, &nr, &nf, &od); + + req->rate = ccu_pll_calc_freq(req->best_parent_rate, nr, nf, od); + + return 0; +} + +/* + * This method is used for PLLs, which support the on-the-fly dividers + * adjustment. So there is no need in gating such clocks. + */ +static int ccu_pll_set_rate_reset(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct ccu_pll *pll = to_ccu_pll(hw); + unsigned long nr, nf, od; + unsigned long flags; + u32 mask, val; + int ret; + + ccu_pll_calc_factors(rate, parent_rate, &nr, &nf, &od); + + mask = CCU_PLL_CTL_CLKR_MASK | CCU_PLL_CTL_CLKF_MASK | + CCU_PLL_CTL_CLKOD_MASK; + val = FIELD_PREP(CCU_PLL_CTL_CLKR_MASK, nr - 1) | + FIELD_PREP(CCU_PLL_CTL_CLKF_MASK, nf - 1) | + FIELD_PREP(CCU_PLL_CTL_CLKOD_MASK, od - 1); + + spin_lock_irqsave(&pll->lock, flags); + regmap_update_bits(pll->sys_regs, pll->reg_ctl, mask, val); + ret = ccu_pll_reset(pll, parent_rate, nr); + spin_unlock_irqrestore(&pll->lock, flags); + if (ret) + pr_err("PLL '%s' reset timed out\n", clk_hw_get_name(hw)); + + return ret; +} + +/* + * This method is used for PLLs, which don't support the on-the-fly dividers + * adjustment. So the corresponding clocks are supposed to be gated first. + */ +static int ccu_pll_set_rate_norst(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct ccu_pll *pll = to_ccu_pll(hw); + unsigned long nr, nf, od; + unsigned long flags; + u32 mask, val; + + ccu_pll_calc_factors(rate, parent_rate, &nr, &nf, &od); + + /* + * Disable PLL if it was enabled by default or left enabled by the + * system bootloader. + */ + mask = CCU_PLL_CTL_CLKR_MASK | CCU_PLL_CTL_CLKF_MASK | + CCU_PLL_CTL_CLKOD_MASK | CCU_PLL_CTL_EN; + val = FIELD_PREP(CCU_PLL_CTL_CLKR_MASK, nr - 1) | + FIELD_PREP(CCU_PLL_CTL_CLKF_MASK, nf - 1) | + FIELD_PREP(CCU_PLL_CTL_CLKOD_MASK, od - 1); + + spin_lock_irqsave(&pll->lock, flags); + regmap_update_bits(pll->sys_regs, pll->reg_ctl, mask, val); + spin_unlock_irqrestore(&pll->lock, flags); + + return 0; +} + +#ifdef CONFIG_DEBUG_FS + +struct ccu_pll_dbgfs_bit { + struct ccu_pll *pll; + const char *name; + unsigned int reg; + u32 mask; +}; + +struct ccu_pll_dbgfs_fld { + struct ccu_pll *pll; + const char *name; + unsigned int reg; + unsigned int lsb; + u32 mask; + u32 min; + u32 max; +}; + +#define CCU_PLL_DBGFS_BIT_ATTR(_name, _reg, _mask) \ + { \ + .name = _name, \ + .reg = _reg, \ + .mask = _mask \ + } + +#define CCU_PLL_DBGFS_FLD_ATTR(_name, _reg, _lsb, _mask, _min, _max) \ + { \ + .name = _name, \ + .reg = _reg, \ + .lsb = _lsb, \ + .mask = _mask, \ + .min = _min, \ + .max = _max \ + } + +static const struct ccu_pll_dbgfs_bit ccu_pll_bits[] = { + CCU_PLL_DBGFS_BIT_ATTR("pll_en", CCU_PLL_CTL, CCU_PLL_CTL_EN), + CCU_PLL_DBGFS_BIT_ATTR("pll_rst", CCU_PLL_CTL, CCU_PLL_CTL_RST), + CCU_PLL_DBGFS_BIT_ATTR("pll_bypass", CCU_PLL_CTL, CCU_PLL_CTL_BYPASS), + CCU_PLL_DBGFS_BIT_ATTR("pll_lock", CCU_PLL_CTL, CCU_PLL_CTL_LOCK) +}; + +#define CCU_PLL_DBGFS_BIT_NUM ARRAY_SIZE(ccu_pll_bits) + +static const struct ccu_pll_dbgfs_fld ccu_pll_flds[] = { + CCU_PLL_DBGFS_FLD_ATTR("pll_nr", CCU_PLL_CTL, CCU_PLL_CTL_CLKR_FLD, + CCU_PLL_CTL_CLKR_MASK, 1, CCU_PLL_NR_MAX), + CCU_PLL_DBGFS_FLD_ATTR("pll_nf", CCU_PLL_CTL, CCU_PLL_CTL_CLKF_FLD, + CCU_PLL_CTL_CLKF_MASK, 1, CCU_PLL_NF_MAX), + CCU_PLL_DBGFS_FLD_ATTR("pll_od", CCU_PLL_CTL, CCU_PLL_CTL_CLKOD_FLD, + CCU_PLL_CTL_CLKOD_MASK, 1, CCU_PLL_OD_MAX), + CCU_PLL_DBGFS_FLD_ATTR("pll_nb", CCU_PLL_CTL1, CCU_PLL_CTL1_BWADJ_FLD, + CCU_PLL_CTL1_BWADJ_MASK, 1, CCU_PLL_NB_MAX) +}; + +#define CCU_PLL_DBGFS_FLD_NUM ARRAY_SIZE(ccu_pll_flds) + +/* + * It can be dangerous to change the PLL settings behind clock framework back, + * therefore we don't provide any kernel config based compile time option for + * this feature to enable. + */ +#undef CCU_PLL_ALLOW_WRITE_DEBUGFS +#ifdef CCU_PLL_ALLOW_WRITE_DEBUGFS + +static int ccu_pll_dbgfs_bit_set(void *priv, u64 val) +{ + const struct ccu_pll_dbgfs_bit *bit = priv; + struct ccu_pll *pll = bit->pll; + unsigned long flags; + + spin_lock_irqsave(&pll->lock, flags); + regmap_update_bits(pll->sys_regs, pll->reg_ctl + bit->reg, + bit->mask, val ? bit->mask : 0); + spin_unlock_irqrestore(&pll->lock, flags); + + return 0; +} + +static int ccu_pll_dbgfs_fld_set(void *priv, u64 val) +{ + struct ccu_pll_dbgfs_fld *fld = priv; + struct ccu_pll *pll = fld->pll; + unsigned long flags; + u32 data; + + val = clamp_t(u64, val, fld->min, fld->max); + data = ((val - 1) << fld->lsb) & fld->mask; + + spin_lock_irqsave(&pll->lock, flags); + regmap_update_bits(pll->sys_regs, pll->reg_ctl + fld->reg, fld->mask, + data); + spin_unlock_irqrestore(&pll->lock, flags); + + return 0; +} + +#define ccu_pll_dbgfs_mode 0644 + +#else /* !CCU_PLL_ALLOW_WRITE_DEBUGFS */ + +#define ccu_pll_dbgfs_bit_set NULL +#define ccu_pll_dbgfs_fld_set NULL +#define ccu_pll_dbgfs_mode 0444 + +#endif /* !CCU_PLL_ALLOW_WRITE_DEBUGFS */ + +static int ccu_pll_dbgfs_bit_get(void *priv, u64 *val) +{ + struct ccu_pll_dbgfs_bit *bit = priv; + struct ccu_pll *pll = bit->pll; + u32 data = 0; + + regmap_read(pll->sys_regs, pll->reg_ctl + bit->reg, &data); + *val = !!(data & bit->mask); + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(ccu_pll_dbgfs_bit_fops, + ccu_pll_dbgfs_bit_get, ccu_pll_dbgfs_bit_set, "%llu\n"); + +static int ccu_pll_dbgfs_fld_get(void *priv, u64 *val) +{ + struct ccu_pll_dbgfs_fld *fld = priv; + struct ccu_pll *pll = fld->pll; + u32 data = 0; + + regmap_read(pll->sys_regs, pll->reg_ctl + fld->reg, &data); + *val = ((data & fld->mask) >> fld->lsb) + 1; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(ccu_pll_dbgfs_fld_fops, + ccu_pll_dbgfs_fld_get, ccu_pll_dbgfs_fld_set, "%llu\n"); + +static void ccu_pll_debug_init(struct clk_hw *hw, struct dentry *dentry) +{ + struct ccu_pll *pll = to_ccu_pll(hw); + struct ccu_pll_dbgfs_bit *bits; + struct ccu_pll_dbgfs_fld *flds; + int idx; + + bits = kzalloc_objs(*bits, CCU_PLL_DBGFS_BIT_NUM); + if (!bits) + return; + + for (idx = 0; idx < CCU_PLL_DBGFS_BIT_NUM; ++idx) { + bits[idx] = ccu_pll_bits[idx]; + bits[idx].pll = pll; + + debugfs_create_file_unsafe(bits[idx].name, ccu_pll_dbgfs_mode, + dentry, &bits[idx], + &ccu_pll_dbgfs_bit_fops); + } + + flds = kzalloc_objs(*flds, CCU_PLL_DBGFS_FLD_NUM); + if (!flds) + return; + + for (idx = 0; idx < CCU_PLL_DBGFS_FLD_NUM; ++idx) { + flds[idx] = ccu_pll_flds[idx]; + flds[idx].pll = pll; + + debugfs_create_file_unsafe(flds[idx].name, ccu_pll_dbgfs_mode, + dentry, &flds[idx], + &ccu_pll_dbgfs_fld_fops); + } +} + +#else /* !CONFIG_DEBUG_FS */ + +#define ccu_pll_debug_init NULL + +#endif /* !CONFIG_DEBUG_FS */ + +static const struct clk_ops ccu_pll_gate_to_set_ops = { + .enable = ccu_pll_enable, + .disable = ccu_pll_disable, + .is_enabled = ccu_pll_is_enabled, + .recalc_rate = ccu_pll_recalc_rate, + .determine_rate = ccu_pll_determine_rate, + .set_rate = ccu_pll_set_rate_norst, + .debug_init = ccu_pll_debug_init +}; + +static const struct clk_ops ccu_pll_straight_set_ops = { + .enable = ccu_pll_enable, + .disable = ccu_pll_disable, + .is_enabled = ccu_pll_is_enabled, + .recalc_rate = ccu_pll_recalc_rate, + .determine_rate = ccu_pll_determine_rate, + .set_rate = ccu_pll_set_rate_reset, + .debug_init = ccu_pll_debug_init +}; + +struct ccu_pll *ccu_pll_hw_register(const struct ccu_pll_init_data *pll_init) +{ + struct clk_parent_data parent_data = { }; + struct clk_init_data hw_init = { }; + struct ccu_pll *pll; + int ret; + + if (!pll_init) + return ERR_PTR(-EINVAL); + + pll = kzalloc_obj(*pll); + if (!pll) + return ERR_PTR(-ENOMEM); + + /* + * Note since Baikal-T1 System Controller registers are MMIO-backed + * we won't check the regmap IO operations return status, because it + * must be zero anyway. + */ + pll->hw.init = &hw_init; + pll->reg_ctl = pll_init->base + CCU_PLL_CTL; + pll->reg_ctl1 = pll_init->base + CCU_PLL_CTL1; + pll->sys_regs = pll_init->sys_regs; + pll->id = pll_init->id; + spin_lock_init(&pll->lock); + + hw_init.name = pll_init->name; + hw_init.flags = pll_init->flags; + + if (hw_init.flags & CLK_SET_RATE_GATE) + hw_init.ops = &ccu_pll_gate_to_set_ops; + else + hw_init.ops = &ccu_pll_straight_set_ops; + + if (!pll_init->parent_name) { + ret = -EINVAL; + goto err_free_pll; + } + parent_data.fw_name = pll_init->parent_name; + hw_init.parent_data = &parent_data; + hw_init.num_parents = 1; + + ret = of_clk_hw_register(pll_init->np, &pll->hw); + if (ret) + goto err_free_pll; + + return pll; + +err_free_pll: + kfree(pll); + + return ERR_PTR(ret); +} + +void ccu_pll_hw_unregister(struct ccu_pll *pll) +{ + clk_hw_unregister(&pll->hw); + + kfree(pll); +} diff --git a/drivers/clk/bt1/ccu-pll.h b/drivers/clk/bt1/ccu-pll.h new file mode 100644 index 0000000000000..a71bfd7b90ecc --- /dev/null +++ b/drivers/clk/bt1/ccu-pll.h @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC + * + * Baikal-T1 CCU PLL interface driver + */ +#ifndef __CLK_BT1_CCU_PLL_H__ +#define __CLK_BT1_CCU_PLL_H__ + +#include +#include +#include +#include +#include + +/* + * CCU PLL private flags + * @CCU_PLL_BASIC: Basic PLL required by the kernel as early as possible. + */ +#define CCU_PLL_BASIC BIT(0) + +/* + * struct ccu_pll_init_data - CCU PLL initialization data + * @id: Clock private identifier. + * @name: Clocks name. + * @parent_name: Clocks parent name in a fw node. + * @base: PLL registers base address with respect to the sys_regs base. + * @sys_regs: Baikal-T1 System Controller registers map. + * @np: Pointer to the node describing the CCU PLLs. + * @flags: PLL clock flags. + * @features: PLL private features. + */ +struct ccu_pll_init_data { + unsigned int id; + const char *name; + const char *parent_name; + unsigned int base; + struct regmap *sys_regs; + struct device_node *np; + unsigned long flags; + unsigned long features; +}; + +/* + * struct ccu_pll - CCU PLL descriptor + * @hw: clk_hw of the PLL. + * @id: Clock private identifier. + * @reg_ctl: PLL control register base. + * @reg_ctl1: PLL control1 register base. + * @sys_regs: Baikal-T1 System Controller registers map. + * @lock: PLL state change spin-lock. + */ +struct ccu_pll { + struct clk_hw hw; + unsigned int id; + unsigned int reg_ctl; + unsigned int reg_ctl1; + struct regmap *sys_regs; + spinlock_t lock; +}; +#define to_ccu_pll(_hw) container_of(_hw, struct ccu_pll, hw) + +static inline struct clk_hw *ccu_pll_get_clk_hw(struct ccu_pll *pll) +{ + return pll ? &pll->hw : NULL; +} + +struct ccu_pll *ccu_pll_hw_register(const struct ccu_pll_init_data *init); + +void ccu_pll_hw_unregister(struct ccu_pll *pll); + +#endif /* __CLK_BT1_CCU_PLL_H__ */ diff --git a/drivers/clk/bt1/ccu-rst.c b/drivers/clk/bt1/ccu-rst.c new file mode 100644 index 0000000000000..969e5de381a8e --- /dev/null +++ b/drivers/clk/bt1/ccu-rst.c @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021 BAIKAL ELECTRONICS, JSC + * + * Authors: + * Serge Semin + * + * Baikal-T1 CCU Resets interface driver + */ + +#define pr_fmt(fmt) "bt1-ccu-rst: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ccu-rst.h" + +#define CCU_AXI_MAIN_BASE 0x030 +#define CCU_AXI_DDR_BASE 0x034 +#define CCU_AXI_SATA_BASE 0x038 +#define CCU_AXI_GMAC0_BASE 0x03C +#define CCU_AXI_GMAC1_BASE 0x040 +#define CCU_AXI_XGMAC_BASE 0x044 +#define CCU_AXI_PCIE_M_BASE 0x048 +#define CCU_AXI_PCIE_S_BASE 0x04C +#define CCU_AXI_USB_BASE 0x050 +#define CCU_AXI_HWA_BASE 0x054 +#define CCU_AXI_SRAM_BASE 0x058 + +#define CCU_SYS_DDR_BASE 0x02c +#define CCU_SYS_SATA_REF_BASE 0x060 +#define CCU_SYS_APB_BASE 0x064 +#define CCU_SYS_PCIE_BASE 0x144 + +#define CCU_RST_DELAY_US 1 + +#define CCU_RST_TRIG(_base, _ofs) \ + { \ + .type = CCU_RST_TRIG, \ + .base = _base, \ + .mask = BIT(_ofs), \ + } + +#define CCU_RST_DIR(_base, _ofs) \ + { \ + .type = CCU_RST_DIR, \ + .base = _base, \ + .mask = BIT(_ofs), \ + } + +struct ccu_rst_info { + enum ccu_rst_type type; + unsigned int base; + unsigned int mask; +}; + +/* + * Each AXI-bus clock divider is equipped with the corresponding clock-consumer + * domain reset (it's self-deasserted reset control). + */ +static const struct ccu_rst_info axi_rst_info[] = { + [CCU_AXI_MAIN_RST] = CCU_RST_TRIG(CCU_AXI_MAIN_BASE, 1), + [CCU_AXI_DDR_RST] = CCU_RST_TRIG(CCU_AXI_DDR_BASE, 1), + [CCU_AXI_SATA_RST] = CCU_RST_TRIG(CCU_AXI_SATA_BASE, 1), + [CCU_AXI_GMAC0_RST] = CCU_RST_TRIG(CCU_AXI_GMAC0_BASE, 1), + [CCU_AXI_GMAC1_RST] = CCU_RST_TRIG(CCU_AXI_GMAC1_BASE, 1), + [CCU_AXI_XGMAC_RST] = CCU_RST_TRIG(CCU_AXI_XGMAC_BASE, 1), + [CCU_AXI_PCIE_M_RST] = CCU_RST_TRIG(CCU_AXI_PCIE_M_BASE, 1), + [CCU_AXI_PCIE_S_RST] = CCU_RST_TRIG(CCU_AXI_PCIE_S_BASE, 1), + [CCU_AXI_USB_RST] = CCU_RST_TRIG(CCU_AXI_USB_BASE, 1), + [CCU_AXI_HWA_RST] = CCU_RST_TRIG(CCU_AXI_HWA_BASE, 1), + [CCU_AXI_SRAM_RST] = CCU_RST_TRIG(CCU_AXI_SRAM_BASE, 1), +}; + +/* + * SATA reference clock domain and APB-bus domain are connected with the + * sefl-deasserted reset control, which can be activated via the corresponding + * clock divider register. DDR and PCIe sub-domains can be reset with directly + * controlled reset signals. Resetting the DDR controller though won't end up + * well while the Linux kernel is working. + */ +static const struct ccu_rst_info sys_rst_info[] = { + [CCU_SYS_SATA_REF_RST] = CCU_RST_TRIG(CCU_SYS_SATA_REF_BASE, 1), + [CCU_SYS_APB_RST] = CCU_RST_TRIG(CCU_SYS_APB_BASE, 1), + [CCU_SYS_DDR_FULL_RST] = CCU_RST_DIR(CCU_SYS_DDR_BASE, 1), + [CCU_SYS_DDR_INIT_RST] = CCU_RST_DIR(CCU_SYS_DDR_BASE, 2), + [CCU_SYS_PCIE_PCS_PHY_RST] = CCU_RST_DIR(CCU_SYS_PCIE_BASE, 0), + [CCU_SYS_PCIE_PIPE0_RST] = CCU_RST_DIR(CCU_SYS_PCIE_BASE, 4), + [CCU_SYS_PCIE_CORE_RST] = CCU_RST_DIR(CCU_SYS_PCIE_BASE, 8), + [CCU_SYS_PCIE_PWR_RST] = CCU_RST_DIR(CCU_SYS_PCIE_BASE, 9), + [CCU_SYS_PCIE_STICKY_RST] = CCU_RST_DIR(CCU_SYS_PCIE_BASE, 10), + [CCU_SYS_PCIE_NSTICKY_RST] = CCU_RST_DIR(CCU_SYS_PCIE_BASE, 11), + [CCU_SYS_PCIE_HOT_RST] = CCU_RST_DIR(CCU_SYS_PCIE_BASE, 12), +}; + +static int ccu_rst_reset(struct reset_controller_dev *rcdev, unsigned long idx) +{ + struct ccu_rst *rst = to_ccu_rst(rcdev); + const struct ccu_rst_info *info = &rst->rsts_info[idx]; + + if (info->type != CCU_RST_TRIG) + return -EOPNOTSUPP; + + regmap_update_bits(rst->sys_regs, info->base, info->mask, info->mask); + + /* The next delay must be enough to cover all the resets. */ + udelay(CCU_RST_DELAY_US); + + return 0; +} + +static int ccu_rst_set(struct reset_controller_dev *rcdev, + unsigned long idx, bool high) +{ + struct ccu_rst *rst = to_ccu_rst(rcdev); + const struct ccu_rst_info *info = &rst->rsts_info[idx]; + + if (info->type != CCU_RST_DIR) + return high ? -EOPNOTSUPP : 0; + + return regmap_update_bits(rst->sys_regs, info->base, + info->mask, high ? info->mask : 0); +} + +static int ccu_rst_assert(struct reset_controller_dev *rcdev, + unsigned long idx) +{ + return ccu_rst_set(rcdev, idx, true); +} + +static int ccu_rst_deassert(struct reset_controller_dev *rcdev, + unsigned long idx) +{ + return ccu_rst_set(rcdev, idx, false); +} + +static int ccu_rst_status(struct reset_controller_dev *rcdev, + unsigned long idx) +{ + struct ccu_rst *rst = to_ccu_rst(rcdev); + const struct ccu_rst_info *info = &rst->rsts_info[idx]; + u32 val; + + if (info->type != CCU_RST_DIR) + return -EOPNOTSUPP; + + regmap_read(rst->sys_regs, info->base, &val); + + return !!(val & info->mask); +} + +static const struct reset_control_ops ccu_rst_ops = { + .reset = ccu_rst_reset, + .assert = ccu_rst_assert, + .deassert = ccu_rst_deassert, + .status = ccu_rst_status, +}; + +struct ccu_rst *ccu_rst_hw_register(const struct ccu_rst_init_data *rst_init) +{ + struct ccu_rst *rst; + int ret; + + if (!rst_init) + return ERR_PTR(-EINVAL); + + rst = kzalloc_obj(*rst); + if (!rst) + return ERR_PTR(-ENOMEM); + + rst->sys_regs = rst_init->sys_regs; + if (of_device_is_compatible(rst_init->np, "baikal,bt1-ccu-axi")) { + rst->rcdev.nr_resets = ARRAY_SIZE(axi_rst_info); + rst->rsts_info = axi_rst_info; + } else if (of_device_is_compatible(rst_init->np, "baikal,bt1-ccu-sys")) { + rst->rcdev.nr_resets = ARRAY_SIZE(sys_rst_info); + rst->rsts_info = sys_rst_info; + } else { + pr_err("Incompatible DT node '%s' specified\n", + of_node_full_name(rst_init->np)); + ret = -EINVAL; + goto err_kfree_rst; + } + + rst->rcdev.owner = THIS_MODULE; + rst->rcdev.ops = &ccu_rst_ops; + rst->rcdev.of_node = rst_init->np; + + ret = reset_controller_register(&rst->rcdev); + if (ret) { + pr_err("Couldn't register '%s' reset controller\n", + of_node_full_name(rst_init->np)); + goto err_kfree_rst; + } + + return rst; + +err_kfree_rst: + kfree(rst); + + return ERR_PTR(ret); +} + +void ccu_rst_hw_unregister(struct ccu_rst *rst) +{ + reset_controller_unregister(&rst->rcdev); + + kfree(rst); +} diff --git a/drivers/clk/bt1/ccu-rst.h b/drivers/clk/bt1/ccu-rst.h new file mode 100644 index 0000000000000..d6e8b2f671f4d --- /dev/null +++ b/drivers/clk/bt1/ccu-rst.h @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2021 BAIKAL ELECTRONICS, JSC + * + * Baikal-T1 CCU Resets interface driver + */ +#ifndef __CLK_BT1_CCU_RST_H__ +#define __CLK_BT1_CCU_RST_H__ + +#include +#include +#include + +struct ccu_rst_info; + +/* + * enum ccu_rst_type - CCU Reset types + * @CCU_RST_TRIG: Self-deasserted reset signal. + * @CCU_RST_DIR: Directly controlled reset signal. + */ +enum ccu_rst_type { + CCU_RST_TRIG, + CCU_RST_DIR, +}; + +/* + * struct ccu_rst_init_data - CCU Resets initialization data + * @sys_regs: Baikal-T1 System Controller registers map. + * @np: Pointer to the node with the System CCU block. + */ +struct ccu_rst_init_data { + struct regmap *sys_regs; + struct device_node *np; +}; + +/* + * struct ccu_rst - CCU Reset descriptor + * @rcdev: Reset controller descriptor. + * @sys_regs: Baikal-T1 System Controller registers map. + * @rsts_info: Reset flag info (base address and mask). + */ +struct ccu_rst { + struct reset_controller_dev rcdev; + struct regmap *sys_regs; + const struct ccu_rst_info *rsts_info; +}; +#define to_ccu_rst(_rcdev) container_of(_rcdev, struct ccu_rst, rcdev) + +#ifdef CONFIG_CLK_BT1_CCU_RST + +struct ccu_rst *ccu_rst_hw_register(const struct ccu_rst_init_data *init); + +void ccu_rst_hw_unregister(struct ccu_rst *rst); + +#else + +static inline +struct ccu_rst *ccu_rst_hw_register(const struct ccu_rst_init_data *init) +{ + return NULL; +} + +static inline void ccu_rst_hw_unregister(struct ccu_rst *rst) {} + +#endif + +#endif /* __CLK_BT1_CCU_RST_H__ */ diff --git a/drivers/clk/bt1/clk-ccu-div.c b/drivers/clk/bt1/clk-ccu-div.c new file mode 100644 index 0000000000000..d32072e4dd495 --- /dev/null +++ b/drivers/clk/bt1/clk-ccu-div.c @@ -0,0 +1,520 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC + * + * Authors: + * Serge Semin + * Dmitry Dunaev + * + * Baikal-T1 CCU Dividers clock driver + */ + +#define pr_fmt(fmt) "bt1-ccu-div: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ccu-div.h" +#include "ccu-rst.h" + +#define CCU_AXI_MAIN_BASE 0x030 +#define CCU_AXI_DDR_BASE 0x034 +#define CCU_AXI_SATA_BASE 0x038 +#define CCU_AXI_GMAC0_BASE 0x03C +#define CCU_AXI_GMAC1_BASE 0x040 +#define CCU_AXI_XGMAC_BASE 0x044 +#define CCU_AXI_PCIE_M_BASE 0x048 +#define CCU_AXI_PCIE_S_BASE 0x04C +#define CCU_AXI_USB_BASE 0x050 +#define CCU_AXI_HWA_BASE 0x054 +#define CCU_AXI_SRAM_BASE 0x058 + +#define CCU_SYS_SATA_REF_BASE 0x060 +#define CCU_SYS_APB_BASE 0x064 +#define CCU_SYS_GMAC0_BASE 0x068 +#define CCU_SYS_GMAC1_BASE 0x06C +#define CCU_SYS_XGMAC_BASE 0x070 +#define CCU_SYS_USB_BASE 0x074 +#define CCU_SYS_PVT_BASE 0x078 +#define CCU_SYS_HWA_BASE 0x07C +#define CCU_SYS_UART_BASE 0x084 +#define CCU_SYS_TIMER0_BASE 0x088 +#define CCU_SYS_TIMER1_BASE 0x08C +#define CCU_SYS_TIMER2_BASE 0x090 +#define CCU_SYS_WDT_BASE 0x150 + +#define CCU_DIV_VAR_INFO(_id, _name, _pname, _base, _width, _flags, _features) \ + { \ + .id = _id, \ + .name = _name, \ + .parent_name = _pname, \ + .base = _base, \ + .type = CCU_DIV_VAR, \ + .width = _width, \ + .flags = _flags, \ + .features = _features \ + } + +#define CCU_DIV_GATE_INFO(_id, _name, _pname, _base, _divider) \ + { \ + .id = _id, \ + .name = _name, \ + .parent_name = _pname, \ + .base = _base, \ + .type = CCU_DIV_GATE, \ + .divider = _divider \ + } + +#define CCU_DIV_BUF_INFO(_id, _name, _pname, _base, _flags) \ + { \ + .id = _id, \ + .name = _name, \ + .parent_name = _pname, \ + .base = _base, \ + .type = CCU_DIV_BUF, \ + .flags = _flags \ + } + +#define CCU_DIV_FIXED_INFO(_id, _name, _pname, _divider) \ + { \ + .id = _id, \ + .name = _name, \ + .parent_name = _pname, \ + .type = CCU_DIV_FIXED, \ + .divider = _divider \ + } + +struct ccu_div_info { + unsigned int id; + const char *name; + const char *parent_name; + unsigned int base; + enum ccu_div_type type; + union { + unsigned int width; + unsigned int divider; + }; + unsigned long flags; + unsigned long features; +}; + +struct ccu_div_data { + struct device_node *np; + struct regmap *sys_regs; + + unsigned int divs_num; + const struct ccu_div_info *divs_info; + struct ccu_div **divs; + + struct ccu_rst *rsts; +}; + +/* + * AXI Main Interconnect (axi_main_clk) and DDR AXI-bus (axi_ddr_clk) clocks + * must be left enabled in any case, since former one is responsible for + * clocking a bus between CPU cores and the rest of the SoC components, while + * the later is clocking the AXI-bus between DDR controller and the Main + * Interconnect. So should any of these clocks get to be disabled, the system + * will literally stop working. That's why we marked them as critical. + */ +static const struct ccu_div_info axi_info[] = { + CCU_DIV_VAR_INFO(CCU_AXI_MAIN_CLK, "axi_main_clk", "pcie_clk", + CCU_AXI_MAIN_BASE, 4, + CLK_IS_CRITICAL, CCU_DIV_RESET_DOMAIN), + CCU_DIV_VAR_INFO(CCU_AXI_DDR_CLK, "axi_ddr_clk", "sata_clk", + CCU_AXI_DDR_BASE, 4, + CLK_IS_CRITICAL | CLK_SET_RATE_GATE, + CCU_DIV_RESET_DOMAIN), + CCU_DIV_VAR_INFO(CCU_AXI_SATA_CLK, "axi_sata_clk", "sata_clk", + CCU_AXI_SATA_BASE, 4, + CLK_SET_RATE_GATE, CCU_DIV_RESET_DOMAIN), + CCU_DIV_VAR_INFO(CCU_AXI_GMAC0_CLK, "axi_gmac0_clk", "eth_clk", + CCU_AXI_GMAC0_BASE, 4, + CLK_SET_RATE_GATE, CCU_DIV_RESET_DOMAIN), + CCU_DIV_VAR_INFO(CCU_AXI_GMAC1_CLK, "axi_gmac1_clk", "eth_clk", + CCU_AXI_GMAC1_BASE, 4, + CLK_SET_RATE_GATE, CCU_DIV_RESET_DOMAIN), + CCU_DIV_VAR_INFO(CCU_AXI_XGMAC_CLK, "axi_xgmac_clk", "eth_clk", + CCU_AXI_XGMAC_BASE, 4, + CLK_SET_RATE_GATE, CCU_DIV_RESET_DOMAIN), + CCU_DIV_VAR_INFO(CCU_AXI_PCIE_M_CLK, "axi_pcie_m_clk", "pcie_clk", + CCU_AXI_PCIE_M_BASE, 4, + CLK_SET_RATE_GATE, CCU_DIV_RESET_DOMAIN), + CCU_DIV_VAR_INFO(CCU_AXI_PCIE_S_CLK, "axi_pcie_s_clk", "pcie_clk", + CCU_AXI_PCIE_S_BASE, 4, + CLK_SET_RATE_GATE, CCU_DIV_RESET_DOMAIN), + CCU_DIV_VAR_INFO(CCU_AXI_USB_CLK, "axi_usb_clk", "sata_clk", + CCU_AXI_USB_BASE, 4, + CLK_SET_RATE_GATE, CCU_DIV_RESET_DOMAIN), + CCU_DIV_VAR_INFO(CCU_AXI_HWA_CLK, "axi_hwa_clk", "sata_clk", + CCU_AXI_HWA_BASE, 4, + CLK_SET_RATE_GATE, CCU_DIV_RESET_DOMAIN), + CCU_DIV_VAR_INFO(CCU_AXI_SRAM_CLK, "axi_sram_clk", "eth_clk", + CCU_AXI_SRAM_BASE, 4, + CLK_SET_RATE_GATE, CCU_DIV_RESET_DOMAIN) +}; + +/* + * APB-bus clock is marked as critical since it's a main communication bus + * for the SoC devices registers IO-operations. + */ +static const struct ccu_div_info sys_info[] = { + CCU_DIV_VAR_INFO(CCU_SYS_SATA_CLK, "sys_sata_clk", + "sata_clk", CCU_SYS_SATA_REF_BASE, 4, + CLK_SET_RATE_GATE, + CCU_DIV_SKIP_ONE | CCU_DIV_LOCK_SHIFTED | + CCU_DIV_RESET_DOMAIN), + CCU_DIV_BUF_INFO(CCU_SYS_SATA_REF_CLK, "sys_sata_ref_clk", + "sys_sata_clk", CCU_SYS_SATA_REF_BASE, + CLK_SET_RATE_PARENT), + CCU_DIV_VAR_INFO(CCU_SYS_APB_CLK, "sys_apb_clk", + "pcie_clk", CCU_SYS_APB_BASE, 5, + CLK_IS_CRITICAL, CCU_DIV_BASIC | CCU_DIV_RESET_DOMAIN), + CCU_DIV_GATE_INFO(CCU_SYS_GMAC0_TX_CLK, "sys_gmac0_tx_clk", + "eth_clk", CCU_SYS_GMAC0_BASE, 5), + CCU_DIV_FIXED_INFO(CCU_SYS_GMAC0_PTP_CLK, "sys_gmac0_ptp_clk", + "eth_clk", 10), + CCU_DIV_GATE_INFO(CCU_SYS_GMAC1_TX_CLK, "sys_gmac1_tx_clk", + "eth_clk", CCU_SYS_GMAC1_BASE, 5), + CCU_DIV_FIXED_INFO(CCU_SYS_GMAC1_PTP_CLK, "sys_gmac1_ptp_clk", + "eth_clk", 10), + CCU_DIV_GATE_INFO(CCU_SYS_XGMAC_CLK, "sys_xgmac_clk", + "eth_clk", CCU_SYS_XGMAC_BASE, 1), + CCU_DIV_FIXED_INFO(CCU_SYS_XGMAC_REF_CLK, "sys_xgmac_ref_clk", + "sys_xgmac_clk", 8), + CCU_DIV_FIXED_INFO(CCU_SYS_XGMAC_PTP_CLK, "sys_xgmac_ptp_clk", + "sys_xgmac_clk", 8), + CCU_DIV_GATE_INFO(CCU_SYS_USB_CLK, "sys_usb_clk", + "eth_clk", CCU_SYS_USB_BASE, 10), + CCU_DIV_VAR_INFO(CCU_SYS_PVT_CLK, "sys_pvt_clk", + "ref_clk", CCU_SYS_PVT_BASE, 5, + CLK_SET_RATE_GATE, 0), + CCU_DIV_VAR_INFO(CCU_SYS_HWA_CLK, "sys_hwa_clk", + "sata_clk", CCU_SYS_HWA_BASE, 4, + CLK_SET_RATE_GATE, 0), + CCU_DIV_VAR_INFO(CCU_SYS_UART_CLK, "sys_uart_clk", + "eth_clk", CCU_SYS_UART_BASE, 17, + CLK_SET_RATE_GATE, 0), + CCU_DIV_FIXED_INFO(CCU_SYS_I2C1_CLK, "sys_i2c1_clk", + "eth_clk", 10), + CCU_DIV_FIXED_INFO(CCU_SYS_I2C2_CLK, "sys_i2c2_clk", + "eth_clk", 10), + CCU_DIV_FIXED_INFO(CCU_SYS_GPIO_CLK, "sys_gpio_clk", + "ref_clk", 25), + CCU_DIV_VAR_INFO(CCU_SYS_TIMER0_CLK, "sys_timer0_clk", + "ref_clk", CCU_SYS_TIMER0_BASE, 17, + CLK_SET_RATE_GATE, CCU_DIV_BASIC), + CCU_DIV_VAR_INFO(CCU_SYS_TIMER1_CLK, "sys_timer1_clk", + "ref_clk", CCU_SYS_TIMER1_BASE, 17, + CLK_SET_RATE_GATE, CCU_DIV_BASIC), + CCU_DIV_VAR_INFO(CCU_SYS_TIMER2_CLK, "sys_timer2_clk", + "ref_clk", CCU_SYS_TIMER2_BASE, 17, + CLK_SET_RATE_GATE, CCU_DIV_BASIC), + CCU_DIV_VAR_INFO(CCU_SYS_WDT_CLK, "sys_wdt_clk", + "eth_clk", CCU_SYS_WDT_BASE, 17, + CLK_SET_RATE_GATE, CCU_DIV_SKIP_ONE_TO_THREE) +}; + +static struct ccu_div_data *axi_data; +static struct ccu_div_data *sys_data; + +static void ccu_div_set_data(struct ccu_div_data *data) +{ + struct device_node *np = data->np; + + if (of_device_is_compatible(np, "baikal,bt1-ccu-axi")) + axi_data = data; + else if (of_device_is_compatible(np, "baikal,bt1-ccu-sys")) + sys_data = data; + else + pr_err("Invalid DT node '%s' specified\n", of_node_full_name(np)); +} + +static struct ccu_div_data *ccu_div_get_data(struct device_node *np) +{ + if (of_device_is_compatible(np, "baikal,bt1-ccu-axi")) + return axi_data; + else if (of_device_is_compatible(np, "baikal,bt1-ccu-sys")) + return sys_data; + + pr_err("Invalid DT node '%s' specified\n", of_node_full_name(np)); + + return NULL; +} + +static struct ccu_div *ccu_div_find_desc(struct ccu_div_data *data, + unsigned int clk_id) +{ + int idx; + + for (idx = 0; idx < data->divs_num; ++idx) { + if (data->divs_info[idx].id == clk_id) + return data->divs[idx]; + } + + return ERR_PTR(-EINVAL); +} + +static struct ccu_div_data *ccu_div_create_data(struct device_node *np) +{ + struct ccu_div_data *data; + int ret; + + data = kzalloc_obj(*data); + if (!data) + return ERR_PTR(-ENOMEM); + + data->np = np; + if (of_device_is_compatible(np, "baikal,bt1-ccu-axi")) { + data->divs_num = ARRAY_SIZE(axi_info); + data->divs_info = axi_info; + } else if (of_device_is_compatible(np, "baikal,bt1-ccu-sys")) { + data->divs_num = ARRAY_SIZE(sys_info); + data->divs_info = sys_info; + } else { + pr_err("Incompatible DT node '%s' specified\n", + of_node_full_name(np)); + ret = -EINVAL; + goto err_kfree_data; + } + + data->divs = kzalloc_objs(*data->divs, data->divs_num); + if (!data->divs) { + ret = -ENOMEM; + goto err_kfree_data; + } + + return data; + +err_kfree_data: + kfree(data); + + return ERR_PTR(ret); +} + +static void ccu_div_free_data(struct ccu_div_data *data) +{ + kfree(data->divs); + + kfree(data); +} + +static int ccu_div_find_sys_regs(struct ccu_div_data *data) +{ + data->sys_regs = syscon_node_to_regmap(data->np->parent); + if (IS_ERR(data->sys_regs)) { + pr_err("Failed to find syscon regs for '%s'\n", + of_node_full_name(data->np)); + return PTR_ERR(data->sys_regs); + } + + return 0; +} + +static struct clk_hw *ccu_div_of_clk_hw_get(struct of_phandle_args *clkspec, + void *priv) +{ + struct ccu_div_data *data = priv; + struct ccu_div *div; + unsigned int clk_id; + + clk_id = clkspec->args[0]; + div = ccu_div_find_desc(data, clk_id); + if (IS_ERR(div)) { + if (div != ERR_PTR(-EPROBE_DEFER)) + pr_info("Invalid clock ID %d specified\n", clk_id); + + return ERR_CAST(div); + } + + return ccu_div_get_clk_hw(div); +} + +static int ccu_div_clk_register(struct ccu_div_data *data, bool defer) +{ + int idx, ret; + + for (idx = 0; idx < data->divs_num; ++idx) { + const struct ccu_div_info *info = &data->divs_info[idx]; + struct ccu_div_init_data init = {0}; + + if (!!(info->features & CCU_DIV_BASIC) ^ defer) { + if (!data->divs[idx]) + data->divs[idx] = ERR_PTR(-EPROBE_DEFER); + + continue; + } + + init.id = info->id; + init.name = info->name; + init.parent_name = info->parent_name; + init.np = data->np; + init.type = info->type; + init.flags = info->flags; + init.features = info->features; + + if (init.type == CCU_DIV_VAR) { + init.base = info->base; + init.sys_regs = data->sys_regs; + init.width = info->width; + } else if (init.type == CCU_DIV_GATE) { + init.base = info->base; + init.sys_regs = data->sys_regs; + init.divider = info->divider; + } else if (init.type == CCU_DIV_BUF) { + init.base = info->base; + init.sys_regs = data->sys_regs; + } else { + init.divider = info->divider; + } + + data->divs[idx] = ccu_div_hw_register(&init); + if (IS_ERR(data->divs[idx])) { + ret = PTR_ERR(data->divs[idx]); + pr_err("Couldn't register divider '%s' hw\n", + init.name); + goto err_hw_unregister; + } + } + + return 0; + +err_hw_unregister: + for (--idx; idx >= 0; --idx) { + if (!!(data->divs_info[idx].features & CCU_DIV_BASIC) ^ defer) + continue; + + ccu_div_hw_unregister(data->divs[idx]); + } + + return ret; +} + +static void ccu_div_clk_unregister(struct ccu_div_data *data, bool defer) +{ + int idx; + + /* Uninstall only the clocks registered on the specified stage */ + for (idx = 0; idx < data->divs_num; ++idx) { + if (!!(data->divs_info[idx].features & CCU_DIV_BASIC) ^ defer) + continue; + + ccu_div_hw_unregister(data->divs[idx]); + } +} + +static int ccu_div_of_register(struct ccu_div_data *data) +{ + int ret; + + ret = of_clk_add_hw_provider(data->np, ccu_div_of_clk_hw_get, data); + if (ret) { + pr_err("Couldn't register dividers '%s' clock provider\n", + of_node_full_name(data->np)); + } + + return ret; +} + +static int ccu_div_rst_register(struct ccu_div_data *data) +{ + struct ccu_rst_init_data init = {0}; + + init.sys_regs = data->sys_regs; + init.np = data->np; + + data->rsts = ccu_rst_hw_register(&init); + if (IS_ERR(data->rsts)) { + pr_err("Couldn't register divider '%s' reset controller\n", + of_node_full_name(data->np)); + return PTR_ERR(data->rsts); + } + + return 0; +} + +static int ccu_div_probe(struct platform_device *pdev) +{ + struct ccu_div_data *data; + int ret; + + data = ccu_div_get_data(dev_of_node(&pdev->dev)); + if (!data) + return -EINVAL; + + ret = ccu_div_clk_register(data, false); + if (ret) + return ret; + + ret = ccu_div_rst_register(data); + if (ret) + goto err_clk_unregister; + + return 0; + +err_clk_unregister: + ccu_div_clk_unregister(data, false); + + return ret; +} + +static const struct of_device_id ccu_div_of_match[] = { + { .compatible = "baikal,bt1-ccu-axi" }, + { .compatible = "baikal,bt1-ccu-sys" }, + { } +}; + +static struct platform_driver ccu_div_driver = { + .probe = ccu_div_probe, + .driver = { + .name = "clk-ccu-div", + .of_match_table = ccu_div_of_match, + .suppress_bind_attrs = true, + }, +}; +builtin_platform_driver(ccu_div_driver); + +static __init void ccu_div_init(struct device_node *np) +{ + struct ccu_div_data *data; + int ret; + + data = ccu_div_create_data(np); + if (IS_ERR(data)) + return; + + ret = ccu_div_find_sys_regs(data); + if (ret) + goto err_free_data; + + ret = ccu_div_clk_register(data, true); + if (ret) + goto err_free_data; + + ret = ccu_div_of_register(data); + if (ret) + goto err_clk_unregister; + + ccu_div_set_data(data); + + return; + +err_clk_unregister: + ccu_div_clk_unregister(data, true); + +err_free_data: + ccu_div_free_data(data); +} +CLK_OF_DECLARE_DRIVER(ccu_axi, "baikal,bt1-ccu-axi", ccu_div_init); +CLK_OF_DECLARE_DRIVER(ccu_sys, "baikal,bt1-ccu-sys", ccu_div_init); diff --git a/drivers/clk/bt1/clk-ccu-pll.c b/drivers/clk/bt1/clk-ccu-pll.c new file mode 100644 index 0000000000000..e5e4a6ea6f782 --- /dev/null +++ b/drivers/clk/bt1/clk-ccu-pll.c @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC + * + * Authors: + * Serge Semin + * Dmitry Dunaev + * + * Baikal-T1 CCU PLL clocks driver + */ + +#define pr_fmt(fmt) "bt1-ccu-pll: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ccu-pll.h" + +#define CCU_CPU_PLL_BASE 0x000 +#define CCU_SATA_PLL_BASE 0x008 +#define CCU_DDR_PLL_BASE 0x010 +#define CCU_PCIE_PLL_BASE 0x018 +#define CCU_ETH_PLL_BASE 0x020 + +#define CCU_PLL_INFO(_id, _name, _pname, _base, _flags, _features) \ + { \ + .id = _id, \ + .name = _name, \ + .parent_name = _pname, \ + .base = _base, \ + .flags = _flags, \ + .features = _features, \ + } + +#define CCU_PLL_NUM ARRAY_SIZE(pll_info) + +struct ccu_pll_info { + unsigned int id; + const char *name; + const char *parent_name; + unsigned int base; + unsigned long flags; + unsigned long features; +}; + +/* + * Alas we have to mark all PLLs as critical. CPU and DDR PLLs are sources of + * CPU cores and DDR controller reference clocks, due to which they obviously + * shouldn't be ever gated. SATA and PCIe PLLs are the parents of APB-bus and + * DDR controller AXI-bus clocks. If they are gated the system will be + * unusable. Moreover disabling SATA and Ethernet PLLs causes automatic reset + * of the corresponding subsystems. So until we aren't ready to re-initialize + * all the devices consuming those PLLs, they will be marked as critical too. + */ +static const struct ccu_pll_info pll_info[] = { + CCU_PLL_INFO(CCU_CPU_PLL, "cpu_pll", "ref_clk", CCU_CPU_PLL_BASE, + CLK_IS_CRITICAL, CCU_PLL_BASIC), + CCU_PLL_INFO(CCU_SATA_PLL, "sata_pll", "ref_clk", CCU_SATA_PLL_BASE, + CLK_IS_CRITICAL | CLK_SET_RATE_GATE, 0), + CCU_PLL_INFO(CCU_DDR_PLL, "ddr_pll", "ref_clk", CCU_DDR_PLL_BASE, + CLK_IS_CRITICAL | CLK_SET_RATE_GATE, 0), + CCU_PLL_INFO(CCU_PCIE_PLL, "pcie_pll", "ref_clk", CCU_PCIE_PLL_BASE, + CLK_IS_CRITICAL, CCU_PLL_BASIC), + CCU_PLL_INFO(CCU_ETH_PLL, "eth_pll", "ref_clk", CCU_ETH_PLL_BASE, + CLK_IS_CRITICAL | CLK_SET_RATE_GATE, 0) +}; + +struct ccu_pll_data { + struct device_node *np; + struct regmap *sys_regs; + struct ccu_pll *plls[CCU_PLL_NUM]; +}; + +static struct ccu_pll_data *pll_data; + +static struct ccu_pll *ccu_pll_find_desc(struct ccu_pll_data *data, + unsigned int clk_id) +{ + int idx; + + for (idx = 0; idx < CCU_PLL_NUM; ++idx) { + if (pll_info[idx].id == clk_id) + return data->plls[idx]; + } + + return ERR_PTR(-EINVAL); +} + +static struct ccu_pll_data *ccu_pll_create_data(struct device_node *np) +{ + struct ccu_pll_data *data; + + data = kzalloc_obj(*data); + if (!data) + return ERR_PTR(-ENOMEM); + + data->np = np; + + return data; +} + +static void ccu_pll_free_data(struct ccu_pll_data *data) +{ + kfree(data); +} + +static int ccu_pll_find_sys_regs(struct ccu_pll_data *data) +{ + data->sys_regs = syscon_node_to_regmap(data->np->parent); + if (IS_ERR(data->sys_regs)) { + pr_err("Failed to find syscon regs for '%s'\n", + of_node_full_name(data->np)); + return PTR_ERR(data->sys_regs); + } + + return 0; +} + +static struct clk_hw *ccu_pll_of_clk_hw_get(struct of_phandle_args *clkspec, + void *priv) +{ + struct ccu_pll_data *data = priv; + struct ccu_pll *pll; + unsigned int clk_id; + + clk_id = clkspec->args[0]; + pll = ccu_pll_find_desc(data, clk_id); + if (IS_ERR(pll)) { + if (pll != ERR_PTR(-EPROBE_DEFER)) + pr_info("Invalid PLL clock ID %d specified\n", clk_id); + + return ERR_CAST(pll); + } + + return ccu_pll_get_clk_hw(pll); +} + +static int ccu_pll_clk_register(struct ccu_pll_data *data, bool defer) +{ + int idx, ret; + + for (idx = 0; idx < CCU_PLL_NUM; ++idx) { + const struct ccu_pll_info *info = &pll_info[idx]; + struct ccu_pll_init_data init = {0}; + + /* Defer non-basic PLLs allocation for the probe stage */ + if (!!(info->features & CCU_PLL_BASIC) ^ defer) { + if (!data->plls[idx]) + data->plls[idx] = ERR_PTR(-EPROBE_DEFER); + + continue; + } + + init.id = info->id; + init.name = info->name; + init.parent_name = info->parent_name; + init.base = info->base; + init.sys_regs = data->sys_regs; + init.np = data->np; + init.flags = info->flags; + init.features = info->features; + + data->plls[idx] = ccu_pll_hw_register(&init); + if (IS_ERR(data->plls[idx])) { + ret = PTR_ERR(data->plls[idx]); + pr_err("Couldn't register PLL hw '%s'\n", + init.name); + goto err_hw_unregister; + } + } + + return 0; + +err_hw_unregister: + for (--idx; idx >= 0; --idx) { + if (!!(pll_info[idx].features & CCU_PLL_BASIC) ^ defer) + continue; + + ccu_pll_hw_unregister(data->plls[idx]); + } + + return ret; +} + +static void ccu_pll_clk_unregister(struct ccu_pll_data *data, bool defer) +{ + int idx; + + /* Uninstall only the clocks registered on the specified stage */ + for (idx = 0; idx < CCU_PLL_NUM; ++idx) { + if (!!(pll_info[idx].features & CCU_PLL_BASIC) ^ defer) + continue; + + ccu_pll_hw_unregister(data->plls[idx]); + } +} + +static int ccu_pll_of_register(struct ccu_pll_data *data) +{ + int ret; + + ret = of_clk_add_hw_provider(data->np, ccu_pll_of_clk_hw_get, data); + if (ret) { + pr_err("Couldn't register PLL provider of '%s'\n", + of_node_full_name(data->np)); + } + + return ret; +} + +static int ccu_pll_probe(struct platform_device *pdev) +{ + struct ccu_pll_data *data = pll_data; + + if (!data) + return -EINVAL; + + return ccu_pll_clk_register(data, false); +} + +static const struct of_device_id ccu_pll_of_match[] = { + { .compatible = "baikal,bt1-ccu-pll" }, + { } +}; + +static struct platform_driver ccu_pll_driver = { + .probe = ccu_pll_probe, + .driver = { + .name = "clk-ccu-pll", + .of_match_table = ccu_pll_of_match, + .suppress_bind_attrs = true, + }, +}; +builtin_platform_driver(ccu_pll_driver); + +static __init void ccu_pll_init(struct device_node *np) +{ + struct ccu_pll_data *data; + int ret; + + data = ccu_pll_create_data(np); + if (IS_ERR(data)) + return; + + ret = ccu_pll_find_sys_regs(data); + if (ret) + goto err_free_data; + + ret = ccu_pll_clk_register(data, true); + if (ret) + goto err_free_data; + + ret = ccu_pll_of_register(data); + if (ret) + goto err_clk_unregister; + + pll_data = data; + + return; + +err_clk_unregister: + ccu_pll_clk_unregister(data, true); + +err_free_data: + ccu_pll_free_data(data); +} +CLK_OF_DECLARE_DRIVER(ccu_pll, "baikal,bt1-ccu-pll", ccu_pll_init); -- cgit v1.2.3 From 287f7ed64f1812a0623a5cad6572be01fdb441ca Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 3 Aug 2026 14:19:51 +0300 Subject: clk: baikal-t1: Add direct AXI DDR reset support Currently only the self-cleared AXI DDR reset is supported by the driver and exposed in the DT-bindings. In some circumstances of the DDR-controller initialization procedure though it gets to be not enough to make the memory controller working. Let's add the flag directly handling the AXI DDR signal, which is a fixup provided by the HW designers just in case the self-cleared version won't work correctly. The resultant signal is (!RST_T & !RST_D). Note it's just a decorative change since the DDR controller is initialized on the bootloader stage anyway. It's also ok to fix the reset IDs like that since there is no user of the bindings in upstream anyway. Any new user shall compile in the correct IDs in the DTBs in time. Signed-off-by: Serge Semin --- drivers/clk/bt1/ccu-rst.c | 5 +++-- include/dt-bindings/reset/bt1-ccu.h | 21 +++++++++++---------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/drivers/clk/bt1/ccu-rst.c b/drivers/clk/bt1/ccu-rst.c index 969e5de381a8e..269994a3d5e1e 100644 --- a/drivers/clk/bt1/ccu-rst.c +++ b/drivers/clk/bt1/ccu-rst.c @@ -64,11 +64,12 @@ struct ccu_rst_info { /* * Each AXI-bus clock divider is equipped with the corresponding clock-consumer - * domain reset (it's self-deasserted reset control). + * domain reset (it's mostly self-deasserted reset control). */ static const struct ccu_rst_info axi_rst_info[] = { [CCU_AXI_MAIN_RST] = CCU_RST_TRIG(CCU_AXI_MAIN_BASE, 1), - [CCU_AXI_DDR_RST] = CCU_RST_TRIG(CCU_AXI_DDR_BASE, 1), + [CCU_AXI_DDR_T_RST] = CCU_RST_TRIG(CCU_AXI_DDR_BASE, 1), + [CCU_AXI_DDR_D_RST] = CCU_RST_DIR(CCU_AXI_DDR_BASE, 8), [CCU_AXI_SATA_RST] = CCU_RST_TRIG(CCU_AXI_SATA_BASE, 1), [CCU_AXI_GMAC0_RST] = CCU_RST_TRIG(CCU_AXI_GMAC0_BASE, 1), [CCU_AXI_GMAC1_RST] = CCU_RST_TRIG(CCU_AXI_GMAC1_BASE, 1), diff --git a/include/dt-bindings/reset/bt1-ccu.h b/include/dt-bindings/reset/bt1-ccu.h index c691efaa678f7..1259bd08bd0e0 100644 --- a/include/dt-bindings/reset/bt1-ccu.h +++ b/include/dt-bindings/reset/bt1-ccu.h @@ -8,16 +8,17 @@ #define __DT_BINDINGS_RESET_BT1_CCU_H #define CCU_AXI_MAIN_RST 0 -#define CCU_AXI_DDR_RST 1 -#define CCU_AXI_SATA_RST 2 -#define CCU_AXI_GMAC0_RST 3 -#define CCU_AXI_GMAC1_RST 4 -#define CCU_AXI_XGMAC_RST 5 -#define CCU_AXI_PCIE_M_RST 6 -#define CCU_AXI_PCIE_S_RST 7 -#define CCU_AXI_USB_RST 8 -#define CCU_AXI_HWA_RST 9 -#define CCU_AXI_SRAM_RST 10 +#define CCU_AXI_DDR_T_RST 1 +#define CCU_AXI_DDR_D_RST 2 +#define CCU_AXI_SATA_RST 3 +#define CCU_AXI_GMAC0_RST 4 +#define CCU_AXI_GMAC1_RST 5 +#define CCU_AXI_XGMAC_RST 6 +#define CCU_AXI_PCIE_M_RST 7 +#define CCU_AXI_PCIE_S_RST 8 +#define CCU_AXI_USB_RST 9 +#define CCU_AXI_HWA_RST 10 +#define CCU_AXI_SRAM_RST 11 #define CCU_SYS_SATA_REF_RST 0 #define CCU_SYS_APB_RST 1 -- cgit v1.2.3 From d91e94462d724ac92fe7696be2dfd2658454b0a2 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 25 Nov 2019 00:46:41 +0300 Subject: mips: Add Baikal-T1 SoC support Baikal-T1 SoC is based on the MIPS Warrior P5600 IP-core with a lot of interfaces embedded. In particular it includes two CPU cores, fast peripherals like PCIe x4 Gen.3, 2x SATA III, 10Gbit Ethernet, 2x Gbit ethernet ports, USB 2.0 port, multiple slow peripheral interfaces like 32x/3x GPIO, 3x i2c, 3x high-resolution APB timers, 2x UARTs, 3x SPI, PVT sensor. This patch adds the Baikal-T1 platform basic support to the MIPS architectural code. First of all the kernel for Baikal-T1 platform is built when MIPS_BAIKAL_T1 is enabled. Since Baikal-T1 is based on P5600 core it provides all the features the IP-core has and implemented in the currently available MIPS architecture code, like SMP for two CPU cores, CM2 v2, FPU3/MSA, CPC/CPS, MIPS GIC, GCR, etc. All of the configs selected by MIPS_BAIKAL_T1 are pretty much well known and the reason of them being enabled is obvious. DMA non-coherent config must be explained in details though. The problem is that all of the peripheral devices desribed above are conneceted to each other with a dedicated X-bar also called main interconnect. This X-bar is attached to the memory OCP port, which leaves no choice but to declare all the devices DMA to be non-coherent, since neither CM2 nor CPU itself have no way to automatically keep the caches and touched by device memory chunks coherent. Note that even though the architecture code are still able to detect IOCU/IOMMU in the chip, it's useless since devices are accessible via the memory port. There is another peculiarity regarding the chip. Alas not all interfaces are able to reach the memory higher than 4GiB. It concerns all the fast peripherals except PCIe, which can do DMA to higher addresses. It makes XPA mode is usable with corresponding limitations. In order to work around the problem the SWIOTLB buffers are enabled when XPA is switched on and keep on with its initialization if there is greater than 4GiB memory available. In order to have the architecture code as clean and as fast as possible the CPU-features flags are macro pre-defined for Baikal-T1 SoC. While this can be switched off, it's recommended to have the features overrides since it may improve the system performance in some cases. Here is the actions the Baikal-T1 platform code performs to initialize the chip. First of all the kernel_entry_setup assembler macro tunes the default initialized with legacy configs memory segments (kuseg, kseg0, kseg1, kseg2) or EVA-mapping and always enables the EVA mode by setting CP0.Status.K0 flag. Note the CONFIG_EVA isn't enabled by default, but can be so extending the low-memory to be up to 2.5GB. Then prom_init() extracts fdt address from the bootup kernel arguments. Platform specific memory initializer just adds the default memory regions and searches fdt blob looking if some more memory chunk is available. Then the boot up procedure proceeds with device tree, CPC/CPS initialiations. In case if XPA is enabled and there is memory above 4GiB the SWIOTLB slubs are allocated. After that the system timers are initialized. At last the code adds the Baikal-T1 SoC instance to the respective kernel bus. Signed-off-by: Serge Semin --- arch/mips/Kbuild.platforms | 1 + arch/mips/Kconfig | 49 ++++ arch/mips/bt1/Kconfig | 99 ++++++++ arch/mips/bt1/Makefile | 9 + arch/mips/bt1/Platform | 16 ++ arch/mips/bt1/early_printk.c | 40 +++ arch/mips/bt1/init.c | 281 +++++++++++++++++++++ arch/mips/bt1/irq.c | 59 +++++ .../include/asm/mach-bt1/cpu-feature-overrides.h | 144 +++++++++++ arch/mips/include/asm/mach-bt1/irq.h | 60 +++++ arch/mips/include/asm/mach-bt1/kernel-entry-init.h | 232 +++++++++++++++++ arch/mips/include/asm/mach-bt1/kmalloc.h | 8 + arch/mips/include/asm/mach-bt1/memory.h | 118 +++++++++ arch/mips/include/asm/mach-bt1/spaces.h | 40 +++ 14 files changed, 1156 insertions(+) create mode 100644 arch/mips/bt1/Kconfig create mode 100644 arch/mips/bt1/Makefile create mode 100644 arch/mips/bt1/Platform create mode 100644 arch/mips/bt1/early_printk.c create mode 100644 arch/mips/bt1/init.c create mode 100644 arch/mips/bt1/irq.c create mode 100644 arch/mips/include/asm/mach-bt1/cpu-feature-overrides.h create mode 100644 arch/mips/include/asm/mach-bt1/irq.h create mode 100644 arch/mips/include/asm/mach-bt1/kernel-entry-init.h create mode 100644 arch/mips/include/asm/mach-bt1/kmalloc.h create mode 100644 arch/mips/include/asm/mach-bt1/memory.h create mode 100644 arch/mips/include/asm/mach-bt1/spaces.h diff --git a/arch/mips/Kbuild.platforms b/arch/mips/Kbuild.platforms index 41a00fa860c18..c3c49e5e14785 100644 --- a/arch/mips/Kbuild.platforms +++ b/arch/mips/Kbuild.platforms @@ -4,6 +4,7 @@ platform-$(CONFIG_MIPS_ALCHEMY) += alchemy/ platform-$(CONFIG_ATH25) += ath25/ platform-$(CONFIG_ATH79) += ath79/ +platform-$(CONFIG_MIPS_BAIKAL_T1) += bt1/ platform-$(CONFIG_BCM47XX) += bcm47xx/ platform-$(CONFIG_BCM63XX) += bcm63xx/ platform-$(CONFIG_BMIPS_GENERIC) += bmips/ diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig index 983749d076aa0..357acab77ee45 100644 --- a/arch/mips/Kconfig +++ b/arch/mips/Kconfig @@ -555,6 +555,54 @@ config MACH_LOONGSON64 and Loongson-2F which will be removed), developed by the Institute of Computing Technology (ICT), Chinese Academy of Sciences (CAS). +config MIPS_BAIKAL_T1 + bool "MIPS Baikal-T1 SoC" + imply MIPS_CPS + select BOOT_ELF32 + select BOOT_RAW + select USE_OF + select GENERIC_ISA_DMA + select DMA_NONCOHERENT + select SWIOTLB if ARCH_DMA_ADDR_T_64BIT + select SCHED_HRTICK + select HAVE_PCI + select PCI_DRIVERS_GENERIC + select COMMON_CLK + select ARCH_HAS_RESET_CONTROLLER + select UHI_BOOT + select MIPS_CPU_SCACHE + select IRQ_MIPS_CPU + select MIPS_GIC + select CLKSRC_MIPS_GIC + select CEVT_R4K + select CSRC_R4K + select HARDIRQS_SW_RESEND + select DW_APB_TIMER_OF + select MIPS_EXTERNAL_TIMER + select GENERIC_CLOCKEVENTS_MIN_ADJUST + select SMP_UP if SMP + select EDAC_SUPPORT + select EDAC_ATOMIC_SCRUB + select SOC_BUS + select STRONG_UC_ORDERING + select SYS_SUPPORTS_MIPS_CPS + select SYS_HAS_CPU_MIPS32_R2 + select SYS_HAS_CPU_MIPS32_R3_5 + select SYS_HAS_CPU_MIPS32_R5 + select SYS_HAS_CPU_P5600 + select SYS_HAS_EARLY_PRINTK + select SYS_SUPPORTS_LITTLE_ENDIAN + select SYS_SUPPORTS_HIGHMEM + select SYS_SUPPORTS_32BIT_KERNEL + select SYS_SUPPORTS_RELOCATABLE + select SYS_SUPPORTS_ZBOOT + select SYS_SUPPORTS_ZBOOT_UART_PROM + select CPU_MIPSR2_IRQ_VI + select CPU_MIPSR2_IRQ_EI + select MIPS_L1_CACHE_SHIFT_5 + help + This enables support of Baikal Electronics Baikal-T1 SoC platform. + config MIPS_MALTA bool "MIPS Malta board" select ARCH_MAY_HAVE_PC_FDC @@ -1064,6 +1112,7 @@ source "arch/mips/ath79/Kconfig" source "arch/mips/bcm47xx/Kconfig" source "arch/mips/bcm63xx/Kconfig" source "arch/mips/bmips/Kconfig" +source "arch/mips/bt1/Kconfig" source "arch/mips/econet/Kconfig" source "arch/mips/generic/Kconfig" source "arch/mips/ingenic/Kconfig" diff --git a/arch/mips/bt1/Kconfig b/arch/mips/bt1/Kconfig new file mode 100644 index 0000000000000..435e98e37a93b --- /dev/null +++ b/arch/mips/bt1/Kconfig @@ -0,0 +1,99 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Copyright (C) 2020 BAIKAL ELECTRONICS, JSC +# +# Baikal-T1 platform configs +# +if MIPS_BAIKAL_T1 + +config BT1_DEBUG + bool "Enable SoC/kernel debug options" + select EXPERT + select DEBUG_KERNEL + select DEBUG_ZBOOT + select DEBUG_MEMORY_INIT + select DEBUG_HIGHMEM if HIGHMEM + select DEBUG_STACKOVERFLOW + select RCU_TRACE + select EDAC_DEBUG if EDAC + select SCACHE_DEBUGFS + select GENERIC_IRQ_DEBUGFS + select CMA_DEBUGFS if CMA + select MIPS_CPS_NS16550_BOOL if MIPS_CPS + help + Use this option if you at the process of the kernel drivers + platform code development. + +config BT1_EARLY_UART + int "Default UART device for early printk and zboot" + range 0 1 + default 0 + help + There are two DW APB UART-based serial interfaces available on + Baikal-T1 SoC. By this option you can select one of them to be used + to print early logs and zboot debug symbols. Note having both + EARLY_PRINTK and SERIAL_EARLYCON configs enabled is prune to + getting duplicated log messages if both of these sub-systems are + using the same console. In case if you need to have the logs on both + UART devices make sure that this parameter and 'stdout-path' DT + property point to the different serial devices. + +config BT1_CPU_FEATURE_OVERRIDES + bool "Declare CPU features" + help + By default nearly all the MIPS IP-core features are detectable on + runtime. Corresponding cpu_has_* flags are constantly checked in + the code to enabled/disable corresponding platform features. Since + we indend to build the Baikal-T1 CPU specific kernel there is no + need in such flexibility, so we can freely define these flags with + values known at build-time. By doing so we not only decrease the + kernel size, but also speed it up. + + If unsure, say N. + +config BT1_SWIOTLB_SIZE + int "SWIOTLB size in MiB" if SWIOTLB + range 4 64 + default 8 + help + Due to the Baikal-T1 main interconnect controller invalid synthesis + parameters, SATA/USB/GMACx aren't able to access the physical memory + higher than 4GiB. So in case if XPA is enabled and bootloader states + there is more than 4GiB of physical memory, we need to have the + SWIOTLB declared. Since by default SWIOTLB consumes too much memory + we create a custom table with compile-time configurable buffer size. + +choice + prompt "Baikal-T1 SoC based boards devicetree" + default BT1_DTB_NONE + help + Select a devicetree of the board with Baikal-T1 SoC installed. + +config BT1_DTB_NONE + bool "None" + +endchoice + +menu "Baikal-T1 Errata" + +config BT1_ERRATA_JR_LS_BUG + bool "Fix load/store bonding and JR prediction bug" + help + Early Baikal-T1 chips had problems when load/store bonding and JR + prediction were enabled. Switch these features off if you are using + the engineering version of the chip. + + If unsure, say N. + +config BT1_ERRATA_GMAC_SPEED_INV_BUG + bool "Fix DW GMAC 10/100Mbit link speed bug" + help + DW GMAC on early Baikal-T1 chip releases had an inverted 10/100Mbit + MAC speed settings. So when 10Mbit link is requested then 100Mbit MAC + link speed should be setup and vise-versa. + + If unsure, say N. + +endmenu + +endif # MIPS_BAIKAL_T1 diff --git a/arch/mips/bt1/Makefile b/arch/mips/bt1/Makefile new file mode 100644 index 0000000000000..9052fa7ca6f6d --- /dev/null +++ b/arch/mips/bt1/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Copyright (C) 2020 BAIKAL ELECTRONICS, JSC +# +# Baikal-T1 platform code makefile +# +obj-y += init.o irq.o + +obj-$(CONFIG_EARLY_PRINTK) += early_printk.o diff --git a/arch/mips/bt1/Platform b/arch/mips/bt1/Platform new file mode 100644 index 0000000000000..82016b641af91 --- /dev/null +++ b/arch/mips/bt1/Platform @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Copyright (C) 2020 BAIKAL ELECTRONICS, JSC +# +# Baikal-T1 platform makefile +# +platform-$(CONFIG_MIPS_BAIKAL_T1) += bt1/ +cflags-$(CONFIG_MIPS_BAIKAL_T1) += -I$(srctree)/arch/mips/include/asm/mach-bt1 +ifdef CONFIG_EVA + load-$(CONFIG_MIPS_BAIKAL_T1) += 0xffffffff00100000 + zload-$(CONFIG_MIPS_BAIKAL_T1) += 0xffffffff00100000 +else + load-$(CONFIG_MIPS_BAIKAL_T1) += 0xffffffff80100000 + zload-$(CONFIG_MIPS_BAIKAL_T1) += 0xffffffff85100000 +endif +all-$(CONFIG_MIPS_BAIKAL_T1) := $(COMPRESSION_FNAME).bin diff --git a/arch/mips/bt1/early_printk.c b/arch/mips/bt1/early_printk.c new file mode 100644 index 0000000000000..949741decf323 --- /dev/null +++ b/arch/mips/bt1/early_printk.c @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC + * + * Authors: + * Alexey Malahov + * Serge Semin + * + * Baikal-T1 early printk + */ +#include +#include +#include +#include + +#include + +#include + +#define BT1_UART_BASE(_id) \ + (void *)KSEG1ADDR(CONCATENATE(BT1_UART, CONCATENATE(_id, _BASE))) + +void prom_putchar(char c) +{ + void __iomem *uart_base = BT1_UART_BASE(CONFIG_BT1_EARLY_UART); + unsigned int timeout = 50000; + int status, bits; + + bits = UART_LSR_TEMT | UART_LSR_THRE; + + do { + status = __raw_readl(uart_base + (UART_LSR << 2)); + + if (--timeout == 0) + break; + } while ((status & bits) != bits); + + if (timeout) + __raw_writel(c, uart_base + (UART_TX << 2)); +} diff --git a/arch/mips/bt1/init.c b/arch/mips/bt1/init.c new file mode 100644 index 0000000000000..ce3104087f495 --- /dev/null +++ b/arch/mips/bt1/init.c @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC + * + * Authors: + * Alexey Malahov + * Serge Semin + * + * Baikal-T1 platform initialization + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static __initdata const void *fdt; + +/* + * The following configuration have been used to synthesize the Baikal-T1 + * MIPS Warroir P5600 core: + * 1) SI_EVAReset = 0 - boot in legacy (not EVA) memory layout mode after + * reset. + * 2) SI_UseExceptionBase = 0 - core uses legacy BEV mode, which selects + * 0xBFC00000 to be exception vector by default after reset. + * 3) SI_ExceptionBase[31:12] = 0xBFC00000 - externally set default exception + * SI_ExceptionBasePA[31:29] = 0x0 base address. It is used when + * CP0.CONFIG5.K = 1. + * 4) SI_EICPresent = 0 - even though GIC is always attached to the cores, + * this pin is hardwaired to the state of the + * GIC_VX_CTL_EIC bit. + */ + +/* + * Redefine the MIPS CDMM phys base method to be used at the earliest boot + * stage before DT is parsed. + */ +#ifdef CONFIG_MIPS_EJTAG_FDC_EARLYCON + +phys_addr_t mips_cdmm_phys_base(void) +{ + return BT1_P5600_CDMM_BASE; +} + +#endif /* CONFIG_MIPS_EJTAG_FDC_EARLYCON */ + +/* + * We have to redefine the L2-sync phys base method, since the default + * region overlaps the Baikal-T1 boot memory following the CM2 GCRs. + */ +phys_addr_t mips_cm_l2sync_phys_base(void) +{ + return BT1_P5600_GCR_L2SYNC_BASE; +} + +void __init *plat_get_fdt(void) +{ + const char *str; + + /* Return already found fdt. */ + if (fdt) + return (void *)fdt; + + /* + * Generic method will search for appended, built-in and UHI DTBs. + * Some older version of Baikal-T1 bootloader could also pass DTB via + * the FW arg3 slot. So check that option too. + */ + fdt = get_fdt(); + if (fdt) { + str = (fw_arg0 == -2) ? "UHI" : "Built-in/Appended"; + } else if (fw_arg3) { + str = "Legacy"; + fdt = (void *)fw_arg3; + } else { + panic("No DTB found. Can't continue."); + } + + /* + * DTB base address can be as physical address in case of passed as + * legacy arg3 or as address within legacy KSEG0 region passed by + * U-boot, but always in the low 128MB. Convert the address to the + * kernel virtual space for unification and EVA support as well. + */ + fdt = phys_to_virt(CPHYSADDR(fdt)); + + if (fdt_check_header(fdt)) + panic("No valid DTB found. Can't continue."); + + pr_info("DTB found at: 0x%lx (%s)\n", (unsigned long)fdt, str); + + return (void *)fdt; +} + +#ifdef CONFIG_RELOCATABLE + +void __init plat_fdt_relocated(void *new_location) +{ + fdt = NULL; + + /* + * Forget about the way dtb has been passed at the system startup. Use + * UHI always. + */ + fw_arg0 = -2; + fw_arg1 = (unsigned long)new_location; +} + +#endif /* CONFIG_RELOCATABLE */ + +void __init prom_init(void) +{ + if (IS_ENABLED(CONFIG_EVA) && (read_c0_config5() & MIPS_CONF5_K)) + pr_info("Enhanced Virtual Addressing (EVA) enabled\n"); + + /* + * Disable Legacy SYNC transaction performed on the L2/Memory port. + * This shall significantly improve the concurrent MMIO access + * performance. + */ + change_gcr_control(CM_GCR_CONTROL_SYNCDIS, CM_GCR_CONTROL_SYNCDIS); + + plat_get_fdt(); +} + +void __init plat_mem_setup(void) +{ + memblock_add(BT1_LOMEM_BASE, BT1_LOMEM_SIZE); + + memblock_add(BT1_HIMEM_BASE, BT1_HIMEM_SIZE); + +#ifdef CONFIG_PCI + PCIBIOS_MIN_IO = 0x100; +#endif + + __dt_setup_arch((void *)fdt); +} + +void __init device_tree_init(void) +{ + int err; + + unflatten_and_copy_device_tree(); + + mips_cpc_probe(); + + err = register_cps_smp_ops(); + if (err) + err = register_up_smp_ops(); +} + +#ifdef CONFIG_SWIOTLB + +void __init plat_swiotlb_setup(void) +{ + phys_addr_t top; + + /* + * Skip SWIOTLB initialization since there is no that much memory to + * cause the peripherals invalid access. + */ + top = memblock_end_of_DRAM(); + if (top <= SIZE_MAX) + return; + + /* + * Override the default SWIOTLB size with the configuration value. + * Note a custom size has been passed via the kernel parameter it won't + * be overwritten. + */ + swiotlb_adjust_size(CONFIG_BT1_SWIOTLB_SIZE * SZ_1M); + swiotlb_init(true, SWIOTLB_VERBOSE); +} + +#endif /* CONFIG_SWIOTLB */ + +void __init prom_free_prom_memory(void) {} + +#define HZ_TO_MHZ(_hz) (_hz / 1000000) +#define HZ_GET_KHZ(_hz) ((_hz / 1000) % 1000) +void __init plat_time_init(void) +{ + struct device_node *np; + unsigned long rate; + struct clk *clk; + + of_clk_init(NULL); + + np = of_get_cpu_node(0, NULL); + if (!np) { + pr_err("Failed to get CPU of node\n"); + goto err_timer_probe; + } + + clk = of_clk_get(np, 0); + if (IS_ERR(clk)) { + pr_err("Failed to get CPU clock (%ld)\n", PTR_ERR(clk)); + goto err_timer_probe; + } + + /* CPU count/compare timer runs at half the CPU frequency. */ + rate = clk_get_rate(clk); + mips_hpt_frequency = rate / 2; + + pr_info("MIPS CPU frequency: %lu.%03lu MHz\n", + HZ_TO_MHZ(rate), HZ_GET_KHZ(rate)); + pr_info("MIPS CPU count/compare timer frequency: %u.%03u MHz\n", + HZ_TO_MHZ(mips_hpt_frequency), HZ_GET_KHZ(mips_hpt_frequency)); + + clk_put(clk); + +err_timer_probe: + timer_probe(); +} + +const char *get_system_type(void) +{ + return "Baikal-T1 SoC"; +} + +static struct bt1_soc { + struct soc_device_attribute dev_attr; + char revision[16]; + char id[16]; +} soc; + +static int __init soc_setup(void) +{ + unsigned int cpuid = boot_cpu_data.processor_id; + struct soc_device *soc_dev; + struct device *parent = NULL; + int ret = 0; + + soc.dev_attr.machine = mips_get_machine_name(); + soc.dev_attr.family = get_system_type(); + soc.dev_attr.revision = soc.revision; + soc.dev_attr.soc_id = soc.id; + + snprintf(soc.revision, sizeof(soc.revision) - 1, "%u.%u.%u", + (cpuid >> 5) & 0x07, (cpuid >> 2) & 0x07, cpuid & 0x03); + snprintf(soc.id, sizeof(soc.id) - 1, "0x%08X", + readl(phys_to_virt(BT1_BOOT_CTRL_BASE + BT1_BOOT_CTRL_DRID))); + + soc_dev = soc_device_register(&soc.dev_attr); + if (IS_ERR(soc_dev)) { + ret = PTR_ERR(soc_dev); + goto err_return; + } + + parent = soc_device_to_device(soc_dev); + +err_return: + return ret; +} +arch_initcall(soc_setup); diff --git a/arch/mips/bt1/irq.c b/arch/mips/bt1/irq.c new file mode 100644 index 0000000000000..9890dd0ee1b3d --- /dev/null +++ b/arch/mips/bt1/irq.c @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC + * + * Baikal-T1 IRQ initialization + */ +#include + +#include +#include +#include +#include +#include + +int get_c0_fdc_int(void) +{ + return gic_get_c0_fdc_int(); +} + +int get_c0_perfcount_int(void) +{ + return gic_get_c0_perfcount_int(); +} + +unsigned int get_c0_compare_int(void) +{ + return gic_get_c0_compare_int(); +} + +/* + * If CP0.Cause.IV == 1 and cpu_has_veic = 1 the next method isn't supposed + * to be called ever. Otherwise we just handle a vectored interrupt, which was + * routed to the generic exception vector. + */ +#if !defined(CONFIG_IRQ_MIPS_CPU) + +asmlinkage void plat_irq_dispatch(void) +{ + extern unsigned long vi_handlers[]; + unsigned int cause = (read_c0_cause() & CAUSEF_IP) >> CAUSEB_IP2; + void (*isr)(void) = (void *)vi_handlers[cause]; + + if (cause && isr) + isr(); + else if (cause && !isr) + panic("Vectored interrupt %u handler is empty\n", cause); + else + spurious_interrupt(); +} + +#endif /* !CONFIG_IRQ_MIPS_CPU */ + +void __init arch_init_irq(void) +{ + if (!cpu_has_veic) + mips_cpu_irq_init(); + + irqchip_init(); +} diff --git a/arch/mips/include/asm/mach-bt1/cpu-feature-overrides.h b/arch/mips/include/asm/mach-bt1/cpu-feature-overrides.h new file mode 100644 index 0000000000000..090600494f6ad --- /dev/null +++ b/arch/mips/include/asm/mach-bt1/cpu-feature-overrides.h @@ -0,0 +1,144 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC + * + * Baikal-T1 core features override + */ +#ifndef __ASM_MACH_BT1_CPU_FEATURE_OVERRIDES_H__ +#define __ASM_MACH_BT1_CPU_FEATURE_OVERRIDES_H__ + +#ifdef CONFIG_BT1_CPU_FEATURE_OVERRIDES + +#define cpu_has_tlb 1 +/* Don't override FTLB flag otherwise 'noftlb' option won't work. */ +/* #define cpu_has_ftlb 1 */ +#define cpu_has_tlbinv 1 +#define cpu_has_segments 1 +#define cpu_has_eva 1 +#define cpu_has_htw 1 +#define cpu_has_ldpte 0 +#define cpu_has_rixiex 1 +#define cpu_has_maar 1 +#define cpu_has_rw_llb 1 + +#define cpu_has_3kex 0 +#define cpu_has_4kex 1 +#define cpu_has_3k_cache 0 +#define cpu_has_4k_cache 1 +#define cpu_has_tx39_cache 0 +#define cpu_has_octeon_cache 0 + +/* Don't override FPU flags otherwise 'nofpu' option won't work. */ +/* #define cpu_has_fpu 1 */ +/* #define raw_cpu_has_fpu 1 */ +#define cpu_has_32fpr 1 + +#define cpu_has_counter 1 +#define cpu_has_watch 1 +#define cpu_has_divec 1 +#define cpu_has_vce 0 +#define cpu_has_cache_cdex_p 0 +#define cpu_has_cache_cdex_s 0 +#define cpu_has_prefetch 1 +#define cpu_has_mcheck 1 +#define cpu_has_ejtag 1 +#define cpu_has_llsc 1 +#define cpu_has_bp_ghist 0 +#define cpu_has_guestctl0ext 1 /* ? */ +#define cpu_has_guestctl1 1 /* ? */ +#define cpu_has_guestctl2 1 /* ? */ +#define cpu_has_guestid 1 +#define cpu_has_drg 0 +#define cpu_has_mips16 0 +#define cpu_has_mips16e2 0 +#define cpu_has_mdmx 0 +#define cpu_has_mips3d 0 +#define cpu_has_smartmips 0 + +#define cpu_has_rixi 1 + +#define cpu_has_mmips 0 +#define cpu_has_lpa 1 +#define cpu_has_mvh 1 +#define cpu_has_xpa 1 +#define cpu_has_vtag_icache 0 +#define cpu_has_dc_aliases 0 +#define cpu_has_ic_fills_f_dc 0 +#define cpu_has_pindexed_dcache 0 +/* Depends on the MIPS_CM/SMP configs. */ +/* #define cpu_icache_snoops_remote_store 1 */ + +/* + * MIPS P5600 Warrior is based on the MIPS32 Release 2 architecture, which + * makes it backward compatible with all 32bits early MIPS architecture + * releases. + */ +#define cpu_has_mips_1 1 +#define cpu_has_mips_2 1 +#define cpu_has_mips_3 0 +#define cpu_has_mips_4 0 +#define cpu_has_mips_5 0 +#define cpu_has_mips32r1 1 +#define cpu_has_mips32r2 1 +#define cpu_has_mips32r5 1 +#define cpu_has_mips32r6 0 +#define cpu_has_mips64r1 0 +#define cpu_has_mips64r2 0 +#define cpu_has_mips64r6 0 +#define cpu_has_mips_r2_exec_hazard 0 + +#define cpu_has_clo_clz 1 +#define cpu_has_wsbh 1 +#define cpu_has_dsp 0 +#define cpu_has_dsp2 0 +#define cpu_has_dsp3 0 +#define cpu_has_loongson_mmi 0 +#define cpu_has_loongson_cam 0 +#define cpu_has_loongson_ext 0 +#define cpu_has_loongson_ext2 0 +#define cpu_has_mipsmt 0 +#define cpu_has_vp 0 +#define cpu_has_userlocal 1 + +#define cpu_has_nofpuex 0 +#define cpu_has_64bits 0 +#define cpu_has_64bit_zero_reg 0 +#define cpu_has_64bit_gp_regs 0 +#define cpu_has_64bit_addresses 0 +/* + * VINT is hardwired to 1 by P5600 design while VEIC as being SI_EICPresent + * and seeing we always have MIPS GIC available in the chip must have been set + * to 1. Alas the IP core engineers mistakenly made it to be wired with + * GIC_VX_CTL_EIC bit. Lets fix it by manually setting the flag to 1. + */ +#define cpu_has_vint 1 +#define cpu_has_veic 1 +/* Chaches line size is fixed by P5600 design. */ +#define cpu_has_inclusive_pcaches 0 +#define cpu_dcache_line_size() 32 +#define cpu_icache_line_size() 32 +#define cpu_scache_line_size() 32 +#define cpu_tcache_line_size() 0 +#define cpu_hwrena_impl_bits 0 +#define cpu_has_perf_cntr_intr_bit 1 +#define cpu_has_vz 1 +#define cpu_has_msa 1 +#define cpu_has_ufr 1 +#define cpu_has_fre 0 +#define cpu_has_cdmm 1 +#define cpu_has_small_pages 0 +#define cpu_has_nan_legacy 0 +#define cpu_has_nan_2008 1 +#define cpu_has_ebase_wg 1 +#define cpu_has_badinstr 1 +#define cpu_has_badinstrp 1 +#define cpu_has_contextconfig 1 +#define cpu_has_perf 1 +#define cpu_has_mac2008_only 0 +#define cpu_has_mmid 0 +#define cpu_has_mm_sysad 0 +#define cpu_has_mm_full 1 + +#endif /* CONFIG_BT1_CPU_FEATURE_OVERRIDES */ + +#endif /* __ASM_MACH_BT1_CPU_FEATURE_OVERRIDES_H__ */ diff --git a/arch/mips/include/asm/mach-bt1/irq.h b/arch/mips/include/asm/mach-bt1/irq.h new file mode 100644 index 0000000000000..68b4d6849078d --- /dev/null +++ b/arch/mips/include/asm/mach-bt1/irq.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC + * + * Baikal-T1 IRQ numbers declaration + */ +#ifndef __ASM_MACH_BT1_IRQ_H__ +#define __ASM_MACH_BT1_IRQ_H__ + +#define NR_IRQS 255 +#define MIPS_CPU_IRQ_BASE 0 + +#define BT1_APB_EHB_IRQ 16 +#define BT1_WDT_IRQ 17 +#define BT1_GPIO32_IRQ 19 +#define BT1_TIMER0_IRQ 24 +#define BT1_TIMER1_IRQ 25 +#define BT1_TIMER2_IRQ 26 +#define BT1_PVT_IRQ 31 +#define BT1_I2C1_IRQ 33 +#define BT1_I2C2_IRQ 34 +#define BT1_SPI1_IRQ 40 +#define BT1_SPI2_IRQ 41 +#define BT1_UART0_IRQ 48 +#define BT1_UART1_IRQ 49 +#define BT1_DMAC_IRQ 56 +#define BT1_SATA_IRQ 64 +#define BT1_USB_IRQ 68 +#define BT1_GMAC0_IRQ 72 +#define BT1_GMAC1_IRQ 73 +#define BT1_XGMAC_IRQ 74 +#define BT1_XGMAC_TX0_IRQ 75 +#define BT1_XGMAC_TX1_IRQ 76 +#define BT1_XGMAC_RX0_IRQ 77 +#define BT1_XGMAC_RX1_IRQ 78 +#define BT1_XGMAC_XPCS_IRQ 79 +#define BT1_PCIE_EDMA_TX0_IRQ 80 +#define BT1_PCIE_EDMA_TX1_IRQ 81 +#define BT1_PCIE_EDMA_TX2_IRQ 82 +#define BT1_PCIE_EDMA_TX3_IRQ 83 +#define BT1_PCIE_EDMA_RX0_IRQ 84 +#define BT1_PCIE_EDMA_RX1_IRQ 85 +#define BT1_PCIE_EDMA_RX2_IRQ 86 +#define BT1_PCIE_EDMA_RX3_IRQ 87 +#define BT1_PCIE_MSI_IRQ 88 +#define BT1_PCIE_AER_IRQ 89 +#define BT1_PCIE_PME_IRQ 90 +#define BT1_PCIE_HP_IRQ 91 +#define BT1_PCIE_BW_IRQ 92 +#define BT1_PCIE_L_REQ_IRQ 93 +#define BT1_DDR_DFI_E_IRQ 96 +#define BT1_DDR_ECC_CE_IRQ 97 +#define BT1_DDR_ECC_UE_IRQ 98 +#define BT1_DDR_ECC_SBR_IRQ 99 +#define BT1_HWA_IRQ 104 +#define BT1_AXI_EHB_IRQ 127 + +#include_next + +#endif /* __ASM_MACH_BT1_IRQ_H__ */ diff --git a/arch/mips/include/asm/mach-bt1/kernel-entry-init.h b/arch/mips/include/asm/mach-bt1/kernel-entry-init.h new file mode 100644 index 0000000000000..b5a87a3803d3b --- /dev/null +++ b/arch/mips/include/asm/mach-bt1/kernel-entry-init.h @@ -0,0 +1,232 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC + * + * Baikal-T1 platform low-level initialization + */ +#ifndef __ASM_MACH_BT1_KERNEL_ENTRY_INIT_H__ +#define __ASM_MACH_BT1_KERNEL_ENTRY_INIT_H__ + +#include +#include +#include + +#include + +#define MIPS_SEGCFG_SET(cfg, pa, am, eu, c) \ + (((pa << MIPS_SEGCFG_PA_SHIFT) | \ + (MIPS_SEGCFG_## am << MIPS_SEGCFG_AM_SHIFT) | \ + (eu << MIPS_SEGCFG_EU_SHIFT) | \ + (c << MIPS_SEGCFG_C_SHIFT)) << (16 * (cfg % 2))) + + /* + * Enable SegCtlX.CFGY_C attribute affecting segments caching + */ + .macro platform_eva_cca + + .set push + .set reorder + + mfc0 t0, CP0_CONFIG5 + li t1, MIPS_CONF5_K + or t0, t0, t1 + mtc0 t0, CP0_CONFIG5 + + sync + la t1, 1f + jr.hb t1 + nop +1: + + .set pop + .endm + + /* + * Prepare segments for EVA boot: + * + * =========================== Mappings =============================== + * CFG Virtual memory Physical memory CCA Mapping + * 5 0x00000000 0x3fffffff 0x00000000 0x3ffffffff CWB MUSUK (xkseg0) + * 4 0x40000000 0x7fffffff 0x40000000 0x7ffffffff CWB MUSUK (xkseg0) + * 3 0x80000000 0x9fffffff 0x80000000 0x9ffffffff CWB MUSUK (xkseg0) + * Flat 2.5GB physical mem + * + * 2 0xa0000000 0xbfffffff 0x00000000 0x1ffffffff UC MUSUK (kseg1) + * 1 0xc0000000 0xdfffffff - CWB MSK (kseg2) + * 0 0xe0000000 0xffffffff - CWB MK (kseg3) + * where CCA encoding: + * UCA = 7 Uncached Accelerated (Write-combine), non-coherent; + * CWB = 5 Cacheable, coherent, write-back, write-allocate, + * read misses request Shared (SMP); + * CWBE = 4 Cacheable, coherent, write-back, write-allocate, + * read misses request Exclusive (SMP); + * WB = 3 Cacheable, non-coherent, write-back, write allocate + * UC = 2 Uncached non-coherent; + * mappings encoding: + * UK = 0 Kernel-only unmapped region; + * MK = 1 Kernel-only mapped region; + * MSK = 2 Supervisor and kernel mapped region; + * MUSK = 3 User, Supervisor and kernel mapped region; + * MUSUK = 4 Used to implement a fully-mapped flat address space in + * user and supervisor modes, with unmapped regions which + * appear in kernel mode; + * USK = 5 Supervisor and kernel unmapped region; + * UUSK = 7 Unrestricted unmapped region + * + * The following code uses the t0 and t1 registers without previously + * preserving them. + * + */ + .macro platform_eva_init + + .set push + .set reorder + + /* SegCtl0 */ + li t0, MIPS_SEGCFG_SET(0, 0, MK, 0, 0) | \ + MIPS_SEGCFG_SET(1, 0, MSK, 0, 0) + mtc0 t0, CP0_SEGCTL0 + + /* SegCtl1 */ + li t0, MIPS_SEGCFG_SET(2, 0, MUSUK, 1, 2) | \ + MIPS_SEGCFG_SET(3, 4, MUSUK, 1, 5) + mtc0 t0, CP0_SEGCTL1 + + /* SegCtl2 */ + li t0, MIPS_SEGCFG_SET(4, 2, MUSUK, 1, 5) | \ + MIPS_SEGCFG_SET(5, 0, MUSUK, 1, 5) + mtc0 t0, CP0_SEGCTL2 + + /* Make sure C0 is written */ + sync + ehb + + /* Enable EVAs CCA */ + platform_eva_cca + + .set pop + .endm + + /* + * Prepare segments for LEGACY boot: + * + * =========================== Mappings ============================= + * CFG Virtual memory Physical memory CCA Mapping + * 5 0x00000000 0x3fffffff - CWB MUSK (kuseg) + * 4 0x40000000 0x7fffffff - CWB MUSK (kuseg) + * 3 0x80000000 0x9fffffff 0x00000000 0x1ffffffff CWB UK (kseg0) + * 2 0xa0000000 0xbfffffff 0x00000000 0x1ffffffff UC UK (kseg1) + * 1 0xc0000000 0xdfffffff - CWB MSK (kseg2) + * 0 0xe0000000 0xffffffff - CWB MK (kseg3) + * + * Note K0 = 2 by default on MIPS Warrior P5600, but bootloader changes + * it to K0 = 3. It must be K0 = 5 for SMP to work correctly. + * + * The following code uses the t0 and t1 registers without previously + * preserving them. + * + */ + .macro platform_legacy_init + + .set push + .set reorder + + /* SegCtl0 */ + li t0, MIPS_SEGCFG_SET(0, 0, MK, 0, 0) | \ + MIPS_SEGCFG_SET(1, 0, MSK, 0, 0) + mtc0 t0, CP0_SEGCTL0 + + /* SegCtl1 */ + li t0, MIPS_SEGCFG_SET(2, 0, UK, 1, 2) | \ + MIPS_SEGCFG_SET(3, 0, UK, 1, 5) + mtc0 t0, CP0_SEGCTL1 + + /* SegCtl2 */ + li t0, MIPS_SEGCFG_SET(4, 2, MUSK, 1, 0) | \ + MIPS_SEGCFG_SET(5, 0, MUSK, 1, 0) + mtc0 t0, CP0_SEGCTL2 + + /* Make sure C0 is written */ + sync + ehb + + /* Enable EVAs CCA */ + platform_eva_cca + + .set pop + .endm + + /* + * Baikal-T1 engineering chip had problems if the next features + * were enabled. + */ + .macro platform_errata_jr_ls_fix + + .set push + .set reorder + + /* Disable load/store bonding. */ + mfc0 t0, CP0_CONFIG, 6 + lui t1, (MTI_CONF6_DLSB >> 16) + or t0, t0, t1 + /* Disable all JR prediction except JR $31. */ + ori t0, t0, MTI_CONF6_JRCD + mtc0 t0, CP0_CONFIG, 6 + sync + jal mips_ihb + nop + + /* Disable all JR $31 prediction through return prediction stack. */ + mfc0 t0, CP0_CONFIG, 7 + ori t0, t0, MIPS_CONF7_RPS + mtc0 t0, CP0_CONFIG, 7 + sync + jal mips_ihb + nop + + .set pop + .endm + + /* + * Setup Baikal-T1 platform specific setups of the memory segments + * layout. In case if the kernel is built for engineering version + * of the chip some errata must be fixed. + */ + .macro kernel_entry_setup + + sync + ehb + +#ifdef CONFIG_EVA + platform_eva_init +#else + platform_legacy_init +#endif + +#ifdef CONFIG_BT1_ERRATA_JR_LS_BUG + platform_errata_jr_ls_fix +#endif + + .endm + + /* + * Do SMP slave processor setup necessary before we can safely execute + * C code. + */ + .macro smp_slave_setup + + sync + ehb + +#ifdef CONFIG_EVA + platform_eva_init +#else + platform_legacy_init +#endif + +#ifdef CONFIG_BT1_ERRATA_JR_LS_BUG + platform_errata_jr_ls_fix +#endif + + .endm +#endif /* __ASM_MACH_BT1_KERNEL_ENTRY_INIT_H__ */ diff --git a/arch/mips/include/asm/mach-bt1/kmalloc.h b/arch/mips/include/asm/mach-bt1/kmalloc.h new file mode 100644 index 0000000000000..7ad20f623f246 --- /dev/null +++ b/arch/mips/include/asm/mach-bt1/kmalloc.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __ASM_MACH_BT1_KMALLOC_H +#define __ASM_MACH_BT1_KMALLOC_H + +/* The default of 128 bytes wastes too much, use 32 (D,I,S cacheline size) */ +#define ARCH_DMA_MINALIGN L1_CACHE_BYTES + +#endif /* __ASM_MACH_BT1_KMALLOC_H */ diff --git a/arch/mips/include/asm/mach-bt1/memory.h b/arch/mips/include/asm/mach-bt1/memory.h new file mode 100644 index 0000000000000..ad51b14f17d6f --- /dev/null +++ b/arch/mips/include/asm/mach-bt1/memory.h @@ -0,0 +1,118 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC + * + * Baikal-T1 SoC Static Memory Mapping + */ +#ifndef __ASM_MACH_BT1_MEMORY_H__ +#define __ASM_MACH_BT1_MEMORY_H__ + +#include +#include + +#define BT1_LOMEM_BASE 0x00000000 +#define BT1_LOMEM_SIZE SZ_128M + +#define BT1_MMIO_START BT1_PCIE_MAP_BASE +#define BT1_MMIO_SIZE (SZ_128M + SZ_256M) + +#define BT1_PCIE_MAP_BASE 0x08000000 +#define BT1_PCIE_MAP_SIZE 0x13DC0000 + +#define BT1_P5600_GIC_BASE 0x1BDC0000 +#define BT1_P5600_GIC_SIZE SZ_128K +#define BT1_P5600_CPC_BASE 0x1BDE0000 +#define BT1_P5600_CPC_SIZE SZ_32K +#define BT1_P5600_CDMM_BASE 0x1BDE8000 +#define BT1_P5600_CDMM_SIZE SZ_32K + +#define BT1_SRAM_BASE 0x1BF80000 +#define BT1_SRAM_SIZE SZ_64K +#define BT1_ROM_BASE 0x1BFC0000 +#define BT1_ROM_SIZE SZ_64K +#define BT1_FLASH_BASE 0x1C000000 +#define BT1_FLASH_SIZE SZ_16M + +#define BT1_BOOT_CTRL_BASE 0x1F040000 +#define BT1_BOOT_CTRL_SIZE SZ_4K +#define BT1_BOOT_CTRL_CSR 0x00 +#define BT1_BOOT_CTRL_MAR 0x04 +#define BT1_BOOT_CTRL_DRID 0x08 +#define BT1_BOOT_CTRL_VID 0x0C + +#define BT1_DMAC_BASE 0x1F041000 +#define BT1_DMAC_SIZE SZ_4K +#define BT1_DDR_UMCTL2_BASE 0x1F042000 +#define BT1_DDR_UMCTL2_SIZE SZ_4K +#define BT1_DDR_PHY_BASE 0x1F043000 +#define BT1_DDR_PHY_SIZE SZ_4K +#define BT1_GPIO32_BASE 0x1F044000 +#define BT1_GPIO32_SIZE SZ_4K +#define BT1_GPIO3_BASE 0x1F045000 +#define BT1_GPIO3_SIZE SZ_4K +#define BT1_I2C1_BASE 0x1F046000 +#define BT1_I2C1_SIZE SZ_4K +#define BT1_I2C2_BASE 0x1F047000 +#define BT1_I2C2_SIZE SZ_4K +#define BT1_TIMERS_BASE 0x1F049000 +#define BT1_TIMERS_SIZE SZ_4K +#define BT1_UART0_BASE 0x1F04A000 +#define BT1_UART0_SIZE SZ_4K +#define BT1_UART1_BASE 0x1F04B000 +#define BT1_UART1_SIZE SZ_4K +#define BT1_WDT_BASE 0x1F04C000 +#define BT1_WDT_SIZE SZ_4K +#define BT1_CCU_BASE 0x1F04D000 +#define BT1_CCU_SIZE SZ_4K +#define BT1_SPI1_BASE 0x1F04E000 +#define BT1_SPI1_SIZE SZ_4K +#define BT1_SPI2_BASE 0x1F04F000 +#define BT1_SPI2_SIZE SZ_4K +#define BT1_SATA_BASE 0x1F050000 +#define BT1_SATA_SIZE SZ_4K +#define BT1_PCIE_BASE 0x1F052000 +#define BT1_PCIE_SIZE SZ_4K +#define BT1_PCIE_DBI2_BASE 0x1F053000 +#define BT1_PCIE_DBI2_SIZE SZ_4K +#define BT1_XGMAC_BASE 0x1F054000 +#define BT1_XGMAC_SIZE SZ_16K +#define BT1_APB_EHB_BASE 0x1F059000 +#define BT1_APB_EHB_SIZE SZ_4K +#define BT1_MAIN_IC_BASE 0x1F05A000 +#define BT1_MAIN_IC_SIZE SZ_4K +#define BT1_HWA_BASE 0x1F05B000 +#define BT1_HWA_SIZE SZ_8K +#define BT1_XGMAC_XPCS_BASE 0x1F05D000 +#define BT1_XGMAC_XPCS_SIZE SZ_4K +#define BT1_GMAC0_BASE 0x1F05E000 +#define BT1_GMAC0_SIZE SZ_8K +#define BT1_GMAC1_BASE 0x1F060000 +#define BT1_GMAC1_SIZE SZ_8K +#define BT1_USB_BASE 0x1F100000 +#define BT1_USB_SIZE SZ_1M +#define BT1_PVT_BASE 0x1F200000 +#define BT1_PVT_SIZE SZ_4K +#define BT1_EFUSE_BASE 0x1F201000 +#define BT1_EFUSE_SIZE SZ_4K + +#define BT1_P5600_GCR_L2SYNC_BASE 0x1FBF0000 +#define BT1_P5600_GCR_L2SYNC_SIZE SZ_4K +#define BT1_P5600_GCB_BASE 0x1FBF8000 +#define BT1_P5600_GCB_SIZE SZ_8K +#define BT1_P5600_CLCB_BASE 0x1FBFA000 +#define BT1_P5600_CLCB_SIZE SZ_8K +#define BT1_P5600_COCB_BASE 0x1FBFC000 +#define BT1_P5600_COCB_SIZE SZ_8K +#define BT1_P5600_DBG_BASE 0x1FBFE000 +#define BT1_P5600_DBG_SIZE SZ_8K +#define BT1_BOOT_MAP_BASE 0x1FC00000 +#define BT1_BOOT_MAP_SIZE SZ_4M + +#define BT1_DEFAULT_BEV KSEG1ADDR(BT1_BOOT_MAP_BASE) + +#define BT1_MMIO_END BT1_HIMEM_BASE + +#define BT1_HIMEM_BASE 0x20000000 +#define BT1_HIMEM_SIZE SZ_256M + +#endif /* __ASM_MACH_BT1_MEMORY_H__ */ diff --git a/arch/mips/include/asm/mach-bt1/spaces.h b/arch/mips/include/asm/mach-bt1/spaces.h new file mode 100644 index 0000000000000..32d80d5233f23 --- /dev/null +++ b/arch/mips/include/asm/mach-bt1/spaces.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC + * + * Baikal-T1 SoC Memory Spaces + */ +#ifndef __ASM_MACH_BT1_SPACES_H__ +#define __ASM_MACH_BT1_SPACES_H__ + +/* + * Baikal-T1 memory mapping: + * + * 0x00000000 - 0x07ffffff: 1st RAM region, 128MB + * 0x08000000 - 0x1fffffff: I/O, GIC, CPC, Flash, etc + * 0x20000000 - 0x20fffffff: 2nd RAM region - up to 8GB (XPA) + * + * The legacy MIPS memory layout provides the traditional 0x80000000->0x0 + * and 0xa0000000->0x0 virt-to-phys mapping of the lowest 512MB. In case of + * EVA there is a 1:1 virtual-physical mapping up to 0x9fffffff address + * with kernel located within the lowest 128MB memory anyway. The uncached + * mapping has been preserved in the 0xa0000000 - 0xbffffffff range. + */ +#ifdef CONFIG_EVA + +#define PAGE_OFFSET _AC(0x0, UL) +#define PHYS_OFFSET _AC(0x0, UL) +#define CAC_BASE _AC(0x0, UL) +#define HIGHMEM_START _AC(0xa0000000, UL) + +#endif /* CONFIG_EVA */ + +#define PCI_IOBASE ((void __iomem *)mips_io_port_base) +#define PCI_IOSIZE SZ_64K +#define IO_SPACE_LIMIT (PCI_IOSIZE - 1) + +#define pci_remap_iospace pci_remap_iospace + +#include + +#endif /* __ASM_MACH_BT1_SPACES_H__ */ -- cgit v1.2.3 From fb80cfe667bad86cd6480220e69733a1d25694c1 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 25 Nov 2019 01:51:33 +0300 Subject: mips: debug: Set Baikal-T1 CPS UART0 base, shift and width Let's set the very first Baikal-T1 serial interface base address and the registers shift to the CPS debug configs. This is handy for debugging seeing the very first console of the chip is used for debugging on all of our and customer devices. Signed-off-by: Serge Semin --- arch/mips/Kconfig.debug | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/mips/Kconfig.debug b/arch/mips/Kconfig.debug index f4ae7900fcd35..85bf874f5b93f 100644 --- a/arch/mips/Kconfig.debug +++ b/arch/mips/Kconfig.debug @@ -138,6 +138,7 @@ config MIPS_CPS_NS16550 config MIPS_CPS_NS16550_BASE hex "UART Base Address" default 0x1b0003f8 if MIPS_MALTA + default 0x1f04a000 if MIPS_BAIKAL_T1 default 0 help The base address of the ns16550 compatible UART on which to output @@ -147,6 +148,7 @@ config MIPS_CPS_NS16550_BASE config MIPS_CPS_NS16550_SHIFT int "UART Register Shift" + default 2 if MIPS_BAIKAL_T1 default 0 help The number of bits to shift ns16550 register indices by in order to @@ -155,6 +157,7 @@ config MIPS_CPS_NS16550_SHIFT config MIPS_CPS_NS16550_WIDTH int "UART Register Width" + default 4 if MIPS_BAIKAL_T1 default 1 help ns16550 registers width. UART registers IO access methods will be -- cgit v1.2.3 From 38302e61be9f167a4fec0455ee6c8f55d640a910 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 20 May 2020 18:45:53 +0300 Subject: mips: zboot: Add Baikal-T1 UART support Compressed bootable kernel images on MIPS may also perform some printouts to a pre-defined UART interface at the time of an image bootup procedure. It's especially useful for debugging. Let's add the Baikal-T1 UARTx interface to accept those printouts by means of the already available prom_putchar() method. Signed-off-by: Serge Semin --- arch/mips/boot/compressed/Makefile | 1 + arch/mips/boot/compressed/uart-bt1.c | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 arch/mips/boot/compressed/uart-bt1.c diff --git a/arch/mips/boot/compressed/Makefile b/arch/mips/boot/compressed/Makefile index e0b8ec9a95162..e5fca5ba890a3 100644 --- a/arch/mips/boot/compressed/Makefile +++ b/arch/mips/boot/compressed/Makefile @@ -42,6 +42,7 @@ vmlinuzobjs-$(CONFIG_DEBUG_ZBOOT) += $(obj)/dbg.o vmlinuzobjs-$(CONFIG_SYS_SUPPORTS_ZBOOT_UART16550) += $(obj)/uart-16550.o vmlinuzobjs-$(CONFIG_SYS_SUPPORTS_ZBOOT_UART_PROM) += $(obj)/uart-prom.o vmlinuzobjs-$(CONFIG_MIPS_ALCHEMY) += $(obj)/uart-alchemy.o +vmlinuzobjs-$(CONFIG_MIPS_BAIKAL_T1) += $(obj)/uart-bt1.o vmlinuzobjs-$(CONFIG_ATH79) += $(obj)/uart-ath79.o endif diff --git a/arch/mips/boot/compressed/uart-bt1.c b/arch/mips/boot/compressed/uart-bt1.c new file mode 100644 index 0000000000000..a580b561f0d1e --- /dev/null +++ b/arch/mips/boot/compressed/uart-bt1.c @@ -0,0 +1,2 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include "../../bt1/early_printk.c" -- cgit v1.2.3 From 0e701c772fe5c392bcb718332fd18da18b55beb6 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 25 Oct 2023 00:05:58 +0300 Subject: mips: dmi: Add Baikal-T1 SMBIOS support It's possible to have the DMI table being pre-defined on the Baikal-T1 SoC-based system at the 0x0 physical memory offset. The availability depends on the bootloader version. Permit the DMI firmware scan procedure to auto-detect the SMBIOS table at that offset. Signed-off-by: Serge Semin --- arch/mips/Kconfig | 4 ++-- arch/mips/include/asm/dmi.h | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig index 357acab77ee45..2f89a222715e2 100644 --- a/arch/mips/Kconfig +++ b/arch/mips/Kconfig @@ -2763,9 +2763,9 @@ config HW_PERF_EVENTS config DMI bool "Enable DMI scanning" - depends on MACH_LOONGSON64 + depends on MACH_LOONGSON64 || MIPS_BAIKAL_T1 select DMI_SCAN_MACHINE_NON_EFI_FALLBACK - default y + default MACH_LOONGSON64 help Enabled scanning of DMI to identify machine quirks. Say Y here unless you have verified that your setup is not diff --git a/arch/mips/include/asm/dmi.h b/arch/mips/include/asm/dmi.h index dc397f630c660..b14620393727a 100644 --- a/arch/mips/include/asm/dmi.h +++ b/arch/mips/include/asm/dmi.h @@ -15,6 +15,8 @@ #if defined(CONFIG_MACH_LOONGSON64) #define SMBIOS_ENTRY_POINT_SCAN_START 0xFFFE000 +#elif defined(CONFIG_MIPS_BAIKAL_T1) +#define SMBIOS_ENTRY_POINT_SCAN_START 0x0 #endif #endif /* _ASM_DMI_H */ -- cgit v1.2.3 From 35a1a99ad6f9130cc325c570fbbbace8808bbc13 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 25 Nov 2019 01:37:28 +0300 Subject: mips: baikal-t1: Add Generic SoC DTS include file Aside from the MIPS P5600 cores Baikal-T1 SoC provides one PCIe x4 Gen.3 port, 2x SATA III, 10Gbit Ethernet, 2x Gbit Ethernet ports, USB 2.0 port, 32x/3x GPIO, 3x i2c, 3x high-resolution APB timers, 2x UARTs, 3x SPI, PVT sensor. Nearly all of them are based on the DesignWare IP blocks. This commit provides the Baikal-T1 dts files with all of these interfaces described. There are two CPU cores declared having the dedicated CCU PLL as a CPU clocks source. The corresponding OPP table is also provided to have the dt-based CPU-freq driver support. As being memory mapped MIPS GIC, CPC, CDMM and CM2 blocks are described in the corresponding dts nodes. CPU can be reset by means of the dedicated CPU control register available via the chip system controller. It's accordingly described in the syscon-reboot node. In addition the reboot reason can be saved in the WDT RCR register by means of the syscond-reboot-mode driver. Clocks Control Unit is also a part of the System Controller. It consists of four components: high-frequency external oscillator (normally XTAL's 25 MHz crystal oscillator, but in general it can provide any frequency supported by the CCU PLLs), PLLs sub-block to generate a specific higher-frequency clocks from a single external signal, AXI-bus clocks dividers, system devices reference clock dividers. Each of these components are represented in a dedicated DT nodes. Thermal zones are described in a way so to keep the chip in working state. The actual temperature can be detected either by the embedded PVT sensor or by some external device. All the fast interfaces are connected to the main interconnect by means of the AXI bus and described in the corresponding simple-bus node. Similarly the slow peripherals are accessed by means of the APB bus. So we grouped them in a dedicated dts sub-node as well. Note that some of the DMA-capable devices can't reach all the physical memory available for CPU in XPA mode. It concerns Eth0/Eth1, USB and SATA interfaces. That's reflected by setting the 32-bit dma-ranges DT-property, so the dma_capable() method would return false value for the DMA requests targeted above 4GiB. Signed-off-by: Serge Semin --- arch/mips/boot/dts/Makefile | 1 + arch/mips/boot/dts/baikal/Makefile | 6 + arch/mips/boot/dts/baikal/bt1.dtsi | 1257 +++++++++++++++++++++++++++++++ include/dt-bindings/soc/bt1-boot-mode.h | 14 + 4 files changed, 1278 insertions(+) create mode 100644 arch/mips/boot/dts/baikal/Makefile create mode 100644 arch/mips/boot/dts/baikal/bt1.dtsi create mode 100644 include/dt-bindings/soc/bt1-boot-mode.h diff --git a/arch/mips/boot/dts/Makefile b/arch/mips/boot/dts/Makefile index 36c5e4c6e6406..c93a25ad5e11d 100644 --- a/arch/mips/boot/dts/Makefile +++ b/arch/mips/boot/dts/Makefile @@ -1,4 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 +subdir-y += baikal subdir-y += brcm subdir-y += cavium-octeon subdir-y += econet diff --git a/arch/mips/boot/dts/baikal/Makefile b/arch/mips/boot/dts/baikal/Makefile new file mode 100644 index 0000000000000..476fc5e19ae42 --- /dev/null +++ b/arch/mips/boot/dts/baikal/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Copyright (C) 2022 BAIKAL ELECTRONICS, JSC +# +# Baikal-T1 DTBs makefile +# diff --git a/arch/mips/boot/dts/baikal/bt1.dtsi b/arch/mips/boot/dts/baikal/bt1.dtsi new file mode 100644 index 0000000000000..f39884f8249a1 --- /dev/null +++ b/arch/mips/boot/dts/baikal/bt1.dtsi @@ -0,0 +1,1257 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2022 BAIKAL ELECTRONICS, JSC + * + * Baikal-T1 SoC device tree + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +/ { + model = "Baikal-T1 SoC"; + compatible = "baikal,bt1"; + #address-cells = <2>; + #size-cells = <2>; + + interrupt-parent = <&gic>; + + aliases { + serial0 = &uart0; + serial1 = &uart1; + i2c0 = &i2c0; + i2c1 = &i2c1; + i2c2 = &i2c2; + spi0 = &spi0; + spi1 = &spi1; + spi2 = &spi2; + mtd0 = &int_rom; + mc0 = &mc; + ethernet0 = &gmac0; + ethernet1 = &gmac1; + ethernet2 = &xgmac; + }; + + cpus { + #address-cells = <1>; + #size-cells = <0>; + + cpu-map { + cluster0 { + core0 { + cpu = <&cpu0>; + }; + + core1 { + cpu = <&cpu1>; + }; + }; + }; + + cpu0: cpu@0 { + compatible = "baikal,bt1", "img,p5600"; + device_type = "cpu"; + reg = <0x0>; + #cooling-cells = <2>; + + clocks = <&ccu_pll CCU_CPU_PLL>, <&ccu_axi CCU_AXI_MAIN_CLK>; + clock-names = "cpu_clk", "bus_clk"; + + operating-points-v2 = <&cpu_opp>; + }; + + cpu1: cpu@1 { + compatible = "baikal,bt1", "img,p5600"; + device_type = "cpu"; + reg = <0x1>; + #cooling-cells = <2>; + + clocks = <&ccu_pll CCU_CPU_PLL>, <&ccu_axi CCU_AXI_MAIN_CLK>; + clock-names = "cpu_clk", "bus_clk"; + + operating-points-v2 = <&cpu_opp>; + }; + }; + + gic: gic@1bdc0000 { + compatible = "mti,gic"; + reg = <0 0x1bdc0000 0 0x20000>; + + interrupt-controller; + #interrupt-cells = <3>; + mti,reserved-ipi-vectors = <108 4>; + + timer_gic: timer { + compatible = "mti,gic-timer"; + + interrupts = ; + + clocks = <&ccu_pll CCU_CPU_PLL>; + }; + + /* TODO Add GIC Watchdog support */ + }; + + cpc: cpc@1bde0000 { + compatible = "mti,mips-cpc"; + reg = <0 0x1bde0000 0 0x8000>; + }; + + cdmm: cdmm@1bde8000 { + compatible = "mti,mips-cdmm"; + reg = <0 0x1bde8000 0 0x8000>; + }; + + /* + * FIXME TODO Update the mips-cm.c to get the based addresses + * from here as it's done in arch/mips/kernel/mips-cpc.c and in + * drivers/bus/mips_cdmm.c + * Note. This isn't that easy to do, since + * setup_arch() + * +-> cpu_probe(); + * +-> mips_cm_probe(); + * +-> prom_init(); + * +-> ... + * +-> device_tree_init() + * +-> unflatten_and_copy_device_tree(); + * +-> mips_cpc_probe(); + * +-> ... + */ + cm2: cm2@1fbf8000 { + compatible = "mti,mips-cm"; + reg = <0 0x1fbf8000 0 0x8000>, + <0 0x1fbf0000 0 0x1000>; + reg-names = "gcr", "l2sync"; + }; + + /* + * Note setting up too low CPU frequency may cause time-critical + * applications not working correctly. For instance in order to have + * the DW APB SSI memory interface (EEPROM-read and Tx-only) working + * correctly with the whole CPU clock range defined below we had to + * accordingly constraint the SPI bus speed. + */ + cpu_opp: opp-table { + compatible = "operating-points-v2"; + opp-shared; + + opp-300000000 { + opp-hz = /bits/ 64 <300000000>; + clock-latency-ns = <20000>; + }; + + opp-400000000 { + opp-hz = /bits/ 64 <400000000>; + clock-latency-ns = <20000>; + }; + + opp-500000000 { + opp-hz = /bits/ 64 <500000000>; + clock-latency-ns = <20000>; + }; + + opp-600000000 { + opp-hz = /bits/ 64 <600000000>; + clock-latency-ns = <20000>; + }; + + opp-700000000 { + opp-hz = /bits/ 64 <700000000>; + clock-latency-ns = <20000>; + }; + + opp-800000000 { + opp-hz = /bits/ 64 <800000000>; + clock-latency-ns = <20000>; + }; + + opp-900000000 { + opp-hz = /bits/ 64 <900000000>; + clock-latency-ns = <20000>; + }; + + opp-1000000000 { + opp-hz = /bits/ 64 <1000000000>; + clock-latency-ns = <20000>; + }; + + opp-1100000000 { + opp-hz = /bits/ 64 <1100000000>; + clock-latency-ns = <20000>; + }; + + opp-1200000000 { + opp-hz = /bits/ 64 <1200000000>; + clock-latency-ns = <20000>; + }; + }; + + thermal-zones { + cpu-thermal { + polling-delay-passive = <250>; + polling-delay = <1000>; + + thermal-sensors = <&pvt>; + + trips { + cpu_alert0: trip0 { + temperature = <80000>; + hysteresis = <2000>; + type = "active"; + }; + + cpu_alert1: trip1 { + temperature = <90000>; + hysteresis = <2000>; + type = "passive"; + }; + + cpu_warn: trip2 { + temperature = <100000>; + hysteresis = <2000>; + type = "hot"; + }; + + cpu_crit: trip3 { + temperature = <110000>; + hysteresis = <2000>; + type = "critical"; + }; + }; + + cooling-maps { + map-alert1 { + trip = <&cpu_alert1>; + cooling-device = <&cpu0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + }; + + /* External fixed reference clocks */ + clocks { + ref_clk: clock-oscillator-ref { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <25000000>; + clock-output-names = "ref25m"; + }; + }; + + apb: bus@1f059000 { + compatible = "baikal,bt1-apb", "simple-bus"; + reg = <0 0x1f059000 0 0x1000>, + <0 0x1d000000 0 0x2040000>; + reg-names = "ehb", "nodev"; + #address-cells = <1>; + #size-cells = <1>; + + ranges = <0x1bfc0000 0 0x1bfc0000 0x03c38000>, + <0x1fc00000 0 0x1fc00000 0x00400000>; + + interrupts = ; + + clocks = <&ccu_sys CCU_SYS_APB_CLK>; + clock-names = "pclk"; + + resets = <&ccu_sys CCU_SYS_APB_RST>; + reset-names = "prst"; + + syscon: syscon@1f04d000 { + compatible = "baikal,bt1-sys-con", "syscon", "simple-mfd"; + reg = <0x1f04d000 0x1000>; + reg-names = "sys"; + #address-cells = <1>; + #size-cells = <1>; + ranges; + + little-endian; + reg-io-width = <4>; + + ccu_pll: clock-controller@1f04d000 { + compatible = "baikal,bt1-ccu-pll"; + reg = <0x1f04d000 0x028>; + #clock-cells = <1>; + + clocks = <&ref_clk>; + clock-names = "ref_clk"; + }; + + ccu_axi: clock-controller@1f04d030 { + compatible = "baikal,bt1-ccu-axi"; + reg = <0x1f04d030 0x030>; + #clock-cells = <1>; + #reset-cells = <1>; + + clocks = <&ccu_pll CCU_SATA_PLL>, + <&ccu_pll CCU_PCIE_PLL>, + <&ccu_pll CCU_ETH_PLL>; + clock-names = "sata_clk", "pcie_clk", "eth_clk"; + }; + + ccu_sys: clock-controller@1f04d060 { + compatible = "baikal,bt1-ccu-sys"; + reg = <0x1f04d060 0x0a0>; + #clock-cells = <1>; + #reset-cells = <1>; + + clocks = <&ref_clk>, + <&ccu_pll CCU_SATA_PLL>, + <&ccu_pll CCU_PCIE_PLL>, + <&ccu_pll CCU_ETH_PLL>; + clock-names = "ref_clk", "sata_clk", "pcie_clk", + "eth_clk"; + }; + + l2: l2@1f04d028 { + compatible = "baikal,bt1-l2-ctl"; + reg = <0x1f04d028 0x004>; + + baikal,l2-ws-latency = <0>; + baikal,l2-tag-latency = <0>; + baikal,l2-data-latency = <1>; + + status = "disabled"; + }; + + reboot { + compatible = "syscon-reboot"; + offset = <0x118>; + + mask = <0x1>; + value = <0x1>; + }; + + reboot-mode { + compatible = "syscon-reboot-mode"; + offset = <0x154>; + + mode-normal = ; + mode-loader = ; + mode-recovery = ; + }; + + i2c0: i2c@1f04d100 { + compatible = "baikal,bt1-sys-i2c"; + reg = <0x1f04d100 0x010>; + #address-cells = <1>; + #size-cells = <0>; + + interrupts = ; + + clocks = <&ccu_sys CCU_SYS_APB_CLK>; + clock-frequency = <400000>; + + status = "disabled"; + }; + }; + + bootcon: syscon@1f040000 { + compatible = "baikal,bt1-boot-con", "syscon", "simple-mfd"; + reg = <0x1f040000 0x1000>, + <0x1fc00000 0x400000>; + reg-names = "boot", "mirror"; + #address-cells = <1>; + #size-cells = <1>; + ranges; + + little-endian; + reg-io-width = <4>; + + boot_mux: mux-controller { + compatible = "mmio-mux"; + #mux-control-cells = <1>; + + mux-reg-masks = <0x0 0x100>, <0x4 0x1>; + idle-states = <0x1>, <0x0>; + }; + + int_rom: rom@1bfc0000 { + compatible = "baikal,bt1-int-rom", "mtd-rom"; + reg = <0x1bfc0000 0x10000>; + + no-unaligned-direct-access; + bank-width = <4>; + + status = "disabled"; + }; + + /* + * Note that using the dirmap region stalls the APB bus + * until an IO operation is finished. It may cause + * significant lags in concurrent access to the system + * MMIO, since each SPI flash dword read operation takes + * at least 2.56 us to be finished (cmd + addr + data). + */ + spi0: spi@1f040100 { + compatible = "baikal,bt1-sys-ssi"; + reg = <0x1f040100 0x900>, + <0x1c000000 0x1000000>; + reg-names = "config", "map"; + #address-cells = <1>; + #size-cells = <0>; + + mux-controls = <&boot_mux 0>; + + clocks = <&ccu_sys CCU_SYS_APB_CLK>; + clock-names = "ssi_clk"; + + status = "disabled"; + }; + }; + + gpio0: gpio@1f044000 { + compatible = "snps,dw-apb-gpio"; + reg = <0x1f044000 0x1000>; + #address-cells = <1>; + #size-cells = <0>; + + clocks = <&ccu_sys CCU_SYS_APB_CLK>, + <&ccu_sys CCU_SYS_GPIO_CLK>; + clock-names = "bus", "db"; + + status = "disabled"; + + port0: gpio-port@0 { + compatible = "snps,dw-apb-gpio-port"; + reg = <0>; + + interrupts = ; + interrupt-controller; + #interrupt-cells = <2>; + + gpio-controller; + #gpio-cells = <2>; + ngpios = <32>; + }; + }; + + gpio1: gpio@1f045000 { + compatible = "snps,dw-apb-gpio"; + reg = <0x1f045000 0x1000>; + #address-cells = <1>; + #size-cells = <0>; + + clocks = <&ccu_sys CCU_SYS_APB_CLK>, + <&ccu_sys CCU_SYS_GPIO_CLK>; + clock-names = "bus", "db"; + + status = "disabled"; + + port1: gpio-port@0 { + compatible = "snps,dw-apb-gpio-port"; + reg = <0>; + + gpio-controller; + #gpio-cells = <2>; + ngpios = <3>; + }; + }; + + i2c1: i2c@1f046000 { + compatible = "snps,designware-i2c"; + reg = <0x1f046000 0x1000>; + #address-cells = <1>; + #size-cells = <0>; + + interrupts = ; + + clocks = <&ccu_sys CCU_SYS_I2C1_CLK>, + <&ccu_sys CCU_SYS_APB_CLK>; + clock-names = "ref", "pclk"; + clock-frequency = <400000>; + + dmas = <&dma 4 0 1 0xff>, <&dma 5 0 1 0xff>; + dma-names = "tx", "rx"; + + status = "disabled"; + }; + + i2c2: i2c@1f047000 { + compatible = "snps,designware-i2c"; + reg = <0x1f047000 0x1000>; + #address-cells = <1>; + #size-cells = <0>; + + interrupts = ; + + clocks = <&ccu_sys CCU_SYS_I2C2_CLK>, + <&ccu_sys CCU_SYS_APB_CLK>; + clock-names = "ref", "pclk"; + clock-frequency = <400000>; + + dmas = <&dma 6 0 1 0xff>, <&dma 7 0 1 0xff>; + dma-names = "tx", "rx"; + + status = "disabled"; + }; + + timer_dw0: timer@1f049000 { + compatible = "snps,dw-apb-timer"; + reg = <0x1f049000 0x14>; + + interrupts = ; + + clocks = <&ccu_sys CCU_SYS_TIMER0_CLK>, + <&ccu_sys CCU_SYS_APB_CLK>; + clock-names = "timer", "pclk"; + + status = "disabled"; + }; + + timer_dw1: timer@1f049014 { + compatible = "snps,dw-apb-timer"; + reg = <0x1f049014 0x14>; + + interrupts = ; + + clocks = <&ccu_sys CCU_SYS_TIMER1_CLK>, + <&ccu_sys CCU_SYS_APB_CLK>; + clock-names = "timer", "pclk"; + + status = "disabled"; + }; + + timer_dw2: timer@1f049028 { + compatible = "snps,dw-apb-timer"; + reg = <0x1f049028 0x14>; + + interrupts = ; + + clocks = <&ccu_sys CCU_SYS_TIMER2_CLK>, + <&ccu_sys CCU_SYS_APB_CLK>; + clock-names = "timer", "pclk"; + + status = "disabled"; + }; + + uart0: serial@1f04a000 { + compatible = "baikal,bt1-uart", "snps,dw-apb-uart"; + reg = <0x1f04a000 0x1000>; + + interrupts = ; + + clocks = <&ccu_sys CCU_SYS_UART_CLK>, + <&ccu_sys CCU_SYS_APB_CLK>; + clock-names = "baudclk", "apb_pclk"; + + dmas = <&dma 0 0 1 0xff>, <&dma 1 0 1 0xff>; + dma-names = "tx", "rx"; + + dcd-override; + dsr-override; + cts-override; + ri-override; + + /* earlycon settings */ + reg-io-width = <4>; + reg-shift = <2>; + + status = "disabled"; + }; + + uart1: serial@1f04b000 { + compatible = "baikal,bt1-uart", "snps,dw-apb-uart"; + reg = <0x1f04b000 0x1000>; + + interrupts = ; + + clocks = <&ccu_sys CCU_SYS_UART_CLK>, + <&ccu_sys CCU_SYS_APB_CLK>; + clock-names = "baudclk", "apb_pclk"; + + dmas = <&dma 2 0 1 0xff>, <&dma 3 0 1 0xff>; + dma-names = "tx", "rx"; + + /* earlycon settings */ + reg-io-width = <4>; + reg-shift = <2>; + + status = "disabled"; + }; + + wdt: watchdog@1f04c000 { + compatible = "snps,dw-wdt"; + reg = <0x1f04c000 0x1000>; + + interrupts = ; + + clocks = <&ccu_sys CCU_SYS_WDT_CLK>, + <&ccu_sys CCU_SYS_APB_CLK>; + clock-names = "tclk", "pclk"; + + /* Adjust ref-clock rate for better TOPs granularity */ + assigned-clocks = <&ccu_sys CCU_SYS_WDT_CLK>; + assigned-clock-rates = <65534>; + + snps,watchdog-tops = <0x000000ff 0x000001ff 0x000003ff + 0x000007ff 0x0000ffff 0x0001ffff + 0x0003ffff 0x0007ffff 0x000fffff + 0x001fffff 0x003fffff 0x007fffff + 0x00ffffff 0x01ffffff 0x03ffffff + 0x07ffffff>; + + status = "disabled"; + }; + + /* + * It's highly recommended to use all DW APB SSI controllers + * with GPIO-based CS, due to the native CS being automatically + * asserted/de-asserted on transmissions. Such HW design isn't + * that suitable for the kernel SPI subsystem, so GPIO-based CS + * will help to prevent very nasty, hard-to-fix errors. + */ + spi1: spi@1f04e000 { + compatible = "baikal,bt1-ssi"; + reg = <0x1f04e000 0x1000>; + #address-cells = <1>; + #size-cells = <0>; + + interrupts = ; + + clocks = <&ccu_sys CCU_SYS_APB_CLK>; + clock-names = "ssi_clk"; + + /* + * Make sure Rx DMA channels have higher priority. Note + * also that first two DW DMAC channels aren't suitable + * for the well-balanced Tx and Rx SPI transfers. + */ + dmas = <&dma 8 0 1 0xe0>, <&dma 9 0 1 0x1c>; + dma-names = "tx", "rx"; + + reg-io-width = <4>; + + status = "disabled"; + }; + + spi2: spi@1f04f000 { + compatible = "baikal,bt1-ssi"; + reg = <0x1f04f000 0x1000>; + #address-cells = <1>; + #size-cells = <0>; + + interrupts = ; + + clocks = <&ccu_sys CCU_SYS_APB_CLK>; + clock-names = "ssi_clk"; + + /* + * Make sure Rx DMA channels have higher priority. Note + * also that first two DW DMAC channels aren't suitable + * for the well-balanced Tx and Rx SPI transfers. + */ + dmas = <&dma 10 0 1 0xe0>, <&dma 11 0 1 0x1c>; + dma-names = "tx", "rx"; + + reg-io-width = <4>; + + status = "disabled"; + }; + + pvt: temperature-sensor@1f200000 { + compatible = "baikal,bt1-pvt"; + reg = <0x1f200000 0x1000>; + #thermal-sensor-cells = <0>; + + interrupts = ; + + clocks = <&ccu_sys CCU_SYS_PVT_CLK>, + <&ccu_sys CCU_SYS_APB_CLK>; + clock-names = "ref", "pclk"; + + status = "disabled"; + }; + + efuse: efuse@1f201000 { + compatible = "baikal,bt1-efuse"; + reg = <0x1f201000 0x1000>; + + clocks = <&ccu_sys CCU_SYS_APB_CLK>; + clock-names = "pclk"; + + status = "disabled"; + }; + }; + + axi: bus@1f05a000 { + compatible = "baikal,bt1-axi", "simple-bus"; + reg = <0 0x1f05a000 0 0x1000>, + <0 0x1f04d110 0 0x8>; + reg-names = "qos", "ehb"; + #address-cells = <2>; + #size-cells = <2>; + #interconnect-cells = <1>; + + /* + * CPU can find the AXI-accessible devices over the next MMIO + * ranges. + */ + ranges = <0 0x08000000 0 0x08000000 0 0x13dc0000>, + <0 0x1bf80000 0 0x1bf80000 0 0x00040000>, + <0 0x1bfc0000 0 0x1bfc0000 0 0x03c38000>; + + /* + * Not all AXI-bus DMA-capable devices can reach any address in + * the physical memory space. SATA/USB/GMACx are limited to work + * with the lowest 4GB of memory. Here we set the normal DMA + * ranges mapping, while device-specific dma-ranges or device + * driver software must make sure the devices have been + * restricted on working with the permited memory range. + */ + dma-ranges = <0 0 0 0 0x100 0>; + + interrupts = ; + + clocks = <&ccu_axi CCU_AXI_MAIN_CLK>; + clock-names = "aclk"; + + resets = <&ccu_axi CCU_AXI_MAIN_RST>; + reset-names = "arst"; + + syscon = <&syscon>; + + /* + * Note the (dma-)ranges mapping must be 64K aligned due to + * iATU constraints (lowest 16 bits aren't writable). Also + * note that we have to split the MEM-range up into two so + * one of them would be 256MB-aligned as some of the PCIe + * peripherals require. It can be done since AXI-interconnect + * doesn't permit the PCIe-master to access the MMIO-range + * anyway, so we can freely use the memory range above + * 0x1bfc0000 locally within the PCIe space. + */ + pcie: pcie@1f052000 { + compatible = "baikal,bt1-pcie"; + device_type = "pci"; + reg = <0 0x1f052000 0 0x1000>, + <0 0x1f053000 0 0x1000>, + <0 0x1bdb0000 0 0x10000>; + reg-names = "dbi", "dbi2", "config"; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x82000000 0 0x08000000 0 0x08000000 0 0x03da0000>, /* mem */ + <0x82000000 0 0x10000000 0 0x0bda0000 0 0x10000000>, /* mem */ + <0x81000000 0 0x0bda0000 0 0x1bda0000 0 0x00010000>; /* io */ + bus-range = <0x0 0xff>; + + interrupts = , + , + , + , + , + , + , + , + , + , + , + , + , + ; + interrupt-names = "dma0", "dma1", "dma2", "dma3", + "dma4", "dma5", "dma6", "dma7", + "msi", "aer", "pme", "hp", "bw_mg", + "l_eq"; + + /* + * Note 1. External reference clock source is required + * for the interface to work (name "ref"). + * Note 2. PCIe AXI M and S clocks and resets are + * defined from the system interrconnected point of + * view so the AXI master and slave interfaces are + * actually connected to the DW PCIe RC AXI Slave and + * Master ports respectively. + */ + clocks = <&ccu_sys CCU_SYS_APB_CLK>, + <&ccu_axi CCU_AXI_PCIE_S_CLK>, + <&ccu_axi CCU_AXI_PCIE_M_CLK>; + clock-names = "dbi", "mstr", "slv"; + + resets = <&ccu_axi CCU_AXI_PCIE_S_RST>, + <&ccu_axi CCU_AXI_PCIE_M_RST>, + <&ccu_sys CCU_SYS_PCIE_PWR_RST>, + <&ccu_sys CCU_SYS_PCIE_HOT_RST>, + <&ccu_sys CCU_SYS_PCIE_PCS_PHY_RST>, + <&ccu_sys CCU_SYS_PCIE_CORE_RST>, + <&ccu_sys CCU_SYS_PCIE_PIPE0_RST>, + <&ccu_sys CCU_SYS_PCIE_STICKY_RST>, + <&ccu_sys CCU_SYS_PCIE_NSTICKY_RST>; + reset-names = "mstr", "slv", "pwr", "hot", "phy", + "core", "pipe", "sticky", "non-sticky"; + + baikal,bt1-syscon = <&syscon>; + + num-lanes = <4>; + max-link-speed = <3>; + + status = "disabled"; + }; + + sram: sram-controller@1bf80000 { + compatible = "baikal,bt1-sram", "mmio-sram"; + reg = <0 0x1bf80000 0 0x10000>; + #address-cells = <1>; + #size-cells = <1>; + ranges = <0 0 0x1bf80000 0x10000>; + + clocks = <&ccu_axi CCU_AXI_SRAM_CLK>; + clock-names = "aclk"; + + resets = <&ccu_axi CCU_AXI_SRAM_RST>; + reset-names = "arst"; + + mux-controls = <&boot_mux 1>; + + status = "disabled"; + + boot-sram@0 { + compatible = "baikal,bt1-boot-sram"; + reg = <0 0x10000>; + label="Internal SRAM"; + export; + }; + }; + + dma: dma-controller@1f041000 { + compatible = "baikal,bt1-dmac", "snps,dma-spear1340"; + reg = <0 0x1f041000 0 0x1000>; + #dma-cells = <4>; + + interrupts = ; + + /* Clock rate up to 200MHz */ + clocks = <&ccu_sys CCU_SYS_APB_CLK>; + clock-names = "hclk"; + + dma-channels = <8>; + dma-requests = <12>; + dma-masters = <2>; + + chan_allocation_order = <0>; + chan_priority = <0>; + block_size = <4095>; + data-width = <16 4>; + multi-block = <0 0 0 0 0 0 0 0>; + snps,max-burst-len = <16 16 4 4 4 4 4 4>; + + status = "disabled"; + }; + + mc: memory-controller@1f042000 { + compatible = "baikal,bt1-ddrc"; + reg = <0 0x1f042000 0 0x1000>; + + interrupts = , + , + , + ; + interrupt-names = "dfi_e", "ecc_ce", "ecc_ue", "ecc_sbr"; + + clocks = <&ccu_sys CCU_SYS_APB_CLK>, + <&ccu_axi CCU_AXI_DDR_CLK>, + <&ccu_pll CCU_DDR_PLL>; + clock-names = "pclk", "aclk", "core"; + + resets = <&ccu_axi CCU_AXI_DDR_D_RST>, + <&ccu_sys CCU_SYS_DDR_INIT_RST>; + reset-names = "arst", "core"; + + phys = <&mc_phy>; + phy-names = "pub"; + + status = "disabled"; + }; + + mc_phy: memory-controller-phy@1f043000 { + compatible = "baikal,bt1-ddrc-phy"; + reg = <0 0x1f043000 0 0x1000>; + #phy-cells = <0>; + + clocks = <&ccu_sys CCU_SYS_APB_CLK>, + <&ccu_pll CCU_DDR_PLL>; + clock-names = "pclk", "core"; + + status = "disabled"; + }; + + /* + * DWC AHCI SATA controller has been configured with 32-bits + * AMBA Master Address Bus width. Make sure any buffer + * allocated above that limit is bounced down to the permitted + * memory space before being passed to the device. + */ + sata: sata@1f050000 { + compatible = "baikal,bt1-ahci"; + reg = <0 0x1f050000 0 0x2000>; + #address-cells = <1>; + #size-cells = <0>; + + interrupts = ; + + /* Using an external 100MHz clock source is preferable */ + clocks = <&ccu_sys CCU_SYS_APB_CLK>, + <&ccu_axi CCU_AXI_SATA_CLK>, + <&ccu_sys CCU_SYS_SATA_REF_CLK>; + clock-names = "pclk", "aclk", "ref"; + + resets = <&ccu_axi CCU_AXI_SATA_RST>, + <&ccu_sys CCU_SYS_SATA_REF_RST>; + reset-names = "arst", "ref"; + + ports-implemented = <0x3>; + + status = "disabled"; + + sata0: sata-port@0 { + reg = <0>; + + snps,tx-ts-max = <16>; + snps,rx-ts-max = <16>; + + status = "disabled"; + }; + + sata1: sata-port@1 { + reg = <1>; + + snps,tx-ts-max = <16>; + snps,rx-ts-max = <16>; + + status = "disabled"; + }; + }; + + xgmac: ethernet@1f054000 { + compatible = "baikal,bt1-xgmac"; + reg = <0 0x1f054000 0 0x4000>; + + interrupts = , + , + , + , + ; + interrupt-names = "macirq", "tx-queue-0", "tx-queue-1", + "rx-queue-0", "rx-queue-1"; + + clocks = <&ccu_sys CCU_SYS_APB_CLK>, + <&ccu_axi CCU_AXI_XGMAC_CLK>, + <&ccu_sys CCU_SYS_XGMAC_REF_CLK>, + <&ccu_sys CCU_SYS_XGMAC_PTP_CLK>; + clock-names = "pclk", "stmmaceth", "tx", "ptp_ref"; + + resets = <&ccu_axi CCU_AXI_XGMAC_RST>; + reset-names = "stmmaceth"; + + pcs-handle = <&xgmac_pcs>; + + /* + * xgmii - auto-negotiate 10GBASE-KX4/KR protocols + * (set the managed property to "in-band-status") + * 10gbase-r - select 10GBASE-R protocol + * 10gbase-x/xaui - select 10GBASE-X protocol + * (set the managed property to "auto") + */ + phy-mode = "xgmii"; + + managed = "in-band-status"; + + rx-fifo-depth = <32768>; + tx-fifo-depth = <32768>; + + /* + * Actual burst length will be (4 * 8 * 16) = 512 bytes + * due to the snps,no-pbl-x8 property absence and having + * the AXI bus data width of 128 bits. + * (AAL prevents random bus errors on high MTU) + */ + snps,pbl = <4>; + snps,data-width = <16>; + snps,aal; + + snps,axi-config = <&xgmac_axi_config>; + snps,mtl-rx-config = <&xgmac_rxq_config>; + snps,mtl-tx-config = <&xgmac_txq_config>; + + /* Enable TSO for all DMA channels (TxPBL >= 4 && PBLx8) */ + snps,tso; + + snps,perfect-filter-entries = <8>; + snps,multicast-filter-bins = <64>; + local-mac-address = [ 00 20 13 ba 1c a1 ]; + + status = "disabled"; + + xgmac_axi_config: stmmac-axi-config { + snps,wr_osr_lmt = <0x7>; + snps,rd_osr_lmt = <0x7>; + /* It's AXI3 bus so up to 16 xfers */ + snps,blen = <0 0 0 0 16 8 4>; + snps,fb; /* Unnecessary. but let's try */ + }; + + /* Up to 4 queues and 2 DMA-channels are available */ + xgmac_rxq_config: rx-queues-config { + snps,rx-queues-to-use = <2>; + snps,rx-sched-sp; + + queue0 { + snps,map-to-dma-channel = <0>; + snps,dcb-algorithm; + snps,priority = <0x1>; + }; + + queue1 { + snps,map-to-dma-channel = <1>; + snps,dcb-algorithm; + snps,priority = <0x2>; + }; + + queue2 { + snps,map-to-dma-channel = <0>; + snps,dcb-algorithm; + snps,priority = <0x4>; + }; + + queue3 { + snps,map-to-dma-channel = <1>; + snps,dcb-algorithm; + snps,priority = <0x8>; + }; + }; + + /* Up to 2 queues and 2 DMA-channels are available */ + xgmac_txq_config: tx-queues-config { + snps,tx-queues-to-use = <2>; + snps,tx-sched-wrr; + + queue0 { + snps,weight = <0x100>; + snps,dcb-algorithm; + snps,priority = <0x3>; + }; + + queue1 { + snps,weight = <0x100>; + snps,dcb-algorithm; + snps,priority = <0x3>; + }; + }; + }; + + hwa: hwa@1f05b000 { + compatible = "baikal,bt1-hwa"; + reg = <0 0x1f05b000 0 0x1000>, + <0 0x1f05c000 0 0x1000>; + reg-names = "core", "dma"; + + interrupts = ; + + clocks = <&ccu_sys CCU_SYS_APB_CLK>, + <&ccu_axi CCU_AXI_HWA_CLK>, + <&ccu_sys CCU_SYS_HWA_CLK>; + clock-names = "pclk", "aclk", "ref"; + + resets = <&ccu_axi CCU_AXI_HWA_RST>; + reset-names = "arst"; + + status = "disabled"; + }; + + xgmac_pcs: ethernet-pcs@1f05d000 { + compatible = "baikal,bt1-xpcs"; + reg = <0 0x1f05d000 0 0x1000>; + reg-names = "indirect"; + + reg-io-width = <4>; + + interrupts = ; + + clocks = <&ccu_sys CCU_SYS_APB_CLK>, + <&ccu_sys CCU_SYS_XGMAC_REF_CLK>; + clock-names = "csr", "core"; + + status = "disabled"; + }; + + gmac0: ethernet@1f05e000 { + compatible = "baikal,bt1-gmac"; + reg = <0 0x1f05e000 0 0x2000>; + #address-cells = <1>; + #size-cells = <2>; + dma-ranges = <0 0 0 0x1 0>; + + interrupts = ; + interrupt-names = "macirq"; + + clocks = <&ccu_sys CCU_SYS_APB_CLK>, + <&ccu_axi CCU_AXI_GMAC0_CLK>, + <&ccu_sys CCU_SYS_GMAC0_TX_CLK>, + <&ccu_sys CCU_SYS_GMAC0_PTP_CLK>; + clock-names = "pclk", "stmmaceth", "tx", "ptp_ref"; + + resets = <&ccu_axi CCU_AXI_GMAC0_RST>; + reset-names = "stmmaceth"; + + /* DW GMAC is configured to export 1xGPI and 1xGPO */ + ngpios = <2>; + gpio-line-names = "G0i", "G0o"; + + interrupt-controller; + #interrupt-cells = <2>; + + gpio-controller; + #gpio-cells = <2>; + + /* + * MAC always adds 2ns delay of TXC with respect to TXD + * so either PCB or PHY shall add some RXC delay. + * + * Note MAC supports getting the in-band link status + * over the RGMII interface as long as the attached + * RGMII PHY supports it. The feature can be enabled + * by specifying managed = "in-band-status". + */ + phy-mode = "rgmii-id"; + tx-internal-delay-ps = <2000>; + + rx-fifo-depth = <16384>; + tx-fifo-depth = <16384>; + + /* + * Actual burst length will be (4 * 8 * 16) = 512 bytes + * due to the snps,no-pbl-x8 property absence and having + * the AXI bus data width of 128 bits. + */ + snps,pbl = <4>; + snps,data-width = <16>; + + snps,axi-config = <&gmac0_axi_config>; + + snps,perfect-filter-entries = <8>; + snps,multicast-filter-bins = <0>; + loacl-mac-address = [ 7a 72 6c 4a 7a 07 ]; + + status = "disabled"; + + gmac0_axi_config: stmmac-axi-config { + snps,wr_osr_lmt = <0x3>; + snps,rd_osr_lmt = <0x3>; + snps,blen = <0 0 0 0 16 8 4>; + }; + + gmac0_mdio: mdio { + compatible = "snps,dwmac-mdio"; + #address-cells = <1>; + #size-cells = <0>; + }; + }; + + gmac1: ethernet@1f060000 { + compatible = "baikal,bt1-gmac"; + reg = <0 0x1f060000 0 0x2000>; + #address-cells = <1>; + #size-cells = <2>; + dma-ranges = <0 0 0 0x1 0>; + + interrupts = ; + interrupt-names = "macirq"; + + clocks = <&ccu_sys CCU_SYS_APB_CLK>, + <&ccu_axi CCU_AXI_GMAC1_CLK>, + <&ccu_sys CCU_SYS_GMAC1_TX_CLK>, + <&ccu_sys CCU_SYS_GMAC1_PTP_CLK>; + clock-names = "pclk", "stmmaceth", "tx", "ptp_ref"; + + resets = <&ccu_axi CCU_AXI_GMAC1_RST>; + reset-names = "stmmaceth"; + + /* DW GMAC is configured to export 1xGPI and 1xGPO */ + ngpios = <2>; + gpio-line-names = "G1i", "G1o"; + + interrupt-controller; + #interrupt-cells = <2>; + + gpio-controller; + #gpio-cells = <2>; + + /* + * MAC always adds 2ns delay of TXC with respect to TXD + * so either PCB or PHY shall add some RXC delay. + * + * Note MAC supports getting the in-band link status + * over the RGMII interface as long as the attached + * RGMII PHY supports it. The feature can be enabled + * by specifying managed = "in-band-status". + */ + phy-mode = "rgmii-id"; + tx-internal-delay-ps = <2000>; + + rx-fifo-depth = <16384>; + tx-fifo-depth = <16384>; + + /* + * Actual burst length will be (4 * 8 * 16) = 512 bytes + * due to the snps,no-pbl-x8 property absence and having + * the AXI bus data width of 128 bits. + */ + snps,pbl = <4>; + snps,data-width = <16>; + + snps,axi-config = <&gmac1_axi_config>; + + snps,perfect-filter-entries = <8>; + snps,multicast-filter-bins = <0>; + loacl-mac-address = [ 7a 72 6c 4a 7b 07 ]; + + status = "disabled"; + + gmac1_axi_config: stmmac-axi-config { + snps,wr_osr_lmt = <0x3>; + snps,rd_osr_lmt = <0x3>; + snps,blen = <0 0 0 0 16 8 4>; + }; + + gmac1_mdio: mdio { + compatible = "snps,dwmac-mdio"; + #address-cells = <1>; + #size-cells = <0>; + }; + }; + + usb: usb@1f100000 { + compatible = "baikal,bt1-usb3", "snps,dwc3"; + reg = <0 0x1f100000 0 0x100000>; + #address-cells = <1>; + #size-cells = <0>; + + interrupts = ; + interrupt-names = "host"; + + clocks = <&ccu_sys CCU_SYS_APB_CLK>, + <&ccu_axi CCU_AXI_USB_CLK>, + <&ccu_sys CCU_SYS_USB_CLK>; + clock-names = "pclk", "bus_early", "ref"; + + resets = <&ccu_axi CCU_AXI_USB_RST>; + reset-names = "arst"; + + dr_mode = "host"; + phy_type = "ulpi"; + maximum-speed = "high-speed"; + + snps,incr-burst-type-adjustment = <1 4 8 16>; + + status = "disabled"; + }; + }; +}; diff --git a/include/dt-bindings/soc/bt1-boot-mode.h b/include/dt-bindings/soc/bt1-boot-mode.h new file mode 100644 index 0000000000000..0e425805bc1d4 --- /dev/null +++ b/include/dt-bindings/soc/bt1-boot-mode.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC + * + * Baikal-T1 Boot Modes + */ +#ifndef __DT_BINDINGS_SOC_BT1_BOOT_MODE_H +#define __DT_BINDINGS_SOC_BT1_BOOT_MODE_H + +#define RCR_BOOT_NORMAL 0x1 +#define RCR_BOOT_LOADER 0x2 +#define RCR_BOOT_RECOVERY 0x3 + +#endif /* __DT_BINDINGS_SOC_BT1_BOOT_MODE_H */ -- cgit v1.2.3 From 64cd36fb089d8417c75ae33ddf598255fd8dd3c9 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 10 Jun 2022 02:15:47 +0300 Subject: mips: baikal-t1: Add overclocking DTS include file In some circumstances if it's really needed the SoC components performance can be increased by overclocking the corresponding domains. In particular it concerns the subsystems like CPU cores, PCIe AXI Master/Slave interfaces, SATA AXI DMA interface, USB AXI Master bus, GMACs/xGMAC AXI Master interface, SRAM Slave bus and APB bus. So if the platform needs a better performance then the bt1-oclk.dtsi file can be included into the platform dts-file. In that case it's highly recommended to add a heat dissipating device (radiator, fan, etc) in order to get a more stably working system. Note use this modification at your own risk since it's purely based on the undocumented SoC capabilities. Signed-off-by: Serge Semin --- arch/mips/boot/dts/baikal/bt1-oclk.dtsi | 87 +++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 arch/mips/boot/dts/baikal/bt1-oclk.dtsi diff --git a/arch/mips/boot/dts/baikal/bt1-oclk.dtsi b/arch/mips/boot/dts/baikal/bt1-oclk.dtsi new file mode 100644 index 0000000000000..86da0f70777b5 --- /dev/null +++ b/arch/mips/boot/dts/baikal/bt1-oclk.dtsi @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2022 BAIKAL ELECTRONICS, JSC + * + * Baikal-T1 SoC overclocking device tree + */ + +#include + +/* + * WARNING! This file provides the Baikal-T1 SoC overclocking settings. Since + * the specified clock rates are officially unsupported there is no firm + * guarantee the system will stably work if they are applied. So use it at your + * own risk. + */ + +&cpu_opp { + opp-1300000000 { + opp-hz = /bits/ 64 <1300000000>; + clock-latency-ns = <20000>; + turbo-mode; + }; + + opp-1400000000 { + opp-hz = /bits/ 64 <1400000000>; + clock-latency-ns = <20000>; + turbo-mode; + }; + + opp-1500000000 { + opp-hz = /bits/ 64 <1500000000>; + clock-latency-ns = <20000>; + turbo-mode; + }; +}; + +/* + * In general the system is working well with the CSRs bus rate above 50MHz + * and up to 300MHz, but it hasn't been fully tested yet. For instance, DW DMA + * won't work well with APB clock being greater than 200 MHz. So if you mean to + * use the DMA-based communications over the I2C/UART/SPI interfaces don't + * exceed the 200MHz limit. + */ +&apb { + assigned-clocks = <&ccu_sys CCU_SYS_APB_CLK>; + assigned-clock-rates = <200000000>; +}; + +/* + * For this to work well the overclocked rates must be set on the fully + * disabled PCIe controller. + */ +&pcie { + assigned-clocks = <&ccu_axi CCU_AXI_PCIE_M_CLK>, + <&ccu_axi CCU_AXI_PCIE_S_CLK>; + assigned-clock-rates = <600000000>, <600000000>; +}; + +&sram { + assigned-clocks = <&ccu_axi CCU_AXI_SRAM_CLK>; + assigned-clock-rates = <250000000>; +}; + +&sata { + assigned-clocks = <&ccu_axi CCU_AXI_SATA_CLK>; + assigned-clock-rates = <300000000>; +}; + +&xgmac { + assigned-clocks = <&ccu_axi CCU_AXI_XGMAC_CLK>; + assigned-clock-rates = <312500000>; +}; + +&gmac0 { + assigned-clocks = <&ccu_axi CCU_AXI_GMAC0_CLK>; + assigned-clock-rates = <250000000>; +}; + +&gmac1 { + assigned-clocks = <&ccu_axi CCU_AXI_GMAC1_CLK>; + assigned-clock-rates = <250000000>; +}; + +&usb { + assigned-clocks = <&ccu_axi CCU_AXI_USB_CLK>; + assigned-clock-rates = <300000000>; +}; -- cgit v1.2.3 From 6f633a907e3e318809f4a1a33acd686b8b20513a Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 16 Dec 2019 10:33:10 +0300 Subject: mips: baikal-t1: Add Generic platform Baikal-T1 SoC includes two MIPS P5600 cores, PCIe x4 Gen.3 port, 2x SATA III, 10Gbit Ethernet, 2x Gbit Ethernet ports, USB 2.0 port, 32x/3x GPIO, 3x i2c, 3x high-resolution APB timers, 2x UARTs, 3x SPI, PVT sensor. Since all of them persist on the SoC they will obviously be on a generic Baikal-T1-based platform. So switch corresponding kernel configs on in the generic Bailal-T1 defconfig file and make sure all the SoC persistent devices are enabled in the dedicated dtb. Signed-off-by: Serge Semin --- arch/mips/boot/dts/baikal/Makefile | 2 + arch/mips/boot/dts/baikal/bt1-gen.dts | 334 +++++++++++++++++++++++ arch/mips/bt1/Kconfig | 11 + arch/mips/configs/baikal_t1_defconfig | 480 ++++++++++++++++++++++++++++++++++ 4 files changed, 827 insertions(+) create mode 100644 arch/mips/boot/dts/baikal/bt1-gen.dts create mode 100644 arch/mips/configs/baikal_t1_defconfig diff --git a/arch/mips/boot/dts/baikal/Makefile b/arch/mips/boot/dts/baikal/Makefile index 476fc5e19ae42..8510c49ebb1f0 100644 --- a/arch/mips/boot/dts/baikal/Makefile +++ b/arch/mips/boot/dts/baikal/Makefile @@ -4,3 +4,5 @@ # # Baikal-T1 DTBs makefile # +dtb-$(CONFIG_BT1_DTB_ALL) += bt1-gen.dtb +dtb-$(CONFIG_BT1_DTB_GENERIC) += bt1-gen.dtb diff --git a/arch/mips/boot/dts/baikal/bt1-gen.dts b/arch/mips/boot/dts/baikal/bt1-gen.dts new file mode 100644 index 0000000000000..58f6d1e48d2de --- /dev/null +++ b/arch/mips/boot/dts/baikal/bt1-gen.dts @@ -0,0 +1,334 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2022 BAIKAL ELECTRONICS, JSC + * + * Baikal-T1 generic platform device tree + */ + +/dts-v1/; + +#include + +#include "bt1.dtsi" + +/ { + model = "Baikal-T1 Generic Platform"; + compatible = "baikal,bt1"; + + chosen { + /* + * Note of having both EARLY_PRINTK and SERIAL_EARLYCON + * activated at the same time. If they both refer to the same + * device, you'll end up with duplicated log messages. + * Here by passing 'earlycon' to the kernel we'll activate it + * to parse the stdout-path property to find the early console + * device. System console will be then activated in accordance + * with it if 'console=' parameter isn't passed. Any of the + * following consoles are valid: ttyS{0,1}/uart{0,1} (which + * alias is serial{0,1}), early_fdc (CDMM-JTAG serial iface). + */ + bootargs = "console=ttyS0,115200n8 earlycon maxcpus=2"; + stdout-path = "serial0:115200n8"; + + /* Bootloader timer if TIMER config is enabled */ + tick-timer = <&timer_dw0>; + + /* It's implied that the bootloader updates the initrd address */ + linux,initrd-start = <0 0>; + linux,initrd-end = <0 0>; + }; + + /* + * Declare SPD I2C EEPROM and MTD device with boot SPI-flash aliases. + * Former one will be used by the bootloader for the DDR3 controller + * initialization. The later device will be used as initial firmware + * storage and UID resource. + */ + aliases { + spd0 = &spd; + mtd1 = &boot_flash; + }; + + memory { + /* + * Declare required low-memory and additional 256MB of high- + * memory, which due to the DW uMCTL2 controller specific setup + * nearly always exists as being remapped upper part of the + * first memory chip. Without low-level remapping that segment + * is hidden behind the MMIO region and isn't reachable. + * NOTE. For the reason of having MMIO above the very first + * 128MB of the low memory, the second 128MB of the physical + * memory is always unavailable as being hidden behind MMIO + * and non-remappable by DW uMCTL2. + */ + device_type = "memory"; + reg = <0 0x00000000 0 0x08000000>, + <0 0x20000000 0 0x10000000>; + }; + + /* Standard xGMAC/PCIe/SATA reference clocks setup */ + clocks { + xgmac_ref_clk: clock-oscillator-xgmac { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <156250000>; + clock-output-names = "xgmac156m"; + }; + + pcie_ref_clk: clock-oscillator-pcie { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <100000000>; + clock-output-names = "pcie100m"; + }; + + sata_ref_clk: clock-oscillator-sata { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <100000000>; + clock-output-names = "sata100m"; + }; + }; +}; + +&l2 { + status = "okay"; +}; + +&i2c0 { + status = "okay"; +}; + +&int_rom { + status = "okay"; +}; + +&spi0 { + num-cs = <1>; + + status = "okay"; + + /* + * Most likely an SPI-nor flash will be always installed on each + * device with Baikal-T1 SoC on board. There is no just better + * alternative to boot a normal system on that CPU. + * Note Baikal-T1 is able to transparently access up to 16MB flash, + * so the system bootloader size can not exceed that limit, but an + * attached SPI-flash can as long as it supports 3bytes addressing + * of the lowest partition. + */ + boot_flash: flash@0 { + compatible = "jedec,spi-nor"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0>; + + spi-max-frequency = <25000000>; + m25p,fast-read; + }; +}; + +&gpio0 { + status = "okay"; +}; + +&gpio1 { + status = "okay"; +}; + +&i2c1 { + status = "okay"; +}; + +&i2c2 { + status = "okay"; + + /* For the devices with SO-DIMM slot */ + spd: eeprom@50 { + compatible = "atmel,24c02"; + reg = <0x50>; + + pagesize = <8>; + }; +}; + +&timer_dw0 { + status = "okay"; +}; + +&timer_dw1 { + status = "okay"; +}; + +&timer_dw2 { + status = "okay"; +}; + +&uart0 { + status = "okay"; +}; + +&uart1 { + status = "okay"; +}; + +&wdt { + status = "okay"; +}; + +&spi1 { + num-cs = <4>; + + status = "okay"; +}; + +&spi2 { + num-cs = <4>; + + status = "okay"; +}; + +&pvt { + status = "okay"; +}; + +&efuse { + status = "okay"; +}; + +&pcie { + clocks = <&ccu_sys CCU_SYS_APB_CLK>, + <&ccu_axi CCU_AXI_PCIE_M_CLK>, + <&ccu_axi CCU_AXI_PCIE_S_CLK>, + <&pcie_ref_clk>; + clock-names = "dbi", "mstr", "slv", "ref"; + + + status = "okay"; +}; + +&sram { + status = "okay"; +}; + +&dma { + status = "okay"; +}; + +&mc { + status = "okay"; +}; + +&mc_phy { + status = "okay"; +}; + +&sata { + clocks = <&ccu_sys CCU_SYS_APB_CLK>, + <&ccu_axi CCU_AXI_SATA_CLK>, + <&sata_ref_clk>; + clock-names = "pclk", "aclk", "ref"; + + status = "okay"; +}; + +&sata0 { + hba-port-cap = ; + + status = "okay"; +}; + +&sata1 { + hba-port-cap = ; + + status = "okay"; +}; + +&xgmac { + mac-address = [ 00 20 13 ba 1c a1 ]; + + status = "okay"; +}; + +&xgmac_pcs { + clocks = <&ccu_sys CCU_SYS_APB_CLK>, + <&ccu_sys CCU_SYS_XGMAC_REF_CLK>, + <&xgmac_ref_clk>; + clock-names = "csr", "core", "pad"; + + status = "okay"; +}; + +&hwa { + status = "okay"; +}; + +&gmac0 { + mac-address = [ 7a 72 6c 4a 7a 07 ]; + + phy-handle = <&gmac0_phy>; + + status = "okay"; +}; + +&gmac0_mdio { + /* + * By default use the GPO/GPI available on DW GMAC to reset the MDIO + * bus and to detect the PHY events. Most of the known Baikal-T1 + * devices have been designed that way. Note using GPO for a particular + * PHY node reset won't work unless PHY address and PHY IDs (802.3 + * clause 22) have been directly specified in the PHY sub-node. + */ + reset-gpios = <&gmac0 1 GPIO_ACTIVE_LOW>; + reset-delay-us = <10000>; + reset-post-delay-us = <30000>; + + /* + * We don't know actual PHY address on a generic device. Let the driver + * auto scan the MDIO bus looking for the IEEE 802.3 Clause 22 + * compatible PHY. + */ + gmac0_phy: ethernet-phy { + compatible = "ethernet-phy-ieee802.3-c22"; + + interrupt-parent = <&gmac0>; + interrupts = <0 IRQ_TYPE_EDGE_FALLING>; + }; +}; + +&gmac1 { + mac-address = [ 7a 72 6c 4a 7b 07 ]; + + phy-handle = <&gmac1_phy>; + + status = "okay"; +}; + +&gmac1_mdio { + /* + * By default use the GPO/GPI available on DW GMAC to reset the MDIO + * bus and to detect the PHY events. Most of the known Baikal-T1 + * devices have been designed that way. Note using GPO for a particular + * PHY node reset won't work unless PHY address and PHY IDs (802.3 + * clause 22) have been directly specified in the PHY sub-node. + */ + reset-gpios = <&gmac1 1 GPIO_ACTIVE_LOW>; + reset-delay-us = <10000>; + reset-post-delay-us = <30000>; + + /* + * We don't know actual PHY address on a generic device. Let the driver + * auto scan the MDIO bus looking for the IEEE 802.3 Clause 22 + * compatible PHY. + */ + gmac1_phy: ethernet-phy { + compatible = "ethernet-phy-ieee802.3-c22"; + + interrupt-parent = <&gmac1>; + interrupts = <0 IRQ_TYPE_EDGE_FALLING>; + }; +}; + +&usb { + status = "okay"; +}; diff --git a/arch/mips/bt1/Kconfig b/arch/mips/bt1/Kconfig index 435e98e37a93b..1c7505a3184b0 100644 --- a/arch/mips/bt1/Kconfig +++ b/arch/mips/bt1/Kconfig @@ -72,6 +72,17 @@ choice config BT1_DTB_NONE bool "None" +config BT1_DTB_ALL + bool "All" + +config BT1_DTB_GENERIC + bool "Generic Baikal-T1 Board" + help + This option provides a dtb for the generic Baikal-T1 boards. It just + activates all the Baikal-T1 SoC peripherals. So all the run-time + detectable and pre-initialized devices will work out-of-box while + the rest of the platform devices will be left untouched. + endchoice menu "Baikal-T1 Errata" diff --git a/arch/mips/configs/baikal_t1_defconfig b/arch/mips/configs/baikal_t1_defconfig new file mode 100644 index 0000000000000..424717abb3fb8 --- /dev/null +++ b/arch/mips/configs/baikal_t1_defconfig @@ -0,0 +1,480 @@ +CONFIG_LOCALVERSION="-bt1" +CONFIG_DEFAULT_HOSTNAME="baikal" +CONFIG_SYSVIPC=y +CONFIG_POSIX_MQUEUE=y +CONFIG_NO_HZ_IDLE=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_CLOCKSOURCE_WATCHDOG_MAX_SKEW_US=100 +CONFIG_BPF_SYSCALL=y +CONFIG_BPF_JIT=y +CONFIG_BPF_JIT_ALWAYS_ON=y +CONFIG_BPF_PRELOAD=y +CONFIG_BPF_PRELOAD_UMD=y +CONFIG_PREEMPT=y +CONFIG_BSD_PROCESS_ACCT=y +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_CGROUPS=y +CONFIG_MEMCG=y +CONFIG_BLK_CGROUP=y +CONFIG_CGROUP_SCHED=y +CONFIG_CFS_BANDWIDTH=y +CONFIG_CGROUP_PIDS=y +CONFIG_CGROUP_FREEZER=y +CONFIG_CPUSETS=y +CONFIG_CGROUP_DEVICE=y +CONFIG_CGROUP_CPUACCT=y +CONFIG_CGROUP_PERF=y +CONFIG_CGROUP_BPF=y +CONFIG_CGROUP_MISC=y +CONFIG_NAMESPACES=y +CONFIG_USER_NS=y +CONFIG_CHECKPOINT_RESTORE=y +CONFIG_RELAY=y +CONFIG_BLK_DEV_INITRD=y +CONFIG_CMDLINE_LOG_WRAP_IDEAL_LEN=100 +CONFIG_SYSFS_SYSCALL=y +CONFIG_EXPERT=y +CONFIG_PROFILING=y +CONFIG_MIPS_BAIKAL_T1=y +CONFIG_BT1_CPU_FEATURE_OVERRIDES=y +CONFIG_BT1_DTB_ALL=y +CONFIG_CPU_P5600=y +CONFIG_CPU_MIPS32_R5_FEATURES=y +CONFIG_CPU_MIPS32_R5_XPA=y +CONFIG_ZBOOT_LOAD_ADDRESS=0x85100000 +CONFIG_ARCH_FORCE_MAX_ORDER=11 +CONFIG_CPU_HAS_MSA=y +CONFIG_DMI=y +CONFIG_NR_CPUS=2 +CONFIG_IEEE754_DEFAULT_RELAXED=y +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_STAT=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y +CONFIG_CPUFREQ_DT=y +CONFIG_PAGE_SIZE_16KB=y +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MODULE_UNLOAD_TAINT_TRACKING=y +CONFIG_MODVERSIONS=y +CONFIG_MODULE_SRCVERSION_ALL=y +# CONFIG_BLOCK_LEGACY_AUTOLOAD is not set +CONFIG_BLK_DEV_BSGLIB=y +# CONFIG_IOSCHED_BFQ is not set +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_PACKET_DIAG=y +CONFIG_UNIX=y +CONFIG_UNIX_DIAG=y +CONFIG_XFRM_USER=y +CONFIG_NET_KEY=y +CONFIG_XDP_SOCKETS=y +CONFIG_XDP_SOCKETS_DIAG=y +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +CONFIG_IP_PNP=y +CONFIG_NET_IPIP=y +CONFIG_NET_IPGRE_DEMUX=y +CONFIG_NET_IPGRE=y +CONFIG_NET_FOU_IP_TUNNELS=y +CONFIG_INET_AH=y +CONFIG_INET_ESP=y +CONFIG_INET_IPCOMP=y +CONFIG_INET_UDP_DIAG=y +CONFIG_INET_RAW_DIAG=y +CONFIG_TCP_CONG_ADVANCED=y +CONFIG_TCP_CONG_BIC=y +CONFIG_TCP_CONG_WESTWOOD=y +CONFIG_TCP_CONG_HTCP=y +CONFIG_TCP_CONG_HSTCP=y +CONFIG_TCP_CONG_BBR=y +CONFIG_INET6_AH=y +CONFIG_INET6_ESP=y +CONFIG_INET6_IPCOMP=y +CONFIG_IPV6_GRE=y +CONFIG_IPV6_SEG6_LWTUNNEL=y +CONFIG_MPTCP=y +CONFIG_NETWORK_PHY_TIMESTAMPING=y +CONFIG_NETFILTER=y +CONFIG_NF_CONNTRACK=y +CONFIG_NF_CONNTRACK_PROCFS=y +CONFIG_NF_TABLES=y +CONFIG_NF_TABLES_INET=y +CONFIG_NF_TABLES_NETDEV=y +CONFIG_NFT_LOG=y +CONFIG_NFT_LIMIT=y +CONFIG_NFT_MASQ=y +CONFIG_NFT_REDIR=y +CONFIG_NFT_NAT=y +CONFIG_NFT_REJECT=y +CONFIG_NFT_HASH=y +CONFIG_NETFILTER_XT_CONNMARK=y +CONFIG_NETFILTER_XT_TARGET_CLASSIFY=y +CONFIG_NETFILTER_XT_TARGET_MARK=y +CONFIG_NETFILTER_XT_NAT=y +CONFIG_NETFILTER_XT_TARGET_NETMAP=y +CONFIG_NETFILTER_XT_TARGET_NFLOG=y +CONFIG_NETFILTER_XT_TARGET_NFQUEUE=y +CONFIG_NETFILTER_XT_TARGET_REDIRECT=y +CONFIG_NETFILTER_XT_TARGET_MASQUERADE=y +CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=y +CONFIG_NETFILTER_XT_MATCH_CGROUP=y +CONFIG_NETFILTER_XT_MATCH_CPU=y +CONFIG_NETFILTER_XT_MATCH_IPRANGE=y +CONFIG_NETFILTER_XT_MATCH_LENGTH=y +CONFIG_NETFILTER_XT_MATCH_LIMIT=y +CONFIG_NETFILTER_XT_MATCH_MAC=y +CONFIG_NETFILTER_XT_MATCH_MULTIPORT=y +CONFIG_NETFILTER_XT_MATCH_OWNER=y +CONFIG_NETFILTER_XT_MATCH_POLICY=y +CONFIG_NETFILTER_XT_MATCH_PKTTYPE=y +CONFIG_NETFILTER_XT_MATCH_RECENT=y +CONFIG_NETFILTER_XT_MATCH_SOCKET=y +CONFIG_NETFILTER_XT_MATCH_STATE=y +CONFIG_NETFILTER_XT_MATCH_TCPMSS=y +CONFIG_NFT_DUP_IPV4=y +CONFIG_NF_TABLES_ARP=y +CONFIG_IP_NF_IPTABLES=y +CONFIG_IP_NF_MATCH_AH=y +CONFIG_IP_NF_MATCH_ECN=y +CONFIG_IP_NF_MATCH_TTL=y +CONFIG_IP_NF_TARGET_SYNPROXY=y +CONFIG_NFT_DUP_IPV6=y +CONFIG_IP6_NF_IPTABLES=y +CONFIG_IP6_NF_MATCH_AH=y +CONFIG_IP6_NF_MATCH_EUI64=y +CONFIG_IP6_NF_MATCH_FRAG=y +CONFIG_IP6_NF_MATCH_OPTS=y +CONFIG_IP6_NF_MATCH_HL=y +CONFIG_IP6_NF_MATCH_IPV6HEADER=y +CONFIG_IP6_NF_MATCH_MH=y +CONFIG_IP6_NF_MATCH_RT=y +CONFIG_BRIDGE=y +CONFIG_BRIDGE_VLAN_FILTERING=y +CONFIG_VLAN_8021Q=y +CONFIG_NET_SCHED=y +CONFIG_NET_SCH_HTB=y +CONFIG_NET_SCH_PRIO=y +CONFIG_NET_SCH_MULTIQ=y +CONFIG_NET_SCH_SFQ=y +CONFIG_NET_SCH_CBS=y +CONFIG_NET_SCH_ETF=y +CONFIG_NET_SCH_TAPRIO=y +CONFIG_NET_SCH_MQPRIO=y +CONFIG_NET_SCH_SKBPRIO=y +CONFIG_NET_SCH_QFQ=y +CONFIG_NET_SCH_FQ=y +CONFIG_NET_SCH_INGRESS=y +CONFIG_NET_SCH_DEFAULT=y +CONFIG_NET_CLS_BASIC=y +CONFIG_NET_CLS_U32=y +CONFIG_CLS_U32_PERF=y +CONFIG_CLS_U32_MARK=y +CONFIG_NET_CLS_FLOW=y +CONFIG_NET_CLS_BPF=y +CONFIG_NET_CLS_FLOWER=y +CONFIG_NET_CLS_ACT=y +CONFIG_NET_ACT_GACT=y +CONFIG_NET_ACT_MIRRED=y +CONFIG_NET_ACT_PEDIT=y +CONFIG_NET_ACT_SIMP=y +CONFIG_NET_ACT_SKBEDIT=y +CONFIG_NET_ACT_VLAN=y +CONFIG_NET_ACT_BPF=y +CONFIG_NET_ACT_SKBMOD=y +CONFIG_DCB=y +CONFIG_DNS_RESOLVER=y +CONFIG_NETLINK_DIAG=y +CONFIG_CGROUP_NET_PRIO=y +CONFIG_CGROUP_NET_CLASSID=y +CONFIG_BPF_STREAM_PARSER=y +CONFIG_NET_PKTGEN=y +# CONFIG_WIRELESS is not set +CONFIG_PAGE_POOL_STATS=y +CONFIG_PCI=y +CONFIG_PCIEPORTBUS=y +CONFIG_HOTPLUG_PCI_PCIE=y +CONFIG_PCIEAER=y +CONFIG_PCIEAER_INJECT=y +CONFIG_PCIE_ECRC=y +CONFIG_PCIEASPM_PERFORMANCE=y +CONFIG_PCI_MSI=y +CONFIG_PCIE_BUS_PERFORMANCE=y +CONFIG_HOTPLUG_PCI=y +CONFIG_PCIE_BT1=y +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +CONFIG_DEBUG_DRIVER=y +CONFIG_DEBUG_DEVRES=y +CONFIG_BT1_APB=y +CONFIG_BT1_AXI=y +CONFIG_MIPS_CDMM=y +CONFIG_DMI_SYSFS=y +CONFIG_MTD=y +CONFIG_MTD_PARTITIONED_MASTER=y +CONFIG_MTD_ROM=y +CONFIG_MTD_PHYSMAP=y +CONFIG_MTD_PHYSMAP_OF=y +CONFIG_MTD_PHYSMAP_BT1_ROM=y +CONFIG_MTD_SPI_NOR=y +# CONFIG_MTD_SPI_NOR_USE_4K_SECTORS is not set +CONFIG_MTD_UBI=y +CONFIG_MTD_UBI_BLOCK=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_LOOP_MIN_COUNT=4 +CONFIG_BLK_DEV_NBD=y +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_COUNT=2 +CONFIG_BLK_DEV_RAM_SIZE=32768 +CONFIG_BLK_DEV_NVME=y +CONFIG_NVME_MULTIPATH=y +CONFIG_NVME_VERBOSE_ERRORS=y +CONFIG_NVME_HWMON=y +CONFIG_SRAM=y +CONFIG_EEPROM_AT24=y +CONFIG_RAID_ATTRS=y +CONFIG_BLK_DEV_SD=y +CONFIG_CHR_DEV_SG=y +CONFIG_SCSI_CONSTANTS=y +CONFIG_SCSI_LOGGING=y +CONFIG_SCSI_SCAN_ASYNC=y +CONFIG_ATA=y +CONFIG_SATA_AHCI_PLATFORM=y +CONFIG_AHCI_DWC=y +# CONFIG_ATA_SFF is not set +CONFIG_NETDEVICES=y +# CONFIG_NET_VENDOR_3COM is not set +# CONFIG_NET_VENDOR_ADAPTEC is not set +# CONFIG_NET_VENDOR_AGERE is not set +# CONFIG_NET_VENDOR_ALACRITECH is not set +# CONFIG_NET_VENDOR_ALTEON is not set +# CONFIG_NET_VENDOR_AMAZON is not set +# CONFIG_NET_VENDOR_AQUANTIA is not set +# CONFIG_NET_VENDOR_ARC is not set +# CONFIG_NET_VENDOR_ASIX is not set +# CONFIG_NET_VENDOR_ATHEROS is not set +# CONFIG_NET_VENDOR_BROADCOM is not set +# CONFIG_NET_VENDOR_CADENCE is not set +# CONFIG_NET_VENDOR_CAVIUM is not set +# CONFIG_NET_VENDOR_CHELSIO is not set +# CONFIG_NET_VENDOR_CISCO is not set +# CONFIG_NET_VENDOR_CORTINA is not set +# CONFIG_NET_VENDOR_DAVICOM is not set +# CONFIG_NET_VENDOR_DEC is not set +# CONFIG_NET_VENDOR_DLINK is not set +# CONFIG_NET_VENDOR_EMULEX is not set +# CONFIG_NET_VENDOR_ENGLEDER is not set +# CONFIG_NET_VENDOR_EZCHIP is not set +# CONFIG_NET_VENDOR_FUNGIBLE is not set +# CONFIG_NET_VENDOR_GOOGLE is not set +# CONFIG_NET_VENDOR_HISILICON is not set +# CONFIG_NET_VENDOR_HUAWEI is not set +# CONFIG_NET_VENDOR_INTEL is not set +# CONFIG_NET_VENDOR_ADI is not set +# CONFIG_NET_VENDOR_MARVELL is not set +# CONFIG_NET_VENDOR_MELLANOX is not set +# CONFIG_NET_VENDOR_META is not set +# CONFIG_NET_VENDOR_MICREL is not set +# CONFIG_NET_VENDOR_MICROCHIP is not set +# CONFIG_NET_VENDOR_MICROSEMI is not set +# CONFIG_NET_VENDOR_MICROSOFT is not set +# CONFIG_NET_VENDOR_MUCSE is not set +# CONFIG_NET_VENDOR_MYRI is not set +# CONFIG_NET_VENDOR_NI is not set +# CONFIG_NET_VENDOR_NATSEMI is not set +# CONFIG_NET_VENDOR_NETRONOME is not set +# CONFIG_NET_VENDOR_NVIDIA is not set +# CONFIG_NET_VENDOR_OKI is not set +# CONFIG_NET_VENDOR_PACKET_ENGINES is not set +# CONFIG_NET_VENDOR_PENSANDO is not set +# CONFIG_NET_VENDOR_QLOGIC is not set +# CONFIG_NET_VENDOR_BROCADE is not set +# CONFIG_NET_VENDOR_QUALCOMM is not set +# CONFIG_NET_VENDOR_RDC is not set +# CONFIG_NET_VENDOR_REALTEK is not set +# CONFIG_NET_VENDOR_RENESAS is not set +# CONFIG_NET_VENDOR_ROCKER is not set +# CONFIG_NET_VENDOR_SAMSUNG is not set +# CONFIG_NET_VENDOR_SEEQ is not set +# CONFIG_NET_VENDOR_SILAN is not set +# CONFIG_NET_VENDOR_SIS is not set +# CONFIG_NET_VENDOR_SOLARFLARE is not set +# CONFIG_NET_VENDOR_SMSC is not set +# CONFIG_NET_VENDOR_SOCIONEXT is not set +CONFIG_STMMAC_ETH=y +CONFIG_STMMAC_SELFTESTS=y +CONFIG_DWMAC_BT1=y +# CONFIG_NET_VENDOR_SUN is not set +# CONFIG_NET_VENDOR_SYNOPSYS is not set +# CONFIG_TEHUTI is not set +# CONFIG_NET_VENDOR_TI is not set +# CONFIG_NET_VENDOR_TOSHIBA is not set +# CONFIG_NET_VENDOR_VERTEXCOM is not set +# CONFIG_NET_VENDOR_VIA is not set +# CONFIG_NET_VENDOR_WANGXUN is not set +# CONFIG_NET_VENDOR_WIZNET is not set +# CONFIG_NET_VENDOR_XILINX is not set +CONFIG_SFP=y +CONFIG_MARVELL_PHY=y +CONFIG_MARVELL_10G_PHY=y +CONFIG_MARVELL_88X2222_PHY=y +CONFIG_MARVELL_88X2222_GPIO=y +CONFIG_MARVELL_88X2222_I2C=y +CONFIG_MICREL_PHY=y +CONFIG_MICROCHIP_PHY=y +CONFIG_MICROSEMI_PHY=y +CONFIG_REALTEK_PHY=y +CONFIG_SMSC_PHY=y +CONFIG_VITESSE_PHY=y +CONFIG_MDIO_BITBANG=y +CONFIG_MDIO_GPIO=y +# CONFIG_USB_NET_DRIVERS is not set +# CONFIG_WLAN is not set +# CONFIG_INPUT_KEYBOARD is not set +# CONFIG_INPUT_MOUSE is not set +# CONFIG_SERIO is not set +# CONFIG_VT_CONSOLE is not set +CONFIG_VT_HW_CONSOLE_BINDING=y +CONFIG_LEGACY_PTY_COUNT=16 +CONFIG_SERIAL_8250=y +CONFIG_SERIAL_8250_CONSOLE=y +CONFIG_SERIAL_8250_NR_UARTS=2 +CONFIG_SERIAL_8250_RUNTIME_UARTS=2 +CONFIG_SERIAL_8250_EXTENDED=y +CONFIG_SERIAL_8250_DW=y +# CONFIG_SERIAL_8250_PERICOM is not set +CONFIG_MIPS_EJTAG_FDC_TTY=y +# CONFIG_HW_RANDOM is not set +CONFIG_I2C=y +CONFIG_I2C_CHARDEV=y +CONFIG_I2C_DESIGNWARE_CORE=y +CONFIG_I2C_SLAVE=y +CONFIG_I2C_SLAVE_EEPROM=y +CONFIG_SPI=y +CONFIG_SPI_DESIGNWARE=y +CONFIG_SPI_DW_DMA=y +CONFIG_SPI_DW_MMIO=y +CONFIG_SPI_DW_BT1=y +CONFIG_SPI_SPIDEV=y +CONFIG_GPIO_SYSFS=y +CONFIG_GPIO_DWAPB=y +CONFIG_GPIO_PCA953X=y +CONFIG_GPIO_PCA953X_IRQ=y +CONFIG_GPIO_PCF857X=y +CONFIG_POWER_RESET=y +CONFIG_POWER_RESET_SYSCON=y +CONFIG_SYSCON_REBOOT_MODE=y +CONFIG_SENSORS_BT1_PVT=y +CONFIG_SENSORS_BT1_PVT_ALARMS=y +CONFIG_SENSORS_TMP102=y +CONFIG_THERMAL=y +CONFIG_THERMAL_STATISTICS=y +CONFIG_THERMAL_GOV_FAIR_SHARE=y +CONFIG_THERMAL_GOV_USER_SPACE=y +CONFIG_CPU_THERMAL=y +CONFIG_WATCHDOG=y +CONFIG_WATCHDOG_SYSFS=y +CONFIG_WATCHDOG_PRETIMEOUT_GOV=y +CONFIG_WATCHDOG_PRETIMEOUT_DEFAULT_GOV_NOOP=y +CONFIG_DW_WATCHDOG=y +CONFIG_SOUND=y +CONFIG_SND=y +CONFIG_SND_HRTIMER=y +# CONFIG_SND_PCI is not set +# CONFIG_SND_SPI is not set +# CONFIG_SND_MIPS is not set +CONFIG_SND_USB_AUDIO=y +# CONFIG_HID_SUPPORT is not set +CONFIG_USB_ULPI_BUS=y +CONFIG_USB=y +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +CONFIG_USB_MON=y +CONFIG_USB_XHCI_HCD=y +CONFIG_USB_STORAGE=y +CONFIG_USB_UAS=y +CONFIG_USB_DWC3=y +CONFIG_USB_DWC3_ULPI=y +# CONFIG_USB_DWC3_GENERIC_PLAT is not set +CONFIG_USB_HUB_USB251XB=y +CONFIG_MMC=y +CONFIG_MMC_SPI=y +CONFIG_NEW_LEDS=y +CONFIG_LEDS_CLASS=y +CONFIG_LEDS_GPIO=y +CONFIG_EDAC=y +CONFIG_EDAC_DEBUG=y +CONFIG_EDAC_SYNOPSYS=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_ABEOZ9=y +CONFIG_RTC_DRV_PCF85363=y +CONFIG_RTC_DRV_PCF2127=y +CONFIG_DMADEVICES=y +CONFIG_DW_DMAC=y +CONFIG_DW_EDMA=y +CONFIG_SYNC_FILE=y +# CONFIG_VIRTIO_MENU is not set +# CONFIG_VHOST_ENABLE_FORK_OWNER_CONTROL is not set +CONFIG_COMMON_CLK_VC5=y +# CONFIG_IOMMU_SUPPORT is not set +CONFIG_MEMORY=y +CONFIG_BT1_L2_CTL=y +CONFIG_NVMEM_U_BOOT_ENV=y +CONFIG_EXT4_FS=y +CONFIG_EXT4_FS_POSIX_ACL=y +CONFIG_FANOTIFY=y +CONFIG_FUSE_FS=y +# CONFIG_FUSE_IO_URING is not set +CONFIG_OVERLAY_FS=y +CONFIG_MSDOS_FS=y +CONFIG_VFAT_FS=y +CONFIG_NTFS3_FS=y +CONFIG_PROC_KCORE=y +CONFIG_TMPFS=y +CONFIG_TMPFS_POSIX_ACL=y +CONFIG_JFFS2_FS=y +CONFIG_JFFS2_FS_WBUF_VERIFY=y +CONFIG_UBIFS_FS=y +CONFIG_SQUASHFS=y +CONFIG_SQUASHFS_LZ4=y +CONFIG_SQUASHFS_LZO=y +CONFIG_SQUASHFS_XZ=y +CONFIG_SQUASHFS_4K_DEVBLK_SIZE=y +CONFIG_NFS_FS=y +CONFIG_NFS_V2=y +CONFIG_NFS_V4=y +CONFIG_NFS_V4_0=y +CONFIG_NFS_V4_2=y +CONFIG_ROOT_NFS=y +CONFIG_NFS_USE_LEGACY_DNS=y +# CONFIG_NFS_V4_2_READ_PLUS is not set +# CONFIG_RPCSEC_GSS_KRB5 is not set +CONFIG_NLS_CODEPAGE_437=y +CONFIG_NLS_CODEPAGE_866=y +CONFIG_NLS_CODEPAGE_1251=y +CONFIG_NLS_ASCII=y +CONFIG_NLS_ISO8859_1=y +CONFIG_NLS_ISO8859_5=y +CONFIG_NLS_KOI8_R=y +CONFIG_NLS_UTF8=y +CONFIG_INIT_STACK_NONE=y +CONFIG_CRYPTO_USER=y +CONFIG_CRYPTO_NULL=y +CONFIG_CRYPTO_BLOWFISH=y +CONFIG_CRYPTO_CCM=y +CONFIG_CRYPTO_XCBC=y +CONFIG_CRYPTO_CRC32C=y +CONFIG_CRYPTO_CRC32=y +CONFIG_CRYPTO_USER_API_HASH=y +# CONFIG_CRYPTO_HW is not set +CONFIG_PRINTK_TIME=y +CONFIG_DEBUG_INFO_DWARF4=y +CONFIG_DEBUG_INFO_BTF=y +CONFIG_FRAME_WARN=1024 +CONFIG_DEBUG_SECTION_MISMATCH=y +CONFIG_VMLINUX_MAP=y +CONFIG_SCHEDSTATS=y +# CONFIG_EARLY_PRINTK is not set +# CONFIG_RUNTIME_TESTING_MENU is not set -- cgit v1.2.3 From 1205c01cd0c1f3d654adaf284956cb0412b89b4f Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 15 Aug 2022 15:36:26 +0300 Subject: mips: baikal-t1: Add Baikal Electronics KR/KX4-MV-SFP+ DTS include file Baikal Electronics BFK and Edelweiss EM406-based boards can be equipped with the KR/KX4 SFI mezzanine card with Marvell 88x2222 10G PHY connected to the Baikal-T1/M1 XGMII interface. The board is connected by means of the vendor specific port with 4xTx XGMII differential lanes, 4xRx XGMII differential lanes, MDIO bus, I2C bus and PORT0.27 GPIO used for the PHY interrupts. For the sake of convenience the mezzanine card support is implemented as a krkx4-mv-sfp.dtsi file so the platforms which support it being attached could just include the DT file. Signed-off-by: Serge Semin --- arch/mips/boot/dts/baikal/krkx4-mv-sfp.dtsi | 105 ++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 arch/mips/boot/dts/baikal/krkx4-mv-sfp.dtsi diff --git a/arch/mips/boot/dts/baikal/krkx4-mv-sfp.dtsi b/arch/mips/boot/dts/baikal/krkx4-mv-sfp.dtsi new file mode 100644 index 0000000000000..f0f0adb68ea3a --- /dev/null +++ b/arch/mips/boot/dts/baikal/krkx4-mv-sfp.dtsi @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2022 BAIKAL ELECTRONICS, JSC + * + * Baikal Electronics KR/KX4 Marvell 88x2222 SFP+ Mezzanine Card device tree + */ + +#include +#include +#include + +/* + * TODO Convert this file to be a subordinate of a nexus node/connector when the + * kernel gets ready for it: + * 3: I2C SCL + * 5: I2C SDA + * 7: MDI/MDIO + * 9: MDO + * 11: MDC + * 12: PHY INT + * 17,18,19,20: TxN3,RxN3,TxP3,RxP3 - Lane 3 10GBASE-X + * 23,24,25,26: TxN2,RxN2,TxP2,RxP2 - Lane 2 10GBASE-X + * 29,30,31,32: TxN1,RxN1,TxP1,RxP1 - Lane 1 10GBASE-X + * 35,36,37,38: TxN0,RxN0,TxP0,RxP0 - Lane 0 10GBASE-X/10GBASE-R + */ + +/ { + aliases { + mdio-gpio0 = &xgmac_mdio; + }; + + xgmac_mdio: mdio { + compatible = "virtual,mdio-gpio"; + #address-cells = <1>; + #size-cells = <0>; + + /* PORT0.9 - MDC, PORT0.10 - MDO, PORT0.11 - MDI/MDIO */ + gpios = <&port0 9 GPIO_ACTIVE_HIGH>, <&port0 11 GPIO_ACTIVE_HIGH>, + <&port0 10 GPIO_ACTIVE_HIGH>; + + reset-gpios = <&xgmac_gpio 0 GPIO_ACTIVE_HIGH>; + reset-delay-us = <10000>; + reset-post-delay-us = <10000>; + + xgmac_phy: ethernet-phy@c { + compatible = "ethernet-phy-ieee802.3-c45"; + reg = <0x0c>; + + interrupt-parent = <&port0>; + interrupts = <27 IRQ_TYPE_LEVEL_LOW>; + interrupt-controller; + #interrupt-cells = <2>; + + gpio-controller; /* 12 */ + #gpio-cells = <2>; + gpio-reserved-ranges = , + , + , + ; + + sfp = <&xgmac_sfp>; + }; + }; + + xgmac_sfp: sfp { + compatible = "sff,sfp"; + + i2c-bus = <&xgmac_phy>; + + mod-def0-gpios = <&xgmac_phy MV_88X2222_MOD_ABS GPIO_ACTIVE_LOW>; + tx-fault-gpios = <&xgmac_phy MV_88X2222_TX_FAULT GPIO_ACTIVE_HIGH>; + los-gpios = <&xgmac_phy MV_88X2222_RX_LOS GPIO_ACTIVE_HIGH>; + tx-disable-gpios = <&xgmac_phy MV_88X2222_TX_DISABLE GPIO_ACTIVE_HIGH>; + }; +}; + +&xgmac { + /* Possible protocols: 10gbase-r, 10gbase-x/xaui */ + phy-mode = "10gbase-r"; + + managed = "auto"; + + phy-handle = <&xgmac_phy>; +}; + +&i2c1 { + /* Marvell PHY Reset-controller (NXP PCA9500 8-bit GPIO) */ + xgmac_gpio: gpio@20 { + compatible = "nxp,pcf8574"; + reg = <0x20>; + + gpio-controller; /* 8 */ + #gpio-cells = <2>; + + gpio-line-names = "RST_PHY", "", "", "", "", "", "", ""; + }; + + /* Mezzanine card firmware (NXP PCA9500 2-kbit EEPROM) */ + xgmac_fw: eeprom@50 { + compatible = "atmel,24c02"; + reg = <0x50>; + + pagesize = <4>; + }; +}; -- cgit v1.2.3 From 1e92700a7c05907dcd2871a48755025b21cd171d Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sat, 29 Jul 2023 03:51:37 +0300 Subject: mips: baikal-t1: Add Baikal Electronics KR-SFP+ DTS include file Baikal Electronics BFK and Edelweiss EM406-based boards can be equipped with the KR-SFP+ mezzanine card with SFP-port directly connected to the Baikal-T1 XGMII interface. The board is connected by means of the vendor-specific port with 4xTx XGMII differential lanes, 4xRx XGMII differential lanes, MDIO bus, I2C bus and a set of GPIOs used to handle the SFP-port signals. For the sake of convenience the mezzanine card support is implemented as a kr-sfp.dtsi file so the platforms which support it being attached could just include the DT file. Signed-off-by: Serge Semin --- arch/mips/boot/dts/baikal/kr-sfp.dtsi | 45 +++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 arch/mips/boot/dts/baikal/kr-sfp.dtsi diff --git a/arch/mips/boot/dts/baikal/kr-sfp.dtsi b/arch/mips/boot/dts/baikal/kr-sfp.dtsi new file mode 100644 index 0000000000000..59cfb4d6bf301 --- /dev/null +++ b/arch/mips/boot/dts/baikal/kr-sfp.dtsi @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2022 BAIKAL ELECTRONICS, JSC + * + * Baikal Electronics KR SFP+ Mezzanine Card device tree + */ + +#include +#include + +/* + * TODO Convert this file to be a subordinate of a nexus node/connector when the + * kernel gets ready for it: + * 3: I2C SCL + * 5: I2C SDA + * 7: SFP Mod ABS + * 9: SFP Tx Disable + * 11: SFP Tx Fault + * 12: SFP Rx Los + * 17,18,19,20: TxN3,RxN3,TxP3,RxP3 - Lane 3 10GBASE-X + * 23,24,25,26: TxN2,RxN2,TxP2,RxP2 - Lane 2 10GBASE-X + * 29,30,31,32: TxN1,RxN1,TxP1,RxP1 - Lane 1 10GBASE-X + * 35,36,37,38: TxN0,RxN0,TxP0,RxP0 - Lane 0 10GBASE-X/10GBASE-R + */ + +/ { + xgmac_sfp: sfp { + compatible = "sff,sfp"; + + i2c-bus = <&i2c1>; + + los-gpios = <&port0 27 GPIO_ACTIVE_HIGH>; + mod-def0-gpios = <&port0 11 GPIO_ACTIVE_LOW>; + tx-disable-gpios = <&port0 10 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>; + tx-fault-gpios = <&port0 9 GPIO_ACTIVE_HIGH>; + }; +}; + +&xgmac_pcs { + sfp = <&xgmac_sfp>; +}; + +&xgmac { + phy-mode = "10gbase-r"; +}; -- cgit v1.2.3 From 513e7898091ae0f350d96542c3730e5d7686fa76 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 27 Apr 2022 12:48:51 +0300 Subject: mips: baikal-t1: Add Baikal Electronics BFK+ platform The Baikal Electronics BFK boards are the evaluation boards specifically designed for SoC-based platforms and software development. They are equipped with the production version of the Baikal-T1 SoC and multiple interfaces: 1x SoDIMM (or soldered DDR3 RAM), 1x PCIe x16, 2x SATA III, 1x 10Gbit Ethernet (attached to a vendor-specific port), 2x Gbit ports with Micrel PHYs, USB 2.0 port, 32x GPIO, 3x i2c, 2x USB UART, 2x SPI. Embedded BMC is capable of rebooting the system or collecting some board-specific data by commands sent over an additional USB serial port. The change adds the platform DT source files and the default kernel config. Signed-off-by: Serge Semin --- arch/mips/boot/dts/baikal/Makefile | 3 +- arch/mips/boot/dts/baikal/bfk16p.dts | 214 ++++++++++++++++ arch/mips/boot/dts/baikal/bfk2.dts | 192 ++++++++++++++ arch/mips/boot/dts/baikal/bfk3.dts | 149 +++++++++++ arch/mips/boot/dts/baikal/bfkp.dtsi | 299 ++++++++++++++++++++++ arch/mips/bt1/Kconfig | 8 + arch/mips/configs/bfkp_defconfig | 467 +++++++++++++++++++++++++++++++++++ 7 files changed, 1331 insertions(+), 1 deletion(-) create mode 100644 arch/mips/boot/dts/baikal/bfk16p.dts create mode 100644 arch/mips/boot/dts/baikal/bfk2.dts create mode 100644 arch/mips/boot/dts/baikal/bfk3.dts create mode 100644 arch/mips/boot/dts/baikal/bfkp.dtsi create mode 100644 arch/mips/configs/bfkp_defconfig diff --git a/arch/mips/boot/dts/baikal/Makefile b/arch/mips/boot/dts/baikal/Makefile index 8510c49ebb1f0..966928ae3e94b 100644 --- a/arch/mips/boot/dts/baikal/Makefile +++ b/arch/mips/boot/dts/baikal/Makefile @@ -4,5 +4,6 @@ # # Baikal-T1 DTBs makefile # -dtb-$(CONFIG_BT1_DTB_ALL) += bt1-gen.dtb +dtb-$(CONFIG_BT1_DTB_ALL) += bt1-gen.dtb bfk16p.dtb bfk2.dtb bfk3.dtb dtb-$(CONFIG_BT1_DTB_GENERIC) += bt1-gen.dtb +dtb-$(CONFIG_BT1_DTB_BFKP) += bfk16p.dtb bfk2.dtb bfk3.dtb diff --git a/arch/mips/boot/dts/baikal/bfk16p.dts b/arch/mips/boot/dts/baikal/bfk16p.dts new file mode 100644 index 0000000000000..61fffe567ca9e --- /dev/null +++ b/arch/mips/boot/dts/baikal/bfk16p.dts @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2022 BAIKAL ELECTRONICS, JSC + * + * Baikal Electronics BFK v1.6+ evaluation board device tree + */ + +/dts-v1/; + +#include +#include + +#include "bfkp.dtsi" + +/ { + model = "Baikal Electronics BFK v1.6+ Evaluation Board"; + compatible = "baikal,bfk16p", "baikal,bt1"; + + aliases { + spd0 = &spd; + mtd2 = &test_flash10; + mtd3 = &test_flash11; + }; + + memory { + /* + * Soldered 8GB RAM +ECC (DD21..DD30): + * low memory - 128MB, high memory - 8GB-256MB. + */ + device_type = "memory"; + reg = <0 0x00000000 0 0x08000000>, + <0 0x20000000 0x1 0xf0000000>; + }; + + clocks { + usb_hub_clk: clock-oscillator-usb-hub { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <24000000>; + clock-output-names = "usbhub24m"; + }; + }; + + /* + * Use XP23 to set bootcfg[1:0] by installing the jumpers: + * 7-8: b00 - ROM mode + * 1-2 7-8: b01 - SRAM mode + * default: b10 - Flash mode + */ +}; + +&spi0 { + /* + * CS0 bus is connected to XP19 and DD71 (N74CBTLV3 NO pins) which SEL + * can be tied to GND by J41 jumper redirecting bus to XP20 and BMC + */ +}; + +&port0 { + gpio-line-names = "XP27:4", "XP26:10", "XP26:8", "XP27:2", "XP25:1", + "XP26:2", "XP26:4", "XP25:4", "XP26:5", "XP29:11", + "XP29:9", "XP29:7", "XP25:6", "nCPU_RDY", "XP26:7", + "XP25:9", "nRTC_IRQ", "XP25:5", "XP25:3", "XP25:7", + "XP26:1", "XP26:3", "XP25:10", "XP27:3", "XP26:6", + "XP27:6", "XP27:1", "XP29:12", "XP25:8", "XP26:9", + "XP27:5", "nSD_CD"; +}; + +&port1 { + gpio-line-names = "nBOOT_SS_DIS", "", ""; +}; + +&i2c1 { + codec: audio-codec@18 { + compatible = "ti,tlv320dac3101"; + reg = <0x18>; + }; + + /* Connected to XP17 pins SDA->3, SCL->4 */ + + /* Connected to XP29 pins SDA->5, SCL->3 */ +}; + +&i2c2 { + /* EEPROM is empty by default */ + spd: eeprom@50 { + compatible = "atmel,24c02"; + reg = <0x50>; + + pagesize = <8>; + }; + + fw: eeprom@51 { + compatible = "atmel,24cs02"; + reg = <0x51>; + + pagesize = <8>; + }; + + rtc: rtc@56 { + compatible = "abracon,abeoz9"; + reg = <0x56>; + + interrupt-parent = <&port0>; + interrupts = <16 IRQ_TYPE_LEVEL_LOW>; + + trickle-resistor-ohms = <5000>; + }; + + hub: usb-hub@58 { + compatible = "microchip,usb2422"; + reg = <0x58>; + + /* Configs are set by the bootloader */ + skip-config; + }; + + /* Connected to XP15 pins SDA->3, SCL->4 */ +}; + +&uart0 { + /* Connected to XS9 RS232:1 port */ +}; + +&uart1 { + /* Connected to XS9 RS232:2 port */ +}; + +&spi1 { + num-cs = <4>; + + /* + * XP11 pins port switches between native CS0 and port0:2 GPIO. + * XP12 pins port switches between native CS1 and port0:28 GPIO. + * Install the 1-2 jumpers to activate the GPIO CS. + */ + cs-gpios = <&port0 2 GPIO_ACTIVE_LOW>, <&port0 28 GPIO_ACTIVE_LOW>, + <0>, <0>; + + /* Micron N25Q256A13EF */ + test_flash10: flash@0 { + compatible = "jedec,spi-nor"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0>; + + spi-max-frequency = <25000000>; + m25p,fast-read; + }; + + /* Micron N25Q256A13EF */ + test_flash11: flash@1 { + compatible = "jedec,spi-nor"; + #address-cells = <1>; + #size-cells = <1>; + reg = <1>; + + spi-max-frequency = <25000000>; + m25p,fast-read; + }; + + /* Won't work with native CS due to no high CS capability */ + mmc: mmc@2 { + compatible = "mmc-spi-slot"; + reg = <2>; + + voltage-ranges = <3200 3400>; + spi-max-frequency = <25000000>; + /* Card-Detect GPIO */ + gpios = <&port0 31 GPIO_ACTIVE_LOW>; + }; + + /* CS3 bus is connected to XP10 pins MOSI->1, MISO->3, CLK->5, CS3->7 */ +}; + +&spi2 { + num-cs = <4>; + + /* + * XP13 pins port switches between native CS0 and port0:17 GPIO. + * Install the 1-2 jumper to activate the GPIO CS. + */ + cs-gpios = <&port0 17 GPIO_ACTIVE_LOW>, <0>, <0>, <0>; + + spi_i2s: cpld@0 { + compatible = "baikal,spi-i2s-cpld"; + reg = <0>; + }; + + /* + * CS1, CS2 and CS3 buses are connected to XP18 pins MOSI->1, MISO->3, + * CLK->5, CS1->7, CS2->8, CS3->6. + */ +}; + +&xgmac { + /* Possible protocols: 10gbase-r only */ + phy-mode = "10gbase-r"; +}; + +&usb { + /* Microchip USB2422 */ + usb-hub@0 { + compatible = "usb424,2422"; + #address-cells = <1>; + #size-cells = <0>; + reg = <0>; + + clocks = <&usb_hub_clk>; + clock-names = "xtal"; + + /* Two USB type-A ports */ + }; +}; diff --git a/arch/mips/boot/dts/baikal/bfk2.dts b/arch/mips/boot/dts/baikal/bfk2.dts new file mode 100644 index 0000000000000..944a8adeca4f4 --- /dev/null +++ b/arch/mips/boot/dts/baikal/bfk2.dts @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2022 BAIKAL ELECTRONICS, JSC + * + * Baikal Electronics BFK v2.x evaluation board device tree + */ + +/dts-v1/; + +#include +#include + +#include "bfkp.dtsi" + +/ { + model = "Baikal Electronics BFK v2.x Evaluation Board"; + compatible = "baikal,bfk2", "baikal,bt1"; + + aliases { + spd0 = &spd; + mtd2 = &test_flash10; + mtd3 = &test_flash11; + }; + + memory { + /* + * Assume at least 512MB of RAM detected in SO-DIMM (DIMM1): + * low memory - 128MB, high memory - 256MB. + */ + device_type = "memory"; + reg = <0 0x00000000 0 0x08000000>, + <0 0x20000000 0 0x10000000>; + }; + + /* + * Use J27/J28 to set bootcfg[1:0] by installing the jumpers: + * J28: b00 - ROM mode + * J27 J28: b01 - SRAM mode + * default: b10 - Flash mode + */ +}; + +&i2c0 { + status = "okay"; + + /* Connected to J38 pins 1.8V->1, GND->2, SDA->3, SCL->4 */ +}; + +&spi0 { + /* + * CS0 bus is connected to J22 and U44 (N74CBTLV3 NO pins) which SEL + * can be tied to GND by J41 jumper and BMC redirecting bus to J39 and BMC. + * + * Note DD71 OE can be tied to 3.3V by the J42 jumper disabling the + * multiplexer. But this seems useless seeing the Boot SPI-flash will + * be just fully unavailable. + * + * Note J45 must have 1-2 jumper to activate Boot SPI-flash chip-select. + * Installing 2-3 jumper there will redirect the chip-select to the Boot + * SD-card slot (J44) which is unusable due to both the SoC not being + * able to start from there and the SPI-controller native CS nature. + */ +}; + +&port0 { + gpio-line-names = "", "", "", "", "nLED_GRN", + "nLED_BLU", "nLED_RED", "", "J26:6", "J13:11", + "J13:9", "J13:7", "J26:8", "J26:10", "J26:12", + "J26:14", "nRTC_IRQ", "", "nSD_CD2", "J26:5", + "J26:7", "J26:9", "J26:11", "", "J26:13", + "J26:15in", "J26:17", "J13:12", "", "", + "J26:16in", "nSD_CD1"; +}; + +&port1 { + gpio-line-names = "J25:1", "J25:2", "J25:3"; +}; + +&i2c1 { + /* Connected to J30 pins SDA->3, SCL->4 */ + + /* Connected to J13 pins SDA->5, SCL->3 */ +}; + +&i2c2 { + /* EEPROM on SO-DIMM (DIMM1) */ + spd: eeprom@50 { + compatible = "atmel,24c02"; + reg = <0x50>; + + pagesize = <8>; + }; + + fw: eeprom@54 { + compatible = "atmel,24cs04"; + reg = <0x54>; + + pagesize = <8>; + }; + + rtc: rtc@56 { + compatible = "abracon,abeoz9"; + reg = <0x56>; + + interrupt-parent = <&port0>; + interrupts = <16 IRQ_TYPE_LEVEL_LOW>; + + trickle-resistor-ohms = <5000>; + }; + + /* Connected to J43 pins SDA->3, SCL->4 */ +}; + +&uart0 { + /* + * Connected to U60 CP2105 SCI port through U59 (SN74CBTLV input pins) + * which SEL can be tied to GND by J52 jumper redirecting interface to + * J53 TX->2, RX->3. + */ +}; + +&uart1 { + /* + * Connected to U60 CP2105 ECI port through U59 (SN74CBTLV input pins) + * which SEL can be tied to GND by J52 jumper redirecting interface to + * J54 TX->2, RX->3. + */ +}; + +&spi1 { + num-cs = <4>; + + /* + * J37 pins port switches between native CS0 and port0:23 GPIO. + * J34 pins port switches between native CS1 and port0:28 GPIO. + * J33 pins port switches between native CS2 and port0:29 GPIO. + * Install the 1-2 jumpers to activate the GPIO CS. + */ + cs-gpios = <&port0 23 GPIO_ACTIVE_LOW>, <&port0 28 GPIO_ACTIVE_LOW>, + <&port0 29 GPIO_ACTIVE_LOW>, <0>; + + /* Micron N25Q256A13EF */ + test_flash10: flash@0 { + compatible = "jedec,spi-nor"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0>; + + spi-max-frequency = <25000000>; + m25p,fast-read; + }; + + /* Micron N25Q256A13EF */ + test_flash11: flash@1 { + compatible = "jedec,spi-nor"; + #address-cells = <1>; + #size-cells = <1>; + reg = <1>; + + spi-max-frequency = <25000000>; + m25p,fast-read; + }; + + /* Won't work with native CS due to no high CS capability */ + mmc: mmc@2 { + compatible = "mmc-spi-slot"; + reg = <2>; + + voltage-ranges = <3200 3400>; + spi-max-frequency = <25000000>; + /* Card-Detect GPIO */ + gpios = <&port0 31 GPIO_ACTIVE_LOW>; + }; + + /* CS3 bus is connected to J29 pins MOSI->2, MISO->4, CLK->6, CS3->5 */ +}; + +&spi2 { + num-cs = <4>; + + /* + * J35 pins port switches between native CS0 and port0:17 GPIO. + * Install the 1-2 jumper to activate the GPIO CS. + */ + cs-gpios = <&port1 2 GPIO_ACTIVE_LOW>, <0>, <0>, <0>; + + /* + * CS1, CS2 and CS3 buses are connected to J49 pins MOSI->1, MISO->3, + * CLK->5, CS0->7, CS1->6, CS2|CS3->8. CS2|CS3 is determined by J50 + * pins: 1-2 - CS2, 2-3 - CS3. + */ +}; diff --git a/arch/mips/boot/dts/baikal/bfk3.dts b/arch/mips/boot/dts/baikal/bfk3.dts new file mode 100644 index 0000000000000..3fc0d9b4371bb --- /dev/null +++ b/arch/mips/boot/dts/baikal/bfk3.dts @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2022 BAIKAL ELECTRONICS, JSC + * + * Baikal Electronics BFK v3.x evaluation board device tree + */ + +/dts-v1/; + +#include +#include + +#include "bfkp.dtsi" + +/ { + model = "Baikal Electronics BFK v3.x Evaluation Board"; + compatible = "baikal,bfk3", "baikal,bt1"; + + aliases { + spd0 = &spd; + mtd2 = &test_flash11; + }; + + memory { + /* + * Assume at least 512MB of RAM detected in SO-DIMM (XS21): + * low memory - 128MB, high memory - 256MB. + */ + device_type = "memory"; + reg = <0 0x00000000 0 0x08000000>, + <0 0x20000000 0 0x10000000>; + }; + + /* + * Use XP9/XP8 to set bootcfg[1:0] by installing the jumpers: + * XP8: b00 - ROM mode + * XP9 XP8: b01 - SRAM mode + * default: b10 - Flash mode + */ +}; + +&i2c0 { + status = "okay"; + + /* Connected to XP25 RPi I2C SDA, SCL (RPi 40 pinout) */ +}; + +&spi0 { + /* + * CS0 bus is connected to DD1 (N74CBTLV3 NO pins) which SEL can be + * tied to GND by XP1 jumper and BMC redirecting bus to BMC. + * + * Note DD1 OE can be tied to 3.3V by the XP2 jumper disabling the + * multiplexer. This will redirect the Boot SPI-flash to XP12 MOSI->2 + * MISO->4, CLK->6, CS0->5 + */ +}; + +&port0 { + gpio-line-names = "XP25:7", "XP25:11", "XP25:12", "XP25:13", "XP25:15", + "XP25:16", "XP25:18", "XP25:22", "XP25:29", "XP26:11", + "XP26:9", "XP26:7", "XP25:31", "XP25:32", "XP25:33", + "XP25:35", "XP25:36", "XP25:26", "XP25:37", "XP25:38", + "XP25:40", "XP27:1", "XP27:2", "XP27:3", "XP27:4", + "XP27:5", "XP27:6", "XP26:12", "XP27:7", "XP27:8", + "XP27:9", "XP27:10"; +}; + +&i2c1 { + /* Connected to XP25 RPi reserved pins SDA->27, SCL->28 */ + + /* Connected to XP26 pins SDA->5, SCL->3 */ +}; + +&i2c2 { + /* EEPROM on SO-DIMM (XS21) */ + spd: eeprom@50 { + compatible = "atmel,24c02"; + reg = <0x50>; + + pagesize = <8>; + }; + + fw: eeprom@54 { + compatible = "atmel,24cs04"; + reg = <0x54>; + + pagesize = <8>; + }; + + rtc: rtc@56 { + compatible = "abracon,abeoz9"; + reg = <0x56>; + + trickle-resistor-ohms = <5000>; + }; + + /* Connected to XP28 pins SDA->3, SCL->4 */ +}; + +&uart0 { + /* Connected to DD24 CP2105 SCI port */ +}; + +&uart1 { + /* + * Connected to XP13 pins TXD->3, RXD->4 + * Install 3-5 and 4-6 jumpers to redirect the interface to DD24 + * (CP2105 ECI port). + * Install 1-3 and 2-4 jumpers to redirect the interface to XP25 + * RPi UART pins TX->8, RX->10. + */ +}; + +&spi1 { + num-cs = <2>; + + /* + * XP20 pins port switches between native CS0 and port1:0 GPIO. + * XP21 pins port switches between native CS1 and port1:1 GPIO. + * Install the 1-2 jumpers to activate the GPIO CS. + */ + cs-gpios = <&port1 0 GPIO_ACTIVE_LOW>, <&port1 1 GPIO_ACTIVE_LOW>; + + /* CS0 bus is connected to XP14 pins MOSI->2, MISO->4, CLK->6, CS0->5 */ + + /* Micron N25Q256A13EF */ + test_flash11: flash@1 { + compatible = "jedec,spi-nor"; + #address-cells = <1>; + #size-cells = <1>; + reg = <1>; + + spi-max-frequency = <25000000>; + m25p,fast-read; + }; +}; + +&spi2 { + num-cs = <2>; + + /* + * XP19 pins port switches between native CS0 and port1:2 GPIO. + * Install the 1-2 jumper to activate the GPIO CS. + */ + cs-gpios = <&port1 2 GPIO_ACTIVE_LOW>, <&port0 17 GPIO_ACTIVE_LOW>; + + /* CS0 and CS1 buses are connected to XP25 RPi SPI pins */ +}; diff --git a/arch/mips/boot/dts/baikal/bfkp.dtsi b/arch/mips/boot/dts/baikal/bfkp.dtsi new file mode 100644 index 0000000000000..90a34585a8659 --- /dev/null +++ b/arch/mips/boot/dts/baikal/bfkp.dtsi @@ -0,0 +1,299 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2022 BAIKAL ELECTRONICS, JSC + * + * Baikal Electronics BFK+ evaluation board series common device tree + */ + +#include +#include + +#include "bt1.dtsi" + +/ { + chosen { + bootargs = "console=ttyS0,115200n8 earlycon maxcpus=2"; + stdout-path = "serial0:115200n8"; + + /* Bootloader timer if TIMER config is enabled */ + tick-timer = <&timer_dw0>; + + /* Bootloader may use these props to pass the initrd image */ + linux,initrd-start = <0 0>; + linux,initrd-end = <0 0>; + }; + + aliases { + mtd1 = &boot_flash; + }; + + clocks { + /* + * SATA/PCIe/xGMAC reference clocks are provided either by the + * IDT 5P49V5901 (BFK v1.6/v3.x) or by the Skyworks Si5342 + * (BFK v2.x) clock generators which are out of the SoC reach + * and either initialized by the embedded BMC or pre-configured + * via the chips pin straps + */ + xgmac_ref_clk: clock-oscillator-xgmac { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <156250000>; + clock-output-names = "xgmac156m"; + }; + + pcie_ref_clk: clock-oscillator-pcie { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <100000000>; + clock-output-names = "pcie100m"; + }; + + sata_ref_clk: clock-oscillator-sata { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <100000000>; + clock-output-names = "sata100m"; + }; + + usb_phy_clk: clock-oscillator-usb-phy { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <24000000>; + clock-output-names = "usbphy24m"; + }; + + gmac0_phy_clk: clock-oscillator-gmac0-phy { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <25000000>; + clock-output-names = "gmac0phy25m"; + }; + + gmac1_phy_clk: clock-oscillator-gmac1-phy { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <25000000>; + clock-output-names = "gmac1phy25m"; + }; + }; +}; + +&l2 { + status = "okay"; +}; + +&int_rom { + status = "okay"; +}; + +&spi0 { + num-cs = <1>; + + status = "okay"; + + /* Micron N25Q128A11 */ + boot_flash: flash@0 { + compatible = "jedec,spi-nor"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0>; + + spi-max-frequency = <25000000>; + m25p,fast-read; + }; +}; + +&gpio0 { + status = "okay"; +}; + +&gpio1 { + status = "okay"; +}; + +&i2c1 { + status = "okay"; + + /* Board Management Controller (STM32F205) */ + bmc: bmc@8 { + compatible = "baikal,bt1-bmc"; + reg = <0x08>; + }; +}; + +&i2c2 { + status = "okay"; +}; + +&timer_dw0 { + status = "okay"; +}; + +&timer_dw1 { + status = "okay"; +}; + +&timer_dw2 { + status = "okay"; +}; + +&uart0 { + status = "okay"; +}; + +&uart1 { + status = "okay"; +}; + +&wdt { + status = "okay"; +}; + +&spi1 { + status = "okay"; +}; + +&spi2 { + status = "okay"; +}; + +&pvt { + status = "okay"; +}; + +&efuse { + status = "okay"; +}; + +&pcie { + clocks = <&ccu_sys CCU_SYS_APB_CLK>, + <&ccu_axi CCU_AXI_PCIE_M_CLK>, + <&ccu_axi CCU_AXI_PCIE_S_CLK>, + <&pcie_ref_clk>; + clock-names = "dbi", "mstr", "slv", "ref"; + + status = "okay"; +}; + +&sram { + status = "okay"; +}; + +&dma { + status = "okay"; +}; + +&mc { + status = "okay"; +}; + +&mc_phy { + status = "okay"; +}; + +&sata { + clocks = <&ccu_sys CCU_SYS_APB_CLK>, + <&ccu_axi CCU_AXI_SATA_CLK>, + <&sata_ref_clk>; + clock-names = "pclk", "aclk", "ref"; + + status = "okay"; +}; + +&sata0 { + hba-port-cap = ; + + status = "okay"; +}; + +&sata1 { + hba-port-cap = ; + + status = "okay"; +}; + +&xgmac { + mac-address = [ 00 20 13 ba 1c a1 ]; + + status = "okay"; +}; + +&xgmac_pcs { + clocks = <&ccu_sys CCU_SYS_APB_CLK>, + <&ccu_sys CCU_SYS_XGMAC_REF_CLK>, + <&xgmac_ref_clk>; + clock-names = "csr", "core", "pad"; + + status = "okay"; +}; + +&hwa { + status = "okay"; +}; + +&gmac0 { + mac-address = [ 00 26 58 80 01 02 ]; + + phy-handle = <&gmac0_phy>; + managed = "in-band-status"; + + status = "okay"; +}; + +&gmac0_mdio { + reset-gpios = <&gmac0 1 GPIO_ACTIVE_LOW>; + reset-delay-us = <10200>; + reset-post-delay-us = <1000>; + + /* Micrel KSZ9031RNX */ + gmac0_phy: ethernet-phy@3 { + compatible = "ethernet-phy-id0022.1620"; + reg = <0x3>; + + interrupt-parent = <&gmac0>; + interrupts = <0 IRQ_TYPE_EDGE_FALLING>; + + clocks = <&gmac0_phy_clk>; + clock-names = "ref"; + }; +}; + +&gmac1 { + mac-address = [ 00 26 58 80 01 03 ]; + + phy-handle = <&gmac1_phy>; + managed = "in-band-status"; + + status = "okay"; +}; + +&gmac1_mdio { + reset-gpios = <&gmac1 1 GPIO_ACTIVE_LOW>; + reset-delay-us = <10200>; + reset-post-delay-us = <1000>; + + /* Micrel KSZ9031RNX */ + gmac1_phy: ethernet-phy@3 { + compatible = "ethernet-phy-id0022.1620"; + reg = <0x3>; + + interrupt-parent = <&gmac1>; + interrupts = <0 IRQ_TYPE_EDGE_FALLING>; + + clocks = <&gmac1_phy_clk>; + clock-names = "ref"; + }; +}; + +&usb { + status = "okay"; + + ulpi { + /* Microchip USB3315 */ + phy { + clocks = <&usb_phy_clk>; + clock-names = "ref"; + }; + }; +}; diff --git a/arch/mips/bt1/Kconfig b/arch/mips/bt1/Kconfig index 1c7505a3184b0..5d59655f436b5 100644 --- a/arch/mips/bt1/Kconfig +++ b/arch/mips/bt1/Kconfig @@ -83,6 +83,14 @@ config BT1_DTB_GENERIC detectable and pre-initialized devices will work out-of-box while the rest of the platform devices will be left untouched. +config BT1_DTB_BFKP + bool "Baikal Electronics BFK v1.6+/v2.x/v3.x" + help + This option provides dtbs for the Baikal Electronics BFK v1.6+, v2.x + and v3.x boards equipped with the production version of the chip. + Naturally these are the Baikal-T1 SoC evaluation boards specifically + designed for the SoC-based platforms and software prototyping. + endchoice menu "Baikal-T1 Errata" diff --git a/arch/mips/configs/bfkp_defconfig b/arch/mips/configs/bfkp_defconfig new file mode 100644 index 0000000000000..d8862d2b7804d --- /dev/null +++ b/arch/mips/configs/bfkp_defconfig @@ -0,0 +1,467 @@ +CONFIG_LOCALVERSION="-bt1" +CONFIG_DEFAULT_HOSTNAME="baikal" +CONFIG_SYSVIPC=y +CONFIG_POSIX_MQUEUE=y +CONFIG_NO_HZ_IDLE=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_CLOCKSOURCE_WATCHDOG_MAX_SKEW_US=100 +CONFIG_BPF_SYSCALL=y +CONFIG_BPF_JIT=y +CONFIG_BPF_JIT_ALWAYS_ON=y +CONFIG_BPF_PRELOAD=y +CONFIG_BPF_PRELOAD_UMD=y +CONFIG_PREEMPT=y +CONFIG_BSD_PROCESS_ACCT=y +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_CGROUPS=y +CONFIG_MEMCG=y +CONFIG_BLK_CGROUP=y +CONFIG_CGROUP_SCHED=y +CONFIG_CFS_BANDWIDTH=y +CONFIG_CGROUP_PIDS=y +CONFIG_CGROUP_FREEZER=y +CONFIG_CPUSETS=y +CONFIG_CGROUP_DEVICE=y +CONFIG_CGROUP_CPUACCT=y +CONFIG_CGROUP_PERF=y +CONFIG_CGROUP_BPF=y +CONFIG_CGROUP_MISC=y +CONFIG_NAMESPACES=y +CONFIG_USER_NS=y +CONFIG_CHECKPOINT_RESTORE=y +CONFIG_RELAY=y +CONFIG_BLK_DEV_INITRD=y +CONFIG_CMDLINE_LOG_WRAP_IDEAL_LEN=100 +CONFIG_SYSFS_SYSCALL=y +CONFIG_EXPERT=y +CONFIG_PROFILING=y +CONFIG_MIPS_BAIKAL_T1=y +CONFIG_BT1_CPU_FEATURE_OVERRIDES=y +CONFIG_BT1_DTB_BFKP=y +CONFIG_CPU_P5600=y +CONFIG_CPU_MIPS32_R5_FEATURES=y +CONFIG_CPU_MIPS32_R5_XPA=y +CONFIG_ZBOOT_LOAD_ADDRESS=0x85100000 +CONFIG_ARCH_FORCE_MAX_ORDER=11 +CONFIG_CPU_HAS_MSA=y +CONFIG_DMI=y +CONFIG_NR_CPUS=2 +CONFIG_IEEE754_DEFAULT_RELAXED=y +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_STAT=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y +CONFIG_CPUFREQ_DT=y +CONFIG_PAGE_SIZE_16KB=y +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MODULE_UNLOAD_TAINT_TRACKING=y +CONFIG_MODVERSIONS=y +CONFIG_MODULE_SRCVERSION_ALL=y +# CONFIG_BLOCK_LEGACY_AUTOLOAD is not set +CONFIG_BLK_DEV_BSGLIB=y +# CONFIG_IOSCHED_BFQ is not set +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_PACKET_DIAG=y +CONFIG_UNIX=y +CONFIG_UNIX_DIAG=y +CONFIG_XFRM_USER=y +CONFIG_NET_KEY=y +CONFIG_XDP_SOCKETS=y +CONFIG_XDP_SOCKETS_DIAG=y +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +CONFIG_IP_PNP=y +CONFIG_NET_IPIP=y +CONFIG_NET_IPGRE_DEMUX=y +CONFIG_NET_IPGRE=y +CONFIG_NET_FOU_IP_TUNNELS=y +CONFIG_INET_AH=y +CONFIG_INET_ESP=y +CONFIG_INET_IPCOMP=y +CONFIG_INET_UDP_DIAG=y +CONFIG_INET_RAW_DIAG=y +CONFIG_TCP_CONG_ADVANCED=y +CONFIG_TCP_CONG_BIC=y +CONFIG_TCP_CONG_WESTWOOD=y +CONFIG_TCP_CONG_HTCP=y +CONFIG_TCP_CONG_HSTCP=y +CONFIG_TCP_CONG_BBR=y +CONFIG_INET6_AH=y +CONFIG_INET6_ESP=y +CONFIG_INET6_IPCOMP=y +CONFIG_IPV6_GRE=y +CONFIG_IPV6_SEG6_LWTUNNEL=y +CONFIG_MPTCP=y +CONFIG_NETWORK_PHY_TIMESTAMPING=y +CONFIG_NETFILTER=y +CONFIG_NF_CONNTRACK=y +CONFIG_NF_CONNTRACK_PROCFS=y +CONFIG_NF_TABLES=y +CONFIG_NF_TABLES_INET=y +CONFIG_NF_TABLES_NETDEV=y +CONFIG_NFT_LOG=y +CONFIG_NFT_LIMIT=y +CONFIG_NFT_MASQ=y +CONFIG_NFT_REDIR=y +CONFIG_NFT_NAT=y +CONFIG_NFT_REJECT=y +CONFIG_NFT_HASH=y +CONFIG_NETFILTER_XT_CONNMARK=y +CONFIG_NETFILTER_XT_TARGET_CLASSIFY=y +CONFIG_NETFILTER_XT_TARGET_MARK=y +CONFIG_NETFILTER_XT_NAT=y +CONFIG_NETFILTER_XT_TARGET_NETMAP=y +CONFIG_NETFILTER_XT_TARGET_NFLOG=y +CONFIG_NETFILTER_XT_TARGET_NFQUEUE=y +CONFIG_NETFILTER_XT_TARGET_REDIRECT=y +CONFIG_NETFILTER_XT_TARGET_MASQUERADE=y +CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=y +CONFIG_NETFILTER_XT_MATCH_CGROUP=y +CONFIG_NETFILTER_XT_MATCH_CPU=y +CONFIG_NETFILTER_XT_MATCH_IPRANGE=y +CONFIG_NETFILTER_XT_MATCH_LENGTH=y +CONFIG_NETFILTER_XT_MATCH_LIMIT=y +CONFIG_NETFILTER_XT_MATCH_MAC=y +CONFIG_NETFILTER_XT_MATCH_MULTIPORT=y +CONFIG_NETFILTER_XT_MATCH_OWNER=y +CONFIG_NETFILTER_XT_MATCH_POLICY=y +CONFIG_NETFILTER_XT_MATCH_PKTTYPE=y +CONFIG_NETFILTER_XT_MATCH_RECENT=y +CONFIG_NETFILTER_XT_MATCH_SOCKET=y +CONFIG_NETFILTER_XT_MATCH_STATE=y +CONFIG_NETFILTER_XT_MATCH_TCPMSS=y +CONFIG_NFT_DUP_IPV4=y +CONFIG_NF_TABLES_ARP=y +CONFIG_IP_NF_IPTABLES=y +CONFIG_IP_NF_MATCH_AH=y +CONFIG_IP_NF_MATCH_ECN=y +CONFIG_IP_NF_MATCH_TTL=y +CONFIG_IP_NF_TARGET_SYNPROXY=y +CONFIG_NFT_DUP_IPV6=y +CONFIG_IP6_NF_IPTABLES=y +CONFIG_IP6_NF_MATCH_AH=y +CONFIG_IP6_NF_MATCH_EUI64=y +CONFIG_IP6_NF_MATCH_FRAG=y +CONFIG_IP6_NF_MATCH_OPTS=y +CONFIG_IP6_NF_MATCH_HL=y +CONFIG_IP6_NF_MATCH_IPV6HEADER=y +CONFIG_IP6_NF_MATCH_MH=y +CONFIG_IP6_NF_MATCH_RT=y +CONFIG_BRIDGE=y +CONFIG_BRIDGE_VLAN_FILTERING=y +CONFIG_VLAN_8021Q=y +CONFIG_NET_SCHED=y +CONFIG_NET_SCH_HTB=y +CONFIG_NET_SCH_PRIO=y +CONFIG_NET_SCH_MULTIQ=y +CONFIG_NET_SCH_SFQ=y +CONFIG_NET_SCH_CBS=y +CONFIG_NET_SCH_ETF=y +CONFIG_NET_SCH_TAPRIO=y +CONFIG_NET_SCH_MQPRIO=y +CONFIG_NET_SCH_SKBPRIO=y +CONFIG_NET_SCH_QFQ=y +CONFIG_NET_SCH_FQ=y +CONFIG_NET_SCH_INGRESS=y +CONFIG_NET_SCH_DEFAULT=y +CONFIG_NET_CLS_BASIC=y +CONFIG_NET_CLS_U32=y +CONFIG_CLS_U32_PERF=y +CONFIG_CLS_U32_MARK=y +CONFIG_NET_CLS_FLOW=y +CONFIG_NET_CLS_BPF=y +CONFIG_NET_CLS_FLOWER=y +CONFIG_NET_CLS_ACT=y +CONFIG_NET_ACT_GACT=y +CONFIG_NET_ACT_MIRRED=y +CONFIG_NET_ACT_PEDIT=y +CONFIG_NET_ACT_SIMP=y +CONFIG_NET_ACT_SKBEDIT=y +CONFIG_NET_ACT_VLAN=y +CONFIG_NET_ACT_BPF=y +CONFIG_NET_ACT_SKBMOD=y +CONFIG_DCB=y +CONFIG_DNS_RESOLVER=y +CONFIG_NETLINK_DIAG=y +CONFIG_CGROUP_NET_PRIO=y +CONFIG_CGROUP_NET_CLASSID=y +CONFIG_BPF_STREAM_PARSER=y +CONFIG_NET_PKTGEN=y +# CONFIG_WIRELESS is not set +CONFIG_PAGE_POOL_STATS=y +CONFIG_PCI=y +CONFIG_PCIEPORTBUS=y +CONFIG_HOTPLUG_PCI_PCIE=y +CONFIG_PCIEAER=y +CONFIG_PCIEAER_INJECT=y +CONFIG_PCIE_ECRC=y +CONFIG_PCIEASPM_PERFORMANCE=y +CONFIG_PCI_MSI=y +CONFIG_PCIE_BUS_PERFORMANCE=y +CONFIG_HOTPLUG_PCI=y +CONFIG_PCIE_BT1=y +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +CONFIG_DEBUG_DRIVER=y +CONFIG_DEBUG_DEVRES=y +CONFIG_BT1_APB=y +CONFIG_BT1_AXI=y +CONFIG_MIPS_CDMM=y +CONFIG_DMI_SYSFS=y +CONFIG_MTD=y +CONFIG_MTD_PARTITIONED_MASTER=y +CONFIG_MTD_ROM=y +CONFIG_MTD_PHYSMAP=y +CONFIG_MTD_PHYSMAP_OF=y +CONFIG_MTD_PHYSMAP_BT1_ROM=y +CONFIG_MTD_SPI_NOR=y +# CONFIG_MTD_SPI_NOR_USE_4K_SECTORS is not set +CONFIG_MTD_UBI=y +CONFIG_MTD_UBI_BLOCK=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_LOOP_MIN_COUNT=4 +CONFIG_BLK_DEV_NBD=y +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_COUNT=2 +CONFIG_BLK_DEV_RAM_SIZE=32768 +CONFIG_BLK_DEV_NVME=y +CONFIG_NVME_MULTIPATH=y +CONFIG_NVME_VERBOSE_ERRORS=y +CONFIG_NVME_HWMON=y +CONFIG_SRAM=y +CONFIG_EEPROM_AT24=y +CONFIG_RAID_ATTRS=y +CONFIG_BLK_DEV_SD=y +CONFIG_CHR_DEV_SG=y +CONFIG_SCSI_CONSTANTS=y +CONFIG_SCSI_LOGGING=y +CONFIG_SCSI_SCAN_ASYNC=y +CONFIG_ATA=y +CONFIG_SATA_AHCI_PLATFORM=y +CONFIG_AHCI_DWC=y +# CONFIG_ATA_SFF is not set +CONFIG_NETDEVICES=y +# CONFIG_NET_VENDOR_3COM is not set +# CONFIG_NET_VENDOR_ADAPTEC is not set +# CONFIG_NET_VENDOR_AGERE is not set +# CONFIG_NET_VENDOR_ALACRITECH is not set +# CONFIG_NET_VENDOR_ALTEON is not set +# CONFIG_NET_VENDOR_AMAZON is not set +# CONFIG_NET_VENDOR_AQUANTIA is not set +# CONFIG_NET_VENDOR_ARC is not set +# CONFIG_NET_VENDOR_ASIX is not set +# CONFIG_NET_VENDOR_ATHEROS is not set +# CONFIG_NET_VENDOR_BROADCOM is not set +# CONFIG_NET_VENDOR_CADENCE is not set +# CONFIG_NET_VENDOR_CAVIUM is not set +# CONFIG_NET_VENDOR_CHELSIO is not set +# CONFIG_NET_VENDOR_CISCO is not set +# CONFIG_NET_VENDOR_CORTINA is not set +# CONFIG_NET_VENDOR_DAVICOM is not set +# CONFIG_NET_VENDOR_DEC is not set +# CONFIG_NET_VENDOR_DLINK is not set +# CONFIG_NET_VENDOR_EMULEX is not set +# CONFIG_NET_VENDOR_ENGLEDER is not set +# CONFIG_NET_VENDOR_EZCHIP is not set +# CONFIG_NET_VENDOR_FUNGIBLE is not set +# CONFIG_NET_VENDOR_GOOGLE is not set +# CONFIG_NET_VENDOR_HISILICON is not set +# CONFIG_NET_VENDOR_HUAWEI is not set +# CONFIG_NET_VENDOR_INTEL is not set +# CONFIG_NET_VENDOR_ADI is not set +# CONFIG_NET_VENDOR_MARVELL is not set +# CONFIG_NET_VENDOR_MELLANOX is not set +# CONFIG_NET_VENDOR_META is not set +# CONFIG_NET_VENDOR_MICREL is not set +# CONFIG_NET_VENDOR_MICROCHIP is not set +# CONFIG_NET_VENDOR_MICROSEMI is not set +# CONFIG_NET_VENDOR_MICROSOFT is not set +# CONFIG_NET_VENDOR_MUCSE is not set +# CONFIG_NET_VENDOR_MYRI is not set +# CONFIG_NET_VENDOR_NI is not set +# CONFIG_NET_VENDOR_NATSEMI is not set +# CONFIG_NET_VENDOR_NETRONOME is not set +# CONFIG_NET_VENDOR_NVIDIA is not set +# CONFIG_NET_VENDOR_OKI is not set +# CONFIG_NET_VENDOR_PACKET_ENGINES is not set +# CONFIG_NET_VENDOR_PENSANDO is not set +# CONFIG_NET_VENDOR_QLOGIC is not set +# CONFIG_NET_VENDOR_BROCADE is not set +# CONFIG_NET_VENDOR_QUALCOMM is not set +# CONFIG_NET_VENDOR_RDC is not set +# CONFIG_NET_VENDOR_REALTEK is not set +# CONFIG_NET_VENDOR_RENESAS is not set +# CONFIG_NET_VENDOR_ROCKER is not set +# CONFIG_NET_VENDOR_SAMSUNG is not set +# CONFIG_NET_VENDOR_SEEQ is not set +# CONFIG_NET_VENDOR_SILAN is not set +# CONFIG_NET_VENDOR_SIS is not set +# CONFIG_NET_VENDOR_SOLARFLARE is not set +# CONFIG_NET_VENDOR_SMSC is not set +# CONFIG_NET_VENDOR_SOCIONEXT is not set +CONFIG_STMMAC_ETH=y +CONFIG_STMMAC_SELFTESTS=y +CONFIG_DWMAC_BT1=y +# CONFIG_NET_VENDOR_SUN is not set +# CONFIG_NET_VENDOR_SYNOPSYS is not set +# CONFIG_TEHUTI is not set +# CONFIG_NET_VENDOR_TI is not set +# CONFIG_NET_VENDOR_TOSHIBA is not set +# CONFIG_NET_VENDOR_VERTEXCOM is not set +# CONFIG_NET_VENDOR_VIA is not set +# CONFIG_NET_VENDOR_WANGXUN is not set +# CONFIG_NET_VENDOR_WIZNET is not set +# CONFIG_NET_VENDOR_XILINX is not set +CONFIG_SFP=y +CONFIG_MARVELL_PHY=y +CONFIG_MARVELL_10G_PHY=y +CONFIG_MARVELL_88X2222_PHY=y +CONFIG_MARVELL_88X2222_GPIO=y +CONFIG_MARVELL_88X2222_I2C=y +CONFIG_MICREL_PHY=y +CONFIG_MDIO_BITBANG=y +CONFIG_MDIO_GPIO=y +# CONFIG_USB_NET_DRIVERS is not set +# CONFIG_WLAN is not set +# CONFIG_INPUT_KEYBOARD is not set +# CONFIG_INPUT_MOUSE is not set +# CONFIG_SERIO is not set +# CONFIG_VT_CONSOLE is not set +CONFIG_VT_HW_CONSOLE_BINDING=y +CONFIG_LEGACY_PTY_COUNT=16 +CONFIG_SERIAL_8250=y +CONFIG_SERIAL_8250_CONSOLE=y +CONFIG_SERIAL_8250_NR_UARTS=2 +CONFIG_SERIAL_8250_RUNTIME_UARTS=2 +CONFIG_SERIAL_8250_EXTENDED=y +CONFIG_SERIAL_8250_DW=y +# CONFIG_SERIAL_8250_PERICOM is not set +CONFIG_MIPS_EJTAG_FDC_TTY=y +# CONFIG_HW_RANDOM is not set +CONFIG_I2C=y +CONFIG_I2C_CHARDEV=y +CONFIG_I2C_DESIGNWARE_CORE=y +CONFIG_I2C_SLAVE=y +CONFIG_I2C_SLAVE_EEPROM=y +CONFIG_SPI=y +CONFIG_SPI_DESIGNWARE=y +CONFIG_SPI_DW_DMA=y +CONFIG_SPI_DW_MMIO=y +CONFIG_SPI_DW_BT1=y +CONFIG_SPI_SPIDEV=y +CONFIG_GPIO_SYSFS=y +CONFIG_GPIO_DWAPB=y +CONFIG_GPIO_PCA953X=y +CONFIG_GPIO_PCA953X_IRQ=y +CONFIG_GPIO_PCF857X=y +CONFIG_POWER_RESET=y +CONFIG_POWER_RESET_SYSCON=y +CONFIG_SYSCON_REBOOT_MODE=y +CONFIG_SENSORS_BT1_PVT=y +CONFIG_SENSORS_BT1_PVT_ALARMS=y +CONFIG_SENSORS_TMP102=y +CONFIG_THERMAL=y +CONFIG_THERMAL_STATISTICS=y +CONFIG_THERMAL_GOV_FAIR_SHARE=y +CONFIG_THERMAL_GOV_USER_SPACE=y +CONFIG_CPU_THERMAL=y +CONFIG_WATCHDOG=y +CONFIG_WATCHDOG_SYSFS=y +CONFIG_WATCHDOG_PRETIMEOUT_GOV=y +CONFIG_WATCHDOG_PRETIMEOUT_DEFAULT_GOV_NOOP=y +CONFIG_DW_WATCHDOG=y +# CONFIG_VGA_CONSOLE is not set +CONFIG_USB_ULPI_BUS=y +CONFIG_USB=y +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +CONFIG_USB_MON=y +CONFIG_USB_XHCI_HCD=y +CONFIG_USB_STORAGE=y +CONFIG_USB_UAS=y +CONFIG_USB_DWC3=y +CONFIG_USB_DWC3_ULPI=y +# CONFIG_USB_DWC3_GENERIC_PLAT is not set +CONFIG_USB_HUB_USB251XB=y +CONFIG_MMC=y +CONFIG_MMC_SPI=y +CONFIG_NEW_LEDS=y +CONFIG_LEDS_CLASS=y +CONFIG_LEDS_GPIO=y +CONFIG_EDAC=y +CONFIG_EDAC_DEBUG=y +CONFIG_EDAC_SYNOPSYS=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_ABEOZ9=y +CONFIG_RTC_DRV_PCF85363=y +CONFIG_DMADEVICES=y +CONFIG_DW_DMAC=y +CONFIG_DW_EDMA=y +CONFIG_SYNC_FILE=y +# CONFIG_VIRTIO_MENU is not set +# CONFIG_VHOST_ENABLE_FORK_OWNER_CONTROL is not set +CONFIG_COMMON_CLK_VC5=y +# CONFIG_IOMMU_SUPPORT is not set +CONFIG_MEMORY=y +CONFIG_BT1_L2_CTL=y +CONFIG_NVMEM_U_BOOT_ENV=y +CONFIG_EXT4_FS=y +CONFIG_EXT4_FS_POSIX_ACL=y +CONFIG_FANOTIFY=y +CONFIG_FUSE_FS=y +# CONFIG_FUSE_IO_URING is not set +CONFIG_OVERLAY_FS=y +CONFIG_MSDOS_FS=y +CONFIG_VFAT_FS=y +CONFIG_NTFS3_FS=y +CONFIG_PROC_KCORE=y +CONFIG_TMPFS=y +CONFIG_TMPFS_POSIX_ACL=y +CONFIG_JFFS2_FS=y +CONFIG_JFFS2_FS_WBUF_VERIFY=y +CONFIG_UBIFS_FS=y +CONFIG_SQUASHFS=y +CONFIG_SQUASHFS_LZ4=y +CONFIG_SQUASHFS_LZO=y +CONFIG_SQUASHFS_XZ=y +CONFIG_SQUASHFS_4K_DEVBLK_SIZE=y +CONFIG_NFS_FS=y +CONFIG_NFS_V2=y +CONFIG_NFS_V4=y +CONFIG_NFS_V4_0=y +CONFIG_NFS_V4_2=y +CONFIG_ROOT_NFS=y +CONFIG_NFS_USE_LEGACY_DNS=y +# CONFIG_NFS_V4_2_READ_PLUS is not set +# CONFIG_RPCSEC_GSS_KRB5 is not set +CONFIG_NLS_CODEPAGE_437=y +CONFIG_NLS_CODEPAGE_866=y +CONFIG_NLS_CODEPAGE_1251=y +CONFIG_NLS_ASCII=y +CONFIG_NLS_ISO8859_1=y +CONFIG_NLS_ISO8859_5=y +CONFIG_NLS_KOI8_R=y +CONFIG_NLS_UTF8=y +CONFIG_INIT_STACK_NONE=y +CONFIG_CRYPTO_USER=y +CONFIG_CRYPTO_NULL=y +CONFIG_CRYPTO_BLOWFISH=y +CONFIG_CRYPTO_CCM=y +CONFIG_CRYPTO_XCBC=y +CONFIG_CRYPTO_CRC32C=y +CONFIG_CRYPTO_CRC32=y +CONFIG_CRYPTO_USER_API_HASH=y +# CONFIG_CRYPTO_HW is not set +CONFIG_PRINTK_TIME=y +CONFIG_DEBUG_INFO_DWARF4=y +CONFIG_DEBUG_INFO_BTF=y +CONFIG_FRAME_WARN=1024 +CONFIG_DEBUG_SECTION_MISMATCH=y +CONFIG_VMLINUX_MAP=y +CONFIG_SCHEDSTATS=y +# CONFIG_EARLY_PRINTK is not set +# CONFIG_RUNTIME_TESTING_MENU is not set -- cgit v1.2.3 From 4114ea516ade31f578c740f95b90e374a2503cdc Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 25 Apr 2022 19:38:30 +0300 Subject: mips: baikal-t1: Add Edelweiss EM406 mezzanine card platform The Edelweiss EM406 Mezzanine Card is the 82x80 SMARC v1.1 (Smart Mobility ARChitecture v1.1) module with almost all the Baikal-T1 chip interfaces connected to the edge fingers: 1x PCIe x4, 2x SATA III, 1x 10Gbit Ethernet, 2x Gbit ports with Realtek PHYs, USB 2.0 port, 32x/3x GPIO, 3x i2c, 2x UARTs, 2x SPI, 2x UART. Besides there are two carrier boards available: a simple one with all the interfaces exposed with respective plugs and a debug board dedicated to perform the EM406 module test procedures. In the framework of this commit the platform DT source files and default kernel config are added. Note the DT source is split up into two parts: the generic EM406 device tree and the platform device tree source files. The former one can be re-used for the new platform based on the EM406 module. Signed-off-by: Serge Semin --- arch/mips/boot/dts/baikal/Makefile | 4 +- arch/mips/boot/dts/baikal/cb2-em406.dts | 21 ++ arch/mips/boot/dts/baikal/em406.dtsi | 378 ++++++++++++++++++++++++++ arch/mips/boot/dts/baikal/xa1-em406.dts | 94 +++++++ arch/mips/bt1/Kconfig | 9 + arch/mips/configs/em406_defconfig | 458 ++++++++++++++++++++++++++++++++ 6 files changed, 963 insertions(+), 1 deletion(-) create mode 100644 arch/mips/boot/dts/baikal/cb2-em406.dts create mode 100644 arch/mips/boot/dts/baikal/em406.dtsi create mode 100644 arch/mips/boot/dts/baikal/xa1-em406.dts create mode 100644 arch/mips/configs/em406_defconfig diff --git a/arch/mips/boot/dts/baikal/Makefile b/arch/mips/boot/dts/baikal/Makefile index 966928ae3e94b..e39eb9dcf6f13 100644 --- a/arch/mips/boot/dts/baikal/Makefile +++ b/arch/mips/boot/dts/baikal/Makefile @@ -4,6 +4,8 @@ # # Baikal-T1 DTBs makefile # -dtb-$(CONFIG_BT1_DTB_ALL) += bt1-gen.dtb bfk16p.dtb bfk2.dtb bfk3.dtb +dtb-$(CONFIG_BT1_DTB_ALL) += bt1-gen.dtb bfk16p.dtb bfk2.dtb bfk3.dtb \ + xa1-em406.dtb cb2-em406.dtb dtb-$(CONFIG_BT1_DTB_GENERIC) += bt1-gen.dtb dtb-$(CONFIG_BT1_DTB_BFKP) += bfk16p.dtb bfk2.dtb bfk3.dtb +dtb-$(CONFIG_BT1_DTB_EM406) += xa1-em406.dtb cb2-em406.dtb diff --git a/arch/mips/boot/dts/baikal/cb2-em406.dts b/arch/mips/boot/dts/baikal/cb2-em406.dts new file mode 100644 index 0000000000000..4e09a21f5cfc4 --- /dev/null +++ b/arch/mips/boot/dts/baikal/cb2-em406.dts @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2022 BAIKAL ELECTRONICS, JSC + * + * Edelweiss EM406 carrier board device tree + */ + +/dts-v1/; + +#include "em406.dtsi" + +/ { + model = "Edelweiss EM406 Carrier Board v2.x"; + compatible = "edelweiss,cb2-em406", "edelweiss,em406", "baikal,bt1"; + + chosen { + /* RS232 tranciever is attached to the UART0 port */ + bootargs = "console=ttyS0,115200n8 earlycon maxcpus=2"; + stdout-path = "serial0:115200n8"; + }; +}; diff --git a/arch/mips/boot/dts/baikal/em406.dtsi b/arch/mips/boot/dts/baikal/em406.dtsi new file mode 100644 index 0000000000000..b2baaa5666302 --- /dev/null +++ b/arch/mips/boot/dts/baikal/em406.dtsi @@ -0,0 +1,378 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2022 BAIKAL ELECTRONICS, JSC + * + * Edelweiss EM406 Mezzanine Card device tree + */ + +#include +#include + +#include "bt1.dtsi" + +/ { + model = "Edelweiss EM406 Mezzanine Card"; + compatible = "edelweiss,em406", "baikal,bt1"; + + chosen { + /* + * Use MIPS eJTAG FDC #1 as alternative early-console: + * "console=early_fdc,115200n8 maxcpus=2" + * Note the debug probe must be attached to drain FDC TX FIFO. + */ + bootargs = "console=ttyS0,115200n8 earlycon maxcpus=2"; + stdout-path = "serial0:115200n8"; + + /* Bootloader timer if TIMER config is enabled */ + tick-timer = <&timer_dw0>; + + /* Bootloader may use these props to pass the initrd image */ + linux,initrd-start = <0 0>; + linux,initrd-end = <0 0>; + }; + + aliases { + spd0 = &spd; + mtd1 = &boot_flash; + }; + + memory { + /* + * The mezzanine card is equiepped with at least 2GB memory: + * low memory - 128MB, high memory - 256MB + 1.5GB. The RAM + * can be extended up to 8GB with additional 2GB ECC. + */ + device_type = "memory"; + reg = <0 0x00000000 0 0x08000000>, + <0 0x20000000 0 0x70000000>; + }; + + clocks { + usb_phy_clk: clock-oscillator-usb-phy { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <24000000>; + clock-output-names = "usbphy24m"; + }; + }; +}; + +&l2 { + status = "okay"; +}; + +&i2c0 { + status = "okay"; + + /* Connected to X1 SMARC LCD I2C SDA, SCL (SMARC v1.1) */ +}; + +&int_rom { + status = "okay"; +}; + +&spi0 { + num-cs = <1>; + + status = "okay"; + + /* CS0 bus is connected to DD34 (TS3A5018R NO pins) handled by BMC */ + + /* Micron MT25QU128ADQ0 */ + boot_flash: flash@0 { + compatible = "jedec,spi-nor"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0>; + + spi-max-frequency = <25000000>; + m25p,fast-read; + + /* Tie X1:S39 to GND to redirect device to X1 SMARC SPI0 */ + }; +}; + +&gpio0 { + status = "okay"; +}; + +&port0 { + /* Connected to X1 SMARC pins */ + gpio-line-names = "S106", "S108", "S144", "S142", "S123", + "S122", "S121", "P117", "S113", "P118", + "S117", "S120", "P115", "P116", "P111", + "P113", "P119", "S116", "S118", "S115", + "S114", "P112", "P114", "P110", "S112", + "nUSB_RST", "S109", "S145", "S111", "P109", + "S107", "P108"; +}; + +&i2c1 { + status = "okay"; + + /* Connected to X1 SMARC GP I2C SDA, SCL (SMARC v2) */ +}; + +&i2c2 { + status = "okay"; + + /* Board Management Controller (STM32L151XB) */ + bmc: bmc@8 { + compatible = "edelweiss,bt1-bmc"; + reg = <0x08>; + }; + + /* EM406-specific system control expander */ + smarc_gpio: gpio@20 { + compatible = "nxp,pca9535"; + reg = <0x20>; + + gpio-controller; /* 16 */ + #gpio-cells = <2>; + + /* IRQ can be optionally fused up to port0:29 GPIO */ + + gpio-line-names = "MAJ0", "MAJ1", "MAJ2", "MAJ3", "", + "SLEEP", "LID", "nCHRG", "nCHRG_PRSNT", + "nLOWBAT", "CAR_STB", "", "", + "nPCI_WAKE", "", "PCI_RST"; + }; + + temp: temperature-sensor@48 { + compatible = "ti,tmp102"; + reg = <0x48>; + }; + + /* EEPROM is empty by default */ + spd: eeprom@50 { + compatible = "atmel,24c02"; + reg = <0x50>; + + pagesize = <8>; + }; + + rtc: rtc@51 { + compatible = "nxp,pcf85263"; + reg = <0x51>; + }; + + fw: eeprom@53 { + compatible = "atmel,24c32"; + reg = <0x53>; + + pagesize = <32>; + }; + + clk_gen: clock@6a { + compatible = "idt,5p49v6901"; + reg = <0x6a>; + #clock-cells = <1>; + + clocks = <&ref_clk>; + clock-names = "xin"; + + /* OUT0: Core act., OUT3: Ext PCIe */ + }; + + /* Connected to X1 SMARC PM I2C SDA, SCL (SMARC v2) */ +}; + +&timer_dw0 { + status = "okay"; +}; + +&timer_dw1 { + status = "okay"; +}; + +&timer_dw2 { + status = "okay"; +}; + +&uart0 { + /* Connected to X1 SMARC SER0 pins */ + status = "okay"; +}; + +&uart1 { + /* Connected to X1 SMARC SER1 pins */ + status = "okay"; +}; + +&wdt { + status = "okay"; +}; + +&spi1 { + num-cs = <4>; + + status = "okay"; + + /* Connected to X1 SMARC SPI1 pins and CS2->S59, CS3->S56 */ +}; + +&spi2 { + num-cs = <4>; + + status = "okay"; + + /* + * Connected to X1 SMARC pins MOSI->S40, MISO->S41, CLK->S38, CS0->S37, + * CS1->S35, CS2->S44, CS3->S51 + */ +}; + +&pvt { + status = "okay"; +}; + +&efuse { + status = "okay"; +}; + +&pcie { + clocks = <&ccu_sys CCU_SYS_APB_CLK>, + <&ccu_axi CCU_AXI_PCIE_M_CLK>, + <&ccu_axi CCU_AXI_PCIE_S_CLK>, + <&clk_gen 2>; + clock-names = "dbi", "mstr", "slv", "ref"; + + /* PCIe PERST# signal */ + reset-gpios = <&smarc_gpio 15 GPIO_ACTIVE_HIGH>; + + status = "okay"; +}; + +&sram { + status = "okay"; +}; + +&dma { + status = "okay"; +}; + +&mc { + status = "okay"; +}; + +&mc_phy { + status = "okay"; +}; + +&sata { + clocks = <&ccu_sys CCU_SYS_APB_CLK>, + <&ccu_axi CCU_AXI_SATA_CLK>, + <&clk_gen 4>; + clock-names = "pclk", "aclk", "ref"; + + status = "okay"; +}; + +&sata0 { + hba-port-cap = ; + + status = "okay"; + + /* Connected to X1 SMARC SATA pins */ +}; + +&sata1 { + hba-port-cap = ; + + status = "okay"; + + /* Connected to X1 SMARC pins TXP->P10, TXN->P11, RXP->P13, RXN->P14 */ +}; + +&xgmac { + mac-address = [ 00 26 58 80 01 01 ]; + + status = "okay"; +}; + +&xgmac_pcs { + clocks = <&ccu_sys CCU_SYS_APB_CLK>, + <&ccu_sys CCU_SYS_XGMAC_REF_CLK>, + <&clk_gen 1>; + clock-names = "csr", "core", "pad"; + + status = "okay"; +}; + +&hwa { + status = "okay"; +}; + +&gmac0 { + mac-address = [ 00 26 58 80 01 02 ]; + + phy-handle = <&gmac0_phy>; + managed = "in-band-status"; + + status = "okay"; +}; + +&gmac0_mdio { + reset-gpios = <&gmac0 1 GPIO_ACTIVE_LOW>; + reset-delay-us = <11000>; + reset-post-delay-us = <31000>; + + /* Realtek RTL8211E */ + gmac0_phy: ethernet-phy@3 { + compatible = "ethernet-phy-id001c.c915"; + reg = <0x3>; + + interrupt-parent = <&gmac0>; + interrupts = <0 IRQ_TYPE_EDGE_FALLING>; + + clocks = <&clk_gen 0>; + clock-names = "xtal"; + + /* Connected to X1 SMARC GBE pins */ + }; +}; + +&gmac1 { + mac-address = [ 00 26 58 80 01 03 ]; + + phy-handle = <&gmac1_phy>; + managed = "in-band-status"; + + status = "okay"; +}; + +&gmac1_mdio { + reset-gpios = <&gmac1 1 GPIO_ACTIVE_LOW>; + reset-delay-us = <11000>; + reset-post-delay-us = <31000>; + + /* Realtek RTL8211E */ + gmac1_phy: ethernet-phy@3 { + compatible = "ethernet-phy-id001c.c915"; + reg = <0x3>; + + interrupt-parent = <&gmac1>; + interrupts = <0 IRQ_TYPE_EDGE_FALLING>; + + clocks = <&clk_gen 0>; + clock-names = "xtal"; + + /* Connected to X1 SMARC AFB_DIFF pins */ + }; +}; + +&usb { + status = "okay"; + + ulpi { + /* Microchip USB3315 */ + phy { + clocks = <&usb_phy_clk>; + clock-names = "ref"; + + reset-gpios = <&port0 25 GPIO_ACTIVE_LOW>; + + /* Connected to X1 SMARC USB1 pins */ + }; + }; +}; diff --git a/arch/mips/boot/dts/baikal/xa1-em406.dts b/arch/mips/boot/dts/baikal/xa1-em406.dts new file mode 100644 index 0000000000000..3d548233d4114 --- /dev/null +++ b/arch/mips/boot/dts/baikal/xa1-em406.dts @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2022 BAIKAL ELECTRONICS, JSC + * + * Edelweiss EM406 debug board device tree + */ + +/dts-v1/; + +#include "em406.dtsi" + +/ { + model = "Edelweiss EM406 XA1 Debug Board"; + compatible = "edelweiss,xa1-em406", "edelweiss,em406", "baikal,bt1"; + + aliases { + mtd2 = &test_flash10; + mtd3 = &test_flash20; + }; +}; + +&i2c0 { + test_temp: temperature-sensor@48 { + compatible = "ti,tmp102"; + reg = <0x48>; + }; +}; + +&i2c1 { + test_gpio0: gpio@22 { + compatible = "ti,tca6424"; + reg = <0x22>; + + gpio-controller; /* 24 */ + #gpio-cells = <2>; + + gpio-line-names = "BT_GPIO0", "BT_GPIO1", "BT_GPIO2", "BT_GPIO3", + "BT_GPIO4", "BT_GPIO5", "BT_GPIO6", "BT_GPIO7", + "BT_GPIO8", "BT_GPIO9", "BT_GPIO10", "BT_GPIO11", + "BT_GPIO12", "BT_GPIO13", "BT_GPIO14", "BT_GPIO15", + "BT_GPIO16", "BT_GPIO17", "BT_GPIO18", "BT_GPIO19", + "BT_GPIO20", "BT_GPIO21", "BT_GPIO22", ""; + }; + + test_gpio1: gpio@23 { + compatible = "ti,tca6424"; + reg = <0x23>; + + gpio-controller; /* 24 */ + #gpio-cells = <2>; + + gpio-line-names = "BT_GPIO23", "BT_GPIO24", "BT_GPIO26", "BT_GPIO27", + "BT_GPIO28", "BT_GPIO29", "BT_GPIO30", "BT_GPIO31", + "nLID", "nSLEEP", "nCHRG", "nCHRG_PRSNT", + "nLOWBAT", "nCAR_STB", "nPCI_WAKE", "CAR_PWR_ON", + "nRST_OUT", "nPCI_ATT_BUT", "SPI1_CS_SEL0", + "SPI1_CS_SEL1", "SPI2_CS_SEL0", "SPI2_CS_SEL1", + "", ""; + }; +}; + +&spi1 { + num-cs = <4>; + + /* CS is determined by test_gpio1:SPI1_CS_SEL[0:1] */ + + /* Micron MT25QU128ABA1 */ + test_flash10: flash@0 { + compatible = "jedec,spi-nor"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0>; + + m25p,fast-read; + spi-max-frequency = <25000000>; + }; +}; + +&spi2 { + num-cs = <4>; + + /* CS is determined by test_gpio1:SPI2_CS_SEL[0:1] */ + + /* Micron MT25QU128ABA1 */ + test_flash20: flash@0 { + compatible = "jedec,spi-nor"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0>; + + m25p,fast-read; + spi-max-frequency = <25000000>; + }; +}; diff --git a/arch/mips/bt1/Kconfig b/arch/mips/bt1/Kconfig index 5d59655f436b5..06c6c01ca9851 100644 --- a/arch/mips/bt1/Kconfig +++ b/arch/mips/bt1/Kconfig @@ -91,6 +91,15 @@ config BT1_DTB_BFKP Naturally these are the Baikal-T1 SoC evaluation boards specifically designed for the SoC-based platforms and software prototyping. +config BT1_DTB_EM406 + bool "Edelweiss EM406" + help + This option provides a dtb for the Edelweiss EM406 Mezzanine + Card-based boards. It has been designed as 82x80 SMARC v1.1 (Smart + Mobility ARChitecture v1.1) module with all the chip interfaces + connected to the edge fingers and destined to simplify the Baikal-T1 + SoC based platforms development. + endchoice menu "Baikal-T1 Errata" diff --git a/arch/mips/configs/em406_defconfig b/arch/mips/configs/em406_defconfig new file mode 100644 index 0000000000000..b7640fd948b4a --- /dev/null +++ b/arch/mips/configs/em406_defconfig @@ -0,0 +1,458 @@ +CONFIG_LOCALVERSION="-bt1" +CONFIG_DEFAULT_HOSTNAME="baikal" +CONFIG_SYSVIPC=y +CONFIG_POSIX_MQUEUE=y +CONFIG_NO_HZ_IDLE=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_CLOCKSOURCE_WATCHDOG_MAX_SKEW_US=100 +CONFIG_BPF_SYSCALL=y +CONFIG_BPF_JIT=y +CONFIG_BPF_JIT_ALWAYS_ON=y +CONFIG_BPF_PRELOAD=y +CONFIG_BPF_PRELOAD_UMD=y +CONFIG_PREEMPT=y +CONFIG_BSD_PROCESS_ACCT=y +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_CGROUPS=y +CONFIG_MEMCG=y +CONFIG_BLK_CGROUP=y +CONFIG_CGROUP_SCHED=y +CONFIG_CFS_BANDWIDTH=y +CONFIG_CGROUP_PIDS=y +CONFIG_CGROUP_FREEZER=y +CONFIG_CPUSETS=y +CONFIG_CGROUP_DEVICE=y +CONFIG_CGROUP_CPUACCT=y +CONFIG_CGROUP_PERF=y +CONFIG_CGROUP_BPF=y +CONFIG_CGROUP_MISC=y +CONFIG_NAMESPACES=y +CONFIG_USER_NS=y +CONFIG_CHECKPOINT_RESTORE=y +CONFIG_RELAY=y +CONFIG_BLK_DEV_INITRD=y +CONFIG_CMDLINE_LOG_WRAP_IDEAL_LEN=100 +CONFIG_SYSFS_SYSCALL=y +CONFIG_EXPERT=y +CONFIG_PROFILING=y +CONFIG_MIPS_BAIKAL_T1=y +CONFIG_BT1_CPU_FEATURE_OVERRIDES=y +CONFIG_BT1_DTB_EM406=y +CONFIG_CPU_P5600=y +CONFIG_CPU_MIPS32_R5_FEATURES=y +CONFIG_CPU_MIPS32_R5_XPA=y +CONFIG_ZBOOT_LOAD_ADDRESS=0x85100000 +CONFIG_ARCH_FORCE_MAX_ORDER=11 +CONFIG_CPU_HAS_MSA=y +CONFIG_DMI=y +CONFIG_NR_CPUS=2 +CONFIG_IEEE754_DEFAULT_RELAXED=y +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_STAT=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y +CONFIG_CPUFREQ_DT=y +CONFIG_PAGE_SIZE_16KB=y +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MODULE_UNLOAD_TAINT_TRACKING=y +CONFIG_MODVERSIONS=y +CONFIG_MODULE_SRCVERSION_ALL=y +# CONFIG_BLOCK_LEGACY_AUTOLOAD is not set +CONFIG_BLK_DEV_BSGLIB=y +# CONFIG_IOSCHED_BFQ is not set +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_PACKET_DIAG=y +CONFIG_UNIX=y +CONFIG_UNIX_DIAG=y +CONFIG_XFRM_USER=y +CONFIG_NET_KEY=y +CONFIG_XDP_SOCKETS=y +CONFIG_XDP_SOCKETS_DIAG=y +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +CONFIG_IP_PNP=y +CONFIG_NET_IPIP=y +CONFIG_NET_IPGRE_DEMUX=y +CONFIG_NET_IPGRE=y +CONFIG_NET_FOU_IP_TUNNELS=y +CONFIG_INET_AH=y +CONFIG_INET_ESP=y +CONFIG_INET_IPCOMP=y +CONFIG_INET_UDP_DIAG=y +CONFIG_INET_RAW_DIAG=y +CONFIG_TCP_CONG_ADVANCED=y +CONFIG_TCP_CONG_BIC=y +CONFIG_TCP_CONG_WESTWOOD=y +CONFIG_TCP_CONG_HTCP=y +CONFIG_TCP_CONG_HSTCP=y +CONFIG_TCP_CONG_BBR=y +CONFIG_INET6_AH=y +CONFIG_INET6_ESP=y +CONFIG_INET6_IPCOMP=y +CONFIG_IPV6_GRE=y +CONFIG_IPV6_SEG6_LWTUNNEL=y +CONFIG_MPTCP=y +CONFIG_NETWORK_PHY_TIMESTAMPING=y +CONFIG_NETFILTER=y +CONFIG_NF_CONNTRACK=y +CONFIG_NF_CONNTRACK_PROCFS=y +CONFIG_NF_TABLES=y +CONFIG_NF_TABLES_INET=y +CONFIG_NF_TABLES_NETDEV=y +CONFIG_NFT_LOG=y +CONFIG_NFT_LIMIT=y +CONFIG_NFT_MASQ=y +CONFIG_NFT_REDIR=y +CONFIG_NFT_NAT=y +CONFIG_NFT_REJECT=y +CONFIG_NFT_HASH=y +CONFIG_NETFILTER_XT_CONNMARK=y +CONFIG_NETFILTER_XT_TARGET_CLASSIFY=y +CONFIG_NETFILTER_XT_TARGET_MARK=y +CONFIG_NETFILTER_XT_NAT=y +CONFIG_NETFILTER_XT_TARGET_NETMAP=y +CONFIG_NETFILTER_XT_TARGET_NFLOG=y +CONFIG_NETFILTER_XT_TARGET_NFQUEUE=y +CONFIG_NETFILTER_XT_TARGET_REDIRECT=y +CONFIG_NETFILTER_XT_TARGET_MASQUERADE=y +CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=y +CONFIG_NETFILTER_XT_MATCH_CGROUP=y +CONFIG_NETFILTER_XT_MATCH_CPU=y +CONFIG_NETFILTER_XT_MATCH_IPRANGE=y +CONFIG_NETFILTER_XT_MATCH_LENGTH=y +CONFIG_NETFILTER_XT_MATCH_LIMIT=y +CONFIG_NETFILTER_XT_MATCH_MAC=y +CONFIG_NETFILTER_XT_MATCH_MULTIPORT=y +CONFIG_NETFILTER_XT_MATCH_OWNER=y +CONFIG_NETFILTER_XT_MATCH_POLICY=y +CONFIG_NETFILTER_XT_MATCH_PKTTYPE=y +CONFIG_NETFILTER_XT_MATCH_RECENT=y +CONFIG_NETFILTER_XT_MATCH_SOCKET=y +CONFIG_NETFILTER_XT_MATCH_STATE=y +CONFIG_NETFILTER_XT_MATCH_TCPMSS=y +CONFIG_NFT_DUP_IPV4=y +CONFIG_NF_TABLES_ARP=y +CONFIG_IP_NF_IPTABLES=y +CONFIG_IP_NF_MATCH_AH=y +CONFIG_IP_NF_MATCH_ECN=y +CONFIG_IP_NF_MATCH_TTL=y +CONFIG_IP_NF_TARGET_SYNPROXY=y +CONFIG_NFT_DUP_IPV6=y +CONFIG_IP6_NF_IPTABLES=y +CONFIG_IP6_NF_MATCH_AH=y +CONFIG_IP6_NF_MATCH_EUI64=y +CONFIG_IP6_NF_MATCH_FRAG=y +CONFIG_IP6_NF_MATCH_OPTS=y +CONFIG_IP6_NF_MATCH_HL=y +CONFIG_IP6_NF_MATCH_IPV6HEADER=y +CONFIG_IP6_NF_MATCH_MH=y +CONFIG_IP6_NF_MATCH_RT=y +CONFIG_BRIDGE=y +CONFIG_BRIDGE_VLAN_FILTERING=y +CONFIG_VLAN_8021Q=y +CONFIG_NET_SCHED=y +CONFIG_NET_SCH_HTB=y +CONFIG_NET_SCH_PRIO=y +CONFIG_NET_SCH_MULTIQ=y +CONFIG_NET_SCH_SFQ=y +CONFIG_NET_SCH_CBS=y +CONFIG_NET_SCH_ETF=y +CONFIG_NET_SCH_TAPRIO=y +CONFIG_NET_SCH_MQPRIO=y +CONFIG_NET_SCH_SKBPRIO=y +CONFIG_NET_SCH_QFQ=y +CONFIG_NET_SCH_FQ=y +CONFIG_NET_SCH_INGRESS=y +CONFIG_NET_SCH_DEFAULT=y +CONFIG_NET_CLS_BASIC=y +CONFIG_NET_CLS_U32=y +CONFIG_CLS_U32_PERF=y +CONFIG_CLS_U32_MARK=y +CONFIG_NET_CLS_FLOW=y +CONFIG_NET_CLS_BPF=y +CONFIG_NET_CLS_FLOWER=y +CONFIG_NET_CLS_ACT=y +CONFIG_NET_ACT_GACT=y +CONFIG_NET_ACT_MIRRED=y +CONFIG_NET_ACT_PEDIT=y +CONFIG_NET_ACT_SIMP=y +CONFIG_NET_ACT_SKBEDIT=y +CONFIG_NET_ACT_VLAN=y +CONFIG_NET_ACT_BPF=y +CONFIG_NET_ACT_SKBMOD=y +CONFIG_DCB=y +CONFIG_DNS_RESOLVER=y +CONFIG_NETLINK_DIAG=y +CONFIG_CGROUP_NET_PRIO=y +CONFIG_CGROUP_NET_CLASSID=y +CONFIG_BPF_STREAM_PARSER=y +CONFIG_NET_PKTGEN=y +# CONFIG_WIRELESS is not set +CONFIG_PAGE_POOL_STATS=y +CONFIG_PCI=y +CONFIG_PCIEPORTBUS=y +CONFIG_HOTPLUG_PCI_PCIE=y +CONFIG_PCIEAER=y +CONFIG_PCIEAER_INJECT=y +CONFIG_PCIE_ECRC=y +CONFIG_PCIEASPM_PERFORMANCE=y +CONFIG_PCI_MSI=y +CONFIG_PCIE_BUS_PERFORMANCE=y +CONFIG_HOTPLUG_PCI=y +CONFIG_PCIE_BT1=y +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +CONFIG_DEBUG_DRIVER=y +CONFIG_DEBUG_DEVRES=y +CONFIG_BT1_APB=y +CONFIG_BT1_AXI=y +CONFIG_MIPS_CDMM=y +CONFIG_MTD=y +CONFIG_MTD_PARTITIONED_MASTER=y +CONFIG_MTD_ROM=y +CONFIG_MTD_PHYSMAP=y +CONFIG_MTD_PHYSMAP_OF=y +CONFIG_MTD_PHYSMAP_BT1_ROM=y +CONFIG_MTD_SPI_NOR=y +# CONFIG_MTD_SPI_NOR_USE_4K_SECTORS is not set +CONFIG_MTD_UBI=y +CONFIG_MTD_UBI_BLOCK=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_LOOP_MIN_COUNT=4 +CONFIG_BLK_DEV_NBD=y +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_COUNT=2 +CONFIG_BLK_DEV_RAM_SIZE=32768 +CONFIG_BLK_DEV_NVME=y +CONFIG_NVME_MULTIPATH=y +CONFIG_NVME_VERBOSE_ERRORS=y +CONFIG_NVME_HWMON=y +CONFIG_SRAM=y +CONFIG_EEPROM_AT24=y +CONFIG_RAID_ATTRS=y +CONFIG_BLK_DEV_SD=y +CONFIG_CHR_DEV_SG=y +CONFIG_SCSI_CONSTANTS=y +CONFIG_SCSI_LOGGING=y +CONFIG_SCSI_SCAN_ASYNC=y +CONFIG_ATA=y +CONFIG_SATA_AHCI_PLATFORM=y +CONFIG_AHCI_DWC=y +# CONFIG_ATA_SFF is not set +CONFIG_NETDEVICES=y +# CONFIG_NET_VENDOR_3COM is not set +# CONFIG_NET_VENDOR_ADAPTEC is not set +# CONFIG_NET_VENDOR_AGERE is not set +# CONFIG_NET_VENDOR_ALACRITECH is not set +# CONFIG_NET_VENDOR_ALTEON is not set +# CONFIG_NET_VENDOR_AMAZON is not set +# CONFIG_NET_VENDOR_AQUANTIA is not set +# CONFIG_NET_VENDOR_ARC is not set +# CONFIG_NET_VENDOR_ASIX is not set +# CONFIG_NET_VENDOR_ATHEROS is not set +# CONFIG_NET_VENDOR_BROADCOM is not set +# CONFIG_NET_VENDOR_CADENCE is not set +# CONFIG_NET_VENDOR_CAVIUM is not set +# CONFIG_NET_VENDOR_CHELSIO is not set +# CONFIG_NET_VENDOR_CISCO is not set +# CONFIG_NET_VENDOR_CORTINA is not set +# CONFIG_NET_VENDOR_DAVICOM is not set +# CONFIG_NET_VENDOR_DEC is not set +# CONFIG_NET_VENDOR_DLINK is not set +# CONFIG_NET_VENDOR_EMULEX is not set +# CONFIG_NET_VENDOR_ENGLEDER is not set +# CONFIG_NET_VENDOR_EZCHIP is not set +# CONFIG_NET_VENDOR_FUNGIBLE is not set +# CONFIG_NET_VENDOR_GOOGLE is not set +# CONFIG_NET_VENDOR_HISILICON is not set +# CONFIG_NET_VENDOR_HUAWEI is not set +# CONFIG_NET_VENDOR_INTEL is not set +# CONFIG_NET_VENDOR_ADI is not set +# CONFIG_NET_VENDOR_MARVELL is not set +# CONFIG_NET_VENDOR_MELLANOX is not set +# CONFIG_NET_VENDOR_META is not set +# CONFIG_NET_VENDOR_MICREL is not set +# CONFIG_NET_VENDOR_MICROCHIP is not set +# CONFIG_NET_VENDOR_MICROSEMI is not set +# CONFIG_NET_VENDOR_MICROSOFT is not set +# CONFIG_NET_VENDOR_MUCSE is not set +# CONFIG_NET_VENDOR_MYRI is not set +# CONFIG_NET_VENDOR_NI is not set +# CONFIG_NET_VENDOR_NATSEMI is not set +# CONFIG_NET_VENDOR_NETRONOME is not set +# CONFIG_NET_VENDOR_NVIDIA is not set +# CONFIG_NET_VENDOR_OKI is not set +# CONFIG_NET_VENDOR_PACKET_ENGINES is not set +# CONFIG_NET_VENDOR_PENSANDO is not set +# CONFIG_NET_VENDOR_QLOGIC is not set +# CONFIG_NET_VENDOR_BROCADE is not set +# CONFIG_NET_VENDOR_QUALCOMM is not set +# CONFIG_NET_VENDOR_RDC is not set +# CONFIG_NET_VENDOR_REALTEK is not set +# CONFIG_NET_VENDOR_RENESAS is not set +# CONFIG_NET_VENDOR_ROCKER is not set +# CONFIG_NET_VENDOR_SAMSUNG is not set +# CONFIG_NET_VENDOR_SEEQ is not set +# CONFIG_NET_VENDOR_SILAN is not set +# CONFIG_NET_VENDOR_SIS is not set +# CONFIG_NET_VENDOR_SOLARFLARE is not set +# CONFIG_NET_VENDOR_SMSC is not set +# CONFIG_NET_VENDOR_SOCIONEXT is not set +CONFIG_STMMAC_ETH=y +CONFIG_STMMAC_SELFTESTS=y +CONFIG_DWMAC_BT1=y +# CONFIG_NET_VENDOR_SUN is not set +# CONFIG_NET_VENDOR_SYNOPSYS is not set +# CONFIG_TEHUTI is not set +# CONFIG_NET_VENDOR_TI is not set +# CONFIG_NET_VENDOR_TOSHIBA is not set +# CONFIG_NET_VENDOR_VERTEXCOM is not set +# CONFIG_NET_VENDOR_VIA is not set +# CONFIG_NET_VENDOR_WANGXUN is not set +# CONFIG_NET_VENDOR_WIZNET is not set +# CONFIG_NET_VENDOR_XILINX is not set +CONFIG_SFP=y +CONFIG_MARVELL_PHY=y +CONFIG_MARVELL_10G_PHY=y +CONFIG_MARVELL_88X2222_PHY=y +CONFIG_MARVELL_88X2222_GPIO=y +CONFIG_MARVELL_88X2222_I2C=y +CONFIG_REALTEK_PHY=y +CONFIG_MDIO_BITBANG=y +CONFIG_MDIO_GPIO=y +# CONFIG_USB_NET_DRIVERS is not set +# CONFIG_WLAN is not set +# CONFIG_INPUT_KEYBOARD is not set +# CONFIG_INPUT_MOUSE is not set +# CONFIG_SERIO is not set +# CONFIG_VT_CONSOLE is not set +CONFIG_VT_HW_CONSOLE_BINDING=y +CONFIG_LEGACY_PTY_COUNT=16 +CONFIG_SERIAL_8250=y +CONFIG_SERIAL_8250_CONSOLE=y +CONFIG_SERIAL_8250_NR_UARTS=2 +CONFIG_SERIAL_8250_RUNTIME_UARTS=2 +CONFIG_SERIAL_8250_EXTENDED=y +CONFIG_SERIAL_8250_DW=y +# CONFIG_SERIAL_8250_PERICOM is not set +CONFIG_MIPS_EJTAG_FDC_TTY=y +# CONFIG_HW_RANDOM is not set +CONFIG_I2C=y +CONFIG_I2C_CHARDEV=y +CONFIG_I2C_DESIGNWARE_CORE=y +CONFIG_I2C_SLAVE=y +CONFIG_I2C_SLAVE_EEPROM=y +CONFIG_SPI=y +CONFIG_SPI_DESIGNWARE=y +CONFIG_SPI_DW_DMA=y +CONFIG_SPI_DW_MMIO=y +CONFIG_SPI_DW_BT1=y +CONFIG_SPI_SPIDEV=y +CONFIG_GPIO_SYSFS=y +CONFIG_GPIO_DWAPB=y +CONFIG_GPIO_PCA953X=y +CONFIG_GPIO_PCA953X_IRQ=y +CONFIG_GPIO_PCF857X=y +CONFIG_POWER_RESET=y +CONFIG_POWER_RESET_SYSCON=y +CONFIG_SYSCON_REBOOT_MODE=y +CONFIG_SENSORS_BT1_PVT=y +CONFIG_SENSORS_BT1_PVT_ALARMS=y +CONFIG_SENSORS_TMP102=y +CONFIG_THERMAL=y +CONFIG_THERMAL_STATISTICS=y +CONFIG_THERMAL_GOV_FAIR_SHARE=y +CONFIG_THERMAL_GOV_USER_SPACE=y +CONFIG_CPU_THERMAL=y +CONFIG_WATCHDOG=y +CONFIG_WATCHDOG_SYSFS=y +CONFIG_WATCHDOG_PRETIMEOUT_GOV=y +CONFIG_WATCHDOG_PRETIMEOUT_DEFAULT_GOV_NOOP=y +CONFIG_DW_WATCHDOG=y +# CONFIG_VGA_CONSOLE is not set +CONFIG_USB_ULPI_BUS=y +CONFIG_USB=y +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +CONFIG_USB_MON=y +CONFIG_USB_XHCI_HCD=y +CONFIG_USB_STORAGE=y +CONFIG_USB_UAS=y +CONFIG_USB_DWC3=y +CONFIG_USB_DWC3_ULPI=y +CONFIG_EDAC=y +CONFIG_EDAC_DEBUG=y +CONFIG_EDAC_SYNOPSYS=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_PCF85363=y +CONFIG_DMADEVICES=y +CONFIG_DW_DMAC=y +CONFIG_DW_EDMA=y +CONFIG_SYNC_FILE=y +# CONFIG_VIRTIO_MENU is not set +# CONFIG_VHOST_ENABLE_FORK_OWNER_CONTROL is not set +CONFIG_COMMON_CLK_VC5=y +# CONFIG_IOMMU_SUPPORT is not set +CONFIG_MEMORY=y +CONFIG_BT1_L2_CTL=y +CONFIG_NVMEM_U_BOOT_ENV=y +CONFIG_EXT4_FS=y +CONFIG_EXT4_FS_POSIX_ACL=y +CONFIG_FANOTIFY=y +CONFIG_FUSE_FS=y +# CONFIG_FUSE_IO_URING is not set +CONFIG_OVERLAY_FS=y +CONFIG_MSDOS_FS=y +CONFIG_VFAT_FS=y +CONFIG_NTFS3_FS=y +CONFIG_PROC_KCORE=y +CONFIG_TMPFS=y +CONFIG_TMPFS_POSIX_ACL=y +CONFIG_JFFS2_FS=y +CONFIG_JFFS2_FS_WBUF_VERIFY=y +CONFIG_UBIFS_FS=y +CONFIG_SQUASHFS=y +CONFIG_SQUASHFS_LZ4=y +CONFIG_SQUASHFS_LZO=y +CONFIG_SQUASHFS_XZ=y +CONFIG_SQUASHFS_4K_DEVBLK_SIZE=y +CONFIG_NFS_FS=y +CONFIG_NFS_V2=y +CONFIG_NFS_V4=y +CONFIG_NFS_V4_0=y +CONFIG_NFS_V4_2=y +CONFIG_ROOT_NFS=y +CONFIG_NFS_USE_LEGACY_DNS=y +# CONFIG_NFS_V4_2_READ_PLUS is not set +# CONFIG_RPCSEC_GSS_KRB5 is not set +CONFIG_NLS_CODEPAGE_437=y +CONFIG_NLS_CODEPAGE_866=y +CONFIG_NLS_CODEPAGE_1251=y +CONFIG_NLS_ASCII=y +CONFIG_NLS_ISO8859_1=y +CONFIG_NLS_ISO8859_5=y +CONFIG_NLS_KOI8_R=y +CONFIG_NLS_UTF8=y +CONFIG_INIT_STACK_NONE=y +CONFIG_CRYPTO_USER=y +CONFIG_CRYPTO_NULL=y +CONFIG_CRYPTO_BLOWFISH=y +CONFIG_CRYPTO_CCM=y +CONFIG_CRYPTO_XCBC=y +CONFIG_CRYPTO_CRC32C=y +CONFIG_CRYPTO_CRC32=y +CONFIG_CRYPTO_USER_API_HASH=y +# CONFIG_CRYPTO_HW is not set +CONFIG_PRINTK_TIME=y +CONFIG_DEBUG_INFO_DWARF4=y +CONFIG_DEBUG_INFO_BTF=y +CONFIG_FRAME_WARN=1024 +CONFIG_DEBUG_SECTION_MISMATCH=y +CONFIG_VMLINUX_MAP=y +CONFIG_SCHEDSTATS=y +# CONFIG_EARLY_PRINTK is not set +# CONFIG_RUNTIME_TESTING_MENU is not set -- cgit v1.2.3 From e1e856369e2b883e268ec76b9ce1bcfca20edd98 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Tue, 26 Apr 2022 19:52:29 +0300 Subject: mips: baikal-t1: Add Edelweiss EM416 mezzanine card platform The Edelweiss EM416 Mezzanine Card module with almost all the Baikal-T1 chip interfaces connected to the edge fingers: 1x PCIe, 2x SATA III, 2x Gbit ports with Realtek PHYs, USB 2.0 port, 8x GPIO, 2x i2c, 1x UARTs, 1x SPI, 1x MMC SPI. The card is specifically designed to be working in the industrial environment. Besides there is debug carrier board available to test out the card interfaces. In the framework of this commit the platform DT source files and the default kernel config are added. Note the DT source is split up into two parts: the generic EM416 device tree and the platform device tree source files. The former one can be re-used for the new platform based on the EM416 module. Signed-off-by: Serge Semin --- arch/mips/boot/dts/baikal/Makefile | 3 +- arch/mips/boot/dts/baikal/em416.dtsi | 359 +++++++++++++++++++++++++ arch/mips/boot/dts/baikal/xa1-em416.dts | 52 ++++ arch/mips/bt1/Kconfig | 9 + arch/mips/configs/em416_defconfig | 447 ++++++++++++++++++++++++++++++++ 5 files changed, 869 insertions(+), 1 deletion(-) create mode 100644 arch/mips/boot/dts/baikal/em416.dtsi create mode 100644 arch/mips/boot/dts/baikal/xa1-em416.dts create mode 100644 arch/mips/configs/em416_defconfig diff --git a/arch/mips/boot/dts/baikal/Makefile b/arch/mips/boot/dts/baikal/Makefile index e39eb9dcf6f13..90a283cc2b971 100644 --- a/arch/mips/boot/dts/baikal/Makefile +++ b/arch/mips/boot/dts/baikal/Makefile @@ -5,7 +5,8 @@ # Baikal-T1 DTBs makefile # dtb-$(CONFIG_BT1_DTB_ALL) += bt1-gen.dtb bfk16p.dtb bfk2.dtb bfk3.dtb \ - xa1-em406.dtb cb2-em406.dtb + xa1-em406.dtb cb2-em406.dtb xa1-em416.dtb dtb-$(CONFIG_BT1_DTB_GENERIC) += bt1-gen.dtb dtb-$(CONFIG_BT1_DTB_BFKP) += bfk16p.dtb bfk2.dtb bfk3.dtb dtb-$(CONFIG_BT1_DTB_EM406) += xa1-em406.dtb cb2-em406.dtb +dtb-$(CONFIG_BT1_DTB_EM416) += xa1-em416.dtb diff --git a/arch/mips/boot/dts/baikal/em416.dtsi b/arch/mips/boot/dts/baikal/em416.dtsi new file mode 100644 index 0000000000000..955c2cf5a8a0b --- /dev/null +++ b/arch/mips/boot/dts/baikal/em416.dtsi @@ -0,0 +1,359 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2022 BAIKAL ELECTRONICS, JSC + * + * Edelweiss EM416 Mezzanine Card device tree + */ + +#include +#include +#include + +#include "bt1.dtsi" + +/ { + model = "Edelweiss EM416 Mezzanine Card"; + compatible = "edelweiss,em416", "baikal,bt1"; + + chosen { + bootargs = "console=ttyS0,115200n8 earlycon maxcpus=2"; + stdout-path = "serial0:115200n8"; + + /* Bootloader timer if TIMER config is enabled */ + tick-timer = <&timer_dw0>; + + /* Bootloader may use these props to pass the initrd image */ + linux,initrd-start = <0 0>; + linux,initrd-end = <0 0>; + }; + + aliases { + spd0 = &spd; + mtd1 = &boot_flash; + }; + + memory { + /* + * The card is always equipped with 2GB of RAM: + * low memory - 128MB, high memory - 256MB + 1.5GB. + */ + device_type = "memory"; + reg = <0 0x00000000 0 0x08000000>, + <0 0x20000000 0 0x70000000>; + }; + + clocks { + vc6_clk: clock-oscillator-vc6 { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <25000000>; + clock-output-names = "vc25m"; + }; + + gmac0_phy_clk: clock-oscillator-gmac0-phy { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <25000000>; + clock-output-names = "gmac0phy25m"; + }; + + gmac1_phy_clk: clock-oscillator-gmac1-phy { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <25000000>; + clock-output-names = "gmac1phy25m"; + }; + }; +}; + +&l2 { + status = "okay"; +}; + +&int_rom { + status = "okay"; +}; + +&spi0 { + num-cs = <1>; + + status = "okay"; + + /* CS0 bus is connected to DD10 (TS3A5018R NO pins) handled by BMC */ + + /* Micron MT25QU128ABA1 */ + boot_flash: flash@0 { + compatible = "jedec,spi-nor"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0>; + + spi-max-frequency = <25000000>; + m25p,fast-read; + + /* + * Use BMC console to redirect device to XP2 MOSI->38, + * MISO->40, CLK->39, CS0->37 + */ + }; +}; + +&gpio0 { + status = "okay"; +}; + +&port0 { + /* GP - general purpose */ + gpio-line-names = "", "", "", "", "", "", "", "", + "nPCI_RST", "nPCI_PRSNT", "", "", "", + "nUSB_RST", "nSD_CS", "nSD_CD", "", "", "", + "", "", "", "GP", "GP", "GP", "GP", "nEXP_INT", + "GP", "GP", "GP", "GP", "GP", "GP"; +}; + +&i2c1 { + status = "okay"; +}; + +&i2c2 { + status = "okay"; + + /* Board Management Controller (STM32F078CB) */ + bmc: bmc@8 { + compatible = "edelweiss,bt1-bmc"; + reg = <0x08>; + }; + + shred_gpio: gpio@21 { + compatible = "nxp,pcf8574"; + reg = <0x21>; + + gpio-controller; /* 8 */ + #gpio-cells = <2>; + + /* Kind of useless though... */ + interrupt-parent = <&port0>; + interrupts = <25 IRQ_TYPE_LEVEL_LOW>; + interrupt-controller; + #interrupt-cells = <2>; + }; + + /* EEPROM is empty by default */ + spd: eeprom@50 { + compatible = "atmel,24c02"; + reg = <0x50>; + + pagesize = <8>; + }; + + fw: eeprom@53 { + compatible = "atmel,24c32"; + reg = <0x53>; + + pagesize = <32>; + }; + + clk_gen: clock@6a { + compatible = "idt,5p49v6901"; + reg = <0x6a>; + #clock-cells = <1>; + + clocks = <&vc6_clk>; + clock-names = "xin"; + + /* OUT0: Core alt., OUT1: Ext PCIe */ + }; +}; + +&timer_dw0 { + status = "okay"; +}; + +&timer_dw1 { + status = "okay"; +}; + +&timer_dw2 { + status = "okay"; +}; + +&uart0 { + status = "okay"; +}; + +&uart1 { + /* Connected to DD14 (BMC) */ + status = "okay"; +}; + +&wdt { + status = "okay"; +}; + +&spi1 { + num-cs = <2>; + + /* + * R40/R39 resistors switch between native CS1 and port0:14 GPIO. + * Solder the R39 resistor in to activate the GPIO CS. + */ + cs-gpios = <0>, <&port0 14 GPIO_ACTIVE_LOW>; + + status = "okay"; + + /* Won't work with native CS due to no high CS capability */ + mmc: mmc@1 { + compatible = "mmc-spi-slot"; + reg = <1>; + + voltage-ranges = <3200 3400>; + spi-max-frequency = <25000000>; + /* Card-Detect GPIO */ + gpios = <&port0 15 GPIO_ACTIVE_LOW>; + }; + +}; + +&spi2 { + num-cs = <4>; + + status = "okay"; +}; + +&pvt { + status = "okay"; +}; + +&efuse { + status = "okay"; +}; + +&pcie { + clocks = <&ccu_sys CCU_SYS_APB_CLK>, + <&ccu_axi CCU_AXI_PCIE_M_CLK>, + <&ccu_axi CCU_AXI_PCIE_S_CLK>, + <&clk_gen 2>; + clock-names = "dbi", "mstr", "slv", "ref"; + + /* PCIe PERST# signal */ + reset-gpios = <&port0 8 GPIO_ACTIVE_LOW>; + + status = "okay"; +}; + +&sram { + status = "okay"; +}; + +&dma { + status = "okay"; +}; + +&mc { + status = "okay"; +}; + +&mc_phy { + status = "okay"; +}; + +&sata { + clocks = <&ccu_sys CCU_SYS_APB_CLK>, + <&ccu_axi CCU_AXI_SATA_CLK>, + <&clk_gen 4>; + clock-names = "pclk", "aclk", "ref"; + + status = "okay"; +}; + +&sata0 { + hba-port-cap = ; + + status = "okay"; +}; + +&sata1 { + hba-port-cap = ; + + status = "okay"; +}; + +&hwa { + status = "okay"; +}; + +&gmac0 { + mac-address = [ 00 26 58 79 00 05 ]; + + phy-handle = <&gmac0_phy>; + + status = "okay"; +}; + +&gmac0_mdio { + reset-gpios = <&gmac0 1 GPIO_ACTIVE_LOW>; + reset-delay-us = <2000>; + reset-post-delay-us = <16000>; + + /* Microsemi VSC8531XMW */ + gmac0_phy: ethernet-phy@1 { + compatible = "ethernet-phy-id0007.0570"; + reg = <0x1>; + + interrupt-parent = <&gmac0>; + interrupts = <0 IRQ_TYPE_EDGE_FALLING>; + + clocks = <&gmac0_phy_clk>; + clock-names = "xtal"; + + vsc8531,vddmac = <1800>; + vsc8531,edge-slowdown = <0>; + vsc8531,led-0-mode = ; + vsc8531,led-1-mode = ; + }; +}; + +&gmac1 { + mac-address = [ 00 26 58 79 00 06 ]; + + phy-handle = <&gmac1_phy>; + + status = "okay"; +}; + +&gmac1_mdio { + reset-gpios = <&gmac1 1 GPIO_ACTIVE_LOW>; + reset-delay-us = <2000>; + reset-post-delay-us = <16000>; + + /* Microsemi VSC8531XMW */ + gmac1_phy: ethernet-phy@2 { + compatible = "ethernet-phy-id0007.0570"; + reg = <0x2>; + + interrupt-parent = <&gmac1>; + interrupts = <0 IRQ_TYPE_EDGE_FALLING>; + + clocks = <&gmac1_phy_clk>; + clock-names = "xtal"; + + vsc8531,vddmac = <1800>; + vsc8531,edge-slowdown = <0>; + vsc8531,led-0-mode = ; + vsc8531,led-1-mode = ; + }; +}; + +&usb { + status = "okay"; + + ulpi { + /* Microchip USB3315 */ + phy { + clocks = <&clk_gen 3>; + clock-names = "ref"; + + reset-gpios = <&port0 13 GPIO_ACTIVE_LOW>; + }; + }; +}; diff --git a/arch/mips/boot/dts/baikal/xa1-em416.dts b/arch/mips/boot/dts/baikal/xa1-em416.dts new file mode 100644 index 0000000000000..2fc92f906f1e0 --- /dev/null +++ b/arch/mips/boot/dts/baikal/xa1-em416.dts @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2022 BAIKAL ELECTRONICS, JSC + * + * Edelweiss EM416 debug board device tree + */ + +/dts-v1/; + +#include "em416.dtsi" + +/ { + model = "Edelweiss EM416 XA1 Debug Board"; + compatible = "edelweiss,xa1-em416", "edelweiss,em416", "baikal,bt1"; + + aliases { + mtd2 = &test_flash20; + }; +}; + +&i2c1 { + test_gpio0: gpio@20 { + compatible = "nxp,pca9535"; + reg = <0x20>; + + gpio-controller; /* 16 */ + #gpio-cells = <2>; + + gpio-line-names = "BMC_GPIO0", "BMC_GPIO1", "BMC_GPIO2", "BMC_GPIO3", + "BT_GPIO21", "BT_GPIO22", "BT_GPIO23", "BT_GPIO24", + "BT_GPIO26", "BT_GPIO27", "BT_GPIO28", "BT_GPIO29", + "BT_GPIO30", "BT_GPIO31", "SPI2_CS_SEL0", + "SPI2_CS_SEL1"; + }; +}; + +&spi2 { + num-cs = <4>; + + /* CS is determined by test_gpio0:SPI2_CS_SEL[0:1] */ + + /* Micron MT25QU128ABA1 */ + test_flash20: flash@0 { + compatible = "jedec,spi-nor"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0>; + + m25p,fast-read; + spi-max-frequency = <25000000>; + }; +}; diff --git a/arch/mips/bt1/Kconfig b/arch/mips/bt1/Kconfig index 06c6c01ca9851..cc30c5b32ddac 100644 --- a/arch/mips/bt1/Kconfig +++ b/arch/mips/bt1/Kconfig @@ -100,6 +100,15 @@ config BT1_DTB_EM406 connected to the edge fingers and destined to simplify the Baikal-T1 SoC based platforms development. +config BT1_DTB_EM416 + bool "Edelweiss EM416" + help + This option provides a dtb for the Edelweiss EM416 Mezzanine + Card-based boards. It has been designed to work in the industrial + environment as a replaceable module with almost all the chip + interfaces connected to the edge fingers and destined to simplify + the Baikal-T1 SoC based critical environment platforms development. + endchoice menu "Baikal-T1 Errata" diff --git a/arch/mips/configs/em416_defconfig b/arch/mips/configs/em416_defconfig new file mode 100644 index 0000000000000..1aa7f56c3493c --- /dev/null +++ b/arch/mips/configs/em416_defconfig @@ -0,0 +1,447 @@ +CONFIG_LOCALVERSION="-bt1" +CONFIG_DEFAULT_HOSTNAME="baikal" +CONFIG_SYSVIPC=y +CONFIG_POSIX_MQUEUE=y +CONFIG_NO_HZ_IDLE=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_CLOCKSOURCE_WATCHDOG_MAX_SKEW_US=100 +CONFIG_BPF_SYSCALL=y +CONFIG_BPF_JIT=y +CONFIG_BPF_JIT_ALWAYS_ON=y +CONFIG_BPF_PRELOAD=y +CONFIG_BPF_PRELOAD_UMD=y +CONFIG_PREEMPT=y +CONFIG_BSD_PROCESS_ACCT=y +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_CGROUPS=y +CONFIG_MEMCG=y +CONFIG_BLK_CGROUP=y +CONFIG_CGROUP_SCHED=y +CONFIG_CFS_BANDWIDTH=y +CONFIG_CGROUP_PIDS=y +CONFIG_CGROUP_FREEZER=y +CONFIG_CPUSETS=y +CONFIG_CGROUP_DEVICE=y +CONFIG_CGROUP_CPUACCT=y +CONFIG_CGROUP_PERF=y +CONFIG_CGROUP_BPF=y +CONFIG_CGROUP_MISC=y +CONFIG_NAMESPACES=y +CONFIG_USER_NS=y +CONFIG_CHECKPOINT_RESTORE=y +CONFIG_RELAY=y +CONFIG_BLK_DEV_INITRD=y +CONFIG_CMDLINE_LOG_WRAP_IDEAL_LEN=100 +CONFIG_SYSFS_SYSCALL=y +CONFIG_EXPERT=y +CONFIG_PROFILING=y +CONFIG_MIPS_BAIKAL_T1=y +CONFIG_BT1_CPU_FEATURE_OVERRIDES=y +CONFIG_BT1_DTB_EM416=y +CONFIG_CPU_P5600=y +CONFIG_CPU_MIPS32_R5_FEATURES=y +CONFIG_CPU_MIPS32_R5_XPA=y +CONFIG_ZBOOT_LOAD_ADDRESS=0x85100000 +CONFIG_ARCH_FORCE_MAX_ORDER=11 +CONFIG_CPU_HAS_MSA=y +CONFIG_NR_CPUS=2 +CONFIG_IEEE754_DEFAULT_RELAXED=y +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_STAT=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y +CONFIG_CPUFREQ_DT=y +CONFIG_PAGE_SIZE_16KB=y +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MODULE_UNLOAD_TAINT_TRACKING=y +CONFIG_MODVERSIONS=y +CONFIG_MODULE_SRCVERSION_ALL=y +# CONFIG_BLOCK_LEGACY_AUTOLOAD is not set +CONFIG_BLK_DEV_BSGLIB=y +# CONFIG_IOSCHED_BFQ is not set +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_PACKET_DIAG=y +CONFIG_UNIX=y +CONFIG_UNIX_DIAG=y +CONFIG_XFRM_USER=y +CONFIG_NET_KEY=y +CONFIG_XDP_SOCKETS=y +CONFIG_XDP_SOCKETS_DIAG=y +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +CONFIG_IP_PNP=y +CONFIG_NET_IPIP=y +CONFIG_NET_IPGRE_DEMUX=y +CONFIG_NET_IPGRE=y +CONFIG_NET_FOU_IP_TUNNELS=y +CONFIG_INET_AH=y +CONFIG_INET_ESP=y +CONFIG_INET_IPCOMP=y +CONFIG_INET_UDP_DIAG=y +CONFIG_INET_RAW_DIAG=y +CONFIG_TCP_CONG_ADVANCED=y +CONFIG_TCP_CONG_BIC=y +CONFIG_TCP_CONG_WESTWOOD=y +CONFIG_TCP_CONG_HTCP=y +CONFIG_TCP_CONG_HSTCP=y +CONFIG_TCP_CONG_BBR=y +CONFIG_INET6_AH=y +CONFIG_INET6_ESP=y +CONFIG_INET6_IPCOMP=y +CONFIG_IPV6_GRE=y +CONFIG_IPV6_SEG6_LWTUNNEL=y +CONFIG_MPTCP=y +CONFIG_NETWORK_PHY_TIMESTAMPING=y +CONFIG_NETFILTER=y +CONFIG_NF_CONNTRACK=y +CONFIG_NF_CONNTRACK_PROCFS=y +CONFIG_NF_TABLES=y +CONFIG_NF_TABLES_INET=y +CONFIG_NF_TABLES_NETDEV=y +CONFIG_NFT_LOG=y +CONFIG_NFT_LIMIT=y +CONFIG_NFT_MASQ=y +CONFIG_NFT_REDIR=y +CONFIG_NFT_NAT=y +CONFIG_NFT_REJECT=y +CONFIG_NFT_HASH=y +CONFIG_NETFILTER_XT_CONNMARK=y +CONFIG_NETFILTER_XT_TARGET_CLASSIFY=y +CONFIG_NETFILTER_XT_TARGET_MARK=y +CONFIG_NETFILTER_XT_NAT=y +CONFIG_NETFILTER_XT_TARGET_NETMAP=y +CONFIG_NETFILTER_XT_TARGET_NFLOG=y +CONFIG_NETFILTER_XT_TARGET_NFQUEUE=y +CONFIG_NETFILTER_XT_TARGET_REDIRECT=y +CONFIG_NETFILTER_XT_TARGET_MASQUERADE=y +CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=y +CONFIG_NETFILTER_XT_MATCH_CGROUP=y +CONFIG_NETFILTER_XT_MATCH_CPU=y +CONFIG_NETFILTER_XT_MATCH_IPRANGE=y +CONFIG_NETFILTER_XT_MATCH_LENGTH=y +CONFIG_NETFILTER_XT_MATCH_LIMIT=y +CONFIG_NETFILTER_XT_MATCH_MAC=y +CONFIG_NETFILTER_XT_MATCH_MULTIPORT=y +CONFIG_NETFILTER_XT_MATCH_OWNER=y +CONFIG_NETFILTER_XT_MATCH_POLICY=y +CONFIG_NETFILTER_XT_MATCH_PKTTYPE=y +CONFIG_NETFILTER_XT_MATCH_RECENT=y +CONFIG_NETFILTER_XT_MATCH_SOCKET=y +CONFIG_NETFILTER_XT_MATCH_STATE=y +CONFIG_NETFILTER_XT_MATCH_TCPMSS=y +CONFIG_NFT_DUP_IPV4=y +CONFIG_NF_TABLES_ARP=y +CONFIG_IP_NF_IPTABLES=y +CONFIG_IP_NF_MATCH_AH=y +CONFIG_IP_NF_MATCH_ECN=y +CONFIG_IP_NF_MATCH_TTL=y +CONFIG_IP_NF_TARGET_SYNPROXY=y +CONFIG_NFT_DUP_IPV6=y +CONFIG_IP6_NF_IPTABLES=y +CONFIG_IP6_NF_MATCH_AH=y +CONFIG_IP6_NF_MATCH_EUI64=y +CONFIG_IP6_NF_MATCH_FRAG=y +CONFIG_IP6_NF_MATCH_OPTS=y +CONFIG_IP6_NF_MATCH_HL=y +CONFIG_IP6_NF_MATCH_IPV6HEADER=y +CONFIG_IP6_NF_MATCH_MH=y +CONFIG_IP6_NF_MATCH_RT=y +CONFIG_BRIDGE=y +CONFIG_BRIDGE_VLAN_FILTERING=y +CONFIG_VLAN_8021Q=y +CONFIG_NET_SCHED=y +CONFIG_NET_SCH_HTB=y +CONFIG_NET_SCH_PRIO=y +CONFIG_NET_SCH_MULTIQ=y +CONFIG_NET_SCH_SFQ=y +CONFIG_NET_SCH_CBS=y +CONFIG_NET_SCH_ETF=y +CONFIG_NET_SCH_TAPRIO=y +CONFIG_NET_SCH_MQPRIO=y +CONFIG_NET_SCH_SKBPRIO=y +CONFIG_NET_SCH_QFQ=y +CONFIG_NET_SCH_FQ=y +CONFIG_NET_SCH_INGRESS=y +CONFIG_NET_SCH_DEFAULT=y +CONFIG_NET_CLS_BASIC=y +CONFIG_NET_CLS_U32=y +CONFIG_CLS_U32_PERF=y +CONFIG_CLS_U32_MARK=y +CONFIG_NET_CLS_FLOW=y +CONFIG_NET_CLS_BPF=y +CONFIG_NET_CLS_FLOWER=y +CONFIG_NET_CLS_ACT=y +CONFIG_NET_ACT_GACT=y +CONFIG_NET_ACT_MIRRED=y +CONFIG_NET_ACT_PEDIT=y +CONFIG_NET_ACT_SIMP=y +CONFIG_NET_ACT_SKBEDIT=y +CONFIG_NET_ACT_VLAN=y +CONFIG_NET_ACT_BPF=y +CONFIG_NET_ACT_SKBMOD=y +CONFIG_DCB=y +CONFIG_DNS_RESOLVER=y +CONFIG_NETLINK_DIAG=y +CONFIG_CGROUP_NET_PRIO=y +CONFIG_CGROUP_NET_CLASSID=y +CONFIG_BPF_STREAM_PARSER=y +CONFIG_NET_PKTGEN=y +# CONFIG_WIRELESS is not set +CONFIG_PAGE_POOL_STATS=y +CONFIG_PCI=y +CONFIG_PCIEPORTBUS=y +CONFIG_HOTPLUG_PCI_PCIE=y +CONFIG_PCIEAER=y +CONFIG_PCIEAER_INJECT=y +CONFIG_PCIE_ECRC=y +CONFIG_PCIEASPM_PERFORMANCE=y +CONFIG_PCI_MSI=y +CONFIG_PCIE_BUS_PERFORMANCE=y +CONFIG_HOTPLUG_PCI=y +CONFIG_PCIE_BT1=y +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +CONFIG_DEBUG_DRIVER=y +CONFIG_DEBUG_DEVRES=y +CONFIG_BT1_APB=y +CONFIG_BT1_AXI=y +CONFIG_MIPS_CDMM=y +CONFIG_MTD=y +CONFIG_MTD_PARTITIONED_MASTER=y +CONFIG_MTD_ROM=y +CONFIG_MTD_PHYSMAP=y +CONFIG_MTD_PHYSMAP_OF=y +CONFIG_MTD_PHYSMAP_BT1_ROM=y +CONFIG_MTD_SPI_NOR=y +# CONFIG_MTD_SPI_NOR_USE_4K_SECTORS is not set +CONFIG_MTD_UBI=y +CONFIG_MTD_UBI_BLOCK=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_LOOP_MIN_COUNT=4 +CONFIG_BLK_DEV_NBD=y +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_COUNT=2 +CONFIG_BLK_DEV_RAM_SIZE=32768 +CONFIG_BLK_DEV_NVME=y +CONFIG_NVME_MULTIPATH=y +CONFIG_NVME_VERBOSE_ERRORS=y +CONFIG_NVME_HWMON=y +CONFIG_SRAM=y +CONFIG_EEPROM_AT24=y +CONFIG_RAID_ATTRS=y +CONFIG_BLK_DEV_SD=y +CONFIG_CHR_DEV_SG=y +CONFIG_SCSI_CONSTANTS=y +CONFIG_SCSI_LOGGING=y +CONFIG_SCSI_SCAN_ASYNC=y +CONFIG_ATA=y +CONFIG_SATA_AHCI_PLATFORM=y +CONFIG_AHCI_DWC=y +# CONFIG_ATA_SFF is not set +CONFIG_NETDEVICES=y +# CONFIG_NET_VENDOR_3COM is not set +# CONFIG_NET_VENDOR_ADAPTEC is not set +# CONFIG_NET_VENDOR_AGERE is not set +# CONFIG_NET_VENDOR_ALACRITECH is not set +# CONFIG_NET_VENDOR_ALTEON is not set +# CONFIG_NET_VENDOR_AMAZON is not set +# CONFIG_NET_VENDOR_AQUANTIA is not set +# CONFIG_NET_VENDOR_ARC is not set +# CONFIG_NET_VENDOR_ASIX is not set +# CONFIG_NET_VENDOR_ATHEROS is not set +# CONFIG_NET_VENDOR_BROADCOM is not set +# CONFIG_NET_VENDOR_CADENCE is not set +# CONFIG_NET_VENDOR_CAVIUM is not set +# CONFIG_NET_VENDOR_CHELSIO is not set +# CONFIG_NET_VENDOR_CISCO is not set +# CONFIG_NET_VENDOR_CORTINA is not set +# CONFIG_NET_VENDOR_DAVICOM is not set +# CONFIG_NET_VENDOR_DEC is not set +# CONFIG_NET_VENDOR_DLINK is not set +# CONFIG_NET_VENDOR_EMULEX is not set +# CONFIG_NET_VENDOR_ENGLEDER is not set +# CONFIG_NET_VENDOR_EZCHIP is not set +# CONFIG_NET_VENDOR_FUNGIBLE is not set +# CONFIG_NET_VENDOR_GOOGLE is not set +# CONFIG_NET_VENDOR_HISILICON is not set +# CONFIG_NET_VENDOR_HUAWEI is not set +# CONFIG_NET_VENDOR_INTEL is not set +# CONFIG_NET_VENDOR_ADI is not set +# CONFIG_NET_VENDOR_MARVELL is not set +# CONFIG_NET_VENDOR_MELLANOX is not set +# CONFIG_NET_VENDOR_META is not set +# CONFIG_NET_VENDOR_MICREL is not set +# CONFIG_NET_VENDOR_MICROCHIP is not set +# CONFIG_NET_VENDOR_MICROSEMI is not set +# CONFIG_NET_VENDOR_MICROSOFT is not set +# CONFIG_NET_VENDOR_MUCSE is not set +# CONFIG_NET_VENDOR_MYRI is not set +# CONFIG_NET_VENDOR_NI is not set +# CONFIG_NET_VENDOR_NATSEMI is not set +# CONFIG_NET_VENDOR_NETRONOME is not set +# CONFIG_NET_VENDOR_NVIDIA is not set +# CONFIG_NET_VENDOR_OKI is not set +# CONFIG_NET_VENDOR_PACKET_ENGINES is not set +# CONFIG_NET_VENDOR_PENSANDO is not set +# CONFIG_NET_VENDOR_QLOGIC is not set +# CONFIG_NET_VENDOR_BROCADE is not set +# CONFIG_NET_VENDOR_QUALCOMM is not set +# CONFIG_NET_VENDOR_RDC is not set +# CONFIG_NET_VENDOR_REALTEK is not set +# CONFIG_NET_VENDOR_RENESAS is not set +# CONFIG_NET_VENDOR_ROCKER is not set +# CONFIG_NET_VENDOR_SAMSUNG is not set +# CONFIG_NET_VENDOR_SEEQ is not set +# CONFIG_NET_VENDOR_SILAN is not set +# CONFIG_NET_VENDOR_SIS is not set +# CONFIG_NET_VENDOR_SOLARFLARE is not set +# CONFIG_NET_VENDOR_SMSC is not set +# CONFIG_NET_VENDOR_SOCIONEXT is not set +CONFIG_STMMAC_ETH=y +CONFIG_STMMAC_SELFTESTS=y +CONFIG_DWMAC_BT1=y +# CONFIG_NET_VENDOR_SUN is not set +# CONFIG_NET_VENDOR_SYNOPSYS is not set +# CONFIG_TEHUTI is not set +# CONFIG_NET_VENDOR_TI is not set +# CONFIG_NET_VENDOR_TOSHIBA is not set +# CONFIG_NET_VENDOR_VERTEXCOM is not set +# CONFIG_NET_VENDOR_VIA is not set +# CONFIG_NET_VENDOR_WANGXUN is not set +# CONFIG_NET_VENDOR_WIZNET is not set +# CONFIG_NET_VENDOR_XILINX is not set +CONFIG_MICROSEMI_PHY=y +# CONFIG_USB_NET_DRIVERS is not set +# CONFIG_WLAN is not set +# CONFIG_INPUT_KEYBOARD is not set +# CONFIG_INPUT_MOUSE is not set +# CONFIG_SERIO is not set +# CONFIG_VT_CONSOLE is not set +CONFIG_VT_HW_CONSOLE_BINDING=y +CONFIG_LEGACY_PTY_COUNT=16 +CONFIG_SERIAL_8250=y +CONFIG_SERIAL_8250_CONSOLE=y +CONFIG_SERIAL_8250_NR_UARTS=2 +CONFIG_SERIAL_8250_RUNTIME_UARTS=2 +CONFIG_SERIAL_8250_EXTENDED=y +CONFIG_SERIAL_8250_DW=y +# CONFIG_SERIAL_8250_PERICOM is not set +CONFIG_MIPS_EJTAG_FDC_TTY=y +# CONFIG_HW_RANDOM is not set +CONFIG_I2C=y +CONFIG_I2C_CHARDEV=y +CONFIG_I2C_DESIGNWARE_CORE=y +CONFIG_I2C_SLAVE=y +CONFIG_I2C_SLAVE_EEPROM=y +CONFIG_SPI=y +CONFIG_SPI_DESIGNWARE=y +CONFIG_SPI_DW_DMA=y +CONFIG_SPI_DW_MMIO=y +CONFIG_SPI_DW_BT1=y +CONFIG_SPI_SPIDEV=y +CONFIG_GPIO_SYSFS=y +CONFIG_GPIO_DWAPB=y +CONFIG_GPIO_PCA953X=y +CONFIG_GPIO_PCA953X_IRQ=y +CONFIG_GPIO_PCF857X=y +CONFIG_POWER_RESET=y +CONFIG_POWER_RESET_SYSCON=y +CONFIG_SYSCON_REBOOT_MODE=y +CONFIG_SENSORS_BT1_PVT=y +CONFIG_SENSORS_BT1_PVT_ALARMS=y +CONFIG_THERMAL=y +CONFIG_THERMAL_STATISTICS=y +CONFIG_THERMAL_GOV_FAIR_SHARE=y +CONFIG_THERMAL_GOV_USER_SPACE=y +CONFIG_CPU_THERMAL=y +CONFIG_WATCHDOG=y +CONFIG_WATCHDOG_SYSFS=y +CONFIG_WATCHDOG_PRETIMEOUT_GOV=y +CONFIG_WATCHDOG_PRETIMEOUT_DEFAULT_GOV_NOOP=y +CONFIG_DW_WATCHDOG=y +# CONFIG_VGA_CONSOLE is not set +CONFIG_USB_ULPI_BUS=y +CONFIG_USB=y +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +CONFIG_USB_MON=y +CONFIG_USB_XHCI_HCD=y +CONFIG_USB_STORAGE=y +CONFIG_USB_UAS=y +CONFIG_USB_DWC3=y +CONFIG_USB_DWC3_ULPI=y +CONFIG_MMC=y +CONFIG_MMC_SPI=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_PCF85363=y +CONFIG_DMADEVICES=y +CONFIG_DW_DMAC=y +CONFIG_DW_EDMA=y +CONFIG_SYNC_FILE=y +# CONFIG_VIRTIO_MENU is not set +# CONFIG_VHOST_ENABLE_FORK_OWNER_CONTROL is not set +CONFIG_COMMON_CLK_VC5=y +# CONFIG_IOMMU_SUPPORT is not set +CONFIG_MEMORY=y +CONFIG_BT1_L2_CTL=y +CONFIG_NVMEM_U_BOOT_ENV=y +CONFIG_EXT4_FS=y +CONFIG_EXT4_FS_POSIX_ACL=y +CONFIG_FANOTIFY=y +CONFIG_FUSE_FS=y +# CONFIG_FUSE_IO_URING is not set +CONFIG_OVERLAY_FS=y +CONFIG_MSDOS_FS=y +CONFIG_VFAT_FS=y +CONFIG_NTFS3_FS=y +CONFIG_PROC_KCORE=y +CONFIG_TMPFS=y +CONFIG_TMPFS_POSIX_ACL=y +CONFIG_JFFS2_FS=y +CONFIG_JFFS2_FS_WBUF_VERIFY=y +CONFIG_UBIFS_FS=y +CONFIG_SQUASHFS=y +CONFIG_SQUASHFS_LZ4=y +CONFIG_SQUASHFS_LZO=y +CONFIG_SQUASHFS_XZ=y +CONFIG_SQUASHFS_4K_DEVBLK_SIZE=y +CONFIG_NFS_FS=y +CONFIG_NFS_V2=y +CONFIG_NFS_V4=y +CONFIG_NFS_V4_0=y +CONFIG_NFS_V4_2=y +CONFIG_ROOT_NFS=y +CONFIG_NFS_USE_LEGACY_DNS=y +# CONFIG_NFS_V4_2_READ_PLUS is not set +# CONFIG_RPCSEC_GSS_KRB5 is not set +CONFIG_NLS_CODEPAGE_437=y +CONFIG_NLS_CODEPAGE_866=y +CONFIG_NLS_CODEPAGE_1251=y +CONFIG_NLS_ASCII=y +CONFIG_NLS_ISO8859_1=y +CONFIG_NLS_ISO8859_5=y +CONFIG_NLS_KOI8_R=y +CONFIG_NLS_UTF8=y +CONFIG_INIT_STACK_NONE=y +CONFIG_CRYPTO_USER=y +CONFIG_CRYPTO_NULL=y +CONFIG_CRYPTO_BLOWFISH=y +CONFIG_CRYPTO_CCM=y +CONFIG_CRYPTO_XCBC=y +CONFIG_CRYPTO_CRC32C=y +CONFIG_CRYPTO_CRC32=y +CONFIG_CRYPTO_USER_API_HASH=y +# CONFIG_CRYPTO_HW is not set +CONFIG_PRINTK_TIME=y +CONFIG_DEBUG_INFO_DWARF4=y +CONFIG_DEBUG_INFO_BTF=y +CONFIG_FRAME_WARN=1024 +CONFIG_DEBUG_SECTION_MISMATCH=y +CONFIG_VMLINUX_MAP=y +CONFIG_SCHEDSTATS=y +# CONFIG_EARLY_PRINTK is not set +# CONFIG_RUNTIME_TESTING_MENU is not set -- cgit v1.2.3 From b3f530659db1b57ba9dd7f34c0b9996b0bd32717 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 27 Apr 2022 23:30:33 +0300 Subject: mips: baikal-t1: Add Edelweiss TF306 board platform Edelweiss TF306 mini-ITX board is the Baikal-T1 SoC-based designed to be of the small mITX form-factor and being utilized as general purpose embedded and desktop solution. Aside with the denoted SoC the board contains DDR3 SoDIMM plug, Silicon Motion SM750 graphic chip, ALC4040 HD audio and high-speed interfaces like SATA, USB2.0, 1Gbit Ethernet, microSD. In the framework of this commit the platform DT source file and the default kernel config are added. Signed-off-by: Serge Semin --- arch/mips/boot/dts/baikal/Makefile | 4 +- arch/mips/boot/dts/baikal/tf306.dts | 380 ++++++++++++++++++++++++++++++ arch/mips/bt1/Kconfig | 9 + arch/mips/configs/tf306_defconfig | 450 ++++++++++++++++++++++++++++++++++++ 4 files changed, 842 insertions(+), 1 deletion(-) create mode 100644 arch/mips/boot/dts/baikal/tf306.dts create mode 100644 arch/mips/configs/tf306_defconfig diff --git a/arch/mips/boot/dts/baikal/Makefile b/arch/mips/boot/dts/baikal/Makefile index 90a283cc2b971..b081f15086dcc 100644 --- a/arch/mips/boot/dts/baikal/Makefile +++ b/arch/mips/boot/dts/baikal/Makefile @@ -5,8 +5,10 @@ # Baikal-T1 DTBs makefile # dtb-$(CONFIG_BT1_DTB_ALL) += bt1-gen.dtb bfk16p.dtb bfk2.dtb bfk3.dtb \ - xa1-em406.dtb cb2-em406.dtb xa1-em416.dtb + xa1-em406.dtb cb2-em406.dtb xa1-em416.dtb \ + tf306.dtb dtb-$(CONFIG_BT1_DTB_GENERIC) += bt1-gen.dtb dtb-$(CONFIG_BT1_DTB_BFKP) += bfk16p.dtb bfk2.dtb bfk3.dtb dtb-$(CONFIG_BT1_DTB_EM406) += xa1-em406.dtb cb2-em406.dtb dtb-$(CONFIG_BT1_DTB_EM416) += xa1-em416.dtb +dtb-$(CONFIG_BT1_DTB_TF306) += tf306.dtb diff --git a/arch/mips/boot/dts/baikal/tf306.dts b/arch/mips/boot/dts/baikal/tf306.dts new file mode 100644 index 0000000000000..1047875c79172 --- /dev/null +++ b/arch/mips/boot/dts/baikal/tf306.dts @@ -0,0 +1,380 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2022 BAIKAL ELECTRONICS, JSC + * + * Edelweiss TF306 mini-ITX board device tree + */ + +/dts-v1/; + +#include +#include + +#include "bt1.dtsi" + +/ { + model = "Edelweiss TF306 mini-ITX Board"; + compatible = "edelweiss,tf306", "baikal,bt1"; + + chosen { + bootargs = "console=ttyS0,115200n8 earlycon maxcpus=2"; + stdout-path = "serial0:115200n8"; + + /* Bootloader timer if TIMER config is enabled */ + tick-timer = <&timer_dw0>; + + /* Bootloader is supposed to pass the initrd image address */ + linux,initrd-start = <0 0>; + linux,initrd-end = <0 0>; + }; + + aliases { + spd0 = &spd; + mtd1 = &boot_flash; + }; + + memory { + /* + * Assume at least 512MB of RAM detected in SO-DIMM (XS1): + * low memory - 128MB, high memory - 256MB. + */ + device_type = "memory"; + reg = <0 0x00000000 0 0x08000000>, + <0 0x20000000 0 0x10000000>; + }; + + + reserved-memory { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + framebuffer: framebuffer@7000000 { + reg = <0 0x07000000 0 0x01000000>; + }; + }; + + leds { + compatible = "gpio-leds"; + + led-0 { + label = "Disk Activity"; + + gpios = <&port0 18 GPIO_ACTIVE_HIGH>; + linux,default-trigger = "disk-activity"; + }; + }; + + clocks { + vc6_clk: clock-oscillator-vc { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <25000000>; + clock-output-names = "vc25m"; + }; + + ssd_clk: clock-oscillator-ssd { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <25000000>; + clock-output-names = "ssd25m"; + }; + + gmac0_phy_clk: clock-oscillator-gmac0-phy { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <25000000>; + clock-output-names = "gmac0phy25m"; + }; + }; +}; + +&l2 { + status = "okay"; +}; + +&int_rom { + status = "okay"; +}; + +&spi0 { + num-cs = <1>; + + status = "okay"; + + /* CS0 bus is connected to DD24 (TS3A5018R NO pins) handled by BMC */ + + /* Micron N25Q128A11 */ + boot_flash: flash@0 { + compatible = "jedec,spi-nor"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0>; + + spi-max-frequency = <25000000>; + m25p,fast-read; + + /* + * Use BMC console to redirect device to XP3 MOSI->10, + * MISO->9, CLK->8, CS0->7 + */ + }; +}; + +&gpio0 { + status = "okay"; +}; + +&port0 { + gpio-line-names = "", "", "", "", "", "", "", "", "", "", "", + "nRTC_INT", "nVIDEO_RST", "nUSB_RST", "nSD_CS", + "nSD_CD", "", "", "HDD_LED", "", "", "", "", "", "", + "nEXP_INT", "", "", "", "", "", ""; +}; + +&i2c2 { + status = "okay"; + + /* Board Management Controller (STM32F078CB) */ + bmc: bmc@8 { + compatible = "edelweiss,bt1-bmc"; + reg = <0x08>; + }; + + shred_gpio: gpio@21 { + compatible = "nxp,pcf8574"; + reg = <0x21>; + + gpio-controller; /* 8 */ + #gpio-cells = <2>; + + /* Kind of useless though... */ + interrupt-parent = <&port0>; + interrupts = <25 IRQ_TYPE_LEVEL_LOW>; + interrupt-controller; + #interrupt-cells = <2>; + }; + + hub: usb-hub@2c { + compatible = "microchip,usb2517"; + reg = <0x2c>; + + /* Configs are set by the bootloader */ + skip-config; + }; + + spd: eeprom@50 { + compatible = "atmel,24c02"; + reg = <0x50>; + + pagesize = <8>; + }; + + rtc: rtc@51 { + compatible = "nxp,pcf2129"; + reg = <0x51>; + + interrupt-parent = <&port0>; + interrupts = <11 IRQ_TYPE_LEVEL_LOW>; + }; + + fw: eeprom@53 { + compatible = "atmel,24c32"; + reg = <0x53>; + + pagesize = <32>; + }; + + clk_gen: clock@6a { + compatible = "idt,5p49v6901"; + reg = <0x6a>; + #clock-cells = <1>; + + clocks = <&vc6_clk>; + clock-names = "xin"; + + /* OUT0: Core alt. */ + }; +}; + +&timer_dw0 { + status = "okay"; +}; + +&timer_dw1 { + status = "okay"; +}; + +&timer_dw2 { + status = "okay"; +}; + +&uart0 { + status = "okay"; +}; + +&uart1 { + /* Connected to DD28 (BMC) */ + status = "okay"; +}; + +&wdt { + status = "okay"; +}; + +&spi1 { + num-cs = <2>; + + /* + * R505/R504 resistors switch between native CS1 and port0:14 GPIO. + * Solder the R504 resistor in to activate the GPIO CS. + */ + cs-gpios = <0>, <&port0 14 GPIO_ACTIVE_LOW>; + + status = "okay"; + + /* Won't work with native CS due to no high CS capability */ + mmc: mmc@1 { + compatible = "mmc-spi-slot"; + reg = <1>; + + voltage-ranges = <3200 3400>; + spi-max-frequency = <25000000>; + /* Card-Detect GPIO */ + gpios = <&port0 15 GPIO_ACTIVE_LOW>; + }; +}; + +&pvt { + status = "okay"; +}; + +&efuse { + status = "okay"; +}; + +&pcie { + clocks = <&ccu_sys CCU_SYS_APB_CLK>, + <&ccu_axi CCU_AXI_PCIE_M_CLK>, + <&ccu_axi CCU_AXI_PCIE_S_CLK>, + <&clk_gen 2>; + clock-names = "dbi", "mstr", "slv", "ref"; + + reset-gpios = <&port0 12 GPIO_ACTIVE_LOW>; + + status = "okay"; + + /* Silicon Motion SM750 VGA */ + video@0,0 { + comptible = "pci126f,0750", "pciclass,030000"; + reg = <0 0 0 0 0>; + + clocks = <&clk_gen 1>; + clock-names = "ref"; + + memory-region = <&framebuffer>; + memory-region-names = "framebuffer"; + }; +}; + +&sram { + status = "okay"; +}; + +&dma { + status = "okay"; +}; + +&mc { + status = "okay"; +}; + +&mc_phy { + status = "okay"; +}; + +&sata { + clocks = <&ccu_sys CCU_SYS_APB_CLK>, + <&ccu_axi CCU_AXI_SATA_CLK>, + <&clk_gen 4>; + clock-names = "pclk", "aclk", "ref"; + + status = "okay"; +}; + +&sata0 { + /* Connected to InnoDisk DENSD-16GD06 (16GB SSD) */ + status = "okay"; +}; + +&sata1 { + hba-port-cap = ; + + status = "okay"; +}; + +&hwa { + status = "okay"; +}; + +&gmac0 { + mac-address = [ 00 26 58 78 10 00 ]; + + phy-handle = <&gmac0_phy>; + managed = "in-band-status"; + + status = "okay"; +}; + +&gmac0_mdio { + reset-gpios = <&gmac0 1 GPIO_ACTIVE_LOW>; + reset-delay-us = <11000>; + reset-post-delay-us = <31000>; + + /* Realtek RTL8211E */ + gmac0_phy: ethernet-phy@1 { + compatible = "ethernet-phy-id001c.c915"; + reg = <0x1>; + + interrupt-parent = <&gmac0>; + interrupts = <0 IRQ_TYPE_EDGE_FALLING>; + + clocks = <&gmac0_phy_clk>; + clock-names = "xtal"; + }; +}; + +&usb { + reset-gpios = <&port0 13 GPIO_ACTIVE_LOW>; + + status = "okay"; + + ulpi { + /* Microchip USB3315 */ + phy { + clocks = <&clk_gen 3>; + clock-names = "ref"; + + reset-gpios = <&port0 13 GPIO_ACTIVE_LOW>; + }; + }; + + /* Microchip USB2517 */ + hub@0 { + compatible = "usb424,2517"; + #address-cells = <1>; + #size-cells = <0>; + reg = <0>; + + clocks = <&clk_gen 3>; + clock-names = "xtal"; + + /* Realtek ALC4040-CG */ + audio@0 { + compatible = "usbbda,4040"; + reg = <0>; + }; + + /* The rest of the ports are USB type-A */ + }; +}; diff --git a/arch/mips/bt1/Kconfig b/arch/mips/bt1/Kconfig index cc30c5b32ddac..203639122e16b 100644 --- a/arch/mips/bt1/Kconfig +++ b/arch/mips/bt1/Kconfig @@ -109,6 +109,15 @@ config BT1_DTB_EM416 interfaces connected to the edge fingers and destined to simplify the Baikal-T1 SoC based critical environment platforms development. +config BT1_DTB_TF306 + bool "Edelweiss TF306" + help + This option provides a dtb for the Edelweiss TF306 mini-ITX board. + It is based on the Baikal-T1 SoC designed to be of mITX form-factor + with DDR3 SoDIMM connector, Silicon Motion SM750 graphic chip, + ALC4040 HD audio and high-speed interfaces like SATA, USB2.0, + 1Gbit Ethernet, microSD. + endchoice menu "Baikal-T1 Errata" diff --git a/arch/mips/configs/tf306_defconfig b/arch/mips/configs/tf306_defconfig new file mode 100644 index 0000000000000..fbaecb128ec73 --- /dev/null +++ b/arch/mips/configs/tf306_defconfig @@ -0,0 +1,450 @@ +CONFIG_LOCALVERSION="-bt1" +CONFIG_DEFAULT_HOSTNAME="baikal" +CONFIG_SYSVIPC=y +CONFIG_POSIX_MQUEUE=y +CONFIG_NO_HZ_IDLE=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_CLOCKSOURCE_WATCHDOG_MAX_SKEW_US=100 +CONFIG_BPF_SYSCALL=y +CONFIG_BPF_JIT=y +CONFIG_BPF_JIT_ALWAYS_ON=y +CONFIG_BPF_PRELOAD=y +CONFIG_BPF_PRELOAD_UMD=y +CONFIG_PREEMPT=y +CONFIG_BSD_PROCESS_ACCT=y +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_CGROUPS=y +CONFIG_MEMCG=y +CONFIG_BLK_CGROUP=y +CONFIG_CGROUP_SCHED=y +CONFIG_CFS_BANDWIDTH=y +CONFIG_CGROUP_PIDS=y +CONFIG_CGROUP_FREEZER=y +CONFIG_CPUSETS=y +CONFIG_CGROUP_DEVICE=y +CONFIG_CGROUP_CPUACCT=y +CONFIG_CGROUP_PERF=y +CONFIG_CGROUP_BPF=y +CONFIG_CGROUP_MISC=y +CONFIG_NAMESPACES=y +CONFIG_USER_NS=y +CONFIG_CHECKPOINT_RESTORE=y +CONFIG_RELAY=y +CONFIG_BLK_DEV_INITRD=y +CONFIG_CMDLINE_LOG_WRAP_IDEAL_LEN=100 +CONFIG_SYSFS_SYSCALL=y +CONFIG_EXPERT=y +CONFIG_PROFILING=y +CONFIG_MIPS_BAIKAL_T1=y +CONFIG_BT1_CPU_FEATURE_OVERRIDES=y +CONFIG_BT1_DTB_TF306=y +CONFIG_CPU_P5600=y +CONFIG_CPU_MIPS32_R5_FEATURES=y +CONFIG_CPU_MIPS32_R5_XPA=y +CONFIG_ZBOOT_LOAD_ADDRESS=0x85100000 +CONFIG_ARCH_FORCE_MAX_ORDER=11 +CONFIG_CPU_HAS_MSA=y +CONFIG_NR_CPUS=2 +CONFIG_IEEE754_DEFAULT_RELAXED=y +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_STAT=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y +CONFIG_CPUFREQ_DT=y +CONFIG_PAGE_SIZE_16KB=y +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MODULE_UNLOAD_TAINT_TRACKING=y +CONFIG_MODVERSIONS=y +CONFIG_MODULE_SRCVERSION_ALL=y +# CONFIG_BLOCK_LEGACY_AUTOLOAD is not set +CONFIG_BLK_DEV_BSGLIB=y +# CONFIG_IOSCHED_BFQ is not set +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_PACKET_DIAG=y +CONFIG_UNIX=y +CONFIG_UNIX_DIAG=y +CONFIG_XFRM_USER=y +CONFIG_NET_KEY=y +CONFIG_XDP_SOCKETS=y +CONFIG_XDP_SOCKETS_DIAG=y +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +CONFIG_IP_PNP=y +CONFIG_INET_AH=y +CONFIG_INET_ESP=y +CONFIG_INET_IPCOMP=y +CONFIG_INET_UDP_DIAG=y +CONFIG_INET_RAW_DIAG=y +CONFIG_TCP_CONG_ADVANCED=y +CONFIG_TCP_CONG_BIC=y +CONFIG_TCP_CONG_WESTWOOD=y +CONFIG_TCP_CONG_HTCP=y +CONFIG_TCP_CONG_HSTCP=y +CONFIG_TCP_CONG_BBR=y +CONFIG_INET6_AH=y +CONFIG_INET6_ESP=y +CONFIG_INET6_IPCOMP=y +CONFIG_MPTCP=y +CONFIG_NETWORK_PHY_TIMESTAMPING=y +CONFIG_NETFILTER=y +CONFIG_NF_CONNTRACK=y +CONFIG_NF_CONNTRACK_PROCFS=y +CONFIG_NF_TABLES=y +CONFIG_NF_TABLES_INET=y +CONFIG_NF_TABLES_NETDEV=y +CONFIG_NFT_LOG=y +CONFIG_NFT_LIMIT=y +CONFIG_NFT_MASQ=y +CONFIG_NFT_REDIR=y +CONFIG_NFT_NAT=y +CONFIG_NFT_REJECT=y +CONFIG_NFT_HASH=y +CONFIG_NETFILTER_XT_CONNMARK=y +CONFIG_NETFILTER_XT_TARGET_CLASSIFY=y +CONFIG_NETFILTER_XT_TARGET_MARK=y +CONFIG_NETFILTER_XT_NAT=y +CONFIG_NETFILTER_XT_TARGET_NETMAP=y +CONFIG_NETFILTER_XT_TARGET_NFLOG=y +CONFIG_NETFILTER_XT_TARGET_NFQUEUE=y +CONFIG_NETFILTER_XT_TARGET_REDIRECT=y +CONFIG_NETFILTER_XT_TARGET_MASQUERADE=y +CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=y +CONFIG_NETFILTER_XT_MATCH_CGROUP=y +CONFIG_NETFILTER_XT_MATCH_CPU=y +CONFIG_NETFILTER_XT_MATCH_IPRANGE=y +CONFIG_NETFILTER_XT_MATCH_LENGTH=y +CONFIG_NETFILTER_XT_MATCH_LIMIT=y +CONFIG_NETFILTER_XT_MATCH_MAC=y +CONFIG_NETFILTER_XT_MATCH_MULTIPORT=y +CONFIG_NETFILTER_XT_MATCH_OWNER=y +CONFIG_NETFILTER_XT_MATCH_POLICY=y +CONFIG_NETFILTER_XT_MATCH_PKTTYPE=y +CONFIG_NETFILTER_XT_MATCH_RECENT=y +CONFIG_NETFILTER_XT_MATCH_SOCKET=y +CONFIG_NETFILTER_XT_MATCH_STATE=y +CONFIG_NETFILTER_XT_MATCH_TCPMSS=y +CONFIG_NFT_DUP_IPV4=y +CONFIG_NF_TABLES_ARP=y +CONFIG_IP_NF_IPTABLES=y +CONFIG_IP_NF_MATCH_AH=y +CONFIG_IP_NF_MATCH_ECN=y +CONFIG_IP_NF_MATCH_TTL=y +CONFIG_IP_NF_TARGET_SYNPROXY=y +CONFIG_NFT_DUP_IPV6=y +CONFIG_IP6_NF_IPTABLES=y +CONFIG_IP6_NF_MATCH_AH=y +CONFIG_IP6_NF_MATCH_EUI64=y +CONFIG_IP6_NF_MATCH_FRAG=y +CONFIG_IP6_NF_MATCH_OPTS=y +CONFIG_IP6_NF_MATCH_HL=y +CONFIG_IP6_NF_MATCH_IPV6HEADER=y +CONFIG_IP6_NF_MATCH_MH=y +CONFIG_IP6_NF_MATCH_RT=y +CONFIG_BRIDGE=y +CONFIG_BRIDGE_VLAN_FILTERING=y +CONFIG_VLAN_8021Q=y +CONFIG_NET_SCHED=y +CONFIG_NET_SCH_HTB=y +CONFIG_NET_SCH_PRIO=y +CONFIG_NET_SCH_MULTIQ=y +CONFIG_NET_SCH_SFQ=y +CONFIG_NET_SCH_CBS=y +CONFIG_NET_SCH_ETF=y +CONFIG_NET_SCH_TAPRIO=y +CONFIG_NET_SCH_MQPRIO=y +CONFIG_NET_SCH_SKBPRIO=y +CONFIG_NET_SCH_QFQ=y +CONFIG_NET_SCH_FQ=y +CONFIG_NET_SCH_INGRESS=y +CONFIG_NET_SCH_DEFAULT=y +CONFIG_NET_CLS_BASIC=y +CONFIG_NET_CLS_U32=y +CONFIG_CLS_U32_PERF=y +CONFIG_CLS_U32_MARK=y +CONFIG_NET_CLS_FLOW=y +CONFIG_NET_CLS_BPF=y +CONFIG_NET_CLS_FLOWER=y +CONFIG_NET_CLS_ACT=y +CONFIG_NET_ACT_GACT=y +CONFIG_NET_ACT_MIRRED=y +CONFIG_NET_ACT_PEDIT=y +CONFIG_NET_ACT_SIMP=y +CONFIG_NET_ACT_SKBEDIT=y +CONFIG_NET_ACT_VLAN=y +CONFIG_NET_ACT_BPF=y +CONFIG_NET_ACT_SKBMOD=y +CONFIG_DCB=y +CONFIG_DNS_RESOLVER=y +CONFIG_NETLINK_DIAG=y +CONFIG_CGROUP_NET_PRIO=y +CONFIG_CGROUP_NET_CLASSID=y +CONFIG_BPF_STREAM_PARSER=y +CONFIG_NET_PKTGEN=y +# CONFIG_WIRELESS is not set +CONFIG_PAGE_POOL_STATS=y +CONFIG_PCI=y +CONFIG_PCIEPORTBUS=y +CONFIG_HOTPLUG_PCI_PCIE=y +CONFIG_PCIEAER=y +CONFIG_PCIEAER_INJECT=y +CONFIG_PCIE_ECRC=y +CONFIG_PCIEASPM_PERFORMANCE=y +CONFIG_PCI_MSI=y +CONFIG_PCIE_BUS_PERFORMANCE=y +CONFIG_HOTPLUG_PCI=y +CONFIG_PCIE_BT1=y +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +CONFIG_DEBUG_DRIVER=y +CONFIG_DEBUG_DEVRES=y +CONFIG_BT1_APB=y +CONFIG_BT1_AXI=y +CONFIG_MIPS_CDMM=y +CONFIG_MTD=y +CONFIG_MTD_PARTITIONED_MASTER=y +CONFIG_MTD_ROM=y +CONFIG_MTD_PHYSMAP=y +CONFIG_MTD_PHYSMAP_OF=y +CONFIG_MTD_PHYSMAP_BT1_ROM=y +CONFIG_MTD_SPI_NOR=y +# CONFIG_MTD_SPI_NOR_USE_4K_SECTORS is not set +CONFIG_MTD_UBI=y +CONFIG_MTD_UBI_BLOCK=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_LOOP_MIN_COUNT=4 +CONFIG_BLK_DEV_NBD=y +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_COUNT=2 +CONFIG_BLK_DEV_RAM_SIZE=32768 +CONFIG_BLK_DEV_NVME=y +CONFIG_NVME_MULTIPATH=y +CONFIG_NVME_VERBOSE_ERRORS=y +CONFIG_NVME_HWMON=y +CONFIG_SRAM=y +CONFIG_EEPROM_AT24=y +CONFIG_RAID_ATTRS=y +CONFIG_BLK_DEV_SD=y +CONFIG_CHR_DEV_SG=y +CONFIG_SCSI_CONSTANTS=y +CONFIG_SCSI_LOGGING=y +CONFIG_SCSI_SCAN_ASYNC=y +CONFIG_ATA=y +CONFIG_SATA_AHCI_PLATFORM=y +CONFIG_AHCI_DWC=y +# CONFIG_ATA_SFF is not set +CONFIG_NETDEVICES=y +# CONFIG_NET_VENDOR_3COM is not set +# CONFIG_NET_VENDOR_ADAPTEC is not set +# CONFIG_NET_VENDOR_AGERE is not set +# CONFIG_NET_VENDOR_ALACRITECH is not set +# CONFIG_NET_VENDOR_ALTEON is not set +# CONFIG_NET_VENDOR_AMAZON is not set +# CONFIG_NET_VENDOR_AQUANTIA is not set +# CONFIG_NET_VENDOR_ARC is not set +# CONFIG_NET_VENDOR_ASIX is not set +# CONFIG_NET_VENDOR_ATHEROS is not set +# CONFIG_NET_VENDOR_BROADCOM is not set +# CONFIG_NET_VENDOR_CADENCE is not set +# CONFIG_NET_VENDOR_CAVIUM is not set +# CONFIG_NET_VENDOR_CHELSIO is not set +# CONFIG_NET_VENDOR_CISCO is not set +# CONFIG_NET_VENDOR_CORTINA is not set +# CONFIG_NET_VENDOR_DAVICOM is not set +# CONFIG_NET_VENDOR_DEC is not set +# CONFIG_NET_VENDOR_DLINK is not set +# CONFIG_NET_VENDOR_EMULEX is not set +# CONFIG_NET_VENDOR_ENGLEDER is not set +# CONFIG_NET_VENDOR_EZCHIP is not set +# CONFIG_NET_VENDOR_FUNGIBLE is not set +# CONFIG_NET_VENDOR_GOOGLE is not set +# CONFIG_NET_VENDOR_HISILICON is not set +# CONFIG_NET_VENDOR_HUAWEI is not set +# CONFIG_NET_VENDOR_INTEL is not set +# CONFIG_NET_VENDOR_ADI is not set +# CONFIG_NET_VENDOR_MARVELL is not set +# CONFIG_NET_VENDOR_MELLANOX is not set +# CONFIG_NET_VENDOR_META is not set +# CONFIG_NET_VENDOR_MICREL is not set +# CONFIG_NET_VENDOR_MICROCHIP is not set +# CONFIG_NET_VENDOR_MICROSEMI is not set +# CONFIG_NET_VENDOR_MICROSOFT is not set +# CONFIG_NET_VENDOR_MUCSE is not set +# CONFIG_NET_VENDOR_MYRI is not set +# CONFIG_NET_VENDOR_NI is not set +# CONFIG_NET_VENDOR_NATSEMI is not set +# CONFIG_NET_VENDOR_NETRONOME is not set +# CONFIG_NET_VENDOR_NVIDIA is not set +# CONFIG_NET_VENDOR_OKI is not set +# CONFIG_NET_VENDOR_PACKET_ENGINES is not set +# CONFIG_NET_VENDOR_PENSANDO is not set +# CONFIG_NET_VENDOR_QLOGIC is not set +# CONFIG_NET_VENDOR_BROCADE is not set +# CONFIG_NET_VENDOR_QUALCOMM is not set +# CONFIG_NET_VENDOR_RDC is not set +# CONFIG_NET_VENDOR_REALTEK is not set +# CONFIG_NET_VENDOR_RENESAS is not set +# CONFIG_NET_VENDOR_ROCKER is not set +# CONFIG_NET_VENDOR_SAMSUNG is not set +# CONFIG_NET_VENDOR_SEEQ is not set +# CONFIG_NET_VENDOR_SILAN is not set +# CONFIG_NET_VENDOR_SIS is not set +# CONFIG_NET_VENDOR_SOLARFLARE is not set +# CONFIG_NET_VENDOR_SMSC is not set +# CONFIG_NET_VENDOR_SOCIONEXT is not set +CONFIG_STMMAC_ETH=y +CONFIG_STMMAC_SELFTESTS=y +CONFIG_DWMAC_BT1=y +# CONFIG_NET_VENDOR_SUN is not set +# CONFIG_NET_VENDOR_SYNOPSYS is not set +# CONFIG_TEHUTI is not set +# CONFIG_NET_VENDOR_TI is not set +# CONFIG_NET_VENDOR_TOSHIBA is not set +# CONFIG_NET_VENDOR_VERTEXCOM is not set +# CONFIG_NET_VENDOR_VIA is not set +# CONFIG_NET_VENDOR_WANGXUN is not set +# CONFIG_NET_VENDOR_WIZNET is not set +# CONFIG_NET_VENDOR_XILINX is not set +CONFIG_REALTEK_PHY=y +# CONFIG_USB_NET_DRIVERS is not set +# CONFIG_WLAN is not set +# CONFIG_INPUT_KEYBOARD is not set +# CONFIG_INPUT_MOUSE is not set +# CONFIG_SERIO is not set +# CONFIG_VT_CONSOLE is not set +CONFIG_VT_HW_CONSOLE_BINDING=y +CONFIG_LEGACY_PTY_COUNT=16 +CONFIG_SERIAL_8250=y +CONFIG_SERIAL_8250_CONSOLE=y +CONFIG_SERIAL_8250_NR_UARTS=2 +CONFIG_SERIAL_8250_RUNTIME_UARTS=2 +CONFIG_SERIAL_8250_EXTENDED=y +CONFIG_SERIAL_8250_DW=y +# CONFIG_SERIAL_8250_PERICOM is not set +CONFIG_MIPS_EJTAG_FDC_TTY=y +# CONFIG_HW_RANDOM is not set +CONFIG_I2C=y +CONFIG_I2C_CHARDEV=y +CONFIG_I2C_DESIGNWARE_CORE=y +CONFIG_I2C_SLAVE=y +CONFIG_I2C_SLAVE_EEPROM=y +CONFIG_SPI=y +CONFIG_SPI_DESIGNWARE=y +CONFIG_SPI_DW_DMA=y +CONFIG_SPI_DW_MMIO=y +CONFIG_SPI_DW_BT1=y +CONFIG_SPI_SPIDEV=y +CONFIG_GPIO_SYSFS=y +CONFIG_GPIO_DWAPB=y +CONFIG_GPIO_PCF857X=y +CONFIG_POWER_RESET=y +CONFIG_POWER_RESET_SYSCON=y +CONFIG_SYSCON_REBOOT_MODE=y +CONFIG_SENSORS_BT1_PVT=y +CONFIG_SENSORS_BT1_PVT_ALARMS=y +CONFIG_THERMAL=y +CONFIG_THERMAL_STATISTICS=y +CONFIG_THERMAL_GOV_FAIR_SHARE=y +CONFIG_THERMAL_GOV_USER_SPACE=y +CONFIG_CPU_THERMAL=y +CONFIG_WATCHDOG=y +CONFIG_WATCHDOG_SYSFS=y +CONFIG_WATCHDOG_PRETIMEOUT_GOV=y +CONFIG_WATCHDOG_PRETIMEOUT_DEFAULT_GOV_NOOP=y +CONFIG_DW_WATCHDOG=y +CONFIG_SOUND=y +CONFIG_SND=y +CONFIG_SND_HRTIMER=y +# CONFIG_SND_PCI is not set +# CONFIG_SND_SPI is not set +# CONFIG_SND_MIPS is not set +CONFIG_SND_USB_AUDIO=y +# CONFIG_HID_SUPPORT is not set +CONFIG_USB_ULPI_BUS=y +CONFIG_USB=y +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +CONFIG_USB_MON=y +CONFIG_USB_XHCI_HCD=y +CONFIG_USB_STORAGE=y +CONFIG_USB_UAS=y +CONFIG_USB_DWC3=y +CONFIG_USB_DWC3_ULPI=y +# CONFIG_USB_DWC3_GENERIC_PLAT is not set +CONFIG_USB_HUB_USB251XB=y +CONFIG_MMC=y +CONFIG_MMC_SPI=y +CONFIG_NEW_LEDS=y +CONFIG_LEDS_CLASS=y +CONFIG_LEDS_GPIO=y +CONFIG_EDAC=y +CONFIG_EDAC_DEBUG=y +CONFIG_EDAC_SYNOPSYS=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_PCF2127=y +CONFIG_DMADEVICES=y +CONFIG_DW_DMAC=y +CONFIG_DW_EDMA=y +CONFIG_SYNC_FILE=y +# CONFIG_VIRTIO_MENU is not set +# CONFIG_VHOST_ENABLE_FORK_OWNER_CONTROL is not set +CONFIG_COMMON_CLK_VC5=y +# CONFIG_IOMMU_SUPPORT is not set +CONFIG_MEMORY=y +CONFIG_BT1_L2_CTL=y +CONFIG_NVMEM_U_BOOT_ENV=y +CONFIG_EXT4_FS=y +CONFIG_EXT4_FS_POSIX_ACL=y +CONFIG_FANOTIFY=y +CONFIG_FUSE_FS=y +# CONFIG_FUSE_IO_URING is not set +CONFIG_OVERLAY_FS=y +CONFIG_MSDOS_FS=y +CONFIG_VFAT_FS=y +CONFIG_NTFS3_FS=y +CONFIG_PROC_KCORE=y +CONFIG_TMPFS=y +CONFIG_TMPFS_POSIX_ACL=y +CONFIG_JFFS2_FS=y +CONFIG_JFFS2_FS_WBUF_VERIFY=y +CONFIG_UBIFS_FS=y +CONFIG_SQUASHFS=y +CONFIG_SQUASHFS_LZ4=y +CONFIG_SQUASHFS_LZO=y +CONFIG_SQUASHFS_XZ=y +CONFIG_SQUASHFS_4K_DEVBLK_SIZE=y +CONFIG_NFS_FS=y +CONFIG_NFS_V2=y +CONFIG_NFS_V4=y +CONFIG_NFS_V4_0=y +CONFIG_NFS_V4_2=y +CONFIG_ROOT_NFS=y +CONFIG_NFS_USE_LEGACY_DNS=y +# CONFIG_NFS_V4_2_READ_PLUS is not set +# CONFIG_RPCSEC_GSS_KRB5 is not set +CONFIG_NLS_CODEPAGE_437=y +CONFIG_NLS_CODEPAGE_866=y +CONFIG_NLS_CODEPAGE_1251=y +CONFIG_NLS_ASCII=y +CONFIG_NLS_ISO8859_1=y +CONFIG_NLS_ISO8859_5=y +CONFIG_NLS_KOI8_R=y +CONFIG_NLS_UTF8=y +CONFIG_INIT_STACK_NONE=y +CONFIG_CRYPTO_USER=y +CONFIG_CRYPTO_NULL=y +CONFIG_CRYPTO_BLOWFISH=y +CONFIG_CRYPTO_CCM=y +CONFIG_CRYPTO_XCBC=y +CONFIG_CRYPTO_CRC32C=y +CONFIG_CRYPTO_CRC32=y +CONFIG_CRYPTO_USER_API_HASH=y +# CONFIG_CRYPTO_HW is not set +CONFIG_PRINTK_TIME=y +CONFIG_DEBUG_SECTION_MISMATCH=y +CONFIG_SCHEDSTATS=y +# CONFIG_EARLY_PRINTK is not set +# CONFIG_RUNTIME_TESTING_MENU is not set -- cgit v1.2.3 From 122195410703b97ffc179d0ddc5335dffeef3c88 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 22 Nov 2023 20:04:52 +0300 Subject: MAINTAINERS: Return MIPS Baikal-T1 platform entry The MIPS Baikal-T1 platform drivers have been and will be supported by the original maintainer. Get back the respective entry to the MAINTAINERS file. Signed-off-by: Serge Semin --- MAINTAINERS | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index d1cc0e12fe1f0..fe4b936fee0dd 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17677,6 +17677,17 @@ F: drivers/platform/mips/ F: include/dt-bindings/mips/ F: include/linux/platform_data/pic32.h +MIPS BAIKAL-T1 PLATFORM +M: Serge Semin +L: linux-mips@vger.kernel.org +S: Supported +F: Documentation/devicetree/bindings/bus/baikal,bt1-*.yaml +F: Documentation/devicetree/bindings/clock/baikal,bt1-*.yaml +F: drivers/bus/bt1-*.c +F: drivers/clk/baikal-t1/ +F: drivers/memory/bt1-l2-ctl.c +F: drivers/mtd/maps/physmap-bt1-rom.[ch] + MIPS BOSTON DEVELOPMENT BOARD M: Paul Burton L: linux-mips@vger.kernel.org -- cgit v1.2.3 From 70a6fc9e175bc27375114c06b923370f5f7a2408 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 22 Nov 2023 15:45:43 +0300 Subject: MAINTAINERS: Add arch-files for MIPS Baikal-T1 platform node All the Baikal-T1 SoC platform code has just been merged in. Thus let's add the respective source files to the MIPS Baikal-T1 platform code node of the MAINTAINERS list. Signed-off-by: Serge Semin --- MAINTAINERS | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index fe4b936fee0dd..8b63425dcdd72 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17683,6 +17683,11 @@ L: linux-mips@vger.kernel.org S: Supported F: Documentation/devicetree/bindings/bus/baikal,bt1-*.yaml F: Documentation/devicetree/bindings/clock/baikal,bt1-*.yaml +F: Documentation/devicetree/bindings/memory-controllers/baikal,bt1-ddrc.yaml +F: Documentation/devicetree/bindings/mfd/baikal,bt1-*.yaml +F: arch/mips/baikal-t1/ +F: arch/mips/boot/dts/baikal-t1/ +F: arch/mips/include/asm/mach-baikal-t1/ F: drivers/bus/bt1-*.c F: drivers/clk/baikal-t1/ F: drivers/memory/bt1-l2-ctl.c -- cgit v1.2.3 From fe41813d0c365f155ecbba9eb873b92b13509312 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 4 Aug 2025 20:00:20 +0300 Subject: net: stmmac: dwmac1000: Fix hangup on Jabber timeout Despite of what the network subsystem documentation says it's possible to have the upper layer passing SKBs with total length greater than MTU, for instance, in the framework of the pktgen module. Currently the driver will try to send as long frame as it was passed by the network core. The driver will submit the passed SKB fragments via separate descriptors so the total frame length may even exceed 16K limit. As a result DW GMAC will send out the truncated frame and respond with the Tx jabber timeout interrupt. The problem is that that will also result in the Tx DMA-interface halt [1]: "When the Jabber Timeout occurs, the transmission process is aborted and placed in the Stopped state. This causes the Transmit Jabber Timeout TDES0[14] flag to assert." Currently the driver doesn't handle that situation at all. Yes, the DW GMAC DMA interrupt handler interprets the DMA_STATUS.TPS flag state as Tx-hard-error. But first of all that IRQ is masked so it won't cause IRQ being raised. Even if it's unmasked it won't solve the problem, since sometimes the Tx DMA-engine just won't get to the STOP state but being stuck with the "Running; Waiting for status" status (presumably due to Tx DMA-engine being configured to operate-on-the-second-frame or due to some another IP-core misconfiguration). Moreover considering the situation as the "tx_hard_error" and calling the already implemented handler will fail to reset the current Tx DMA-descriptors head pointer of the controller. As a result the situation will look as if the Tx procedure got hangup. The hangup-to-look happens also if the Tx DMA-interface stop event (caused by the Jabber timeout) is missed. The only solution that actually fixes the problem is to just resume the stopped Tx DMA-engine. It will restore the Tx procedure where it has been stopped. The respective DMA-descriptor will have the Jabber timeout error status so the Tx-DMA-descriptors cleanup procedure will be able to handle the error and increment the Jabber timeout statistics counter. Let's implement this solution then. Note the described problem is specific to the DW GMAC v3.73a and earlier. Neither DW QoS Ether nor DW XGMAC/XLGAMC are affected since they don't stop the Tx DMA-interfaces on the Jabber timeout error. [1] DesignWare Cores Ethernet MAC Universal Databook, Revision 3.73a, October 2013, p. 389. Fixes: 47dd7a540b8a ("net: add support for STMicroelectronics Ethernet controllers.") Fixes: 803fc61df261 ("net: stmmac: dwmac-loongson: Add Loongson Multi-channels GMAC support") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/common.h | 1 + .../net/ethernet/stmicro/stmmac/dwmac-loongson.c | 14 +++++--- drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h | 3 +- drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c | 10 +++--- drivers/net/ethernet/stmicro/stmmac/stmmac.h | 1 + drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 38 ++++++++++++++++++++++ 6 files changed, 57 insertions(+), 10 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/common.h b/drivers/net/ethernet/stmicro/stmmac/common.h index 16c311d4c5879..a137bc89cb379 100644 --- a/drivers/net/ethernet/stmicro/stmmac/common.h +++ b/drivers/net/ethernet/stmicro/stmmac/common.h @@ -380,6 +380,7 @@ enum dma_irq_status { io_fatal_error = 0x4, tx_hard_error = 0x8, tx_hard_error_bump_tc = 0x10, + tx_soft_stop = 0x80, }; enum dma_irq_dir { diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-loongson.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-loongson.c index 8dd45b1134141..0d7f9f412af57 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-loongson.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-loongson.c @@ -30,7 +30,8 @@ #define DMA_INTR_ABNORMAL_LOONGSON (DMA_INTR_ENA_AIE_TX_LOONGSON | \ DMA_INTR_ENA_AIE_RX_LOONGSON | \ - DMA_INTR_ENA_FBE | DMA_INTR_ENA_UNE) + DMA_INTR_ENA_FBE | DMA_INTR_ENA_UNE | \ + DMA_INTR_ENA_TJE) #define DMA_INTR_DEFAULT_MASK_LOONGSON (DMA_INTR_NORMAL_LOONGSON | \ DMA_INTR_ABNORMAL_LOONGSON) @@ -249,8 +250,12 @@ static int loongson_dwmac_dma_interrupt(struct stmmac_priv *priv, ret = tx_hard_error_bump_tc; x->tx_undeflow_irq++; } - if (unlikely(intr_status & DMA_STATUS_TJT)) + + if (unlikely(intr_status & DMA_STATUS_TJT)) { x->tx_jabber_irq++; + ret = tx_soft_stop; + } + if (unlikely(intr_status & DMA_STATUS_OVF)) x->rx_overflow_irq++; if (unlikely(intr_status & DMA_STATUS_RU)) @@ -261,10 +266,9 @@ static int loongson_dwmac_dma_interrupt(struct stmmac_priv *priv, x->rx_watchdog_irq++; if (unlikely(intr_status & DMA_STATUS_ETI)) x->tx_early_irq++; - if (unlikely(intr_status & DMA_STATUS_TPS)) { + if (unlikely(intr_status & DMA_STATUS_TPS)) x->tx_process_stopped_irq++; - ret = tx_hard_error; - } + if (unlikely(fb_intr_status)) { x->fatal_bus_error_irq++; ret = io_fatal_error; diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h b/drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h index e1c37ac2c99d2..6d6de05d909d0 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h @@ -84,9 +84,10 @@ #define DMA_INTR_ENA_AIE 0x00008000 /* Abnormal Summary */ #define DMA_INTR_ENA_FBE 0x00002000 /* Fatal Bus Error */ #define DMA_INTR_ENA_UNE 0x00000020 /* Tx Underflow */ +#define DMA_INTR_ENA_TJE 0x00000008 /* Transmit Jabber */ #define DMA_INTR_ABNORMAL (DMA_INTR_ENA_AIE | DMA_INTR_ENA_FBE | \ - DMA_INTR_ENA_UNE) + DMA_INTR_ENA_UNE | DMA_INTR_ENA_TJE) /* DMA default interrupt mask */ #define DMA_INTR_DEFAULT_MASK (DMA_INTR_NORMAL | DMA_INTR_ABNORMAL) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c b/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c index 4153546bf9fd8..cce67679fbb37 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c @@ -184,8 +184,11 @@ int dwmac_dma_interrupt(struct stmmac_priv *priv, void __iomem *ioaddr, ret = tx_hard_error_bump_tc; x->tx_undeflow_irq++; } - if (unlikely(intr_status & DMA_STATUS_TJT)) + + if (unlikely(intr_status & DMA_STATUS_TJT)) { x->tx_jabber_irq++; + ret = tx_soft_stop; + } if (unlikely(intr_status & DMA_STATUS_OVF)) x->rx_overflow_irq++; @@ -198,10 +201,9 @@ int dwmac_dma_interrupt(struct stmmac_priv *priv, void __iomem *ioaddr, x->rx_watchdog_irq++; if (unlikely(intr_status & DMA_STATUS_ETI)) x->tx_early_irq++; - if (unlikely(intr_status & DMA_STATUS_TPS)) { + if (unlikely(intr_status & DMA_STATUS_TPS)) x->tx_process_stopped_irq++; - ret = tx_hard_error; - } + if (unlikely(intr_status & DMA_STATUS_FBI)) { x->fatal_bus_error_irq++; ret = io_fatal_error; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h index 9216cb7c1b689..a5444edd10949 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h @@ -44,6 +44,7 @@ struct stmmac_resources { enum stmmac_state { STMMAC_STATE_RESET, STMMAC_STATE_RESET_TX, + STMMAC_STATE_RESUME_TX, STMMAC_STATE_COUNT }; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index f710dae188cca..528c66d01136e 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -365,6 +365,19 @@ static void stmmac_handle_tx_err(struct stmmac_priv *priv, u32 queue) stmmac_sch_state(priv, queue, RESET_TX); } +/** + * stmmac_handle_tx_stop - Handle Tx soft stop + * @priv: driver private structure + * @queue: TX queue index + * Description: + * Handle Tx queue stop event raised on some IP-cores for instance due + * to minor inconsistencies, like Jabber timeout. + */ +static void stmmac_handle_tx_stop(struct stmmac_priv *priv, u32 queue) +{ + stmmac_sch_state(priv, queue, RESUME_TX); +} + static void print_pkt(unsigned char *buf, int len) { pr_debug("len = %d byte, buf addr: 0x%p\n", len, buf); @@ -3161,6 +3174,8 @@ static void stmmac_dma_interrupt(struct stmmac_priv *priv) } if (unlikely(status[chan] & tx_hard_error)) stmmac_handle_tx_err(priv, chan); + if (unlikely(status[chan] & tx_soft_stop)) + stmmac_handle_tx_stop(priv, chan); } } @@ -6354,6 +6369,8 @@ static irqreturn_t stmmac_msi_intr_tx(int irq, void *data) } if (unlikely(status & tx_hard_error)) stmmac_handle_tx_err(priv, chan); + if (unlikely(status & tx_soft_stop)) + stmmac_handle_tx_stop(priv, chan); return IRQ_HANDLED; } @@ -7444,6 +7461,24 @@ static void stmmac_reset_tx_subtask(struct stmmac_priv *priv, u32 queue) __netif_tx_unlock_bh(nq); } +/** + * stmmac_resume_tx_subtask - Tx DMA-engine resume subtask + * @priv: driver private structure + * Description: + * Resume Tx-queue. Normally it's caused by some minor internal errors + * like Jabber timeout. The respective frame might have been corrupted and + * dropped, but the Tx procedure can be safely resumed. + */ +static void stmmac_resume_tx_subtask(struct stmmac_priv *priv, u32 queue) +{ + /* Skip if controller is inactive (nothing to resume) */ + if (!netif_running(priv->dev)) + return; + + /* Start the Tx DMA back */ + stmmac_start_tx_dma(priv, queue); +} + /** * stmmac_state_work - MAC state-change handler * @work: worker structure @@ -7464,6 +7499,9 @@ static void stmmac_state_work(struct work_struct *work) if (stmmac_clr_state(ch, RESET_TX)) stmmac_reset_tx_subtask(priv, queue); + + if (stmmac_clr_state(ch, RESUME_TX)) + stmmac_resume_tx_subtask(priv, queue); } if (stmmac_clr_state(priv, RESET)) -- cgit v1.2.3 From e7b68822464f9988c6f09cb804905d0b26ee59c6 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 8 Aug 2025 22:50:27 +0300 Subject: net: stmmac: dwxgmac2: Fix fatal bus error missing Even though the Fatal Bus Error is handled in the DMA IRQ ISR it isn't enabled making it missing should one happen. Let's enable the FBE IRQ to make sure it won't be and so the error wouldn't correctly handled on the occasion. Fixes: d6ddfacd95c7 ("net: stmmac: Add DMA related callbacks for XGMAC2") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h index a1327b19fce5d..a35cc9f668877 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h @@ -398,12 +398,13 @@ #define XGMAC_DMA_CH_INT_EN(x) (0x00003138 + (0x80 * (x))) #define XGMAC_NIE BIT(15) #define XGMAC_AIE BIT(14) +#define XGMAC_FBEE BIT(12) #define XGMAC_RBUE BIT(7) #define XGMAC_RIE BIT(6) #define XGMAC_TBUE BIT(2) #define XGMAC_TIE BIT(0) -#define XGMAC_DMA_INT_DEFAULT_EN (XGMAC_NIE | XGMAC_AIE | XGMAC_RBUE | \ - XGMAC_RIE | XGMAC_TIE) +#define XGMAC_DMA_INT_DEFAULT_EN (XGMAC_NIE | XGMAC_AIE | XGMAC_FBEE | \ + XGMAC_RBUE | XGMAC_RIE | XGMAC_TIE) #define XGMAC_DMA_INT_DEFAULT_RX (XGMAC_RBUE | XGMAC_RIE) #define XGMAC_DMA_INT_DEFAULT_TX (XGMAC_TIE) #define XGMAC_DMA_CH_Rx_WATCHDOG(x) (0x0000313c + (0x80 * (x))) -- cgit v1.2.3 From 8fe0930f27a12e2048958af4554350e51e52e34b Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 6 Aug 2025 15:53:56 +0300 Subject: net: stmmac: Fix XDP ZC malfunction after reset/resume Currently the tx_hard_error status handler and PM-resume procedure imply the Tx buffers being cleared. The xsk_buff_pool instance pointer is cleared with that too (see for instance the dma_free_tx_skbufs() method). This basically causes the interface not working anymore since having the stmmac_tx_queue::xsk_pool field nullified leads to disabled NAPI handlers being utilized (rx_napi/tx_napi instead of rxtx_napi). There might be worse consequences of that up to even sudden crash. Let's fix the problem by dropping the xsk_pool pointers nullification on cleanups. So any interface setups after minor events (like hard-error or PM-suspend/resume) won't require a full Tx/Rx desc rings re-initialization. As a result this change brings the next protagonist/antagonist dependencies: stmmac_xmit()/stmmac_xmit_xdp_zc() -> dma_free_tx_skbufs() stmmac_alloc_rx_buffers()//stmmac_refill() -> dma_free_rx_skbufs() stmmac_alloc_rx_buffers_zc()/stmmac_refill_zc() -> dma_free_rx_xskbufs() Fixes: bba2556efad6 ("net: stmmac: Enable RX via AF_XDP zero-copy") Fixes: 132c32ee5bc0 ("net: stmmac: Add TX via XDP zero-copy socket") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 30 +++++++++++++++-------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 528c66d01136e..ef0d1a9696f7c 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -1768,6 +1768,10 @@ static void stmmac_free_tx_buffer(struct stmmac_priv *priv, * @priv: private structure * @dma_conf: structure to take the dma data * @queue: RX queue index + * Description: Revert what has been added to the descriptors in the + * framework of the stmmac_alloc_rx_buffers()//stmmac_refill() methods. + * Basically it means to unmap the respective buffers and get them back + * to the pages pool. */ static void dma_free_rx_skbufs(struct stmmac_priv *priv, struct stmmac_dma_conf *dma_conf, @@ -1779,6 +1783,8 @@ static void dma_free_rx_skbufs(struct stmmac_priv *priv, for (i = 0; i < dma_conf->dma_rx_size; i++) stmmac_free_rx_buffer(priv, rx_q, i); + rx_q->buf_alloc_num = 0; + if (rx_q->state_saved) dev_kfree_skb(rx_q->state.skb); rx_q->state_saved = false; @@ -1816,6 +1822,10 @@ static int stmmac_alloc_rx_buffers(struct stmmac_priv *priv, * @priv: private structure * @dma_conf: structure to take the dma data * @queue: RX queue index + * Description: Revert what has been added to the descriptors in the + * framework of the stmmac_alloc_rx_buffers_zc()/stmmac_refill_zc() + * methods. Basically it means to unmap the respective buffers and get + * them back to the XDP buff pool. */ static void dma_free_rx_xskbufs(struct stmmac_priv *priv, struct stmmac_dma_conf *dma_conf, @@ -1833,6 +1843,8 @@ static void dma_free_rx_xskbufs(struct stmmac_priv *priv, xsk_buff_free(buf->xdp); buf->xdp = NULL; } + + rx_q->buf_alloc_num = 0; } static int stmmac_alloc_rx_buffers_zc(struct stmmac_priv *priv, @@ -1905,6 +1917,7 @@ static int __init_dma_rx_desc_rings(struct stmmac_priv *priv, xdp_rxq_info_unreg_mem_model(&rx_q->xdp_rxq); + /* This must be performed on each SKB/XSK mode switch */ rx_q->xsk_pool = stmmac_get_xsk_pool(priv, queue); if (rx_q->xsk_pool) { @@ -1980,9 +1993,6 @@ err_init_rx_buffers: else dma_free_rx_skbufs(priv, dma_conf, queue); - rx_q->buf_alloc_num = 0; - rx_q->xsk_pool = NULL; - queue--; } @@ -2021,6 +2031,7 @@ static int __init_dma_tx_desc_rings(struct stmmac_priv *priv, dma_conf->dma_tx_size, 0); } + /* This must be performed on each SKB/XSK mode switch */ tx_q->xsk_pool = stmmac_get_xsk_pool(priv, queue); for (i = 0; i < dma_conf->dma_tx_size; i++) { @@ -2095,6 +2106,9 @@ static int init_dma_desc_rings(struct net_device *dev, * @priv: private structure * @dma_conf: structure to take the dma data * @queue: TX queue index + * Description: Revert what has been added to the descriptors in the + * framework of the stmmac_xmit()/stmmac_xmit_xdp_zc() methods work. + * Basically it means to unmap/free the respective buffers. */ static void dma_free_tx_skbufs(struct stmmac_priv *priv, struct stmmac_dma_conf *dma_conf, @@ -2108,11 +2122,10 @@ static void dma_free_tx_skbufs(struct stmmac_priv *priv, for (i = 0; i < dma_conf->dma_tx_size; i++) stmmac_free_tx_buffer(priv, dma_conf, queue, i); - if (tx_q->xsk_pool && tx_q->xsk_frames_done) { + if (tx_q->xsk_pool && tx_q->xsk_frames_done) xsk_tx_completed(tx_q->xsk_pool, tx_q->xsk_frames_done); - tx_q->xsk_frames_done = 0; - tx_q->xsk_pool = NULL; - } + + tx_q->xsk_frames_done = 0; } /** @@ -2146,9 +2159,6 @@ static void __free_dma_rx_desc_resources(struct stmmac_priv *priv, else dma_free_rx_skbufs(priv, dma_conf, queue); - rx_q->buf_alloc_num = 0; - rx_q->xsk_pool = NULL; - /* Free DMA regions of consistent memory previously allocated */ if (!priv->extend_desc) dma_free_coherent(priv->device, dma_conf->dma_rx_size * -- cgit v1.2.3 From 3618d790308d01b7a6a5160616ef81049d724d38 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 6 Aug 2025 20:48:17 +0300 Subject: net: stmmac: Add Rx hard error handling support DW QoS Ether (GMAC4/GMAC5) and DW XGMAC/XGLMAC are able to survive after the Fatal Bus errors with no major consequences. Their databooks claim that such errors cause the respective DMA-engine to stop and it can be safely started again after re-initializing the descriptors list: "The application can either perform a reset to DWC_ether_qos or re-initialize the DMA descriptor list and start again. The rest of the DMA channels are not affected by such errors." [1, 2] That is proofed to be correct on practice. The problem is that currently the driver resets the Tx DMA-engine only, which of course is pointless should the error happens on the Rx path. Let's fix that by adding the Rx hard error handling support in a similar way as it's done for the Tx hard errors. Note even though the TPS status causes the respective DMA-engine to stop it doesn't happens by itself but due to problems indicated by the another statuses (like DMA-engine stop command issued by the driver itself). So it shouldn't be handled as hard error since it may lead to a false-negative event raised. But it still worth collecting the status statistics. So is the RPS status wise. [1] DesignWare Cores Ethernet Quality-of-Service Databook, Revision 5.20a, April 2020, pp. 92. [2] DesignWare Cores XGMAC - 10G Ethernet MAC Databook, Revision 3.20a, September 2022, p.77. Fixes: 48863ce5940f ("stmmac: add DMA support for GMAC 4.xx") Fixes: d6ddfacd95c7 ("net: stmmac: Add DMA related callbacks for XGMAC2") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/common.h | 5 +- drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.h | 4 +- drivers/net/ethernet/stmicro/stmmac/dwmac4_lib.c | 10 ++- drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h | 9 ++- drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c | 13 +++- drivers/net/ethernet/stmicro/stmmac/stmmac.h | 1 + drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 91 ++++++++++++++++++++++ 7 files changed, 120 insertions(+), 13 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/common.h b/drivers/net/ethernet/stmicro/stmmac/common.h index a137bc89cb379..60e8a6e7a617a 100644 --- a/drivers/net/ethernet/stmicro/stmmac/common.h +++ b/drivers/net/ethernet/stmicro/stmmac/common.h @@ -378,8 +378,9 @@ enum dma_irq_status { handle_rx = 0x1, handle_tx = 0x2, io_fatal_error = 0x4, - tx_hard_error = 0x8, - tx_hard_error_bump_tc = 0x10, + rx_hard_error = 0x8, + tx_hard_error = 0x10, + tx_hard_error_bump_tc = 0x20, tx_soft_stop = 0x80, }; diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.h b/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.h index f7105a2b786f9..166ac17a42f7d 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.h @@ -162,6 +162,7 @@ static inline u32 dma_chanx_base_addr(const struct dwmac4_addrs *addrs, /* Interrupt status per channel */ #define DMA_CHAN_STATUS_REB GENMASK(21, 19) +#define DMA_CHAN_STATUS_TEB GENMASK(18, 16) #define DMA_CHAN_STATUS_NIS BIT(15) #define DMA_CHAN_STATUS_AIS BIT(14) #define DMA_CHAN_STATUS_CDE BIT(13) @@ -189,7 +190,8 @@ static inline u32 dma_chanx_base_addr(const struct dwmac4_addrs *addrs, DMA_CHAN_STATUS_RI | \ DMA_CHAN_STATUS_MSK_COMMON) -#define DMA_CHAN_STATUS_MSK_TX (DMA_CHAN_STATUS_ETI | \ +#define DMA_CHAN_STATUS_MSK_TX (DMA_CHAN_STATUS_TEB | \ + DMA_CHAN_STATUS_ETI | \ DMA_CHAN_STATUS_TBU | \ DMA_CHAN_STATUS_TPS | \ DMA_CHAN_STATUS_TI | \ diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_lib.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_lib.c index c098047a3bff8..0527ad1799c7b 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_lib.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_lib.c @@ -187,13 +187,15 @@ int dwmac4_dma_interrupt(struct stmmac_priv *priv, void __iomem *ioaddr, x->rx_process_stopped_irq++; if (unlikely(intr_status & DMA_CHAN_STATUS_ETI)) x->tx_early_irq++; - if (unlikely(intr_status & DMA_CHAN_STATUS_TPS)) { + if (unlikely(intr_status & DMA_CHAN_STATUS_TPS)) x->tx_process_stopped_irq++; - ret = tx_hard_error; - } + if (unlikely(intr_status & DMA_CHAN_STATUS_FBE)) { x->fatal_bus_error_irq++; - ret = tx_hard_error; + if (intr_status & DMA_CHAN_STATUS_REB) + ret = rx_hard_error; + if (intr_status & DMA_CHAN_STATUS_TEB) + ret = tx_hard_error; } } diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h index a35cc9f668877..88954be64f454 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h @@ -410,9 +410,12 @@ #define XGMAC_DMA_CH_Rx_WATCHDOG(x) (0x0000313c + (0x80 * (x))) #define XGMAC_RWT GENMASK(7, 0) #define XGMAC_DMA_CH_STATUS(x) (0x00003160 + (0x80 * (x))) +#define XGMAC_REB GENMASK(21, 19) +#define XGMAC_TEB GENMASK(18, 16) #define XGMAC_NIS BIT(15) #define XGMAC_AIS BIT(14) #define XGMAC_FBE BIT(12) +#define XGMAC_RPS BIT(8) #define XGMAC_RBU BIT(7) #define XGMAC_RI BIT(6) #define XGMAC_TBU BIT(2) @@ -421,9 +424,11 @@ #define XGMAC_REGSIZE ((0x0000317c + (0x80 * 15)) / 4) #define XGMAC_DMA_STATUS_MSK_COMMON (XGMAC_NIS | XGMAC_AIS | XGMAC_FBE) -#define XGMAC_DMA_STATUS_MSK_RX (XGMAC_RBU | XGMAC_RI | \ +#define XGMAC_DMA_STATUS_MSK_RX (XGMAC_REB | XGMAC_RBU | \ + XGMAC_RPS | XGMAC_RI | \ XGMAC_DMA_STATUS_MSK_COMMON) -#define XGMAC_DMA_STATUS_MSK_TX (XGMAC_TBU | XGMAC_TPS | XGMAC_TI | \ +#define XGMAC_DMA_STATUS_MSK_TX (XGMAC_TEB | XGMAC_TBU | \ + XGMAC_TPS | XGMAC_TI | \ XGMAC_DMA_STATUS_MSK_COMMON) /* Descriptors */ diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c index 1770751917eb5..696eba7d3a125 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c @@ -344,13 +344,18 @@ static int dwxgmac2_dma_interrupt(struct stmmac_priv *priv, x->rx_buf_unav_irq++; ret |= handle_rx; } - if (unlikely(intr_status & XGMAC_TPS)) { + + if (unlikely(intr_status & XGMAC_RPS)) + x->rx_process_stopped_irq++; + if (unlikely(intr_status & XGMAC_TPS)) x->tx_process_stopped_irq++; - ret |= tx_hard_error; - } + if (unlikely(intr_status & XGMAC_FBE)) { x->fatal_bus_error_irq++; - ret |= tx_hard_error; + if (intr_status & XGMAC_REB) + ret |= rx_hard_error; + if (intr_status & XGMAC_TEB) + ret |= tx_hard_error; } } diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h index a5444edd10949..5c2da89e2019d 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h @@ -43,6 +43,7 @@ struct stmmac_resources { enum stmmac_state { STMMAC_STATE_RESET, + STMMAC_STATE_RESET_RX, STMMAC_STATE_RESET_TX, STMMAC_STATE_RESUME_TX, STMMAC_STATE_COUNT diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index ef0d1a9696f7c..9bd98476e861d 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -352,6 +352,19 @@ static void stmmac_handle_fatal_err(struct stmmac_priv *priv) stmmac_srv_state(priv, RESET); } +/** + * stmmac_handle_rx_err - Handle Rx queue error + * @priv: driver private structure + * @queue: RX queue index + * Description: + * Handle recoverable Rx queue error by updating the channel state and + * invoking the service task. + */ +static void stmmac_handle_rx_err(struct stmmac_priv *priv, u32 queue) +{ + stmmac_sch_state(priv, queue, RESET_RX); +} + /** * stmmac_handle_tx_err - Handle Tx queue error * @priv: driver private structure @@ -3182,6 +3195,8 @@ static void stmmac_dma_interrupt(struct stmmac_priv *priv) /* Try to bump up the dma threshold on this failure */ stmmac_bump_dma_threshold(priv, chan); } + if (unlikely(status[chan] & rx_hard_error)) + stmmac_handle_rx_err(priv, chan); if (unlikely(status[chan] & tx_hard_error)) stmmac_handle_tx_err(priv, chan); if (unlikely(status[chan] & tx_soft_stop)) @@ -6402,6 +6417,8 @@ static irqreturn_t stmmac_msi_intr_rx(int irq, void *data) status = stmmac_napi_check(priv, chan, DMA_DIR_RX); if (unlikely(status & io_fatal_error)) stmmac_handle_fatal_err(priv); + if (unlikely(status & rx_hard_error)) + stmmac_handle_rx_err(priv, chan); return IRQ_HANDLED; } @@ -7423,6 +7440,77 @@ static void stmmac_reset_subtask(struct stmmac_priv *priv) dev_open(priv->dev, NULL); } +/** + * stmmac_reset_rx_subtask - Rx DMA-engine reset subtask + * @priv: driver private structure + * @queue: RX queue index + * Description: + * Perform Rx-queue reset procedure. Normally it's caused by an upstream + * DMA-bus error response. In anyway the DMA is implied to be stopped + * due to that and the affected DMA-descriptor is closed with an error status. + */ +static void stmmac_reset_rx_subtask(struct stmmac_priv *priv, u32 queue) +{ + struct stmmac_rx_queue *rx_q; + struct napi_struct *napi; + int ret; + + /* Skip if controller is inactive (queue will be reset on open anyway) */ + if (!netif_running(priv->dev)) + return; + + netdev_err(priv->dev, "Reset Rx queue %u\n", queue); + + rx_q = &priv->dma_conf.rx_queue[queue]; + napi = rx_q->xsk_pool ? &priv->channel[queue].rxtx_napi : + &priv->channel[queue].rx_napi; + + /* Make sure no receive procedure along the way */ + napi_disable(napi); + + /* Rx DMA must be stopped for sure to proceed */ + stmmac_stop_rx_dma(priv, queue); + + /* Free Rx SKBs and data buffers */ + if (rx_q->xsk_pool) + dma_free_rx_xskbufs(priv, &priv->dma_conf, queue); + else + dma_free_rx_skbufs(priv, &priv->dma_conf, queue); + + /* Clean up all the Tx descriptor and reallocate the buffers */ + stmmac_clear_rx_descriptors(priv, &priv->dma_conf, queue); + + if (rx_q->xsk_pool) { + stmmac_alloc_rx_buffers_zc(priv, &priv->dma_conf, queue); + } else { + ret = stmmac_alloc_rx_buffers(priv, &priv->dma_conf, queue, GFP_KERNEL); + if (ret) { + netdev_err(priv->dev, "Failed to alloc Rx queue %u buffers\n", + queue); + return; + } + } + + /* Reset the desc ring head and tail addresses */ + stmmac_reset_rx_queue(priv, queue); + + stmmac_init_rx_chan(priv, priv->ioaddr, priv->plat->dma_cfg, + rx_q->dma_rx_phy, queue); + + rx_q->rx_tail_addr = rx_q->dma_rx_phy + + (rx_q->buf_alloc_num * + sizeof(struct dma_desc)); + stmmac_set_rx_tail_ptr(priv, priv->ioaddr, + rx_q->rx_tail_addr, queue); + + /* Start the Rx DMA back */ + stmmac_start_rx_dma(priv, queue); + + priv->xstats.rx_errors++; + + napi_enable(napi); +} + /** * stmmac_reset_tx_subtask - Tx DMA-engine reset subtask * @priv: driver private structure @@ -7507,6 +7595,9 @@ static void stmmac_state_work(struct work_struct *work) for (queue = 0; queue < maxq; queue++) { struct stmmac_channel *ch = &priv->channel[queue]; + if (stmmac_clr_state(ch, RESET_RX)) + stmmac_reset_rx_subtask(priv, queue); + if (stmmac_clr_state(ch, RESET_TX)) stmmac_reset_tx_subtask(priv, queue); -- cgit v1.2.3 From 08481939bba24ff25fd22b4f659c072893477e43 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 7 Aug 2025 23:44:00 +0300 Subject: net: stmmac: Fix MTL FIFO threshold correction procedure Tx and Rx FIFO threshold is a parameter that defines the level at when Tx/Rx is proceeded in the Cut-through DMA-mode, which is utilized when the MAC is synthesized with a small FIFO to fit in any normal frame. In opposite to Store-and-forward mode it implies to start transferring a frame as long as the FIFO level reaches the threshold. But currently the whole implementation is full with inconsistencies and even bugs. First of all there is a race condition with the DMA on/off methods (on DW GMAC) and Flush Tx FIFO procedures (on all IP-cores). So it affects at the very least the stmmac_{start,stop}_{rx,tx}_dma() methods called in the errors handlers. Secondly the threshold correction procedure is highly unscalable and buggy. In case if the Tx FIFO underflow happens a static variable "tc" will be increased and utilized as a new threshold on the affected channel only. If a similar problem happens on another channel, then the threshold will be raised further even though the problem on the affected channel has been caused by initial threshold value. Thirdly there is no threshold correction procedure implemented for the Rx FIFO. In the meantime it's possible to have too slow Rx DMA-interface too to keep up with the incoming data frames so catch the FIFO overflow event thus having the received frame corrupted. Fourthly Tx/Rx MTL FIFO size isn't taken into account on the threshold correction, which is potentially buggy at least on the initial frames exchange. Having threshold level to be greater or equal the FIFO depth will cause the FIFO overflow error at least on the Rx path. The last but not least the current implementation will also cause difficulties to add the NETIF_F_RXALL feature since it is configured on the same CSR as the RTC/TTC parameters. So let's fix all the problems above at once by converting the MTL FIFO threshold level procedure to be execute in the framework of the a single state-change worker and under the rtnl-lock. This will solve the race-related issues described above. In addition provide the Rx FIFO threshold correction procedure and make sure the TTC/RTC levels are queue-specific. Fixes: 47dd7a540b8a ("net: add support for STMicroelectronics Ethernet controllers.") Fixes: 803fc61df261 ("net: stmmac: dwmac-loongson: Add Loongson Multi-channels GMAC support") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/common.h | 4 +- .../net/ethernet/stmicro/stmmac/dwmac-loongson.c | 9 +- drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h | 4 +- drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c | 6 +- drivers/net/ethernet/stmicro/stmmac/stmmac.h | 4 + .../net/ethernet/stmicro/stmmac/stmmac_ethtool.c | 1 - drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 234 ++++++++++++++------- 7 files changed, 182 insertions(+), 80 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/common.h b/drivers/net/ethernet/stmicro/stmmac/common.h index 60e8a6e7a617a..45d068daded91 100644 --- a/drivers/net/ethernet/stmicro/stmmac/common.h +++ b/drivers/net/ethernet/stmicro/stmmac/common.h @@ -158,7 +158,6 @@ struct stmmac_extra_stats { unsigned long fatal_bus_error_irq; /* Tx/Rx IRQ Events */ unsigned long rx_early_irq; - unsigned long threshold; unsigned long irq_receive_pmt_irq_n; /* MMC info */ unsigned long mmc_tx_irq_n; @@ -380,7 +379,8 @@ enum dma_irq_status { io_fatal_error = 0x4, rx_hard_error = 0x8, tx_hard_error = 0x10, - tx_hard_error_bump_tc = 0x20, + rx_ovf_error = 0x20, + tx_unf_error = 0x40, tx_soft_stop = 0x80, }; diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-loongson.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-loongson.c index 0d7f9f412af57..02d716f9fcb97 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-loongson.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-loongson.c @@ -31,7 +31,7 @@ #define DMA_INTR_ABNORMAL_LOONGSON (DMA_INTR_ENA_AIE_TX_LOONGSON | \ DMA_INTR_ENA_AIE_RX_LOONGSON | \ DMA_INTR_ENA_FBE | DMA_INTR_ENA_UNE | \ - DMA_INTR_ENA_TJE) + DMA_INTR_ENA_OVE | DMA_INTR_ENA_TJE) #define DMA_INTR_DEFAULT_MASK_LOONGSON (DMA_INTR_NORMAL_LOONGSON | \ DMA_INTR_ABNORMAL_LOONGSON) @@ -247,8 +247,8 @@ static int loongson_dwmac_dma_interrupt(struct stmmac_priv *priv, /* ABNORMAL interrupts */ if (unlikely(abnor_intr_status)) { if (unlikely(intr_status & DMA_STATUS_UNF)) { - ret = tx_hard_error_bump_tc; x->tx_undeflow_irq++; + ret = tx_unf_error; } if (unlikely(intr_status & DMA_STATUS_TJT)) { @@ -256,8 +256,11 @@ static int loongson_dwmac_dma_interrupt(struct stmmac_priv *priv, ret = tx_soft_stop; } - if (unlikely(intr_status & DMA_STATUS_OVF)) + if (unlikely(intr_status & DMA_STATUS_OVF)) { x->rx_overflow_irq++; + ret = rx_ovf_error; + } + if (unlikely(intr_status & DMA_STATUS_RU)) x->rx_buf_unav_irq++; if (unlikely(intr_status & DMA_STATUS_RPS)) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h b/drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h index 6d6de05d909d0..151e38450f609 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h @@ -84,10 +84,12 @@ #define DMA_INTR_ENA_AIE 0x00008000 /* Abnormal Summary */ #define DMA_INTR_ENA_FBE 0x00002000 /* Fatal Bus Error */ #define DMA_INTR_ENA_UNE 0x00000020 /* Tx Underflow */ +#define DMA_INTR_ENA_OVE 0x00000010 /* Receive Overflow */ #define DMA_INTR_ENA_TJE 0x00000008 /* Transmit Jabber */ #define DMA_INTR_ABNORMAL (DMA_INTR_ENA_AIE | DMA_INTR_ENA_FBE | \ - DMA_INTR_ENA_UNE | DMA_INTR_ENA_TJE) + DMA_INTR_ENA_UNE | DMA_INTR_ENA_OVE | \ + DMA_INTR_ENA_TJE) /* DMA default interrupt mask */ #define DMA_INTR_DEFAULT_MASK (DMA_INTR_NORMAL | DMA_INTR_ABNORMAL) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c b/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c index cce67679fbb37..eea6456b70ce6 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c @@ -181,8 +181,8 @@ int dwmac_dma_interrupt(struct stmmac_priv *priv, void __iomem *ioaddr, /* ABNORMAL interrupts */ if (unlikely(intr_status & DMA_STATUS_AIS)) { if (unlikely(intr_status & DMA_STATUS_UNF)) { - ret = tx_hard_error_bump_tc; x->tx_undeflow_irq++; + ret = tx_unf_error; } if (unlikely(intr_status & DMA_STATUS_TJT)) { @@ -190,8 +190,10 @@ int dwmac_dma_interrupt(struct stmmac_priv *priv, void __iomem *ioaddr, ret = tx_soft_stop; } - if (unlikely(intr_status & DMA_STATUS_OVF)) + if (unlikely(intr_status & DMA_STATUS_OVF)) { x->rx_overflow_irq++; + ret = rx_ovf_error; + } if (unlikely(intr_status & DMA_STATUS_RU)) x->rx_buf_unav_irq++; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h index 5c2da89e2019d..323de4fd5c71f 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h @@ -45,6 +45,8 @@ enum stmmac_state { STMMAC_STATE_RESET, STMMAC_STATE_RESET_RX, STMMAC_STATE_RESET_TX, + STMMAC_STATE_DROP_RTC, + STMMAC_STATE_BUMP_TTC, STMMAC_STATE_RESUME_TX, STMMAC_STATE_COUNT }; @@ -118,6 +120,7 @@ struct stmmac_tx_queue { dma_addr_t dma_tx_phy; dma_addr_t tx_tail_addr; u32 mss; + u16 ttc; }; struct stmmac_rx_buffer { @@ -173,6 +176,7 @@ struct stmmac_rx_queue { unsigned int napi_skb_frag_size; dma_addr_t dma_rx_phy; u32 rx_tail_addr; + u16 rtc; unsigned int state_saved; struct { struct sk_buff *skb; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c index cc04fc62e7017..b37a4d58da903 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c @@ -89,7 +89,6 @@ static const struct stmmac_stats stmmac_gstrings_stats[] = { STMMAC_STAT(fatal_bus_error_irq), /* Tx/Rx IRQ Events */ STMMAC_STAT(rx_early_irq), - STMMAC_STAT(threshold), STMMAC_STAT(irq_receive_pmt_irq_n), /* MMC info */ STMMAC_STAT(mmc_tx_irq_n), diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 9bd98476e861d..0d23189c0cb7e 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -100,10 +100,13 @@ static int pause = PAUSE_TIME; module_param(pause, int, 0644); MODULE_PARM_DESC(pause, "Flow Control Pause Time (units of 512 bit times)"); -#define TC_DEFAULT 64 -static int tc = TC_DEFAULT; -module_param(tc, int, 0644); -MODULE_PARM_DESC(tc, "DMA threshold control value"); +static int rtc = 128; +module_param(rtc, int, 0644); +MODULE_PARM_DESC(rtc, "DMA Rx threshold control value"); + +static int ttc = 32; +module_param(ttc, int, 0644); +MODULE_PARM_DESC(tc, "DMA Tx threshold control value"); #define DEFAULT_BUFSIZE 1536U static unsigned int buf_sz = DEFAULT_BUFSIZE; @@ -156,8 +159,6 @@ static void stmmac_reset_tx_queue(struct stmmac_priv *priv, u32 queue); static void stmmac_reset_queues_param(struct stmmac_priv *priv); static void stmmac_tx_timer_arm(struct stmmac_priv *priv, u32 queue); static void stmmac_flush_tx_descriptors(struct stmmac_priv *priv, int queue); -static void stmmac_set_dma_operation_mode(struct stmmac_priv *priv, u32 txmode, - u32 rxmode, u32 chan); static void stmmac_vlan_restore(struct stmmac_priv *priv); #ifdef CONFIG_DEBUG_FS @@ -378,6 +379,33 @@ static void stmmac_handle_tx_err(struct stmmac_priv *priv, u32 queue) stmmac_sch_state(priv, queue, RESET_TX); } +/** + * stmmac_handle_rx_ovf_error - Handle Rx FIFO overflow error + * @priv: driver private structure + * @queue: RX queue index + * Description: + * Handle Rx overflow event raised on the IP-cores for instance due + * to too slow DMA-interface so it doesn't keep up with the upcoming + * frame. + */ +static void stmmac_handle_rx_ovf_error(struct stmmac_priv *priv, u32 queue) +{ + stmmac_sch_state(priv, queue, DROP_RTC); +} + +/** + * stmmac_handle_tx_unf_error - Handle Tx FIFO underflow error + * @priv: driver private structure + * @queue: TX queue index + * Description: + * Handle Tx underrun event raised on some IP-cores for instance due + * to too slow DMA-interface comparing to the link speed. + */ +static void stmmac_handle_tx_unf_error(struct stmmac_priv *priv, u32 queue) +{ + stmmac_sch_state(priv, queue, BUMP_TTC); +} + /** * stmmac_handle_tx_stop - Handle Tx soft stop * @priv: driver private structure @@ -2652,8 +2680,8 @@ static void stmmac_dma_operation_mode(struct stmmac_priv *priv) } if (priv->plat->force_thresh_dma_mode) { - txmode = tc; - rxmode = tc; + txmode = ttc; + rxmode = rtc; } else if (priv->plat->force_sf_dma_mode || priv->plat->tx_coe) { /* * In case of GMAC, SF mode can be enabled @@ -2664,9 +2692,8 @@ static void stmmac_dma_operation_mode(struct stmmac_priv *priv) */ txmode = SF_DMA_MODE; rxmode = SF_DMA_MODE; - priv->xstats.threshold = SF_DMA_MODE; } else { - txmode = tc; + txmode = ttc; rxmode = SF_DMA_MODE; } @@ -2676,6 +2703,7 @@ static void stmmac_dma_operation_mode(struct stmmac_priv *priv) u32 buf_size; qmode = priv->plat->rx_queues_cfg[chan].mode_to_use; + rx_q->rtc = rxmode; stmmac_dma_rx_mode(priv, priv->ioaddr, rxmode, chan, rxfifosz, qmode); @@ -2693,7 +2721,10 @@ static void stmmac_dma_operation_mode(struct stmmac_priv *priv) } for (chan = 0; chan < tx_channels_count; chan++) { + struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[chan]; + qmode = priv->plat->tx_queues_cfg[chan].mode_to_use; + tx_q->ttc = txmode; stmmac_dma_tx_mode(priv, priv->ioaddr, txmode, chan, txfifosz, qmode); @@ -2872,21 +2903,6 @@ static bool stmmac_xdp_xmit_zc(struct stmmac_priv *priv, u32 queue, u32 budget) return !!budget && work_done; } -static void stmmac_bump_dma_threshold(struct stmmac_priv *priv, u32 chan) -{ - if (unlikely(priv->xstats.threshold != SF_DMA_MODE) && tc <= 256) { - tc += 64; - - if (priv->plat->force_thresh_dma_mode) - stmmac_set_dma_operation_mode(priv, tc, tc, chan); - else - stmmac_set_dma_operation_mode(priv, tc, SF_DMA_MODE, - chan); - - priv->xstats.threshold = tc; - } -} - /** * stmmac_tx_clean - to manage the transmission completion * @priv: driver private structure @@ -2959,7 +2975,7 @@ static int stmmac_tx_clean(struct stmmac_priv *priv, int budget, u32 queue, if (unlikely(status & tx_err)) { tx_errors++; if (unlikely(status & tx_err_bump_tc)) - stmmac_bump_dma_threshold(priv, queue); + stmmac_handle_tx_unf_error(priv, queue); } else { tx_packets++; } @@ -3082,39 +3098,6 @@ static int stmmac_tx_clean(struct stmmac_priv *priv, int budget, u32 queue, return max(count, xmits); } -/** - * stmmac_set_dma_operation_mode - Set DMA operation mode by channel - * @priv: driver private structure - * @txmode: TX operating mode - * @rxmode: RX operating mode - * @chan: channel index - * Description: it is used for configuring of the DMA operation mode in - * runtime in order to program the tx/rx DMA thresholds or Store-And-Forward - * mode. - */ -static void stmmac_set_dma_operation_mode(struct stmmac_priv *priv, u32 txmode, - u32 rxmode, u32 chan) -{ - u8 rxqmode = priv->plat->rx_queues_cfg[chan].mode_to_use; - u8 txqmode = priv->plat->tx_queues_cfg[chan].mode_to_use; - u32 rx_channels_count = priv->plat->rx_queues_to_use; - u32 tx_channels_count = priv->plat->tx_queues_to_use; - int rxfifosz = priv->plat->rx_fifo_size; - int txfifosz = priv->plat->tx_fifo_size; - - if (rxfifosz == 0) - rxfifosz = priv->dma_cap.rx_fifo_size; - if (txfifosz == 0) - txfifosz = priv->dma_cap.tx_fifo_size; - - /* Adjust for real per queue fifo size */ - rxfifosz /= rx_channels_count; - txfifosz /= tx_channels_count; - - stmmac_dma_rx_mode(priv, priv->ioaddr, rxmode, chan, rxfifosz, rxqmode); - stmmac_dma_tx_mode(priv, priv->ioaddr, txmode, chan, txfifosz, txqmode); -} - static bool stmmac_safety_feat_interrupt(struct stmmac_priv *priv) { int ret; @@ -3191,14 +3174,14 @@ static void stmmac_dma_interrupt(struct stmmac_priv *priv) for (chan = 0; chan < channels_to_check; chan++) { if (unlikely(status[chan] & io_fatal_error)) stmmac_handle_fatal_err(priv); - if (unlikely(status[chan] & tx_hard_error_bump_tc)) { - /* Try to bump up the dma threshold on this failure */ - stmmac_bump_dma_threshold(priv, chan); - } if (unlikely(status[chan] & rx_hard_error)) stmmac_handle_rx_err(priv, chan); if (unlikely(status[chan] & tx_hard_error)) stmmac_handle_tx_err(priv, chan); + if (unlikely(status[chan] & rx_ovf_error)) + stmmac_handle_rx_ovf_error(priv, chan); + if (unlikely(status[chan] & tx_unf_error)) + stmmac_handle_tx_unf_error(priv, chan); if (unlikely(status[chan] & tx_soft_stop)) stmmac_handle_tx_stop(priv, chan); } @@ -6388,12 +6371,10 @@ static irqreturn_t stmmac_msi_intr_tx(int irq, void *data) status = stmmac_napi_check(priv, chan, DMA_DIR_TX); if (unlikely(status & io_fatal_error)) stmmac_handle_fatal_err(priv); - if (unlikely(status & tx_hard_error_bump_tc)) { - /* Try to bump up the dma threshold on this failure */ - stmmac_bump_dma_threshold(priv, chan); - } if (unlikely(status & tx_hard_error)) stmmac_handle_tx_err(priv, chan); + if (unlikely(status & tx_unf_error)) + stmmac_handle_tx_unf_error(priv, chan); if (unlikely(status & tx_soft_stop)) stmmac_handle_tx_stop(priv, chan); @@ -6419,6 +6400,8 @@ static irqreturn_t stmmac_msi_intr_rx(int irq, void *data) stmmac_handle_fatal_err(priv); if (unlikely(status & rx_hard_error)) stmmac_handle_rx_err(priv, chan); + if (unlikely(status & rx_ovf_error)) + stmmac_handle_rx_ovf_error(priv, chan); return IRQ_HANDLED; } @@ -7559,6 +7542,108 @@ static void stmmac_reset_tx_subtask(struct stmmac_priv *priv, u32 queue) __netif_tx_unlock_bh(nq); } +/** + * stmmac_drop_rtc_subtask - Rx DMA-threshold reduction subtask + * @priv: driver private structure + * @queue: TX queue index + * Description: + * Perform Rx-queue DMA-threshold decrease procedure. Normally it's caused + * by the Rx FIFO overflow due to too slow DMA-interface so one can't keep up + * with the upcoming traffic. It's applicable for the DMA threshold mode + * only (in opposite to the store-and-forward mode). + */ +static void stmmac_drop_rtc_subtask(struct stmmac_priv *priv, u32 queue) +{ + int rxfifosz = priv->plat->rx_fifo_size; + struct stmmac_rx_queue *rx_q; + u8 rxqmode; + + rx_q = &priv->dma_conf.rx_queue[queue]; + + if (rx_q->rtc == SF_DMA_MODE) { + netdev_err(priv->dev, "Unexpected drop RTC event on Q%u\n", + queue); + return; + } + + if (rxfifosz == 0) + rxfifosz = priv->dma_cap.rx_fifo_size; + + if (dwmac_is_xmac(priv->plat->core_type)) + rxfifosz /= priv->plat->rx_queues_to_use; + + /* Lower limits: DW GMAC/GMAC4 - 32, DW XGMAC/XLGMAC - 64 */ + if (rx_q->rtc <= 32) { + netdev_err_once(priv->dev, "Rx TC reached limit %u on Q%u\n", + rx_q->rtc, queue); + return; + } else if (rx_q->rtc > rxfifosz) { + rx_q->rtc = rxfifosz; + } + + rx_q->rtc -= 32; + + netdev_warn(priv->dev, "DMA Rx threshold reduced to %u on Q%u\n", + rx_q->rtc, queue); + + rxqmode = priv->plat->rx_queues_cfg[queue].mode_to_use; + + stmmac_dma_rx_mode(priv, priv->ioaddr, rx_q->rtc, queue, rxfifosz, rxqmode); +} + +/** + * stmmac_bump_ttc_subtask - Tx DMA-threshold increase subtask + * @priv: driver private structure + * @queue: TX queue index + * Description: + * Perform Tx-queue DMA-threshold increase procedure. Normally it's caused + * by the Tx FIFO underrun due to too slow DMA-interface so one can't keep up + * with the outgoing traffic. Obviously it's applicable for the DMA threshold + * mode only (in opposite to the store-and-forward mode). + */ +static void stmmac_bump_ttc_subtask(struct stmmac_priv *priv, u32 queue) +{ + int txfifosz = priv->plat->tx_fifo_size; + struct stmmac_tx_queue *tx_q; + u8 txqmode, addend; + + tx_q = &priv->dma_conf.tx_queue[queue]; + + if (tx_q->ttc == SF_DMA_MODE) { + netdev_err(priv->dev, "Unexpected bump TTC event on Q%u\n", + queue); + return; + } + + if (txfifosz == 0) + txfifosz = priv->dma_cap.tx_fifo_size; + + if (dwmac_is_xmac(priv->plat->core_type)) + txfifosz /= priv->plat->tx_queues_to_use; + + /* 64-bytes step threshold: DW GMAC - 64, DW QoS/XGMAC/etc - 128. + * 128-bytes step threshold: DW QoS/XGMAC/etc - 256. + */ + addend = (tx_q->ttc < 128) ? 32 : + (tx_q->ttc < 256) ? 64 : 128; + + /* Upper limits: DW GMAC/QoS - 256, DW XGMAC/XLGMAC - 512 or FIFO depth */ + if (tx_q->ttc + addend > min(512, txfifosz)) { + netdev_err_once(priv->dev, "Tx TC reached limit %u on Q%u\n", + tx_q->ttc, queue); + return; + } + + tx_q->ttc += addend; + + netdev_warn(priv->dev, "DMA Tx threshold raised to %u on Q%u\n", + tx_q->ttc, queue); + + txqmode = priv->plat->tx_queues_cfg[queue].mode_to_use; + + stmmac_dma_tx_mode(priv, priv->ioaddr, tx_q->ttc, queue, txfifosz, txqmode); +} + /** * stmmac_resume_tx_subtask - Tx DMA-engine resume subtask * @priv: driver private structure @@ -7601,6 +7686,12 @@ static void stmmac_state_work(struct work_struct *work) if (stmmac_clr_state(ch, RESET_TX)) stmmac_reset_tx_subtask(priv, queue); + if (stmmac_clr_state(ch, DROP_RTC)) + stmmac_drop_rtc_subtask(priv, queue); + + if (stmmac_clr_state(ch, BUMP_TTC)) + stmmac_bump_ttc_subtask(priv, queue); + if (stmmac_clr_state(ch, RESUME_TX)) stmmac_resume_tx_subtask(priv, queue); } @@ -8271,8 +8362,6 @@ static int __stmmac_dvr_probe(struct device *device, #endif priv->msg_enable = netif_msg_init(debug, default_msg_level); - priv->xstats.threshold = tc; - /* Initialize RSS */ rxq = priv->plat->rx_queues_to_use; netdev_rss_key_fill(priv->rss.key, sizeof(priv->rss.key)); @@ -8674,8 +8763,11 @@ static int __init stmmac_cmdline_opt(char *str) } else if (!strncmp(opt, "buf_sz:", 7)) { if (kstrtoint(opt + 7, 0, &buf_sz)) goto err; - } else if (!strncmp(opt, "tc:", 3)) { - if (kstrtoint(opt + 3, 0, &tc)) + } else if (!strncmp(opt, "rtc:", 4)) { + if (kstrtoint(opt + 4, 0, &rtc)) + goto err; + } else if (!strncmp(opt, "ttc:", 4)) { + if (kstrtoint(opt + 4, 0, &ttc)) goto err; } else if (!strncmp(opt, "watchdog:", 9)) { if (kstrtoint(opt + 9, 0, &watchdog)) -- cgit v1.2.3 From 6133c10112cb185cd01d712f317537206bd03b58 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sat, 9 Aug 2025 02:09:26 +0300 Subject: net: stmmac: Add MTL FIFO threshold correction for modern IP-cores Currently the MTL FIFO threshold doesn't get to be dynamically adjusted for the DW QoS Ether and DW XGMAC/XLGMAC/etc controllers. In case of the Cut-through DMA mode and relatively slow DMA-interface it will cause the interfaces to occasionally loose packets due to the Rx FIFO overflow and Tx FIFO underflow. For the hardware setups relying on the Cut-through DMA mode let's add the MTL FIFO threshold correction support for the DW QoS Ether and DW XGMAC/XLGMAC/etc IP-cores in the same way it has been already done for DW MAC100/GMAC. The only difference is that the Rx FIFO overflow and Tx FIFO underflow IRQs are handled not via DMA CSRs but by means of the MTL CSRs. Note earlier there already was an attempt to add provide the solution for the denoted problem for the DW QoS Ether IP-core in the framework of the commit 3a6c12a0c6c3 ("net: stmmac: bump tc when get underflow error from DMA descriptor"). The problem is that solution was first incomplete due to lack the Rx MTL FIFO threshold correction and second relied on the Tx descriptor status which might be detected too late so more comming frames could be corrupted due to the FIFO underflows. Moreover the commit claimed that there was no underflow interrupt bit on DW GMAC4, which as you can see is incorrect. Fixes: 3a6c12a0c6c3 ("net: stmmac: bump tc when get underflow error from DMA descriptor") Fixes: d6ddfacd95c7 ("net: stmmac: Add DMA related callbacks for XGMAC2") Fixes: 477286b53f55 ("stmmac: add GMAC4 core support") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/common.h | 18 ++++++++-------- drivers/net/ethernet/stmicro/stmmac/dwmac4.h | 3 +++ drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c | 12 +++++------ drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c | 4 +--- drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c | 8 ++++++++ drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h | 2 ++ .../net/ethernet/stmicro/stmmac/dwxgmac2_core.c | 4 +++- drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c | 2 ++ drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 24 ++++++++++++++-------- 9 files changed, 49 insertions(+), 28 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/common.h b/drivers/net/ethernet/stmicro/stmmac/common.h index 45d068daded91..6a5973e7b1970 100644 --- a/drivers/net/ethernet/stmicro/stmmac/common.h +++ b/drivers/net/ethernet/stmicro/stmmac/common.h @@ -370,7 +370,6 @@ enum tx_frame_status { tx_not_ls = 0x1, tx_err = 0x2, tx_dma_own = 0x4, - tx_err_bump_tc = 0x8, }; enum dma_irq_status { @@ -390,6 +389,15 @@ enum dma_irq_dir { DMA_DIR_RXTX = 0x3, }; +enum mtl_irq_status { + CORE_IRQ_TX_PATH_IN_LPI_MODE = 0x1, + CORE_IRQ_TX_PATH_EXIT_LPI_MODE = 0x2, + CORE_IRQ_RX_PATH_IN_LPI_MODE = 0x4, + CORE_IRQ_RX_PATH_EXIT_LPI_MODE = 0x8, + CORE_IRQ_MTL_RX_OVERFLOW = 0x100, + CORE_IRQ_MTL_TX_UNDERFLOW = 0x200, +}; + enum request_irq_err { REQ_IRQ_ERR_ALL, REQ_IRQ_ERR_TX, @@ -402,12 +410,6 @@ enum request_irq_err { REQ_IRQ_ERR_NO, }; -/* EEE and LPI defines */ -#define CORE_IRQ_TX_PATH_IN_LPI_MODE (1 << 0) -#define CORE_IRQ_TX_PATH_EXIT_LPI_MODE (1 << 1) -#define CORE_IRQ_RX_PATH_IN_LPI_MODE (1 << 2) -#define CORE_IRQ_RX_PATH_EXIT_LPI_MODE (1 << 3) - /* FPE defines */ #define FPE_EVENT_UNKNOWN 0 #define FPE_EVENT_TRSP BIT(0) @@ -415,8 +417,6 @@ enum request_irq_err { #define FPE_EVENT_RRSP BIT(2) #define FPE_EVENT_RVER BIT(3) -#define CORE_IRQ_MTL_RX_OVERFLOW BIT(8) - /* DMA HW capabilities */ struct dma_features { unsigned int mbps_10_100; diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4.h b/drivers/net/ethernet/stmicro/stmmac/dwmac4.h index d797d936aee16..dff584ea5ab21 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4.h @@ -457,6 +457,9 @@ static inline u32 mtl_low_credx_base_addr(const struct dwmac4_addrs *addrs, /* MTL interrupt */ #define MTL_RX_OVERFLOW_INT_EN BIT(24) #define MTL_RX_OVERFLOW_INT BIT(16) +#define MTL_TX_UNDERFLOW_INT_EN BIT(8) +#define MTL_TX_UNDERFLOW_INT BIT(0) +#define MTL_INT_DEFAULT_ENABLE (MTL_RX_OVERFLOW_INT | MTL_TX_UNDERFLOW_INT) /* Default operating mode of the MAC */ #define GMAC_CORE_INIT (GMAC_CONFIG_JD | GMAC_CONFIG_PS | \ diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c index 7665aa5c3d918..429a344a7896c 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c @@ -603,12 +603,12 @@ static int dwmac4_irq_mtl_status(struct stmmac_priv *priv, u32 status = readl(ioaddr + MTL_CHAN_INT_CTRL(dwmac4_addrs, chan)); - if (status & MTL_RX_OVERFLOW_INT) { - /* clear Interrupt */ - writel(status | MTL_RX_OVERFLOW_INT, - ioaddr + MTL_CHAN_INT_CTRL(dwmac4_addrs, chan)); - ret = CORE_IRQ_MTL_RX_OVERFLOW; - } + if (unlikely(status & MTL_RX_OVERFLOW_INT)) + ret |= CORE_IRQ_MTL_RX_OVERFLOW; + if (unlikely(status & MTL_TX_UNDERFLOW_INT)) + ret |= CORE_IRQ_MTL_TX_UNDERFLOW; + + writel(status, ioaddr + MTL_CHAN_INT_CTRL(dwmac4_addrs, chan)); } return ret; diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c index e226dc6a1b17d..d722f73788184 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c @@ -49,10 +49,8 @@ static int dwmac4_wrback_get_tx_status(struct stmmac_extra_stats *x, if (unlikely(tdes3 & TDES3_EXCESSIVE_DEFERRAL)) x->tx_deferred++; - if (unlikely(tdes3 & TDES3_UNDERFLOW_ERROR)) { + if (unlikely(tdes3 & TDES3_UNDERFLOW_ERROR)) x->tx_underflow++; - ret |= tx_err_bump_tc; - } if (unlikely(tdes3 & TDES3_IP_HDR_ERROR)) x->tx_ip_header_error++; diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c index 1868683f0f02b..398ca3df560f2 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c @@ -128,6 +128,10 @@ static void dwmac4_dma_init_channel(struct stmmac_priv *priv, /* Mask interrupts by writing to CSR7 */ writel(DMA_CHAN_INTR_DEFAULT_MASK, ioaddr + DMA_CHAN_INTR_ENA(dwmac4_addrs, chan)); + + /* Enable interrupts by writing to MTL INT CSR */ + writel(MTL_INT_DEFAULT_ENABLE, + ioaddr + MTL_CHAN_INT_CTRL(dwmac4_addrs, chan)); } static void dwmac410_dma_init_channel(struct stmmac_priv *priv, @@ -147,6 +151,10 @@ static void dwmac410_dma_init_channel(struct stmmac_priv *priv, /* Mask interrupts by writing to CSR7 */ writel(DMA_CHAN_INTR_DEFAULT_MASK_4_10, ioaddr + DMA_CHAN_INTR_ENA(dwmac4_addrs, chan)); + + /* Enable interrupts by writing to MTL INT CSR */ + writel(MTL_INT_DEFAULT_ENABLE, + ioaddr + MTL_CHAN_INT_CTRL(dwmac4_addrs, chan)); } static void dwmac4_dma_init(void __iomem *ioaddr, diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h index 88954be64f454..eb6c9bab94cbb 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h @@ -311,6 +311,8 @@ #define XGMAC_RFA GENMASK(15, 1) #define XGMAC_MTL_QINTEN(x) (0x00001170 + (0x80 * (x))) #define XGMAC_RXOIE BIT(16) +#define XGMAC_TXUIE BIT(0) +#define XGMAC_MTL_INT_DEFAULT_EN (XGMAC_RXOIE | XGMAC_TXUIE) #define XGMAC_MTL_QINT_STATUS(x) (0x00001174 + (0x80 * (x))) #define XGMAC_RXOVFIS BIT(16) #define XGMAC_ABPSIS BIT(1) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c index 8d77f6e7e71bb..639ba71a728a1 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c @@ -346,8 +346,10 @@ static int dwxgmac2_host_mtl_irq_status(struct stmmac_priv *priv, if (chan_status & XGMAC_RXOVFIS) ret |= CORE_IRQ_MTL_RX_OVERFLOW; + if (chan_status & XGMAC_TXUNFIS) + ret |= CORE_IRQ_MTL_TX_UNDERFLOW; - writel(~0x0, ioaddr + XGMAC_MTL_QINT_STATUS(chan)); + writel(chan_status, ioaddr + XGMAC_MTL_QINT_STATUS(chan)); } return ret; diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c index 696eba7d3a125..17be7e46aa236 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c @@ -44,6 +44,8 @@ static void dwxgmac2_dma_init_chan(struct stmmac_priv *priv, writel(value, ioaddr + XGMAC_DMA_CH_CONTROL(chan)); writel(XGMAC_DMA_INT_DEFAULT_EN, ioaddr + XGMAC_DMA_CH_INT_EN(chan)); + + writel(XGMAC_MTL_INT_DEFAULT_EN, ioaddr + XGMAC_MTL_QINTEN(chan)); } static void dwxgmac2_dma_init_rx_chan(struct stmmac_priv *priv, diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 0d23189c0cb7e..81f28aba6df4f 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -2972,13 +2972,11 @@ static int stmmac_tx_clean(struct stmmac_priv *priv, int budget, u32 queue, /* Just consider the last segment and ...*/ if (likely(!(status & tx_not_ls))) { /* ... verify the status error condition */ - if (unlikely(status & tx_err)) { + if (unlikely(status & tx_err)) tx_errors++; - if (unlikely(status & tx_err_bump_tc)) - stmmac_handle_tx_unf_error(priv, queue); - } else { + else tx_packets++; - } + if (skb) { stmmac_get_tx_hwtstamp(priv, p, skb); } else if (tx_q->xsk_pool && @@ -6259,6 +6257,7 @@ static void stmmac_common_interrupt(struct stmmac_priv *priv) u32 rx_cnt = priv->plat->rx_queues_to_use; u32 tx_cnt = priv->plat->tx_queues_to_use; u32 queues_count; + int status; u32 queue; bool xmac; @@ -6277,7 +6276,7 @@ static void stmmac_common_interrupt(struct stmmac_priv *priv) /* To handle GMAC own interrupts */ if (priv->plat->core_type == DWMAC_CORE_GMAC || xmac) { - int status = stmmac_host_irq_status(priv, &priv->xstats); + status = stmmac_host_irq_status(priv, &priv->xstats); if (unlikely(status)) { /* For LPI we need to save the tx status */ @@ -6287,11 +6286,18 @@ static void stmmac_common_interrupt(struct stmmac_priv *priv) priv->tx_path_in_lpi_mode = false; } - for (queue = 0; queue < queues_count; queue++) - stmmac_host_mtl_irq_status(priv, priv->hw, queue); - stmmac_timestamp_interrupt(priv, priv); } + + if (xmac) { + for (queue = 0; queue < queues_count; queue++) { + status = stmmac_host_mtl_irq_status(priv, priv->hw, queue); + if (unlikely(status & CORE_IRQ_MTL_RX_OVERFLOW)) + stmmac_handle_rx_ovf_error(priv, queue); + if (unlikely(status & CORE_IRQ_MTL_TX_UNDERFLOW)) + stmmac_handle_tx_unf_error(priv, queue); + } + } } /** -- cgit v1.2.3 From e1437a7a9e676f38e2a7768231d734f2983a46f3 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sat, 9 Aug 2025 03:30:50 +0300 Subject: net: stmmac: dwmac1000: Add Rx-buf-unavailable IRQ handling Currently the Rx-buffer-unavailable IRQ is missed due to being masked and never checked in the DMA IRQ handler. It's wrong since the incoming frames may start disappearing unnoticed for instance due to too slow Rx DMA-descriptors handling on the heavily overloaded system. Also the IRQ can be utilized as a last-resort in case of the Rx DMA-descriptors handling procedure was halted for a long time (e.g. due to broken RIWT). Let's fix the situation described above by enabling the RU IRQ and handling it in the ISR as normal Rx IRQ. This it won't be missed by incrementing the respective statistics counter and normal Rx interrupt handler will be notified about that it should start handling the Rx DMA-descriptors immediately. Fixes: 47dd7a540b8a ("net: add support for STMicroelectronics Ethernet controllers.") Fixes: 803fc61df261 ("net: stmmac: dwmac-loongson: Add Loongson Multi-channels GMAC support") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/dwmac-loongson.c | 10 +++++++--- drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h | 7 ++++--- drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c | 5 ++++- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-loongson.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-loongson.c index 02d716f9fcb97..075a4bdb34437 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-loongson.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-loongson.c @@ -30,8 +30,9 @@ #define DMA_INTR_ABNORMAL_LOONGSON (DMA_INTR_ENA_AIE_TX_LOONGSON | \ DMA_INTR_ENA_AIE_RX_LOONGSON | \ - DMA_INTR_ENA_FBE | DMA_INTR_ENA_UNE | \ - DMA_INTR_ENA_OVE | DMA_INTR_ENA_TJE) + DMA_INTR_ENA_FBE | DMA_INTR_ENA_RUE | \ + DMA_INTR_ENA_UNE | DMA_INTR_ENA_OVE | \ + DMA_INTR_ENA_TJE) #define DMA_INTR_DEFAULT_MASK_LOONGSON (DMA_INTR_NORMAL_LOONGSON | \ DMA_INTR_ABNORMAL_LOONGSON) @@ -261,8 +262,11 @@ static int loongson_dwmac_dma_interrupt(struct stmmac_priv *priv, ret = rx_ovf_error; } - if (unlikely(intr_status & DMA_STATUS_RU)) + if (unlikely(intr_status & DMA_STATUS_RU)) { x->rx_buf_unav_irq++; + ret = handle_rx; + } + if (unlikely(intr_status & DMA_STATUS_RPS)) x->rx_process_stopped_irq++; if (unlikely(intr_status & DMA_STATUS_RWT)) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h b/drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h index 151e38450f609..79c4eb8f42d4b 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h @@ -83,17 +83,18 @@ /* DMA Abnormal interrupt */ #define DMA_INTR_ENA_AIE 0x00008000 /* Abnormal Summary */ #define DMA_INTR_ENA_FBE 0x00002000 /* Fatal Bus Error */ +#define DMA_INTR_ENA_RUE 0x00000080 /* Receive Buffer Unavailable */ #define DMA_INTR_ENA_UNE 0x00000020 /* Tx Underflow */ #define DMA_INTR_ENA_OVE 0x00000010 /* Receive Overflow */ #define DMA_INTR_ENA_TJE 0x00000008 /* Transmit Jabber */ #define DMA_INTR_ABNORMAL (DMA_INTR_ENA_AIE | DMA_INTR_ENA_FBE | \ - DMA_INTR_ENA_UNE | DMA_INTR_ENA_OVE | \ - DMA_INTR_ENA_TJE) + DMA_INTR_ENA_RUE | DMA_INTR_ENA_UNE | \ + DMA_INTR_ENA_OVE | DMA_INTR_ENA_TJE) /* DMA default interrupt mask */ #define DMA_INTR_DEFAULT_MASK (DMA_INTR_NORMAL | DMA_INTR_ABNORMAL) -#define DMA_INTR_DEFAULT_RX (DMA_INTR_ENA_RIE) +#define DMA_INTR_DEFAULT_RX (DMA_INTR_ENA_RUE | DMA_INTR_ENA_RIE) #define DMA_INTR_DEFAULT_TX (DMA_INTR_ENA_TIE) #define DMA_MISSED_FRAME_CTR 0x00001020 /* Missed Frame Counter */ diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c b/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c index eea6456b70ce6..2f265bc3ad0d2 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c @@ -195,8 +195,11 @@ int dwmac_dma_interrupt(struct stmmac_priv *priv, void __iomem *ioaddr, ret = rx_ovf_error; } - if (unlikely(intr_status & DMA_STATUS_RU)) + if (unlikely(intr_status & DMA_STATUS_RU)) { x->rx_buf_unav_irq++; + ret = handle_rx; + } + if (unlikely(intr_status & DMA_STATUS_RPS)) x->rx_process_stopped_irq++; if (unlikely(intr_status & DMA_STATUS_RWT)) -- cgit v1.2.3 From ab08cc44a920b1309dad353c322e535263295365 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sat, 9 Aug 2025 03:58:58 +0300 Subject: net: stmmac: dwmac4: Add Rx-buf-unavailable IRQ handling Currently the Rx-buffer-unavailable IRQ is missed due to being masked and never checked in the DMA IRQ handler. It's wrong since the incoming frames may start disappearing unnoticed for instance due to too slow Rx DMA-descriptors handling on the heavily overloaded system. Also the IRQ can be utilized as a last-resort in case of the Rx DMA-descriptors handling procedure was halted for a long time (e.g. due to broken RIWT). Let's fix the situation described above by enabling the RU IRQ and handling it in the ISR as normal Rx IRQ. Thus it won't be missed by incrementing the respective statistics counter and normal Rx interrupt handler will be notified about that it should start handling the Rx DMA-descriptors immediately. Fixes: 48863ce5940f ("stmmac: add DMA support for GMAC 4.xx") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.h | 13 +++++++++---- drivers/net/ethernet/stmicro/stmmac/dwmac4_lib.c | 5 ++++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.h b/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.h index 166ac17a42f7d..677a606db5bea 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.h @@ -123,6 +123,7 @@ static inline u32 dma_chanx_base_addr(const struct dwmac4_addrs *addrs, #define DMA_CHAN_INTR_ENA_NIE_4_10 BIT(15) #define DMA_CHAN_INTR_ENA_AIE_4_10 BIT(14) #define DMA_CHAN_INTR_ENA_FBE BIT(12) +#define DMA_CHAN_INTR_ENA_RBUE BIT(7) #define DMA_CHAN_INTR_ENA_RIE BIT(6) #define DMA_CHAN_INTR_ENA_TIE BIT(0) @@ -131,11 +132,13 @@ static inline u32 dma_chanx_base_addr(const struct dwmac4_addrs *addrs, DMA_CHAN_INTR_ENA_TIE) #define DMA_CHAN_INTR_ABNORMAL (DMA_CHAN_INTR_ENA_AIE | \ - DMA_CHAN_INTR_ENA_FBE) + DMA_CHAN_INTR_ENA_FBE | \ + DMA_CHAN_INTR_ENA_RBUE) /* DMA default interrupt mask for 4.00 */ #define DMA_CHAN_INTR_DEFAULT_MASK (DMA_CHAN_INTR_NORMAL | \ DMA_CHAN_INTR_ABNORMAL) -#define DMA_CHAN_INTR_DEFAULT_RX (DMA_CHAN_INTR_ENA_RIE) +#define DMA_CHAN_INTR_DEFAULT_RX (DMA_CHAN_INTR_ENA_RBUE | \ + DMA_CHAN_INTR_ENA_RIE) #define DMA_CHAN_INTR_DEFAULT_TX (DMA_CHAN_INTR_ENA_TIE) #define DMA_CHAN_INTR_NORMAL_4_10 (DMA_CHAN_INTR_ENA_NIE_4_10 | \ @@ -143,11 +146,13 @@ static inline u32 dma_chanx_base_addr(const struct dwmac4_addrs *addrs, DMA_CHAN_INTR_ENA_TIE) #define DMA_CHAN_INTR_ABNORMAL_4_10 (DMA_CHAN_INTR_ENA_AIE_4_10 | \ - DMA_CHAN_INTR_ENA_FBE) + DMA_CHAN_INTR_ENA_FBE | \ + DMA_CHAN_INTR_ENA_RBUE) /* DMA default interrupt mask for 4.10a */ #define DMA_CHAN_INTR_DEFAULT_MASK_4_10 (DMA_CHAN_INTR_NORMAL_4_10 | \ DMA_CHAN_INTR_ABNORMAL_4_10) -#define DMA_CHAN_INTR_DEFAULT_RX_4_10 (DMA_CHAN_INTR_ENA_RIE) +#define DMA_CHAN_INTR_DEFAULT_RX_4_10 (DMA_CHAN_INTR_ENA_RBUE | \ + DMA_CHAN_INTR_ENA_RIE) #define DMA_CHAN_INTR_DEFAULT_TX_4_10 (DMA_CHAN_INTR_ENA_TIE) #define DMA_CHAN_RX_WATCHDOG(addrs, x) (dma_chanx_base_addr(addrs, x) + 0x38) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_lib.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_lib.c index 0527ad1799c7b..851bfbac67874 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_lib.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_lib.c @@ -181,8 +181,11 @@ int dwmac4_dma_interrupt(struct stmmac_priv *priv, void __iomem *ioaddr, /* ABNORMAL interrupts */ if (unlikely(intr_status & DMA_CHAN_STATUS_AIS)) { - if (unlikely(intr_status & DMA_CHAN_STATUS_RBU)) + if (unlikely(intr_status & DMA_CHAN_STATUS_RBU)) { x->rx_buf_unav_irq++; + ret = handle_rx; + } + if (unlikely(intr_status & DMA_CHAN_STATUS_RPS)) x->rx_process_stopped_irq++; if (unlikely(intr_status & DMA_CHAN_STATUS_ETI)) -- cgit v1.2.3 From 5a1d9a4d1caa993bcff8a917ca65e96cfaa66582 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sat, 9 Aug 2025 04:03:40 +0300 Subject: net: stmmac: dwxgmac2: Fix false Tx-handle event detection Currently the "handle_tx" status will be returned from the DW XGMAC DMA IRQ handler in case is either TI or _TBU_ IRQ flag is set. If having the TI bit set always means that the Tx IRQ is set, the TBU flag being set just means that the Tx DMA-engine reached the CPU-own DMA-descriptors and the engine was suspended since no more data left to be handled. So it doesn't mean that the driver should immediately start cleaning up the consumed DMA-descriptors. It shall be done upon the timer-based Tx mitigation event detection. That's what was implied in the framework of the commit 8d07a7930434 ("net: stmmac: xgmac: Do not enable TBU interrupt"). The problem is that disabling the IRQ for preventing the TBU IRQ from being accidentally detected wasn't enough. It still can be and will be detected on each dwxgmac2_dma_interrupt() invocation especially in case if there is a single IRQ line for all MAC and DMA-channels events. So to speak in order to prevent the situation of catching the TBU IRQ for sure let's drop the TBU-status checking in the DW XGMAC DMA IRQ handler. Fixes: 8d07a7930434 ("net: stmmac: xgmac: Do not enable TBU interrupt") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c index 17be7e46aa236..0425199e91a0e 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c @@ -368,7 +368,7 @@ static int dwxgmac2_dma_interrupt(struct stmmac_priv *priv, u64_stats_update_end(&stats->syncp); ret |= handle_rx; } - if (likely(intr_status & (XGMAC_TI | XGMAC_TBU))) { + if (likely(intr_status & XGMAC_TI)) { u64_stats_update_begin(&stats->syncp); u64_stats_inc(&stats->tx_normal_irq_n[chan]); u64_stats_update_end(&stats->syncp); -- cgit v1.2.3 From 7c8072539b3b5b85a5bd974f42f55ea199b895a6 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 11 Aug 2025 19:04:23 +0300 Subject: net: stmmac: dwmac1000: Drop Tx FIFO flushing on desc errors Really none of the DW MAC databooks require to flush Tx FIFO on any of the errors the Tx DMA-descriptor can indicate. BIT(13) FF: Frame Flushed - indicates that the DMA or MTL flushed the frame because of a software Flush command given by the CPU. It means that the frame retrieved via this DMA-descriptor has already been flushed. So no additional flushing is needed. BIT(12) IPE: IP Payload Error - indicates that MAC transmitter detected an error in the TCP, UDP, or ICMP IP datagram payload. As the HW databooks says in this case the payload checksum either won't be inserted or will be inserted for a wrong amount of data. In anyway the frame will be pushed out to the wire. BIT(2) UF: Underflow Error - indicates that the MAC aborted the frame because the data arrived late from the Host memory. Underflow Error indicates that the DMA encountered an empty transmit buffer while transmitting the frame. In this case no more data will be fetched from the memory, the transmission will be halted with the initial part of the frame still sent out to the wire. Thus no data would be left in Tx FIFO. So as described above none of the error bits above require to flush out the Tx FIFO since the respective frame will be sent out of the FIFO anyway. It's also proofed by the normal descriptors implementation in the driver where no flushing is done in the same cases. The same refers to the DW QoS Ether or DW XGMAC/XLGMAC cases. Moreover due to the flushing performed in the descriptors cleanup procedure it will be done for the Tx data of the proceeding frames, thus dropping completely unrelated packets. This is definitely wrong. So let's discard the Tx FIFO flushing then for good. [1] DesignWare Cores Ethernet MAC Universal Databook, Revision 3.73a, October 2013, p. 550. Fixes: 47dd7a540b8a ("net: add support for STMicroelectronics Ethernet controllers.") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/common.h | 2 -- drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c | 3 +-- drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c | 8 -------- drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c | 2 +- drivers/net/ethernet/stmicro/stmmac/enh_desc.c | 14 ++++---------- drivers/net/ethernet/stmicro/stmmac/hwif.h | 3 +-- drivers/net/ethernet/stmicro/stmmac/norm_desc.c | 2 +- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 2 +- 8 files changed, 9 insertions(+), 27 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/common.h b/drivers/net/ethernet/stmicro/stmmac/common.h index 6a5973e7b1970..6a87b8c6218b1 100644 --- a/drivers/net/ethernet/stmicro/stmmac/common.h +++ b/drivers/net/ethernet/stmicro/stmmac/common.h @@ -685,6 +685,4 @@ void stmmac_dwmac4_get_mac_addr(void __iomem *ioaddr, unsigned char *addr, unsigned int high, unsigned int low); void stmmac_dwmac4_set_mac(void __iomem *ioaddr, bool enable); -void dwmac_dma_flush_tx_fifo(void __iomem *ioaddr); - #endif /* __COMMON_H__ */ diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c index d722f73788184..4a07dc1883f55 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c @@ -14,8 +14,7 @@ #include "dwmac4_descs.h" static int dwmac4_wrback_get_tx_status(struct stmmac_extra_stats *x, - struct dma_desc *p, - void __iomem *ioaddr) + struct dma_desc *p) { u32 tdes3 = le32_to_cpu(p->des3); int ret = tx_done; diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c b/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c index 2f265bc3ad0d2..64e43adc51a61 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c @@ -246,14 +246,6 @@ int dwmac_dma_interrupt(struct stmmac_priv *priv, void __iomem *ioaddr, return ret; } -void dwmac_dma_flush_tx_fifo(void __iomem *ioaddr) -{ - u32 csr6 = readl(ioaddr + DMA_CONTROL); - writel((csr6 | DMA_CONTROL_FTF), ioaddr + DMA_CONTROL); - - do {} while ((readl(ioaddr + DMA_CONTROL) & DMA_CONTROL_FTF)); -} - void stmmac_set_mac_addr(void __iomem *ioaddr, const u8 addr[6], unsigned int high, unsigned int low) { diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c index 41e5b420a2151..11c68c05d9b80 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c @@ -10,7 +10,7 @@ #include "dwxgmac2.h" static int dwxgmac2_get_tx_status(struct stmmac_extra_stats *x, - struct dma_desc *p, void __iomem *ioaddr) + struct dma_desc *p) { u32 tdes3 = le32_to_cpu(p->des3); int ret = tx_done; diff --git a/drivers/net/ethernet/stmicro/stmmac/enh_desc.c b/drivers/net/ethernet/stmicro/stmmac/enh_desc.c index 36dc1e8682cff..1c3123d41ee98 100644 --- a/drivers/net/ethernet/stmicro/stmmac/enh_desc.c +++ b/drivers/net/ethernet/stmicro/stmmac/enh_desc.c @@ -13,7 +13,7 @@ #include "descs_com.h" static int enh_desc_get_tx_status(struct stmmac_extra_stats *x, - struct dma_desc *p, void __iomem *ioaddr) + struct dma_desc *p) { u32 tdes0 = le32_to_cpu(p->des0); int ret = tx_done; @@ -30,10 +30,8 @@ static int enh_desc_get_tx_status(struct stmmac_extra_stats *x, if (unlikely(tdes0 & ETDES0_JABBER_TIMEOUT)) x->tx_jabber++; - if (unlikely(tdes0 & ETDES0_FRAME_FLUSHED)) { + if (unlikely(tdes0 & ETDES0_FRAME_FLUSHED)) x->tx_frame_flushed++; - dwmac_dma_flush_tx_fifo(ioaddr); - } if (unlikely(tdes0 & ETDES0_LOSS_CARRIER)) { x->tx_losscarrier++; @@ -49,18 +47,14 @@ static int enh_desc_get_tx_status(struct stmmac_extra_stats *x, if (unlikely(tdes0 & ETDES0_EXCESSIVE_DEFERRAL)) x->tx_deferred++; - if (unlikely(tdes0 & ETDES0_UNDERFLOW_ERROR)) { - dwmac_dma_flush_tx_fifo(ioaddr); + if (unlikely(tdes0 & ETDES0_UNDERFLOW_ERROR)) x->tx_underflow++; - } if (unlikely(tdes0 & ETDES0_IP_HEADER_ERROR)) x->tx_ip_header_error++; - if (unlikely(tdes0 & ETDES0_PAYLOAD_ERROR)) { + if (unlikely(tdes0 & ETDES0_PAYLOAD_ERROR)) x->tx_payload_error++; - dwmac_dma_flush_tx_fifo(ioaddr); - } ret = tx_err; } diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index 0db96a3872592..8b32cd401ab4e 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -64,8 +64,7 @@ struct stmmac_desc_ops { /* Get the valid status of descriptor */ bool (*get_rx_vlan_valid)(struct dma_desc *p); /* Return the transmit status looking at the TDES1 */ - int (*tx_status)(struct stmmac_extra_stats *x, - struct dma_desc *p, void __iomem *ioaddr); + int (*tx_status)(struct stmmac_extra_stats *x, struct dma_desc *p); /* Get the buffer size from the descriptor */ int (*get_tx_len)(struct dma_desc *p); /* Handle extra events on specific interrupts hw dependent */ diff --git a/drivers/net/ethernet/stmicro/stmmac/norm_desc.c b/drivers/net/ethernet/stmicro/stmmac/norm_desc.c index 4ee8bda644c0c..eedb0efa43d03 100644 --- a/drivers/net/ethernet/stmicro/stmmac/norm_desc.c +++ b/drivers/net/ethernet/stmicro/stmmac/norm_desc.c @@ -13,7 +13,7 @@ #include "descs_com.h" static int ndesc_get_tx_status(struct stmmac_extra_stats *x, - struct dma_desc *p, void __iomem *ioaddr) + struct dma_desc *p) { u32 tdes0 = le32_to_cpu(p->des0); u32 tdes1 = le32_to_cpu(p->des1); diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 81f28aba6df4f..b506f3372b585 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -2957,7 +2957,7 @@ static int stmmac_tx_clean(struct stmmac_priv *priv, int budget, u32 queue, else p = tx_q->dma_tx + entry; - status = stmmac_tx_status(priv, &priv->xstats, p, priv->ioaddr); + status = stmmac_tx_status(priv, &priv->xstats, p); /* Check if the descriptor is owned by the DMA */ if (unlikely(status & tx_dma_own)) break; -- cgit v1.2.3 From 8cb54c7085d74fa8b32d064d8a7f5245ed826c91 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 15 Aug 2025 18:54:29 +0300 Subject: net: stmmac: Stop dropping Rx buffers on errors It's pointless since the buffers haven't been touched by CPU anyway so can be freely re-used for next transfers. Moreover the stmmac_rx_refill() and stmmac_rx_refill_zc() methods have been optimized for that since the initial versions. "discard_frame" status doesn't mean to drop the buffers but merely to discard/re-initialize the descriptor. In the meantime dropping Rx-buffers in case of "discard_frame" status just slows down the cleanup procedure and nothing more. Remove that code for good. Fixes: bba2556efad6 ("net: stmmac: Enable RX via AF_XDP zero-copy") Fixes: 2af6106ae949 ("net: stmmac: Introducing support for Page Pool") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index b506f3372b585..98b23b20b4e91 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -5587,8 +5587,6 @@ read_again: stmmac_rx_extended_status(priv, &priv->xstats, rx_q->dma_erx + entry); if (unlikely(status == discard_frame)) { - xsk_buff_free(buf->xdp); - buf->xdp = NULL; dirty++; error = 1; if (!priv->hwts_rx_en) @@ -5604,8 +5602,6 @@ read_again: /* XSK pool expects RX frame 1:1 mapped to XSK buffer */ if (likely(status & rx_not_ls)) { - xsk_buff_free(buf->xdp); - buf->xdp = NULL; dirty++; count++; goto read_again; @@ -5776,8 +5772,6 @@ read_again: if (priv->extend_desc) stmmac_rx_extended_status(priv, &priv->xstats, rx_q->dma_erx + entry); if (unlikely(status == discard_frame)) { - page_pool_put_page(rx_q->page_pool, buf->page, 0, true); - buf->page = NULL; error = 1; if (!priv->hwts_rx_en) rx_errors++; -- cgit v1.2.3 From f150ff58bac9a045d9f4b2459e8883992b206559 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 6 Feb 2025 13:56:15 +0300 Subject: net: stmmac: Add Page Pool statistics collection The STMMAC driver extensively have the Page Pool API utilized for the Rx DMA-buffers handling. In order be aware of how the framework is used let's add the Page Pool statistics printout in the ethtool stats getting ops. Signed-off-by: Serge Semin --- .../net/ethernet/stmicro/stmmac/stmmac_ethtool.c | 23 +++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c index b37a4d58da903..2dca1689d0a38 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c @@ -16,6 +16,8 @@ #include #include +#include + #include "stmmac.h" #include "stmmac_fpe.h" #include "dwmac_dma.h" @@ -482,6 +484,9 @@ static void stmmac_get_per_qstats(struct stmmac_priv *priv, u64 *data) { u32 tx_cnt = priv->plat->tx_queues_to_use; u32 rx_cnt = priv->plat->rx_queues_to_use; +#ifdef CONFIG_PAGE_POOL_STATS + struct page_pool_stats stats = {}; +#endif unsigned int start; int q; @@ -510,6 +515,19 @@ static void stmmac_get_per_qstats(struct stmmac_priv *priv, u64 *data) *data++ = pkt_n; *data++ = stmmac_get_rx_normal_irq_n(priv, q); } + +#ifdef CONFIG_PAGE_POOL_STATS + for (q = 0; q < rx_cnt; q++) { + struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[q]; + + if (rx_q->page_pool) + page_pool_get_stats(rx_q->page_pool, &stats); + if (rx_q->sec_page_pool) + page_pool_get_stats(rx_q->sec_page_pool, &stats); + } + + page_pool_ethtool_stats_get(data, &stats); +#endif } static void stmmac_get_ethtool_stats(struct net_device *dev, @@ -629,7 +647,8 @@ static int stmmac_get_sset_count(struct net_device *netdev, int sset) case ETH_SS_STATS: len = STMMAC_STATS_LEN + STMMAC_QSTATS + STMMAC_TXQ_STATS * tx_cnt + - STMMAC_RXQ_STATS * rx_cnt; + STMMAC_RXQ_STATS * rx_cnt + + page_pool_ethtool_stats_get_count(); if (priv->dma_cap.rmon) len += STMMAC_MMC_STATS_LEN; @@ -672,6 +691,8 @@ static void stmmac_get_qstats_string(struct stmmac_priv *priv, u8 *data) data += ETH_GSTRING_LEN; } } + + page_pool_ethtool_stats_get_strings(data); } static void stmmac_get_strings(struct net_device *dev, u32 stringset, u8 *data) -- cgit v1.2.3 From 1f00c201e6e9b133daf85002c6526a376d7809f1 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 23 Apr 2025 14:35:12 +0300 Subject: net: stmmac: dwxgmac2: Collect Rx-frames statistics DW XGMAC2 write-back Receive DMA-descriptors contains some info about the received payload frame. In particular it's: 1. error conditions 2. level 2 frame type 3. level 3 packet type 4. filters status Let's use that info to collect the controller-specific statistics as it's done in the DW GMAC and DW QoE Ether sub-modules. Note alas the write-back Transmit DMA-descriptors don't provide even the error-status data. That's why the change concerns the Rx DMA-descriptors only. Also note this may worsens the iperf3 results a bit on the systems with quite weak CPU and system bus performance. Our DW XGMAC lost up to 50-70 Mbit/s due to the additional DMA-descriptors parsing. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h | 36 +++++++++- .../net/ethernet/stmicro/stmmac/dwxgmac2_descs.c | 76 +++++++++++++++++++++- 2 files changed, 108 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h index eb6c9bab94cbb..b8b3a0519484b 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h @@ -460,6 +460,13 @@ #define XGMAC_TDES3_VLTV BIT(16) #define XGMAC_TDES3_VT GENMASK(15, 0) #define XGMAC_TDES3_FL GENMASK(14, 0) +#define XGMAC_RDES2_L4FM BIT(28) +#define XGMAC_RDES2_L3FM BIT(27) +#define XGMAC_RDES2_MADRM GENMASK(26, 19) +#define XGMAC_RDES2_HF BIT(18) +#define XGMAC_RDES2_DAF BIT(17) +#define XGMAC_RDES2_SAF BIT(16) +#define XGMAC_RDES2_VF BIT(15) #define XGMAC_RDES2_HL GENMASK(9, 0) #define XGMAC_RDES3_OWN BIT(31) #define XGMAC_RDES3_CTXT BIT(30) @@ -468,11 +475,38 @@ #define XGMAC_RDES3_CDA BIT(27) #define XGMAC_RDES3_RSV BIT(26) #define XGMAC_RDES3_L34T GENMASK(23, 20) -#define XGMAC_RDES3_ET_LT GENMASK(19, 16) #define XGMAC_L34T_IP4TCP 0x1 #define XGMAC_L34T_IP4UDP 0x2 +#define XGMAC_L34T_IP4ICMP 0x3 +#define XGMAC_L34T_IP4IGMP 0x4 +#define XGMAC_L34T_IP4UNKNOWN 0x7 #define XGMAC_L34T_IP6TCP 0x9 #define XGMAC_L34T_IP6UDP 0xA +#define XGMAC_L34T_IP6ICMP 0xB +#define XGMAC_L34T_IP6UNKNOWN 0xF +#define XGMAC_RDES3_ET_LT GENMASK(19, 16) +#define XGMAC_ET_RECEIVE_WATCHDOG 1 +#define XGMAC_ET_XGMII_ERROR 2 +#define XGMAC_ET_CRC_ERROR 3 +#define XGMAC_ET_GIANT_PACKET 4 +#define XGMAC_ET_IP_HDR_ERROR 5 +#define XGMAC_ET_IP_PAYLOAD_ERROR 6 +#define XGMAC_ET_OVERFLOW_ERROR 7 +#define XGMAC_ET_DESC_ERROR 8 +#define XGMAC_L2T_LENGTH_PACKET 0 +#define XGMAC_L2T_MAC_CONTROL 1 +#define XGMAC_L2T_DCB_CONTROL 2 +#define XGMAC_L2T_ARP_REQUEST 3 +#define XGMAC_L2T_OAM_PACKET 4 +#define XGMAC_L2T_CUSTOM_TYPE 5 +#define XGMAC_L2T_AV_CONTROL 6 +#define XGMAC_L2T_OTHER_TYPE 7 +#define XGMAC_L2T_VLAN_STAG 8 +#define XGMAC_L2T_VLAN_CTAG 9 +#define XGMAC_L2T_DVLAN_CCTAG 10 +#define XGMAC_L2T_DVLAN_SSTAG 11 +#define XGMAC_L2T_DVLAN_SCTAG 12 +#define XGMAC_L2T_DVLAN_CSTAG 13 #define XGMAC_RDES3_ES BIT(15) #define XGMAC_RDES3_PL GENMASK(13, 0) #define XGMAC_RDES3_TSD BIT(6) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c index 11c68c05d9b80..7a32fc8018cf6 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c @@ -26,7 +26,10 @@ static int dwxgmac2_get_tx_status(struct stmmac_extra_stats *x, static int dwxgmac2_get_rx_status(struct stmmac_extra_stats *x, struct dma_desc *p) { + u32 rdes2 = le32_to_cpu(p->des2); u32 rdes3 = le32_to_cpu(p->des3); + int ret = good_frame; + int l34t; if (unlikely(rdes3 & XGMAC_RDES3_OWN)) return dma_own; @@ -34,10 +37,77 @@ static int dwxgmac2_get_rx_status(struct stmmac_extra_stats *x, return discard_frame; if (likely(!(rdes3 & XGMAC_RDES3_LD))) return rx_not_ls; - if (unlikely((rdes3 & XGMAC_RDES3_ES) && (rdes3 & XGMAC_RDES3_LD))) - return discard_frame; - return good_frame; + if (likely(!(rdes3 & XGMAC_RDES3_ES))) { + int l2t = FIELD_GET(XGMAC_RDES3_ET_LT, rdes3); + + switch (l2t) { + case XGMAC_L2T_AV_CONTROL: + x->av_pkt_rcvd++; + break; + case XGMAC_L2T_VLAN_STAG ... XGMAC_L2T_DVLAN_CSTAG: + x->rx_vlan++; + break; + } + } else { + int et = FIELD_GET(XGMAC_RDES3_ET_LT, rdes3); + + switch (et) { + case XGMAC_ET_RECEIVE_WATCHDOG: + x->rx_watchdog++; + break; + case XGMAC_ET_XGMII_ERROR: + x->rx_mii++; + break; + case XGMAC_ET_CRC_ERROR: + x->rx_crc_errors++; + break; + case XGMAC_ET_GIANT_PACKET: + x->rx_length++; + break; + case XGMAC_ET_IP_HDR_ERROR: + x->ip_hdr_err++; + break; + case XGMAC_ET_IP_PAYLOAD_ERROR: + x->ip_payload_err++; + break; + case XGMAC_ET_OVERFLOW_ERROR: + x->rx_gmac_overflow++; + break; + case XGMAC_ET_DESC_ERROR: + x->rx_desc++; + break; + } + + ret = discard_frame; + } + + l34t = FIELD_GET(XGMAC_RDES3_L34T, rdes3); + + switch (l34t) { + case XGMAC_L34T_IP4TCP ... XGMAC_L34T_IP4IGMP: + case XGMAC_L34T_IP4UNKNOWN: + x->ipv4_pkt_rcvd++; + break; + case XGMAC_L34T_IP6TCP ... XGMAC_L34T_IP6ICMP: + case XGMAC_L34T_IP6UNKNOWN: + x->ipv6_pkt_rcvd++; + break; + } + + if (unlikely(rdes2 & XGMAC_RDES2_SAF)) + x->sa_rx_filter_fail++; + + if (unlikely(rdes2 & XGMAC_RDES2_DAF)) + x->da_rx_filter_fail++; + + if (unlikely(rdes2 & XGMAC_RDES2_L3FM)) + x->l3_filter_match++; + + if (unlikely(rdes2 & XGMAC_RDES2_L4FM)) + x->l4_filter_match++; + + return ret; } static int dwxgmac2_get_tx_len(struct dma_desc *p) -- cgit v1.2.3 From 2961718acc14ac70f51ea7ecfa4851b04e317500 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 20 Aug 2025 00:48:16 +0300 Subject: net: stmmac: dwxgmac2: Collect Debug statistics DW XGMAC2 exposes several Debug registers which can be utilized to collect useful info about the MAC/MTL internal processes. In fact the registers layout looks very much similar to what can be found on DW QoS Ethernet controllers except a few flags. So let's add the Debug-data statistics collection for the DW XGMACs too in a way similar to what has been added for DW QoS Ether submodule. Note the conditional statement affecting the stmmac_ops::debug() callback invocation can be dropped since first it won't be called if the callback isn't initialized and second even if the callback is initialized and the MAC_DEBUG register isn't available the statistics won't collected anyway. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h | 31 ++++++++ .../net/ethernet/stmicro/stmmac/dwxgmac2_core.c | 87 +++++++++++++++++++++- .../net/ethernet/stmicro/stmmac/stmmac_ethtool.c | 7 +- 3 files changed, 119 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h index b8b3a0519484b..ca86112a7f236 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h @@ -101,6 +101,15 @@ #define XGMAC_LPI_CTRL 0x000000d0 /* For definitions, see LPI_CTRL_STATUS_xxx in common.h */ #define XGMAC_LPI_TIMER_CTRL 0x000000d4 +#define XGMAC_DEBUG 0x00000114 +#define XGMAC_TFCSTS_MASK GENMASK(18, 17) +#define XGMAC_TFCSTS_IDLE 0 +#define XGMAC_TFCSTS_WAIT 1 +#define XGMAC_TFCSTS_GEN_PAUSE 2 +#define XGMAC_TFCSTS_XFER 3 +#define XGMAC_TPESTS BIT(16) +#define XGMAC_RFCFCSTS_MASK GENMASK(2, 1) +#define XGMAC_RPESTS BIT(0) #define XGMAC_HW_FEATURE0 0x0000011c #define XGMAC_HWFEAT_EDMA BIT(31) #define XGMAC_HWFEAT_EDIFFC BIT(30) @@ -291,6 +300,16 @@ #define XGMAC_TTC GENMASK(6, 4) #define XGMAC_TXQEN GENMASK(3, 2) #define XGMAC_TSF BIT(1) +#define XGMAC_MTL_TXQ_DEBUG(x) (0x00001108 + (0x80 * (x))) +#define XGMAC_TRCPSTS BIT(5) +#define XGMAC_TXQSTS BIT(4) +#define XGMAC_TWCSTS BIT(3) +#define XGMAC_TRCSTS_MASK GENMASK(2, 1) +#define XGMAC_TRCSTS_IDLE 0 +#define XGMAC_TRCSTS_READ 1 +#define XGMAC_TRCSTS_TXW 2 +#define XGMAC_TRCSTS_WRITE 3 +#define XGMAC_TXPAUSED BIT(0) #define XGMAC_MTL_TCx_ETS_CONTROL(x) (0x00001110 + (0x80 * (x))) #define XGMAC_MTL_TCx_QUANTUM_WEIGHT(x) (0x00001118 + (0x80 * (x))) #define XGMAC_MTL_TCx_SENDSLOPE(x) (0x0000111c + (0x80 * (x))) @@ -306,6 +325,18 @@ #define XGMAC_EHFC BIT(7) #define XGMAC_RSF BIT(5) #define XGMAC_RTC GENMASK(1, 0) +#define XGMAC_MTL_RXQ_DEBUG(x) (0x00001148 + (0x80 * (x))) +#define XGMAC_RXQSTS_MASK GENMASK(5, 4) +#define XGMAC_RXQSTS_EMPTY 0 +#define XGMAC_RXQSTS_BT 1 +#define XGMAC_RXQSTS_AT 2 +#define XGMAC_RXQSTS_FULL 3 +#define XGMAC_RRCSTS_MASK GENMASK(2, 1) +#define XGMAC_RRCSTS_IDLE 0 +#define XGMAC_RRCSTS_RDATA 1 +#define XGMAC_RRCSTS_RSTAT 2 +#define XGMAC_RRCSTS_FLUSH 3 +#define XGMAC_RWCSTS BIT(0) #define XGMAC_MTL_RXQ_FLOW_CONTROL(x) (0x00001150 + (0x80 * (x))) #define XGMAC_RFD GENMASK(31, 17) #define XGMAC_RFA GENMASK(15, 1) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c index 639ba71a728a1..01d1f43ae819d 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c @@ -551,6 +551,89 @@ static void dwxgmac2_set_filter(struct mac_device_info *hw, writel(value, ioaddr + XGMAC_PACKET_FILTER); } +static void dwxgmac2_debug(struct stmmac_priv *priv, void __iomem *ioaddr, + struct stmmac_extra_stats *x, + u32 rx_queues, u32 tx_queues) +{ + u32 value, queue; + + for (queue = 0; queue < tx_queues; queue++) { + value = readl(ioaddr + XGMAC_MTL_TXQ_DEBUG(queue)); + + if (value & XGMAC_TXQSTS) + x->mtl_tx_fifo_not_empty++; + if (value & XGMAC_TWCSTS) + x->mmtl_fifo_ctrl++; + if (value & XGMAC_TRCSTS_MASK) { + u32 trcsts = FIELD_GET(XGMAC_TRCSTS_MASK, value); + + if (trcsts == XGMAC_TRCSTS_WRITE) + x->mtl_tx_fifo_read_ctrl_write++; + else if (trcsts == XGMAC_TRCSTS_TXW) + x->mtl_tx_fifo_read_ctrl_wait++; + else if (trcsts == XGMAC_TRCSTS_READ) + x->mtl_tx_fifo_read_ctrl_read++; + else + x->mtl_tx_fifo_read_ctrl_idle++; + } + if (value & XGMAC_TXPAUSED) + x->mac_tx_in_pause++; + } + + for (queue = 0; queue < rx_queues; queue++) { + value = readl(ioaddr + XGMAC_MTL_RXQ_DEBUG(queue)); + + if (value & XGMAC_RXQSTS_MASK) { + u32 rxfsts = FIELD_GET(XGMAC_RXQSTS_MASK, value); + + if (rxfsts == XGMAC_RXQSTS_FULL) + x->mtl_rx_fifo_fill_level_full++; + else if (rxfsts == XGMAC_RXQSTS_AT) + x->mtl_rx_fifo_fill_above_thresh++; + else if (rxfsts == XGMAC_RXQSTS_BT) + x->mtl_rx_fifo_fill_below_thresh++; + else + x->mtl_rx_fifo_fill_level_empty++; + } + if (value & XGMAC_RRCSTS_MASK) { + u32 rrcsts = FIELD_GET(XGMAC_RRCSTS_MASK, value); + + if (rrcsts == XGMAC_RRCSTS_FLUSH) + x->mtl_rx_fifo_read_ctrl_flush++; + else if (rrcsts == XGMAC_RRCSTS_RSTAT) + x->mtl_rx_fifo_read_ctrl_read_data++; + else if (rrcsts == XGMAC_RRCSTS_RDATA) + x->mtl_rx_fifo_read_ctrl_status++; + else + x->mtl_rx_fifo_read_ctrl_idle++; + } + if (value & XGMAC_RWCSTS) + x->mtl_rx_fifo_ctrl_active++; + } + + /* XGMAC debug */ + value = readl(ioaddr + XGMAC_DEBUG); + + if (value & XGMAC_TFCSTS_MASK) { + u32 tfcsts = FIELD_GET(XGMAC_TFCSTS_MASK, value); + + if (tfcsts == XGMAC_TFCSTS_XFER) + x->mac_tx_frame_ctrl_xfer++; + else if (tfcsts == XGMAC_TFCSTS_GEN_PAUSE) + x->mac_tx_frame_ctrl_pause++; + else if (tfcsts == XGMAC_TFCSTS_WAIT) + x->mac_tx_frame_ctrl_wait++; + else + x->mac_tx_frame_ctrl_idle++; + } + if (value & XGMAC_TPESTS) + x->mac_gmii_tx_proto_engine++; + if (value & XGMAC_RFCFCSTS_MASK) + x->mac_rx_frame_ctrl_fifo = FIELD_GET(XGMAC_RFCFCSTS_MASK, value); + if (value & XGMAC_RPESTS) + x->mac_gmii_rx_proto_engine++; +} + static void dwxgmac2_set_mac_loopback(void __iomem *ioaddr, bool enable) { u32 value = readl(ioaddr + XGMAC_RX_CONFIG); @@ -1448,7 +1531,7 @@ const struct stmmac_ops dwxgmac210_ops = { .set_lpi_mode = dwxgmac2_set_lpi_mode, .set_eee_timer = dwxgmac2_set_eee_timer, .set_eee_pls = dwxgmac2_set_eee_pls, - .debug = NULL, + .debug = dwxgmac2_debug, .set_filter = dwxgmac2_set_filter, .safety_feat_config = dwxgmac3_safety_feat_config, .safety_feat_irq_status = dwxgmac3_safety_feat_irq_status, @@ -1503,7 +1586,7 @@ const struct stmmac_ops dwxlgmac2_ops = { .set_lpi_mode = dwxgmac2_set_lpi_mode, .set_eee_timer = dwxgmac2_set_eee_timer, .set_eee_pls = dwxgmac2_set_eee_pls, - .debug = NULL, + .debug = dwxgmac2_debug, .set_filter = dwxgmac2_set_filter, .safety_feat_config = dwxgmac3_safety_feat_config, .safety_feat_irq_status = dwxgmac3_safety_feat_irq_status, diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c index 2dca1689d0a38..9881b8df1e644 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c @@ -571,10 +571,9 @@ static void stmmac_get_ethtool_stats(struct net_device *dev, priv->xstats.phy_eee_wakeup_error_n = val; } - if (priv->synopsys_id >= DWMAC_CORE_3_50) - stmmac_mac_debug(priv, priv->ioaddr, - (void *)&priv->xstats, - rx_queues_count, tx_queues_count); + stmmac_mac_debug(priv, priv->ioaddr, + (void *)&priv->xstats, + rx_queues_count, tx_queues_count); } for (i = 0; i < STMMAC_STATS_LEN; i++) { char *p = (char *)priv + stmmac_gstrings_stats[i].stat_offset; -- cgit v1.2.3 From e21aefd28685730a6a09fe38635ef6fade3fd4d0 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 20 Aug 2025 01:17:22 +0300 Subject: net: stmmac: Collect DMA missing frames stats non-mutually Every DW MAC IP-core has a register called like "Missed Frame and Buffer Overflow Counter". It exposes two counters being incremented on each frame dropped due to FIFO overflow or DMA Rx-descriptor unavailable (Rx MTL FIFO flushing). They exist irrespective from the MMC module availability. Moreover expose the statistics unavailable in the MAC Management Counters. So let's convert the code collecting MAC Management/Debug and DMA-missing frames statistics to being linear and not mutually exclusive. Also note currently the DMA missing frames stats is collected for the old DW MAC IP-core. This change is also a preparation before adding the counters support for the rest of the DW controllers. Fixes: 1c901a46d576 ("stmmac: add MMC support exported via ethtool (v3)") Signed-off-by: Serge Semin --- .../net/ethernet/stmicro/stmmac/stmmac_ethtool.c | 43 +++++++++++----------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c index 9881b8df1e644..b3ad74fe8f69a 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c @@ -537,10 +537,11 @@ static void stmmac_get_ethtool_stats(struct net_device *dev, u32 rx_queues_count = priv->plat->rx_queues_to_use; u32 tx_queues_count = priv->plat->tx_queues_to_use; u64 napi_poll = 0, normal_irq_n = 0; - int i, j = 0, pos, ret; unsigned long count; unsigned int start; + int i, j = 0, pos; + /* Update the statistics by reading from the HW counters */ if (priv->dma_cap.asp) { for (i = 0; i < STMMAC_SAFETY_FEAT_SIZE; i++) { if (!stmmac_safety_feat_dump(priv, &priv->sstats, i, @@ -549,32 +550,30 @@ static void stmmac_get_ethtool_stats(struct net_device *dev, } } - /* Update the DMA HW counters for dwmac10/100 */ - ret = stmmac_dma_diagnostic_fr(priv, &priv->xstats, priv->ioaddr); - if (ret) { - /* If supported, for new GMAC chips expose the MMC counters */ - if (priv->dma_cap.rmon) { - stmmac_mmc_read(priv, priv->mmcaddr, &priv->mmc); + if (priv->dma_cap.rmon) { + stmmac_mmc_read(priv, priv->mmcaddr, &priv->mmc); - for (i = 0; i < STMMAC_MMC_STATS_LEN; i++) { - char *p; - p = (char *)priv + stmmac_mmc[i].stat_offset; + for (i = 0; i < STMMAC_MMC_STATS_LEN; i++) { + char *p; + p = (char *)priv + stmmac_mmc[i].stat_offset; - data[j++] = (stmmac_mmc[i].sizeof_stat == - sizeof(u64)) ? (*(u64 *)p) : - (*(u32 *)p); - } - } - if (priv->dma_cap.eee) { - int val = phylink_get_eee_err(priv->phylink); - if (val) - priv->xstats.phy_eee_wakeup_error_n = val; + data[j++] = (stmmac_mmc[i].sizeof_stat == sizeof(u64)) ? + (*(u64 *)p) : (*(u32 *)p); } + } - stmmac_mac_debug(priv, priv->ioaddr, - (void *)&priv->xstats, - rx_queues_count, tx_queues_count); + if (priv->dma_cap.eee) { + int val = phylink_get_eee_err(priv->phylink); + if (val) + priv->xstats.phy_eee_wakeup_error_n = val; } + + stmmac_mac_debug(priv, priv->ioaddr, + (void *)&priv->xstats, + rx_queues_count, tx_queues_count); + + stmmac_dma_diagnostic_fr(priv, &priv->xstats, priv->ioaddr); + for (i = 0; i < STMMAC_STATS_LEN; i++) { char *p = (char *)priv + stmmac_gstrings_stats[i].stat_offset; data[j++] = (stmmac_gstrings_stats[i].sizeof_stat == -- cgit v1.2.3 From 5b30901e83d34769a3e59fdfde75cb4eb50fb554 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 20 Aug 2025 14:12:59 +0300 Subject: net: stmmac: Add per-channel DMA missing frames stats reading The commit ad72f783de06 ("net: stmmac: Add multi-channel support") must have fixed the stmmac_dma_ops::dma_diagnostic_fr() method prototype to support the DMA missing frames statistics collection on the per-channel basis seeing there is the DMA_CHAN_MISSED_FRAME_CTR(chan) macro returning the respective CSR address for the passed DMA-channel ID. Note besides this change is a preparation before adding the counters support for the modern multi-channel DW controllers. While at it let's add an additional argument to the stmmac_dma_ops::dma_diagnostic_fr() callback - pointer to the stmmac_priv structure. It will be utilized after adding the DMA missing frames stats collection support for DW QoS Ether (DW GMAC v4.x/v5.x). Fixes: ad72f783de06 ("net: stmmac: Add multi-channel support") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/dwmac100_dma.c | 6 ++++-- drivers/net/ethernet/stmicro/stmmac/hwif.h | 6 +++--- drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c | 3 ++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac100_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwmac100_dma.c index 12b2bf2d739ab..0acdf3e23ac11 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac100_dma.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac100_dma.c @@ -83,8 +83,10 @@ static void dwmac100_dump_dma_regs(struct stmmac_priv *priv, } /* DMA controller has two counters to track the number of the missed frames. */ -static void dwmac100_dma_diagnostic_fr(struct stmmac_extra_stats *x, - void __iomem *ioaddr) +static void dwmac100_dma_diagnostic_fr(struct stmmac_priv *priv, + void __iomem *ioaddr, + struct stmmac_extra_stats *x, + u32 chan) { u32 csr8 = readl(ioaddr + DMA_MISSED_FRAME_CTR); diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index 8b32cd401ab4e..de3c32a4faa73 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -197,8 +197,8 @@ struct stmmac_dma_ops { void (*dma_tx_mode)(struct stmmac_priv *priv, void __iomem *ioaddr, int mode, u32 channel, int fifosz, u8 qmode); /* To track extra statistic (if supported) */ - void (*dma_diagnostic_fr)(struct stmmac_extra_stats *x, - void __iomem *ioaddr); + void (*dma_diagnostic_fr)(struct stmmac_priv *priv, void __iomem *ioaddr, + struct stmmac_extra_stats *x, u32 chan); void (*enable_dma_transmission)(void __iomem *ioaddr, u32 chan); void (*enable_dma_reception)(void __iomem *ioaddr, u32 chan); void (*enable_dma_irq)(struct stmmac_priv *priv, void __iomem *ioaddr, @@ -258,7 +258,7 @@ struct stmmac_dma_ops { #define stmmac_dma_tx_mode(__priv, __args...) \ stmmac_do_void_callback(__priv, dma, dma_tx_mode, __priv, __args) #define stmmac_dma_diagnostic_fr(__priv, __args...) \ - stmmac_do_void_callback(__priv, dma, dma_diagnostic_fr, __args) + stmmac_do_void_callback(__priv, dma, dma_diagnostic_fr, __priv, __args) #define stmmac_enable_dma_transmission(__priv, __args...) \ stmmac_do_void_callback(__priv, dma, enable_dma_transmission, __args) #define stmmac_enable_dma_reception(__priv, __args...) \ diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c index b3ad74fe8f69a..4524c5e271524 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c @@ -572,7 +572,8 @@ static void stmmac_get_ethtool_stats(struct net_device *dev, (void *)&priv->xstats, rx_queues_count, tx_queues_count); - stmmac_dma_diagnostic_fr(priv, &priv->xstats, priv->ioaddr); + for (i = 0; i < rx_queues_count; i++) + stmmac_dma_diagnostic_fr(priv, priv->ioaddr, &priv->xstats, i); for (i = 0; i < STMMAC_STATS_LEN; i++) { char *p = (char *)priv + stmmac_gstrings_stats[i].stat_offset; -- cgit v1.2.3 From b3e0ead2417a30df046b237e7fc6959976f97541 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 20 Aug 2025 15:03:46 +0300 Subject: net: stmmac: dwmac1000: Collect DMA missing frames statistics DW GMACs have been supporting the missing frames (due to Rx buffer unavailable or MTL FIFO overflow events) statistics from very initial controller release. It has been implemented in the same way as can be found on the even older DW MAC100 devices. It isn't clear why the support hasn't been added to the driver for both versions of the controller together, but let's fix that flaw especially seeing the counters are exceptionally useful on the interface performance debugging. The solution is to just reuse the already implemented dwmac100_dma_diagnostic_fr() method for DW GMACs too by moving the function to the dwmac_lib.c module. Note the change also fixes the counter data missing after overflows. Indeed the former dwmac_dma_diagnostic_fr() method didn't add the current counters value if the counters overflow had happened. It's wrong since the counters will continue to be incremented even after that. In the meantime reading the CSR will erase the current values from the CSR. Fixes: 1c901a46d576 ("stmmac: add MMC support exported via ethtool (v3)") Fixes: 47dd7a540b8a ("net: add support for STMicroelectronics Ethernet controllers.") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/dwmac100.h | 6 ----- .../net/ethernet/stmicro/stmmac/dwmac1000_dma.c | 1 + drivers/net/ethernet/stmicro/stmmac/dwmac100_dma.c | 28 +--------------------- drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h | 9 +++++++ drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c | 19 +++++++++++++++ 5 files changed, 30 insertions(+), 33 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac100.h b/drivers/net/ethernet/stmicro/stmmac/dwmac100.h index 547863cb982fd..576cbf766cc1d 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac100.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac100.h @@ -68,12 +68,6 @@ enum ttc_control { DMA_CONTROL_OSF = 0x00000004, /* Operate On 2nd Frame */ }; -/* STMAC110 DMA Missed Frame Counter register defines */ -#define DMA_MISSED_FRAME_OVE 0x10000000 /* FIFO Overflow Overflow */ -#define DMA_MISSED_FRAME_OVE_CNTR 0x0ffe0000 /* Overflow Frame Counter */ -#define DMA_MISSED_FRAME_OVE_M 0x00010000 /* Missed Frame Overflow */ -#define DMA_MISSED_FRAME_M_CNTR 0x0000ffff /* Missed Frame Couinter */ - extern const struct stmmac_dma_ops dwmac100_dma_ops; #endif /* __DWMAC100_H__ */ diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c index 3ac7a7949529c..a4b5b1a51f0a0 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c @@ -259,6 +259,7 @@ const struct stmmac_dma_ops dwmac1000_dma_ops = { .dump_regs = dwmac1000_dump_dma_regs, .dma_rx_mode = dwmac1000_dma_operation_mode_rx, .dma_tx_mode = dwmac1000_dma_operation_mode_tx, + .dma_diagnostic_fr = dwmac_dma_diagnostic_fr, .enable_dma_transmission = dwmac_enable_dma_transmission, .enable_dma_reception = dwmac_enable_dma_reception, .enable_dma_irq = dwmac_enable_dma_irq, diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac100_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwmac100_dma.c index 0acdf3e23ac11..093d09267e38b 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac100_dma.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac100_dma.c @@ -82,32 +82,6 @@ static void dwmac100_dump_dma_regs(struct stmmac_priv *priv, readl(ioaddr + DMA_CUR_RX_BUF_ADDR); } -/* DMA controller has two counters to track the number of the missed frames. */ -static void dwmac100_dma_diagnostic_fr(struct stmmac_priv *priv, - void __iomem *ioaddr, - struct stmmac_extra_stats *x, - u32 chan) -{ - u32 csr8 = readl(ioaddr + DMA_MISSED_FRAME_CTR); - - if (unlikely(csr8)) { - if (csr8 & DMA_MISSED_FRAME_OVE) { - x->rx_overflow_cntr += 0x800; - } else { - unsigned int ove_cntr; - ove_cntr = ((csr8 & DMA_MISSED_FRAME_OVE_CNTR) >> 17); - x->rx_overflow_cntr += ove_cntr; - } - - if (csr8 & DMA_MISSED_FRAME_OVE_M) { - x->rx_missed_cntr += 0xffff; - } else { - unsigned int miss_f = (csr8 & DMA_MISSED_FRAME_M_CNTR); - x->rx_missed_cntr += miss_f; - } - } -} - const struct stmmac_dma_ops dwmac100_dma_ops = { .reset = dwmac_dma_reset, .init = dwmac100_dma_init, @@ -115,7 +89,7 @@ const struct stmmac_dma_ops dwmac100_dma_ops = { .init_tx_chan = dwmac100_dma_init_tx, .dump_regs = dwmac100_dump_dma_regs, .dma_tx_mode = dwmac100_dma_operation_mode_tx, - .dma_diagnostic_fr = dwmac100_dma_diagnostic_fr, + .dma_diagnostic_fr = dwmac_dma_diagnostic_fr, .enable_dma_transmission = dwmac_enable_dma_transmission, .enable_dma_irq = dwmac_enable_dma_irq, .disable_dma_irq = dwmac_disable_dma_irq, diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h b/drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h index 79c4eb8f42d4b..a4e034c9fecf5 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h @@ -119,9 +119,16 @@ static inline u32 dma_chan_base_addr(u32 base, u32 chan) #define DMA_CHAN_STATUS(chan) dma_chan_base_addr(DMA_STATUS, chan) #define DMA_CHAN_CONTROL(chan) dma_chan_base_addr(DMA_CONTROL, chan) #define DMA_CHAN_INTR_ENA(chan) dma_chan_base_addr(DMA_INTR_ENA, chan) +#define DMA_CHAN_MISSED_FRAME_CTR(chan) \ + dma_chan_base_addr(DMA_MISSED_FRAME_CTR, chan) #define DMA_CHAN_RX_WATCHDOG(chan) \ dma_chan_base_addr(DMA_RX_WATCHDOG, chan) +/* DMA Missed Frame Counter */ +#define DMA_MISSED_FRAME_OVFCNTOVF BIT(28) +#define DMA_MISSED_FRAME_OVFFRMCNT GENMASK(27, 17) +#define DMA_MISSED_FRAME_MISCNTOVF BIT(16) +#define DMA_MISSED_FRAME_MISFRMCNT GENMASK(15, 0) /* Rx watchdog register */ #define DMA_RX_WATCHDOG 0x00001024 @@ -162,6 +169,8 @@ void dwmac_dma_stop_rx(struct stmmac_priv *priv, void __iomem *ioaddr, u32 chan); int dwmac_dma_interrupt(struct stmmac_priv *priv, void __iomem *ioaddr, struct stmmac_extra_stats *x, u32 chan, u32 dir); +void dwmac_dma_diagnostic_fr(struct stmmac_priv *priv, void __iomem *ioaddr, + struct stmmac_extra_stats *x, u32 chan); int dwmac_dma_reset(void __iomem *ioaddr); #endif /* __DWMAC_DMA_H__ */ diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c b/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c index 64e43adc51a61..75dd1d370f641 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c @@ -246,6 +246,25 @@ int dwmac_dma_interrupt(struct stmmac_priv *priv, void __iomem *ioaddr, return ret; } +void dwmac_dma_diagnostic_fr(struct stmmac_priv *priv, void __iomem *ioaddr, + struct stmmac_extra_stats *x, u32 chan) +{ + u32 csr8 = readl(ioaddr + DMA_CHAN_MISSED_FRAME_CTR(chan)); + unsigned long cntr; + + cntr = FIELD_GET(DMA_MISSED_FRAME_OVFFRMCNT, csr8); + if (csr8 & DMA_MISSED_FRAME_OVFCNTOVF) + cntr += FIELD_MAX(DMA_MISSED_FRAME_OVFFRMCNT) + 1; + + x->rx_overflow_cntr += cntr; + + cntr = FIELD_GET(DMA_MISSED_FRAME_MISFRMCNT, csr8); + if (csr8 & DMA_MISSED_FRAME_MISCNTOVF) + cntr += FIELD_MAX(DMA_MISSED_FRAME_MISFRMCNT) + 1; + + x->rx_missed_cntr += cntr; +} + void stmmac_set_mac_addr(void __iomem *ioaddr, const u8 addr[6], unsigned int high, unsigned int low) { -- cgit v1.2.3 From dda9cd0a96dcb47ad8fc1140505187ace1ee2140 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 20 Aug 2025 16:01:48 +0300 Subject: net: stmmac: dwxgmac2: Collect MTL missing frames statistics DW XGMAC/XLGMAC expose the frames missing due to Rx buffer unavailable or MTL Rx FIFO overflow statistics via MTL_RxQ(#i)_Missed_Pkt_Overflow_Cnt CSR for each available queue. Functionally it's equivalent to what is available in DW MAC100/GMAC IP-cores in the framework of the DMA-missing-frames counters. Such counters are exceptionally useful on the interface performance debugging to track down the reasons of the sudden packets disappearance. So let's add their support in the same way as it has been done for DW MAC100/GMAC controllers - via the stmmac_dma_ops::dma_diagnostic_fr() callback implementation. Fixes: d6ddfacd95c7 ("net: stmmac: Add DMA related callbacks for XGMAC2") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h | 5 +++++ drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c | 20 ++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h index ca86112a7f236..a2037d224ce9f 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h @@ -325,6 +325,11 @@ #define XGMAC_EHFC BIT(7) #define XGMAC_RSF BIT(5) #define XGMAC_RTC GENMASK(1, 0) +#define XGMAC_MTL_RXQ_MISSED_PKT_CTR(x) (0x00001144 + (0x80 * (x))) +#define XGMAC_MISCNTOVF BIT(31) +#define XGMAC_MISPKTCNT GENMASK(26, 16) +#define XGMAC_OVFCNTOVF BIT(15) +#define XGMAC_OVFPKTCNT GENMASK(10, 0) #define XGMAC_MTL_RXQ_DEBUG(x) (0x00001148 + (0x80 * (x))) #define XGMAC_RXQSTS_MASK GENMASK(5, 4) #define XGMAC_RXQSTS_EMPTY 0 diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c index 0425199e91a0e..c5888055c4e65 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c @@ -614,6 +614,25 @@ static int dwxgmac2_enable_tbs(struct stmmac_priv *priv, void __iomem *ioaddr, return 0; } +static void dwxgmac2_dma_diagnostic_fr(struct stmmac_priv *priv, void __iomem *ioaddr, + struct stmmac_extra_stats *x, u32 chan) +{ + u32 value = readl(ioaddr + XGMAC_MTL_RXQ_MISSED_PKT_CTR(chan)); + unsigned long cntr; + + cntr = FIELD_GET(XGMAC_MISPKTCNT, value); + if (value & XGMAC_MISCNTOVF) + cntr += FIELD_MAX(XGMAC_MISPKTCNT) + 1; + + x->rx_missed_cntr += cntr; + + cntr = FIELD_GET(XGMAC_OVFPKTCNT, value); + if (value & XGMAC_OVFCNTOVF) + cntr += FIELD_MAX(XGMAC_OVFPKTCNT) + 1; + + x->rx_overflow_cntr += cntr; +} + const struct stmmac_dma_ops dwxgmac210_dma_ops = { .reset = dwxgmac2_dma_reset, .init = dwxgmac2_dma_init, @@ -624,6 +643,7 @@ const struct stmmac_dma_ops dwxgmac210_dma_ops = { .dump_regs = dwxgmac2_dma_dump_regs, .dma_rx_mode = dwxgmac2_dma_rx_mode, .dma_tx_mode = dwxgmac2_dma_tx_mode, + .dma_diagnostic_fr = dwxgmac2_dma_diagnostic_fr, .enable_dma_irq = dwxgmac2_enable_dma_irq, .disable_dma_irq = dwxgmac2_disable_dma_irq, .start_tx = dwxgmac2_dma_start_tx, -- cgit v1.2.3 From 5c4fa85a353444e70f80d6cd0ca0804fa0dc3450 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 20 Aug 2025 18:36:19 +0300 Subject: net: stmmac: dwmac4: Collect MTL missing frames statistics DW QoS Ether (GMAC v4.x/v5.x) exposes the frames missing due to Rx buffer unavailable or MTL Rx FIFO overflow statistics via MTL_RxQ(#i)_Missed_Packet_Overflow_Cnt CSR for each available queue. It's equivalent to what can be found in DW XGMAC/XLGMAC IP-cores but with minor difference in the fields offset. Such counters are exceptionally useful on the interface performance debugging to track down the reasons of the sudden packets disappearance. So let's add their support in the same way as it has been done for DW XGMAC/XLGMAC. Fixes: 48863ce5940f ("stmmac: add DMA support for GMAC 4.xx") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/dwmac4.h | 7 +++++++ drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c | 22 ++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4.h b/drivers/net/ethernet/stmicro/stmmac/dwmac4.h index dff584ea5ab21..25572ac6a3f22 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4.h @@ -296,6 +296,7 @@ static inline u32 mtl_chanx_base_addr(const struct dwmac4_addrs *addrs, #define MTL_CHAN_TX_DEBUG(addrs, x) (mtl_chanx_base_addr(addrs, x) + 0x8) #define MTL_CHAN_INT_CTRL(addrs, x) (mtl_chanx_base_addr(addrs, x) + 0x2c) #define MTL_CHAN_RX_OP_MODE(addrs, x) (mtl_chanx_base_addr(addrs, x) + 0x30) +#define MTL_CHAN_RX_MISSED_PKT_CTR(addrs, x) (mtl_chanx_base_addr(addrs, x) + 0x34) #define MTL_CHAN_RX_DEBUG(addrs, x) (mtl_chanx_base_addr(addrs, x) + 0x38) #define MTL_OP_MODE_RSF BIT(5) @@ -428,6 +429,12 @@ static inline u32 mtl_low_credx_base_addr(const struct dwmac4_addrs *addrs, #define MTL_HIGH_CRED_LC_MASK GENMASK(28, 0) +/* MTL Rx Queue Missed Packet and Overflow Counter */ +#define MTL_MISSED_PKT_MISCNTOVF BIT(27) +#define MTL_MISSED_PKT_MISPKTCNT GENMASK(26, 16) +#define MTL_MISSED_PKT_OVFCNTOVF BIT(11) +#define MTL_MISSED_PKT_OVFPKTCNT GENMASK(10, 0) + /* MTL debug */ #define MTL_DEBUG_TXSTSFSTS BIT(5) #define MTL_DEBUG_TXFSTS BIT(4) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c index 398ca3df560f2..00a8abd11bf2d 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c @@ -575,6 +575,26 @@ static int dwmac4_enable_tbs(struct stmmac_priv *priv, void __iomem *ioaddr, return 0; } +static void dwmac4_dma_diagnostic_fr(struct stmmac_priv *priv, void __iomem *ioaddr, + struct stmmac_extra_stats *x, u32 chan) +{ + const struct dwmac4_addrs *dwmac4_addrs = priv->plat->dwmac4_addrs; + u32 value = readl(ioaddr + MTL_CHAN_RX_MISSED_PKT_CTR(dwmac4_addrs, chan)); + unsigned long cntr; + + cntr = FIELD_GET(MTL_MISSED_PKT_MISPKTCNT, value); + if (value & MTL_MISSED_PKT_MISCNTOVF) + cntr += FIELD_MAX(MTL_MISSED_PKT_MISPKTCNT) + 1; + + x->rx_missed_cntr += cntr; + + cntr = FIELD_GET(MTL_MISSED_PKT_OVFPKTCNT, value); + if (value & MTL_MISSED_PKT_OVFCNTOVF) + cntr += FIELD_MAX(MTL_MISSED_PKT_OVFPKTCNT) + 1; + + x->rx_overflow_cntr += cntr; +} + const struct stmmac_dma_ops dwmac4_dma_ops = { .reset = dwmac4_dma_reset, .init = dwmac4_dma_init, @@ -585,6 +605,7 @@ const struct stmmac_dma_ops dwmac4_dma_ops = { .dump_regs = dwmac4_dump_dma_regs, .dma_rx_mode = dwmac4_dma_rx_chan_op_mode, .dma_tx_mode = dwmac4_dma_tx_chan_op_mode, + .dma_diagnostic_fr = dwmac4_dma_diagnostic_fr, .enable_dma_irq = dwmac4_enable_dma_irq, .disable_dma_irq = dwmac4_disable_dma_irq, .start_tx = dwmac4_dma_start_tx, @@ -614,6 +635,7 @@ const struct stmmac_dma_ops dwmac410_dma_ops = { .dump_regs = dwmac4_dump_dma_regs, .dma_rx_mode = dwmac4_dma_rx_chan_op_mode, .dma_tx_mode = dwmac4_dma_tx_chan_op_mode, + .dma_diagnostic_fr = dwmac4_dma_diagnostic_fr, .enable_dma_irq = dwmac410_enable_dma_irq, .disable_dma_irq = dwmac410_disable_dma_irq, .start_tx = dwmac4_dma_start_tx, -- cgit v1.2.3 From 2f68514ead4e12cb242ebbb73d093a61cb984cd3 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Tue, 22 Apr 2025 22:27:57 +0300 Subject: net: stmmac: Drop redundant TSO boolean flag Indeed it has been absolutely unnecessary to add the stmmac_priv::tso flag in the commit f748be531d70 ("stmmac: support new GMAC4"). The flag was supposed to indicate the TSO feature state. But it can be as well successfully done by using the net_device::features flags NETIF_F_TSO and NETIF_F_TSO6. Let's do that and drop the redundant field then. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac.h | 1 - drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 15 +++------------ 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h index 323de4fd5c71f..d9d8df31cfc44 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h @@ -304,7 +304,6 @@ struct stmmac_priv { int hwts_tx_en; bool tx_path_in_lpi_mode; - bool tso; bool sph_active; bool sph_capable; u32 sarc_type; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 98b23b20b4e91..eee665c43db89 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -3757,7 +3757,7 @@ static int stmmac_hw_setup(struct net_device *dev) stmmac_set_rings_length(priv); /* Enable TSO */ - if (priv->tso) { + if (dev->features & (NETIF_F_TSO | NETIF_F_TSO6)) { for (chan = 0; chan < tx_cnt; chan++) { struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[chan]; @@ -4746,8 +4746,8 @@ static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev) if (priv->tx_path_in_lpi_mode && priv->eee_sw_timer_en) stmmac_stop_sw_lpi(priv); - /* Manage oversized TCP frames for GMAC4 device */ - if (skb_is_gso(skb) && priv->tso) { + /* Manage oversized TCP frames for TSO-capable device */ + if (skb_is_gso(skb)) { if (gso & (SKB_GSO_TCPV4 | SKB_GSO_TCPV6)) return stmmac_tso_xmit(skb, dev); if (priv->plat->core_type == DWMAC_CORE_GMAC4 && @@ -6200,14 +6200,6 @@ static netdev_features_t stmmac_fix_features(struct net_device *dev, if (priv->plat->bugged_jumbo && (dev->mtu > ETH_DATA_LEN)) features &= ~NETIF_F_CSUM_MASK; - /* Disable tso if asked by ethtool */ - if ((priv->plat->flags & STMMAC_FLAG_TSO_EN) && (priv->dma_cap.tsoen)) { - if (features & NETIF_F_TSO) - priv->tso = true; - else - priv->tso = false; - } - return features; } @@ -8297,7 +8289,6 @@ static int __stmmac_dvr_probe(struct device *device, ndev->hw_features |= NETIF_F_TSO | NETIF_F_TSO6; if (priv->plat->core_type == DWMAC_CORE_GMAC4) ndev->hw_features |= NETIF_F_GSO_UDP_L4; - priv->tso = true; netif_set_tso_max_size(ndev, TSO_MAX_LOAD_SIZE); dev_info(priv->device, "TSO feature enabled\n"); } -- cgit v1.2.3 From cc45abee5b7c9c146dc7cf032d81ab6792b84cca Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 21 May 2025 21:26:38 +0300 Subject: net: stmmac: Fully disable TSO if required Currently even if the NETIF_F_TSO and NETIF_F_TSO6 features are disabled the TSO engine will be actually left enabled for the DW QoS Ether and DW XGMAC IP-cores since the DMA_CH(#i)_TX_Control.TSE flag will be left set. This makes the TSO feature toggling incomplete and at the very least slows down the outbound packets handling a little bit. Let's fix that by clearing the denoted flag in the ndo_set_features() callback. Besides as the commit 5e6038b88a57 ("net: stmmac: fix TSO and TBS feature enabling during driver open") correctly noted the TBS and TSO features are mutually exclusive and can't be both simultaneously enabled on the same channel. Thus let's convert the code handling these features enable/disable to imply that requirement. Fixes: f748be531d70 ("stmmac: support new GMAC4") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 32 +++++++++++------------ 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index eee665c43db89..d78d2756a517d 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -3756,19 +3756,6 @@ static int stmmac_hw_setup(struct net_device *dev) /* set TX and RX rings length */ stmmac_set_rings_length(priv); - /* Enable TSO */ - if (dev->features & (NETIF_F_TSO | NETIF_F_TSO6)) { - for (chan = 0; chan < tx_cnt; chan++) { - struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[chan]; - - /* TSO and TBS cannot co-exist */ - if (tx_q->tbs & STMMAC_TBS_AVAIL) - continue; - - stmmac_enable_tso(priv, priv->ioaddr, 1, chan); - } - } - /* Enable Split Header */ sph_en = (priv->hw->rx_csum > 0) && priv->sph_active; for (chan = 0; chan < rx_cnt; chan++) @@ -3779,12 +3766,15 @@ static int stmmac_hw_setup(struct net_device *dev) if (priv->dma_cap.vlins) stmmac_enable_vlan(priv, priv->hw, STMMAC_VLAN_INSERT); - /* TBS */ + /* Enable TSO and/or TBS */ for (chan = 0; chan < tx_cnt; chan++) { struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[chan]; - int enable = tx_q->tbs & STMMAC_TBS_AVAIL; - stmmac_enable_tbs(priv, priv->ioaddr, enable, chan); + /* TSO and TBS cannot co-exist */ + if (tx_q->tbs & STMMAC_TBS_AVAIL) + stmmac_enable_tbs(priv, priv->ioaddr, true, chan); + else if (dev->features & (NETIF_F_TSO | NETIF_F_TSO6)) + stmmac_enable_tso(priv, priv->ioaddr, true, chan); } /* Configure real RX and TX queues */ @@ -6207,6 +6197,7 @@ static int stmmac_set_features(struct net_device *netdev, netdev_features_t features) { struct stmmac_priv *priv = netdev_priv(netdev); + u32 chan; /* Keep the COE Type in case of csum is supporting */ if (features & NETIF_F_RXCSUM) @@ -6218,9 +6209,16 @@ static int stmmac_set_features(struct net_device *netdev, */ stmmac_rx_ipc(priv, priv->hw); + for (chan = 0; chan < priv->plat->tx_queues_to_use; chan++) { + struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[chan]; + bool tso_en = !(tx_q->tbs & STMMAC_TBS_AVAIL) && + features & (NETIF_F_TSO | NETIF_F_TSO6); + + stmmac_enable_tso(priv, priv->ioaddr, tso_en, chan); + } + if (priv->sph_capable) { bool sph_en = (priv->hw->rx_csum > 0) && priv->sph_active; - u32 chan; for (chan = 0; chan < priv->plat->rx_queues_to_use; chan++) stmmac_enable_sph(priv, priv->ioaddr, sph_en, chan); -- cgit v1.2.3 From aab6ebb72d249d08cb7b97b63c2938a7c61939fc Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 26 Sep 2025 20:09:03 +0300 Subject: net: stmmac: dwxgmac2: Fix RSS feature overriding DMA-chan map Currently calling the stmmac_rss_configure() with RSS feature enabled will override the Rx-Queue-to-DMA mapping platform settings specified by the stmmac_rxq_cfg::chan field or via the "snps,map-to-dma-channel" DT property. The later parameter is responsible for the static Rx-queue-DMA mapping, meanwhile the stmmac_rss_configure() method force-enables the dynamic Rx-queue-DMA mapping. It isn't that much of the problem if the RSS-feature couldn't be switched on/off. But even that case the situation will look confusing since no warning or anything else would indicate such behaviour. The problem gets to be actual as long as RSS-feature is disabled in runtime. In that case the static mapping won't be restored basically causing single DMA-channel utilized for all the Rx-queues. Let's fix that by preventing the static Rx-queue-DMA mapping override in the RSS-feature enabling procedure. Fixes: 76067459c686 ("net: stmmac: Implement RSS and enable it in XGMAC core") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h | 2 +- .../net/ethernet/stmicro/stmmac/dwxgmac2_core.c | 24 +++++++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h index a2037d224ce9f..fc15776bd8f6f 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h @@ -266,7 +266,7 @@ #define XGMAC_MTL_INT_STATUS 0x00001020 #define XGMAC_MTL_RXQ_DMA_MAP0 0x00001030 #define XGMAC_MTL_RXQ_DMA_MAP1 0x00001034 -#define XGMAC_QxMDMACH(x) GENMASK((x) * 8 + 7, (x) * 8) +#define XGMAC_QxMDMACH(x) GENMASK((x) * 8 + 6, (x) * 8) #define XGMAC_QxMDMACH_SHIFT(x) ((x) * 8) #define XGMAC_QDDMACH BIT(7) #define XGMAC_TC_PRTY_MAP0 0x00001040 diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c index 01d1f43ae819d..0f9c61e2c47fa 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c @@ -251,6 +251,24 @@ static void dwxgmac2_set_mtl_tx_queue_weight(struct stmmac_priv *priv, writel(weight, ioaddr + XGMAC_MTL_TCx_QUANTUM_WEIGHT(queue)); } +static void dwxgmac2_map_mtl_to_dyn(struct mac_device_info *hw, u32 queue, + bool enable) +{ + void __iomem *ioaddr = hw->pcsr; + u32 value, reg; + + reg = (queue < 4) ? XGMAC_MTL_RXQ_DMA_MAP0 : XGMAC_MTL_RXQ_DMA_MAP1; + if (queue >= 4) + queue -= 4; + + value = readl(ioaddr + reg); + if (enable) + value |= (XGMAC_QDDMACH << XGMAC_QxMDMACH_SHIFT(queue)); + else + value &= ~(XGMAC_QDDMACH << XGMAC_QxMDMACH_SHIFT(queue)); + writel(value, ioaddr + reg); +} + static void dwxgmac2_map_mtl_to_dma(struct mac_device_info *hw, u32 queue, u32 chan) { @@ -672,6 +690,10 @@ static int dwxgmac2_rss_configure(struct mac_device_info *hw, if (!cfg || !cfg->enable) { value &= ~XGMAC_RSSE; writel(value, ioaddr + XGMAC_RSS_CTRL); + + for (i = 0; i < num_rxq; i++) + dwxgmac2_map_mtl_to_dyn(hw, i, false); + return 0; } @@ -689,7 +711,7 @@ static int dwxgmac2_rss_configure(struct mac_device_info *hw, } for (i = 0; i < num_rxq; i++) - dwxgmac2_map_mtl_to_dma(hw, i, XGMAC_QDDMACH); + dwxgmac2_map_mtl_to_dyn(hw, i, true); value |= XGMAC_UDP4TE | XGMAC_TCP4TE | XGMAC_IP2TE | XGMAC_RSSE; writel(value, ioaddr + XGMAC_RSS_CTRL); -- cgit v1.2.3 From 89ae9f43f75bd235c8d91f59d4a5c28aa66bcf50 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 22 May 2025 21:40:33 +0300 Subject: net: stmmac: Implement RSS dependency from Rx COE feature The driver permits toggling the NETIF_F_RXCSUM feature flag by the system user requests. In the meantime the RSS feature fully depends on the Rx Checksum Offload Engine to "parse the received packets and extract two or four L3/L4 tuples from them" [1]. So RSS won't work without Rx COE being enabled. Let's fix that by implementing the respective dependencies in the ndo_fix_features()/ndo_set_features() network device callbacks. [1] DesignWare Cores XGMAC - 10G Ethernet MAC Databook, Revision 3.20a, September 2022, p.263. Fixes: 76067459c686 ("net: stmmac: Implement RSS and enable it in XGMAC core") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index d78d2756a517d..6b0fd17bd381d 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -6190,6 +6190,10 @@ static netdev_features_t stmmac_fix_features(struct net_device *dev, if (priv->plat->bugged_jumbo && (dev->mtu > ETH_DATA_LEN)) features &= ~NETIF_F_CSUM_MASK; + /* RSS feature depends on Rx COE */ + if (!(features & NETIF_F_RXCSUM)) + features &= ~NETIF_F_RXHASH; + return features; } @@ -6224,6 +6228,8 @@ static int stmmac_set_features(struct net_device *netdev, stmmac_enable_sph(priv, priv->ioaddr, sph_en, chan); } + stmmac_mac_config_rss(priv); + if (features & NETIF_F_HW_VLAN_CTAG_RX) priv->hw->hw_vlan_en = true; else -- cgit v1.2.3 From ea536792041591667f3dc7acc23c7ba5d03f6d95 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sat, 24 May 2025 02:16:55 +0300 Subject: net: stmmac: Convert RSS feature to being switchable Currently the Receive Side Scaling feature is always enabled if detected. This isn't that suitable especially seeing it's mutually exclusive with the L3/L4 filters. So let's make the driver to be more flexible by implementing the code switching the RSS feature on/off based on the NETIF_F_RXHASH feature flag state. It's not that much problematic. First the stmmac_ops::rss_configure() callback must be fixed to accept the feature enable/disable flag. Second the callback must be called in the net_device_ops::ndo_set_features() method with the feature state flag passed to enable/disable feature per the system user request. (Note calling rss_configure() from the ndo_set_features() method has been pointless before this change since RSS feature has never been switchable.) Thirdly the NETIF_F_RXHASH flag must be set in the net_device::hw_features field so to be available for the user to change. Finally the selftest code must be aligned with the main changes provided described above. Signed-off-by: Serge Semin --- .../net/ethernet/stmicro/stmmac/dwxgmac2_core.c | 4 +-- drivers/net/ethernet/stmicro/stmmac/hwif.h | 2 +- drivers/net/ethernet/stmicro/stmmac/stmmac.h | 1 - .../net/ethernet/stmicro/stmmac/stmmac_ethtool.c | 1 + drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 35 +++++++++------------- .../net/ethernet/stmicro/stmmac/stmmac_selftests.c | 32 +++++++------------- drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c | 2 +- 7 files changed, 30 insertions(+), 47 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c index 0f9c61e2c47fa..57b0f35e0b78b 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c @@ -680,14 +680,14 @@ static int dwxgmac2_rss_write_reg(void __iomem *ioaddr, bool is_key, int idx, } static int dwxgmac2_rss_configure(struct mac_device_info *hw, - struct stmmac_rss *cfg, u32 num_rxq) + struct stmmac_rss *cfg, bool enable, u32 num_rxq) { void __iomem *ioaddr = hw->pcsr; u32 value, *key; int i, ret; value = readl(ioaddr + XGMAC_RSS_CTRL); - if (!cfg || !cfg->enable) { + if (!enable) { value &= ~XGMAC_RSSE; writel(value, ioaddr + XGMAC_RSS_CTRL); diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index de3c32a4faa73..39062c7f67781 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -401,7 +401,7 @@ struct stmmac_ops { void (*set_mac_loopback)(void __iomem *ioaddr, bool enable); /* RSS */ int (*rss_configure)(struct mac_device_info *hw, - struct stmmac_rss *cfg, u32 num_rxq); + struct stmmac_rss *cfg, bool enable, u32 num_rxq); /* TX Timestamp */ int (*get_mac_tx_timestamp)(struct mac_device_info *hw, u64 *ts); /* Source Address Insertion / Replacement */ diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h index d9d8df31cfc44..3d5f84431a24a 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h @@ -233,7 +233,6 @@ struct stmmac_pps_cfg { }; struct stmmac_rss { - int enable; u8 key[STMMAC_RSS_HASH_KEY_SIZE]; u32 table[STMMAC_RSS_MAX_TABLE_SIZE]; }; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c index 4524c5e271524..152c2657da6a7 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c @@ -992,6 +992,7 @@ static int stmmac_set_rxfh(struct net_device *dev, memcpy(priv->rss.key, rxfh->key, sizeof(priv->rss.key)); return stmmac_rss_configure(priv, priv->hw, &priv->rss, + priv->dev->features & NETIF_F_RXHASH, priv->plat->rx_queues_to_use); } diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 6b0fd17bd381d..154e83664f03c 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -3596,17 +3596,8 @@ static void stmmac_mac_config_rx_queues_routing(struct stmmac_priv *priv) static void stmmac_mac_config_rss(struct stmmac_priv *priv) { - if (!priv->dma_cap.rssen || !priv->plat->rss_en) { - priv->rss.enable = false; - return; - } - - if (priv->dev->features & NETIF_F_RXHASH) - priv->rss.enable = true; - else - priv->rss.enable = false; - stmmac_rss_configure(priv, priv->hw, &priv->rss, + priv->dev->features & NETIF_F_RXHASH, priv->plat->rx_queues_to_use); } @@ -6228,7 +6219,9 @@ static int stmmac_set_features(struct net_device *netdev, stmmac_enable_sph(priv, priv->ioaddr, sph_en, chan); } - stmmac_mac_config_rss(priv); + stmmac_rss_configure(priv, priv->hw, &priv->rss, + features & NETIF_F_RXHASH, + priv->plat->rx_queues_to_use); if (features & NETIF_F_HW_VLAN_CTAG_RX) priv->hw->hw_vlan_en = true; @@ -8174,7 +8167,6 @@ static int __stmmac_dvr_probe(struct device *device, { struct net_device *ndev = NULL; struct stmmac_priv *priv; - u32 rxq; int i, ret = 0; ndev = devm_alloc_etherdev_mqs(device, sizeof(struct stmmac_priv), @@ -8339,6 +8331,16 @@ static int __stmmac_dvr_probe(struct device *device, } } + /* Initialize RSS */ + netdev_rss_key_fill(priv->rss.key, sizeof(priv->rss.key)); + for (i = 0; i < ARRAY_SIZE(priv->rss.table); i++) { + priv->rss.table[i] = + ethtool_rxfh_indir_default(i, priv->plat->rx_queues_to_use); + } + + if (priv->dma_cap.rssen && priv->plat->rss_en) + ndev->hw_features |= NETIF_F_RXHASH; + ndev->features |= ndev->hw_features | NETIF_F_SG | NETIF_F_HIGHDMA; ndev->watchdog_timeo = msecs_to_jiffies(watchdog); #ifdef STMMAC_VLAN_TAG_USED @@ -8357,15 +8359,6 @@ static int __stmmac_dvr_probe(struct device *device, #endif priv->msg_enable = netif_msg_init(debug, default_msg_level); - /* Initialize RSS */ - rxq = priv->plat->rx_queues_to_use; - netdev_rss_key_fill(priv->rss.key, sizeof(priv->rss.key)); - for (i = 0; i < ARRAY_SIZE(priv->rss.table); i++) - priv->rss.table[i] = ethtool_rxfh_indir_default(i, rxq); - - if (priv->dma_cap.rssen && priv->plat->rss_en) - ndev->features |= NETIF_F_RXHASH; - ndev->vlan_features |= ndev->features; /* MTU range: 46 - hw-specific max */ diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c index a0c75886587c3..e50097d64abc5 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c @@ -821,7 +821,7 @@ static int stmmac_test_rss(struct stmmac_priv *priv) { struct stmmac_packet_attrs attr = { }; - if (!priv->dma_cap.rssen || !priv->rss.enable) + if (!(priv->dev->features & NETIF_F_RXHASH)) return -EOPNOTSUPP; attr.dst = priv->dev->dev_addr; @@ -1335,19 +1335,16 @@ static int __stmmac_test_l3filt(struct stmmac_priv *priv, u32 dst, u32 src, struct stmmac_packet_attrs attr = { }; struct flow_dissector *dissector; struct flow_cls_offload *cls; - int ret, old_enable = 0; struct flow_rule *rule; + int ret; if (!tc_can_offload(priv->dev)) return -EOPNOTSUPP; if (!priv->dma_cap.l3l4fnum) return -EOPNOTSUPP; - if (priv->rss.enable) { - old_enable = priv->rss.enable; - priv->rss.enable = false; - stmmac_rss_configure(priv, priv->hw, NULL, + if (priv->dev->features & NETIF_F_RXHASH) + stmmac_rss_configure(priv, priv->hw, &priv->rss, false, priv->plat->rx_queues_to_use); - } dissector = kzalloc_obj(*dissector); if (!dissector) { @@ -1415,11 +1412,9 @@ cleanup_cls: cleanup_dissector: kfree(dissector); cleanup_rss: - if (old_enable) { - priv->rss.enable = old_enable; - stmmac_rss_configure(priv, priv->hw, &priv->rss, + if (priv->dev->features & NETIF_F_RXHASH) + stmmac_rss_configure(priv, priv->hw, &priv->rss, true, priv->plat->rx_queues_to_use); - } return ret; } @@ -1461,19 +1456,16 @@ static int __stmmac_test_l4filt(struct stmmac_priv *priv, u32 dst, u32 src, struct stmmac_packet_attrs attr = { }; struct flow_dissector *dissector; struct flow_cls_offload *cls; - int ret, old_enable = 0; struct flow_rule *rule; + int ret; if (!tc_can_offload(priv->dev)) return -EOPNOTSUPP; if (!priv->dma_cap.l3l4fnum) return -EOPNOTSUPP; - if (priv->rss.enable) { - old_enable = priv->rss.enable; - priv->rss.enable = false; - stmmac_rss_configure(priv, priv->hw, NULL, + if (priv->dev->features & NETIF_F_RXHASH) + stmmac_rss_configure(priv, priv->hw, &priv->rss, false, priv->plat->rx_queues_to_use); - } dissector = kzalloc_obj(*dissector); if (!dissector) { @@ -1546,11 +1538,9 @@ cleanup_cls: cleanup_dissector: kfree(dissector); cleanup_rss: - if (old_enable) { - priv->rss.enable = old_enable; - stmmac_rss_configure(priv, priv->hw, &priv->rss, + if (priv->dev->features & NETIF_F_RXHASH) + stmmac_rss_configure(priv, priv->hw, &priv->rss, true, priv->plat->rx_queues_to_use); - } return ret; } diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c index e6c36cb19ddd1..850fa179454f1 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c @@ -874,7 +874,7 @@ static int tc_setup_cls(struct stmmac_priv *priv, return -EOPNOTSUPP; /* When RSS is enabled, the filtering will be bypassed */ - if (priv->rss.enable) + if (priv->dev->features & NETIF_F_RXHASH) return -EBUSY; switch (cls->command) { -- cgit v1.2.3 From 88d323097388c3cc422236acf45ef09685c2c0b0 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 23 May 2025 16:44:31 +0300 Subject: net: stmmac: Implement SPH dependency from Rx COE feature The driver permits toggling the NETIF_F_RXCSUM feature flag by the system user requests. In the meantime the SPH feature fully depends on the Rx Checksum Offload Engine to "determine the packet type" and respectively split header and payload. If the packet header is detached then the payload will be delivered by means of the secondary buffer of the current and subsequent descriptors. If the header couldn't be split, then all the buffers will be utilized for the packets reception. [1] The later case isn't implemented in the driver. So the Jumbo-frames received by means of greater than one descriptor won't work. Let's fix that in simple way just by preventing the Rx COE feature disabling in case if the Split Packet Header feature is enabled. [1] DesignWare Cores XGMAC - 10G Ethernet MAC Databook, Revision 3.20a, September 2022, p.1662. Fixes: 67afd6d1cfdf ("net: stmmac: Add Split Header support and enable it in XGMAC cores") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 154e83664f03c..5d98b16329db9 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -6181,6 +6181,10 @@ static netdev_features_t stmmac_fix_features(struct net_device *dev, if (priv->plat->bugged_jumbo && (dev->mtu > ETH_DATA_LEN)) features &= ~NETIF_F_CSUM_MASK; + /* SPH feature depends on Rx COE */ + if (priv->sph_active) + features |= NETIF_F_RXCSUM; + /* RSS feature depends on Rx COE */ if (!(features & NETIF_F_RXCSUM)) features &= ~NETIF_F_RXHASH; -- cgit v1.2.3 From e6e804c14a9638fab04f3dfb25732d7f02f35a31 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sun, 25 May 2025 22:45:25 +0300 Subject: net: stmmac: Drop SPH code no longer relevant Since recent commits the Split Payload-Header feature is no longer available if no relevant dependencies are provided, like Rx CoE feature. As such let's drop the no longer relevant code and redundant field from the device private date: 1. stmmac_priv::sph_cap field has been redundant in the first place, since it just reflects the SPH feature availability and could be replaced with a one more conditional statement in the XDP sub-module. 2. (priv->hw.rx_csum > 0) check is no longer required since Rx CoE won't be switchable if the SPH feature is activated. 3. SPH-enable code can be freely dropped from the ndo_set_features() callback since the SPH-feature enable/disable is performed in the device open/close cycle in the stmmac_hw_setup() method. All of the above shall greatly simplify the SPH feature related code. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac.h | 1 - drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 27 +++++------------------ drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.c | 5 ++++- 3 files changed, 9 insertions(+), 24 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h index 3d5f84431a24a..74f7817b6af4e 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h @@ -304,7 +304,6 @@ struct stmmac_priv { int hwts_tx_en; bool tx_path_in_lpi_mode; bool sph_active; - bool sph_capable; u32 sarc_type; unsigned int rx_copybreak; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 5d98b16329db9..4471eab3692ea 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -3679,7 +3679,6 @@ static int stmmac_hw_setup(struct net_device *dev) struct stmmac_priv *priv = netdev_priv(dev); u32 rx_cnt = priv->plat->rx_queues_to_use; u32 tx_cnt = priv->plat->tx_queues_to_use; - bool sph_en; u32 chan; int ret; @@ -3748,10 +3747,8 @@ static int stmmac_hw_setup(struct net_device *dev) stmmac_set_rings_length(priv); /* Enable Split Header */ - sph_en = (priv->hw->rx_csum > 0) && priv->sph_active; for (chan = 0; chan < rx_cnt; chan++) - stmmac_enable_sph(priv, priv->ioaddr, sph_en, chan); - + stmmac_enable_sph(priv, priv->ioaddr, priv->sph_active, chan); /* VLAN Tag Insertion */ if (priv->dma_cap.vlins) @@ -5045,10 +5042,8 @@ static inline void stmmac_rx_refill(struct stmmac_priv *priv, u32 queue) } stmmac_set_desc_addr(priv, p, buf->addr); - if (priv->sph_active) - stmmac_set_desc_sec_addr(priv, p, buf->sec_addr, true); - else - stmmac_set_desc_sec_addr(priv, p, buf->sec_addr, false); + stmmac_set_desc_sec_addr(priv, p, buf->sec_addr, priv->sph_active); + stmmac_refill_desc3(priv, rx_q, p); rx_q->rx_count_frames++; @@ -6216,13 +6211,6 @@ static int stmmac_set_features(struct net_device *netdev, stmmac_enable_tso(priv, priv->ioaddr, tso_en, chan); } - if (priv->sph_capable) { - bool sph_en = (priv->hw->rx_csum > 0) && priv->sph_active; - - for (chan = 0; chan < priv->plat->rx_queues_to_use; chan++) - stmmac_enable_sph(priv, priv->ioaddr, sph_en, chan); - } - stmmac_rss_configure(priv, priv->hw, &priv->rss, features & NETIF_F_RXHASH, priv->plat->rx_queues_to_use); @@ -7185,7 +7173,6 @@ int stmmac_xdp_open(struct net_device *dev) struct stmmac_rx_queue *rx_q; struct stmmac_tx_queue *tx_q; u32 buf_size; - bool sph_en; u32 chan; int ret; @@ -7211,9 +7198,6 @@ int stmmac_xdp_open(struct net_device *dev) stmmac_disable_dma_irq(priv, priv->ioaddr, chan, 1, 1); } - /* Adjust Split header */ - sph_en = (priv->hw->rx_csum > 0) && priv->sph_active; - /* DMA RX Channel Configuration */ for (chan = 0; chan < rx_cnt; chan++) { rx_q = &priv->dma_conf.rx_queue[chan]; @@ -7238,7 +7222,7 @@ int stmmac_xdp_open(struct net_device *dev) rx_q->queue_index); } - stmmac_enable_sph(priv, priv->ioaddr, sph_en, chan); + stmmac_enable_sph(priv, priv->ioaddr, priv->sph_active, chan); } /* DMA TX Channel Configuration */ @@ -8296,8 +8280,7 @@ static int __stmmac_dvr_probe(struct device *device, if (priv->dma_cap.sphen && !(priv->plat->flags & STMMAC_FLAG_SPH_DISABLE)) { ndev->hw_features |= NETIF_F_GRO; - priv->sph_capable = true; - priv->sph_active = priv->sph_capable; + priv->sph_active = true; dev_info(priv->device, "SPH feature enabled\n"); } diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.c index d7e4db7224b0c..8627f887fec81 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_xdp.c @@ -129,7 +129,10 @@ int stmmac_xdp_set_prog(struct stmmac_priv *priv, struct bpf_prog *prog, bpf_prog_put(old_prog); /* Disable RX SPH for XDP operation */ - priv->sph_active = priv->sph_capable && !stmmac_xdp_is_enabled(priv); + if (priv->dma_cap.sphen && + !(priv->plat->flags & STMMAC_FLAG_SPH_DISABLE)) { + priv->sph_active = !stmmac_xdp_is_enabled(priv); + } if (if_running && need_update) stmmac_xdp_open(dev); -- cgit v1.2.3 From 6c216f0f650ef304a36e8fe11e31fbefff758c39 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 29 May 2025 15:46:33 +0300 Subject: net: stmmac: Discard Rx COE type 1 "support" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Here is what the DW GMAC databooks say about the Rx COE type 1 abilities: "The application can enable IPv4 header checksum _checking_ and TCP/UDP checksum _offload_ by setting the GMAC Configuration register’s IPC bit. This module calculates the 16-bit ones’ complement of the Ethernet frame’s payload data’s (DATA field) ones’ complement sum. ..." [1,2,3] Basically it means that the engine _verifies_ the IPv4 header checksum only and just calculates the 16-bit one's complement sum of the IPv4 datagrams for TCP/UDP protocols. The calculated value is just appended to the frame pushed up to the system memory, which the driver just _ignores_. The problem is that despite of all that the NETIF_F_RXCSUM feature is set for Rx COE type 1 and the packets Rx procedure reports that the all the IPv4 payloads csum is verified. It's definitely wrong. In order to fix that let's just drop the entire Rx COE type 1 support since it has never been properly handled in the driver anyway and most importantly has been declared as legacy since DW GMAC v3.30a release [2,3] claiming that the Type 2 engine is generally preferable. Regarding the IPv4 header checking which is claimed to be performed by the type 1 engine, it's performed by the network core subsystem anyway. Note originally, prior the commit 38912bdbde5f ("stmmac: sanitize the rx coe and add the type-1 csum (v2)"), the driver had had the Rx COE type 2 support only [4]. So the implemented solution means to basically revert the blamed commit provided never actually properly working change. [1] DesignWare Cores Ethernet MAC Universal Databook, Revision 3.20a, August 2003, p. 85. [2] DesignWare Cores Ethernet MAC Universal Databook, Revision 3.50a, February 2009, p. 114. [3] DesignWare Cores Ethernet MAC Universal Databook, Revision 3.73a, October 2013, p. 188. [4] Link: https://lore.kernel.org/netdev/4F7A9D7E.8080007@st.com/ Fixes: 38912bdbde5f ("stmmac: sanitize the rx coe and add the type-1 csum (v2)") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c | 2 +- drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c | 2 +- drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c | 2 +- drivers/net/ethernet/stmicro/stmmac/enh_desc.c | 14 ++------------ drivers/net/ethernet/stmicro/stmmac/hwif.h | 2 +- drivers/net/ethernet/stmicro/stmmac/norm_desc.c | 15 ++------------- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 20 +++++--------------- include/linux/stmmac.h | 4 ---- 8 files changed, 13 insertions(+), 48 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c index f06cc70130221..e2d4675826982 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c @@ -1186,7 +1186,7 @@ static int sun8i_dwmac_probe(struct platform_device *pdev) /* platform data specifying hardware features and callbacks. * hardware features were copied from Allwinner drivers. */ - plat_dat->rx_coe = STMMAC_RX_COE_TYPE2; + plat_dat->rx_coe = 1; plat_dat->tx_coe = 1; plat_dat->flags |= STMMAC_FLAG_HAS_SUN8I; plat_dat->bsp_priv = gmac; diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c index 4a07dc1883f55..3ff30bc9f8c22 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c @@ -210,7 +210,7 @@ static bool dwmac4_wrback_get_rx_vlan_valid(struct dma_desc *p) (le32_to_cpu(p->des3) & RDES3_RDES0_VALID)); } -static int dwmac4_wrback_get_rx_frame_len(struct dma_desc *p, int rx_coe) +static int dwmac4_wrback_get_rx_frame_len(struct dma_desc *p) { return (le32_to_cpu(p->des3) & RDES3_PACKET_SIZE_MASK); } diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c index 7a32fc8018cf6..029fbd24ded87 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c @@ -155,7 +155,7 @@ static bool dwxgmac2_wrback_get_rx_vlan_valid(struct dma_desc *p) et_lt <= XGMAC_ET_LT_DVLAN_STAG_CTAG; } -static int dwxgmac2_get_rx_frame_len(struct dma_desc *p, int rx_coe) +static int dwxgmac2_get_rx_frame_len(struct dma_desc *p) { return (le32_to_cpu(p->des3) & XGMAC_RDES3_PL); } diff --git a/drivers/net/ethernet/stmicro/stmmac/enh_desc.c b/drivers/net/ethernet/stmicro/stmmac/enh_desc.c index 1c3123d41ee98..0884fac85f297 100644 --- a/drivers/net/ethernet/stmicro/stmmac/enh_desc.c +++ b/drivers/net/ethernet/stmicro/stmmac/enh_desc.c @@ -343,19 +343,9 @@ static void enh_desc_set_tx_ic(struct dma_desc *p) p->des0 |= cpu_to_le32(ETDES0_INTERRUPT); } -static int enh_desc_get_rx_frame_len(struct dma_desc *p, int rx_coe_type) +static int enh_desc_get_rx_frame_len(struct dma_desc *p) { - unsigned int csum = 0; - /* The type-1 checksum offload engines append the checksum at - * the end of frame and the two bytes of checksum are added in - * the length. - * Adjust for that in the framelen for type-1 checksum offload - * engines. - */ - if (rx_coe_type == STMMAC_RX_COE_TYPE1) - csum = 2; - - return FIELD_GET(RDES0_FRAME_LEN_MASK, le32_to_cpu(p->des0)) - csum; + return FIELD_GET(RDES0_FRAME_LEN_MASK, le32_to_cpu(p->des0)); } static void enh_desc_enable_tx_timestamp(struct dma_desc *p) diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index 39062c7f67781..8ee2f3fbef233 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -70,7 +70,7 @@ struct stmmac_desc_ops { /* Handle extra events on specific interrupts hw dependent */ void (*set_rx_owner)(struct dma_desc *p, int disable_rx_ic); /* Get the receive frame size */ - int (*get_rx_frame_len)(struct dma_desc *p, int rx_coe_type); + int (*get_rx_frame_len)(struct dma_desc *p); /* Return the reception status looking at the RDES1 */ int (*rx_status)(struct stmmac_extra_stats *x, struct dma_desc *p); diff --git a/drivers/net/ethernet/stmicro/stmmac/norm_desc.c b/drivers/net/ethernet/stmicro/stmmac/norm_desc.c index eedb0efa43d03..4fdf75c6bab05 100644 --- a/drivers/net/ethernet/stmicro/stmmac/norm_desc.c +++ b/drivers/net/ethernet/stmicro/stmmac/norm_desc.c @@ -205,20 +205,9 @@ static void ndesc_set_tx_ic(struct dma_desc *p) p->des1 |= cpu_to_le32(TDES1_INTERRUPT); } -static int ndesc_get_rx_frame_len(struct dma_desc *p, int rx_coe_type) +static int ndesc_get_rx_frame_len(struct dma_desc *p) { - unsigned int csum = 0; - - /* The type-1 checksum offload engines append the checksum at - * the end of frame and the two bytes of checksum are added in - * the length. - * Adjust for that in the framelen for type-1 checksum offload - * engines - */ - if (rx_coe_type == STMMAC_RX_COE_TYPE1) - csum = 2; - - return FIELD_GET(RDES0_FRAME_LEN_MASK, le32_to_cpu(p->des0)) - csum; + return FIELD_GET(RDES0_FRAME_LEN_MASK, le32_to_cpu(p->des0)); } static void ndesc_enable_tx_timestamp(struct dma_desc *p) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 4471eab3692ea..91aa8e9187ce5 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -3719,7 +3719,7 @@ static int stmmac_hw_setup(struct net_device *dev) ret = stmmac_rx_ipc(priv, priv->hw); if (!ret) { netdev_warn(priv->dev, "RX IPC Checksum Offload disabled\n"); - priv->plat->rx_coe = STMMAC_RX_COE_NONE; + priv->plat->rx_coe = 0; priv->hw->rx_csum = 0; } @@ -5072,7 +5072,6 @@ static unsigned int stmmac_rx_buf1_len(struct stmmac_priv *priv, struct dma_desc *p, int status, unsigned int len) { - int coe = priv->hw->rx_csum; unsigned int plen = 0; /* Split header enabled */ @@ -5098,7 +5097,7 @@ static unsigned int stmmac_rx_buf1_len(struct stmmac_priv *priv, return len ? plen : plen - stmmac_fs_offset(); } - plen = stmmac_get_rx_frame_len(priv, p, coe); + plen = stmmac_get_rx_frame_len(priv, p); /* First descriptor and last descriptor and not split header */ return min_t(unsigned int, priv->dma_conf.dma_buf_sz, plen); @@ -5108,7 +5107,6 @@ static unsigned int stmmac_rx_buf2_len(struct stmmac_priv *priv, struct dma_desc *p, int status, unsigned int len) { - int coe = priv->hw->rx_csum; unsigned int plen = 0; /* Not split header, buffer is not available */ @@ -5134,7 +5132,7 @@ static unsigned int stmmac_rx_buf2_len(struct stmmac_priv *priv, return priv->dma_conf.dma_buf_sz; /* GMAC4 or last descriptor */ - plen = stmmac_get_rx_frame_len(priv, p, coe); + plen = stmmac_get_rx_frame_len(priv, p); return plen - len; } @@ -6162,7 +6160,7 @@ static netdev_features_t stmmac_fix_features(struct net_device *dev, { struct stmmac_priv *priv = netdev_priv(dev); - if (priv->plat->rx_coe == STMMAC_RX_COE_NONE) + if (!priv->plat->rx_coe) features &= ~NETIF_F_RXCSUM; if (!priv->plat->tx_coe) @@ -7787,13 +7785,7 @@ static int stmmac_hw_init(struct stmmac_priv *priv) else priv->plat->tx_coe = priv->dma_cap.tx_coe; - /* In case of GMAC4 rx_coe is from HW cap register. */ - priv->plat->rx_coe = priv->dma_cap.rx_coe; - - if (priv->dma_cap.rx_coe_type2) - priv->plat->rx_coe = STMMAC_RX_COE_TYPE2; - else if (priv->dma_cap.rx_coe_type1) - priv->plat->rx_coe = STMMAC_RX_COE_TYPE1; + priv->plat->rx_coe = priv->dma_cap.rx_coe || priv->dma_cap.rx_coe_type2; stmmac_print_actphyif(priv); } else { @@ -7803,8 +7795,6 @@ static int stmmac_hw_init(struct stmmac_priv *priv) if (priv->plat->rx_coe) { priv->hw->rx_csum = priv->plat->rx_coe; dev_info(priv->device, "RX Checksum Offload Engine supported\n"); - if (priv->synopsys_id < DWMAC_CORE_4_00) - dev_info(priv->device, "COE Type %d\n", priv->hw->rx_csum); } if (priv->plat->tx_coe) dev_info(priv->device, "TX Checksum insertion supported\n"); diff --git a/include/linux/stmmac.h b/include/linux/stmmac.h index b96bcb3905a7e..934a536a3a0f3 100644 --- a/include/linux/stmmac.h +++ b/include/linux/stmmac.h @@ -19,10 +19,6 @@ #define MTL_MAX_TX_QUEUES 8 #define STMMAC_CH_MAX 8 -#define STMMAC_RX_COE_NONE 0 -#define STMMAC_RX_COE_TYPE1 1 -#define STMMAC_RX_COE_TYPE2 2 - /* Define the macros for CSR clock range parameters to be passed by * platform code. * This could also be configured at run time using CPU freq framework. */ -- cgit v1.2.3 From c7d553e7168e40e62796c223ef68dc1a9c09957b Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 29 May 2025 18:08:11 +0300 Subject: net: stmmac: Fix Rx COE re-enabling after interface re-uping If NETIF_F_RXCSUM feature is user-disabled and the network interface is cycled down/up, then the feature will be completely off and couldn't be re-enabled until the driver or system is reloaded. This happens due to the way the runtime-check has been implemented in the stmmac_hw_setup() method. See if the stmmac_rx_ipc() method couldn't enable the Checksum Offload engine (which happens for sure if the feature was disabled by the user), then driver assumes that no Rx COE detected and clears out all the flags (mac_device_info::rx_csum and plat_stmmacenet_data::rx_coe) indicating the engine availability. As such the user won't be able to enable feature anymore since the ndo_set_feature() callback will constantly call disable Rx COE (call stmmac_rx_ipc() with mac_device_info::rx_csum cleared) despite of the NETIF_F_RXCSUM flag state. Let's fix that by re-developing the RXCSUM feature support a bit. First let's runtime check the RxCOE availability only once during the net-device registration. It can be done because the run-time check on each device-open has turned to be pointless since the blamed commit if the feature isn't declared to be available. Second let's drop the mac_device_info::rx_csum and always use the NETIF_F_RXCSUM flag state to determined the Rx COE feature state since the driver won't assume anymore that the feature might be somehow failed to be enabled. Fixes: d2afb5bdffde ("stmmac: fix the rx csum feature") Signed-off-by: Serge Semin --- Note the problem could be fixed in a less invasive way but the suggested solution makes the RXCSUM feature implementation looking cleaner and more maintainable. --- drivers/net/ethernet/stmicro/stmmac/common.h | 1 - drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c | 4 +- .../net/ethernet/stmicro/stmmac/dwmac1000_core.c | 4 +- .../net/ethernet/stmicro/stmmac/dwmac100_core.c | 6 --- drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c | 4 +- .../net/ethernet/stmicro/stmmac/dwxgmac2_core.c | 4 +- drivers/net/ethernet/stmicro/stmmac/hwif.h | 2 +- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 43 +++++++++------------- 8 files changed, 26 insertions(+), 42 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/common.h b/drivers/net/ethernet/stmicro/stmmac/common.h index 6a87b8c6218b1..02df8523111ab 100644 --- a/drivers/net/ethernet/stmicro/stmmac/common.h +++ b/drivers/net/ethernet/stmicro/stmmac/common.h @@ -646,7 +646,6 @@ struct mac_device_info { unsigned int multicast_filter_bins; unsigned int unicast_filter_entries; unsigned int mcast_bits_log2; - unsigned int rx_csum; unsigned int pcs; unsigned int xlgmac; unsigned int num_vlan; diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c index e2d4675826982..4e82954920980 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c @@ -663,14 +663,14 @@ static void sun8i_dwmac_get_umac_addr(struct mac_device_info *hw, } /* caution this function must return non 0 to work */ -static int sun8i_dwmac_rx_ipc_enable(struct mac_device_info *hw) +static int sun8i_dwmac_rx_ipc_enable(struct mac_device_info *hw, bool enable) { void __iomem *ioaddr = hw->pcsr; u32 v; v = readl(ioaddr + EMAC_RX_CTL0); - if (hw->rx_csum) + if (enable) v |= EMAC_RX_DO_CRC; else v &= ~EMAC_RX_DO_CRC; diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c index ba24be32220ed..83ec2c00b49a5 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c @@ -72,12 +72,12 @@ static void dwmac1000_irq_modify(struct mac_device_info *hw, u32 disable, spin_unlock_irqrestore(&hw->irq_ctrl_lock, flags); } -static int dwmac1000_rx_ipc_enable(struct mac_device_info *hw) +static int dwmac1000_rx_ipc_enable(struct mac_device_info *hw, bool enable) { void __iomem *ioaddr = hw->pcsr; u32 value = readl(ioaddr + GMAC_CONTROL); - if (hw->rx_csum) + if (enable) value |= GMAC_CONTROL_IPC; else value &= ~GMAC_CONTROL_IPC; diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac100_core.c b/drivers/net/ethernet/stmicro/stmmac/dwmac100_core.c index db4fbe64a38af..9c3e1ab992dc5 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac100_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac100_core.c @@ -48,11 +48,6 @@ static void dwmac100_dump_mac_regs(struct mac_device_info *hw, u32 *reg_space) reg_space[MAC_VLAN2 / 4] = readl(ioaddr + MAC_VLAN2); } -static int dwmac100_rx_ipc_enable(struct mac_device_info *hw) -{ - return 0; -} - static int dwmac100_irq_status(struct stmmac_priv *priv, struct stmmac_extra_stats *x) { @@ -157,7 +152,6 @@ static void dwmac100_set_mac_loopback(void __iomem *ioaddr, bool enable) const struct stmmac_ops dwmac100_ops = { .core_init = dwmac100_core_init, .set_mac = stmmac_set_mac, - .rx_ipc = dwmac100_rx_ipc_enable, .dump_regs = dwmac100_dump_mac_regs, .host_irq_status = dwmac100_irq_status, .set_filter = dwmac100_set_filter, diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c index 429a344a7896c..5337376452a7c 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c @@ -318,12 +318,12 @@ static void dwmac4_dump_regs(struct mac_device_info *hw, u32 *reg_space) reg_space[i] = readl(ioaddr + i * 4); } -static int dwmac4_rx_ipc_enable(struct mac_device_info *hw) +static int dwmac4_rx_ipc_enable(struct mac_device_info *hw, bool enable) { void __iomem *ioaddr = hw->pcsr; u32 value = readl(ioaddr + GMAC_CONFIG); - if (hw->rx_csum) + if (enable) value |= GMAC_CONFIG_IPC; else value &= ~GMAC_CONFIG_IPC; diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c index 57b0f35e0b78b..8be75536b284d 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c @@ -65,13 +65,13 @@ static void dwxgmac2_set_mac(void __iomem *ioaddr, bool enable) writel(rx, ioaddr + XGMAC_RX_CONFIG); } -static int dwxgmac2_rx_ipc(struct mac_device_info *hw) +static int dwxgmac2_rx_ipc(struct mac_device_info *hw, bool enable) { void __iomem *ioaddr = hw->pcsr; u32 value; value = readl(ioaddr + XGMAC_RX_CONFIG); - if (hw->rx_csum) + if (enable) value |= XGMAC_CONFIG_IPC; else value &= ~XGMAC_CONFIG_IPC; diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index 8ee2f3fbef233..c15fa1cafdea4 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -326,7 +326,7 @@ struct stmmac_ops { /* Enable the MAC RX/TX */ void (*set_mac)(void __iomem *ioaddr, bool enable); /* Enable and verify that the IPC module is supported */ - int (*rx_ipc)(struct mac_device_info *hw); + int (*rx_ipc)(struct mac_device_info *hw, bool enable); /* Enable RX Queues */ void (*rx_queue_enable)(struct mac_device_info *hw, u8 mode, u32 queue); /* RX Queues Priority */ diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 91aa8e9187ce5..8235401a8e3b9 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -3716,12 +3716,8 @@ static int stmmac_hw_setup(struct net_device *dev) /* Initialize Safety Features */ stmmac_safety_feat_configuration(priv); - ret = stmmac_rx_ipc(priv, priv->hw); - if (!ret) { - netdev_warn(priv->dev, "RX IPC Checksum Offload disabled\n"); - priv->plat->rx_coe = 0; - priv->hw->rx_csum = 0; - } + /* Initialize Rx Checksum Offload Engine */ + stmmac_rx_ipc(priv, priv->hw, dev->features & NETIF_F_RXCSUM); /* Enable the MAC Rx/Tx */ stmmac_mac_set(priv, priv->ioaddr, true); @@ -5365,9 +5361,9 @@ static void stmmac_dispatch_skb_zc(struct stmmac_priv *priv, u32 queue, { struct stmmac_rxq_stats *rxq_stats = &priv->xstats.rxq_stats[queue]; struct stmmac_channel *ch = &priv->channel[queue]; + int coe = priv->dev->features & NETIF_F_RXCSUM; unsigned int len = xdp->data_end - xdp->data; enum pkt_hash_types hash_type; - int coe = priv->hw->rx_csum; struct sk_buff *skb; u32 hash; @@ -5666,14 +5662,14 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue) struct stmmac_rxq_stats *rxq_stats = &priv->xstats.rxq_stats[queue]; struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue]; struct stmmac_channel *ch = &priv->channel[queue]; + int coe = priv->dev->features & NETIF_F_RXCSUM; unsigned int count = 0, error = 0, len = 0; - int status = 0, coe = priv->hw->rx_csum; unsigned int next_entry = rx_q->cur_rx; enum dma_data_direction dma_dir; + int status = 0, xdp_status = 0; unsigned int desc_size; struct sk_buff *skb = NULL; struct stmmac_xdp_buff ctx; - int xdp_status = 0; dma_dir = page_pool_get_dma_dir(rx_q->page_pool); limit = min(priv->dma_conf.dma_rx_size - 1, (unsigned int)limit); @@ -6160,9 +6156,6 @@ static netdev_features_t stmmac_fix_features(struct net_device *dev, { struct stmmac_priv *priv = netdev_priv(dev); - if (!priv->plat->rx_coe) - features &= ~NETIF_F_RXCSUM; - if (!priv->plat->tx_coe) features &= ~NETIF_F_CSUM_MASK; @@ -6191,15 +6184,7 @@ static int stmmac_set_features(struct net_device *netdev, struct stmmac_priv *priv = netdev_priv(netdev); u32 chan; - /* Keep the COE Type in case of csum is supporting */ - if (features & NETIF_F_RXCSUM) - priv->hw->rx_csum = priv->plat->rx_coe; - else - priv->hw->rx_csum = 0; - /* No check needed because rx_coe has been set before and it will be - * fixed in case of issue. - */ - stmmac_rx_ipc(priv, priv->hw); + stmmac_rx_ipc(priv, priv->hw, features & NETIF_F_RXCSUM); for (chan = 0; chan < priv->plat->tx_queues_to_use; chan++) { struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[chan]; @@ -7785,17 +7770,20 @@ static int stmmac_hw_init(struct stmmac_priv *priv) else priv->plat->tx_coe = priv->dma_cap.tx_coe; - priv->plat->rx_coe = priv->dma_cap.rx_coe || priv->dma_cap.rx_coe_type2; + /* RXCOE runtime check (for backward compatibility) */ + if (priv->dma_cap.rx_coe || priv->dma_cap.rx_coe_type2) { + priv->plat->rx_coe = + (stmmac_rx_ipc(priv, priv->hw, true) == 1); + } stmmac_print_actphyif(priv); } else { dev_info(priv->device, "No HW DMA feature register supported\n"); } - if (priv->plat->rx_coe) { - priv->hw->rx_csum = priv->plat->rx_coe; + if (priv->plat->rx_coe) dev_info(priv->device, "RX Checksum Offload Engine supported\n"); - } + if (priv->plat->tx_coe) dev_info(priv->device, "TX Checksum insertion supported\n"); @@ -8248,10 +8236,13 @@ static int __stmmac_dvr_probe(struct device *device, ndev->xdp_metadata_ops = &stmmac_xdp_metadata_ops; ndev->xsk_tx_metadata_ops = &stmmac_xsk_tx_metadata_ops; - ndev->hw_features = NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM | NETIF_F_RXCSUM; + ndev->hw_features = NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM; ndev->xdp_features = NETDEV_XDP_ACT_BASIC | NETDEV_XDP_ACT_REDIRECT | NETDEV_XDP_ACT_XSK_ZEROCOPY; + if (priv->plat->rx_coe) + ndev->hw_features |= NETIF_F_RXCSUM; + if (priv->dma_cap.frpsel || priv->dma_cap.av || priv->dma_cap.l3l4fnum || priv->dma_cap.tbssel || priv->dma_cap.estsel) { ret = stmmac_tc_init(priv, priv); -- cgit v1.2.3 From e7eb059c05ecb8730e4658d255658894e0fe7901 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sat, 31 May 2025 01:00:00 +0300 Subject: net: stmmac: dwmac1000: Fix improper Rx COE type 2 status getting Rx Checksum Offload Engine has been available in the DW GMAC IP-core since v3.30a release. Back then the IP-core already provided two types of the DMA-descriptors to indicate the results of the IP header and payload checksumming: normal and enhanced. The problem was that both of these descriptor types didn't have free flags for that. So the engineers invented the RDES0.{0,5,7} bits permutation (the bits originally indicating the least important statuses) so if the Rx COE type 2 was available their semantics was changed to indicate the results of the engine work. The bits state parsing was developed in the enh_desc_coe_rdes0() method. The situation changed in the DW GMAC v3.50a IP-core. Since that release the controller has supported the _extended_ version of the enhanced descriptor. It meant that if Rx COE type 2 (so called Full Checksum Offload Engine) or Advanced Timestamp features were enabled in the IP-core, the descriptor can have been optionally extended with four more dwords containing the features status. Due to that the RDES0.{0,5,7} bits permutation was no required and was dropped in the v3.50a IP-core release. There wouldn't have been a problem in the situation described above if the STMMAC driver was left supporting the STM GMAC onlu. But since the commit 84c9f8c41df9 ("net: stmmac: Add ip version to dts bindings") the driver was declared to support the generic DW GMAC IP-cores which was wrong without properly implementing the specifics of the newer GMAC revisions. Let's fix that by parsing the permuted RDES0.{0,5,7} bits on the DW GMACs earlier than v3.50a. The Rx COE type 2 status parsing of the extended enhanced descriptor will be fixed later. The reasonable question is how come there has been no problem officially spotted so far? Well, most likely that's because the RDES0.{0,5,7} bits semantics in the extended enhanced descriptor didn't cause any traffic loses when parsed in the enh_desc_coe_rdes0() method. For instance the most frequently seen statuses 0x5/0x4 just means Ethernet-II frame with/without the extended status available in the RDES4-RDES7 dwords. [1] DesignWare Cores Ethernet MAC Universal Databook, Revision 3.50a, February 2009, p. 113. [2] DesignWare Cores Ethernet MAC Universal Databook, Revision 3.73a, October 2013, p. 188. [3] Link: https://lore.kernel.org/netdev/20240111-prevent_dsa_tags-v5-1-63e795a4d129@bootlin.com Fixes: 84c9f8c41df9 ("net: stmmac: Add ip version to dts bindings") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/descs.h | 1 + drivers/net/ethernet/stmicro/stmmac/descs_com.h | 38 ++++++++ drivers/net/ethernet/stmicro/stmmac/enh_desc.c | 106 ++++++++++++---------- drivers/net/ethernet/stmicro/stmmac/hwif.c | 6 +- drivers/net/ethernet/stmicro/stmmac/hwif.h | 2 + drivers/net/ethernet/stmicro/stmmac/norm_desc.c | 59 ++++++++++-- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 4 +- 7 files changed, 156 insertions(+), 60 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/descs.h b/drivers/net/ethernet/stmicro/stmmac/descs.h index e62e2ebcf2733..acb765fbaee21 100644 --- a/drivers/net/ethernet/stmicro/stmmac/descs.h +++ b/drivers/net/ethernet/stmicro/stmmac/descs.h @@ -23,6 +23,7 @@ #define RDES0_FRAME_TYPE BIT(5) #define RDES0_COLLISION BIT(6) #define RDES0_IPC_CSUM_ERROR BIT(7) +#define RDES0_GIANT_FRAME_ERROR RDES0_IPC_CSUM_ERROR #define RDES0_LAST_DESCRIPTOR BIT(8) #define RDES0_FIRST_DESCRIPTOR BIT(9) #define RDES0_VLAN_TAG BIT(10) diff --git a/drivers/net/ethernet/stmicro/stmmac/descs_com.h b/drivers/net/ethernet/stmicro/stmmac/descs_com.h index 9d1a94a4fa49c..7cb8c20fbe6f2 100644 --- a/drivers/net/ethernet/stmicro/stmmac/descs_com.h +++ b/drivers/net/ethernet/stmicro/stmmac/descs_com.h @@ -126,4 +126,42 @@ static inline void norm_set_tx_desc_len_on_chain(struct dma_desc *p, int len) { p->des1 |= cpu_to_le32(len & TDES1_BUFFER1_SIZE_MASK); } + +/* Functions used for all ring/chain modes, enhanced/normal descriptors */ + +static inline int com_desc_rx_coe_rdes0(int ipc_err, int type, int payload_err) +{ + u32 status = (type << 2 | ipc_err << 1 | payload_err) & 0x7; + + /* bits 5 7 0 | Frame status + * ---------------------------------------------------------- + * 0 0 0 | IEEE 802.3 Type frame (length < 1536 octets) + * 1 0 0 | IPv4/6 No CSUM errorS. + * 1 0 1 | IPv4/6 CSUM PAYLOAD error + * 1 1 0 | IPv4/6 CSUM IP HR error + * 1 1 1 | IPv4/6 IP PAYLOAD AND HEADER errorS + * 0 0 1 | IPv4/6 unsupported IP PAYLOAD + * 0 1 1 | COE bypassed.. no IPv4/6 frame + * 0 1 0 | Reserved. + */ + switch (status) { + case 0x0: + return llc_snap; + case 0x1: + return csum_none; + case 0x3: + return csum_none; + case 0x4: + return good_frame; + case 0x5: + return discard_frame; + case 0x6: + return discard_frame; + case 0x7: + return discard_frame; + default: + return csum_none; + } +} + #endif /* __DESC_COM_H__ */ diff --git a/drivers/net/ethernet/stmicro/stmmac/enh_desc.c b/drivers/net/ethernet/stmicro/stmmac/enh_desc.c index 0884fac85f297..56f430f69ffc6 100644 --- a/drivers/net/ethernet/stmicro/stmmac/enh_desc.c +++ b/drivers/net/ethernet/stmicro/stmmac/enh_desc.c @@ -75,39 +75,6 @@ static int enh_desc_get_tx_len(struct dma_desc *p) return (le32_to_cpu(p->des1) & ETDES1_BUFFER1_SIZE_MASK); } -static int enh_desc_coe_rdes0(int ipc_err, int type, int payload_err) -{ - int ret = good_frame; - u32 status = (type << 2 | ipc_err << 1 | payload_err) & 0x7; - - /* bits 5 7 0 | Frame status - * ---------------------------------------------------------- - * 0 0 0 | IEEE 802.3 Type frame (length < 1536 octets) - * 1 0 0 | IPv4/6 No CSUM errorS. - * 1 0 1 | IPv4/6 CSUM PAYLOAD error - * 1 1 0 | IPv4/6 CSUM IP HR error - * 1 1 1 | IPv4/6 IP PAYLOAD AND HEADER errorS - * 0 0 1 | IPv4/6 unsupported IP PAYLOAD - * 0 1 1 | COE bypassed.. no IPv4/6 frame - * 0 1 0 | Reserved. - */ - if (status == 0x0) - ret = llc_snap; - else if (status == 0x4) - ret = good_frame; - else if (status == 0x5) - ret = csum_none; - else if (status == 0x6) - ret = csum_none; - else if (status == 0x7) - ret = csum_none; - else if (status == 0x1) - ret = discard_frame; - else if (status == 0x3) - ret = discard_frame; - return ret; -} - static void enh_desc_get_ext_status(struct stmmac_extra_stats *x, struct dma_extended_desc *p) { @@ -172,10 +139,9 @@ static void enh_desc_get_ext_status(struct stmmac_extra_stats *x, } } -static int enh_desc_get_rx_status(struct stmmac_extra_stats *x, - struct dma_desc *p) +static int enh_desc_get_rx_basic_status(unsigned int rdes0, + struct stmmac_extra_stats *x) { - u32 rdes0 = le32_to_cpu(p->des0); int ret = good_frame; if (unlikely(rdes0 & RDES0_OWN)) @@ -194,11 +160,12 @@ static int enh_desc_get_rx_status(struct stmmac_extra_stats *x, if (unlikely(rdes0 & RDES0_OVERFLOW_ERROR)) x->rx_gmac_overflow++; - if (unlikely(rdes0 & RDES0_IPC_CSUM_ERROR)) - pr_err("\tIPC Csum Error/Giant frame\n"); - if (unlikely(rdes0 & RDES0_COLLISION)) x->rx_collision++; + + if (unlikely(rdes0 & RDES0_GIANT_FRAME_ERROR)) + x->rx_length++; + if (unlikely(rdes0 & RDES0_RECEIVE_WATCHDOG)) x->rx_watchdog++; @@ -211,15 +178,6 @@ static int enh_desc_get_rx_status(struct stmmac_extra_stats *x, ret = discard_frame; } - /* After a payload csum error, the ES bit is set. - * It doesn't match with the information reported into the databook. - * At any rate, we need to understand if the CSUM hw computation is ok - * and report this info to the upper layers. */ - if (likely(ret == good_frame)) - ret = enh_desc_coe_rdes0(!!(rdes0 & RDES0_IPC_CSUM_ERROR), - !!(rdes0 & RDES0_FRAME_TYPE), - !!(rdes0 & ERDES0_RX_MAC_ADDR)); - if (unlikely(rdes0 & RDES0_DRIBBLING)) x->dribbling_bit++; @@ -243,6 +201,33 @@ static int enh_desc_get_rx_status(struct stmmac_extra_stats *x, return ret; } +static int enh_desc_get_rx_status_nocoe(struct stmmac_extra_stats *x, + struct dma_desc *p) +{ + return enh_desc_get_rx_basic_status(le32_to_cpu(p->des0), x); +} + +static int enh_desc_get_rx_status_noext(struct stmmac_extra_stats *x, + struct dma_desc *p) +{ + unsigned int rdes0 = le32_to_cpu(p->des0); + int ret; + + ret = enh_desc_get_rx_basic_status(rdes0, x); + if (ret & (dma_own | rx_not_ls)) + return ret; + + /* Rx COE type 2 has been available since v3.30a. If it's synthesized + * into the GMAC the Bits 5, 7, and 0 state reflects the Rx COE + * outcome. The bits permutation had been available up to the DW GMAC + * v3.50a IP-core release which initially introduced the extended + * enhanced descriptors. + */ + return ret | com_desc_rx_coe_rdes0(!!(rdes0 & RDES0_IPC_CSUM_ERROR), + !!(rdes0 & RDES0_FRAME_TYPE), + !!(rdes0 & RDES0_PAYLOAD_CSUM_ERR)); +} + static void enh_desc_init_rx_desc(struct dma_desc *p, int disable_rx_ic, int mode, int end, int bfsize) { @@ -428,7 +413,7 @@ static void enh_desc_clear(struct dma_desc *p) const struct stmmac_desc_ops enh_desc_ops = { .tx_status = enh_desc_get_tx_status, - .rx_status = enh_desc_get_rx_status, + .rx_status = enh_desc_get_rx_status_nocoe, .get_tx_len = enh_desc_get_tx_len, .init_rx_desc = enh_desc_init_rx_desc, .init_tx_desc = enh_desc_init_tx_desc, @@ -449,3 +434,26 @@ const struct stmmac_desc_ops enh_desc_ops = { .set_addr = enh_desc_set_addr, .clear = enh_desc_clear, }; + +const struct stmmac_desc_ops enh_desc_noext_ops = { + .tx_status = enh_desc_get_tx_status, + .rx_status = enh_desc_get_rx_status_noext, + .get_tx_len = enh_desc_get_tx_len, + .init_rx_desc = enh_desc_init_rx_desc, + .init_tx_desc = enh_desc_init_tx_desc, + .get_tx_owner = enh_desc_get_tx_owner, + .release_tx_desc = enh_desc_release_tx_desc, + .prepare_tx_desc = enh_desc_prepare_tx_desc, + .set_tx_ic = enh_desc_set_tx_ic, + .get_tx_ls = enh_desc_get_tx_ls, + .set_tx_owner = enh_desc_set_tx_owner, + .set_rx_owner = enh_desc_set_rx_owner, + .get_rx_frame_len = enh_desc_get_rx_frame_len, + .enable_tx_timestamp = enh_desc_enable_tx_timestamp, + .get_tx_timestamp_status = enh_desc_get_tx_timestamp_status, + .get_timestamp = enh_desc_get_timestamp, + .get_rx_timestamp_status = enh_desc_get_rx_timestamp_status, + .display_ring = enh_desc_display_ring, + .set_addr = enh_desc_set_addr, + .clear = enh_desc_clear, +}; diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.c b/drivers/net/ethernet/stmicro/stmmac/hwif.c index 7e69ff4b9a98d..38cebde519304 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.c +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.c @@ -77,14 +77,14 @@ static int stmmac_dwmac1_quirks(struct stmmac_priv *priv) if (priv->synopsys_id >= DWMAC_CORE_3_50) { dev_info(priv->device, "Enabled extended descriptors\n"); priv->extend_desc = 1; + mac->desc = &enh_desc_ops; } else { dev_warn(priv->device, "Extended descriptors not supported\n"); + mac->desc = &enh_desc_noext_ops; } - - mac->desc = &enh_desc_ops; } else { dev_info(priv->device, "Normal descriptors\n"); - mac->desc = &ndesc_ops; + mac->desc = priv->plat->rx_coe ? &ndesc_rxcoe2_ops : &ndesc_ops; } stmmac_dwmac_mode_quirk(priv); diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index c15fa1cafdea4..ca362ab0f61d8 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -677,7 +677,9 @@ struct stmmac_regs_off { }; extern const struct stmmac_desc_ops enh_desc_ops; +extern const struct stmmac_desc_ops enh_desc_noext_ops; extern const struct stmmac_desc_ops ndesc_ops; +extern const struct stmmac_desc_ops ndesc_rxcoe2_ops; extern const struct stmmac_hwtimestamp stmmac_ptp; extern const struct stmmac_hwtimestamp dwmac1000_ptp; diff --git a/drivers/net/ethernet/stmicro/stmmac/norm_desc.c b/drivers/net/ethernet/stmicro/stmmac/norm_desc.c index 4fdf75c6bab05..fa95a1eeff5e7 100644 --- a/drivers/net/ethernet/stmicro/stmmac/norm_desc.c +++ b/drivers/net/ethernet/stmicro/stmmac/norm_desc.c @@ -64,10 +64,9 @@ static int ndesc_get_tx_len(struct dma_desc *p) * and, if required, updates the multicast statistics. * In case of success, it returns good_frame because the GMAC device * is supposed to be able to compute the csum in HW. */ -static int ndesc_get_rx_status(struct stmmac_extra_stats *x, - struct dma_desc *p) +static int ndesc_get_rx_basic_status(unsigned int rdes0, + struct stmmac_extra_stats *x) { - u32 rdes0 = le32_to_cpu(p->des0); int ret = good_frame; if (unlikely(rdes0 & RDES0_OWN)) @@ -85,8 +84,8 @@ static int ndesc_get_rx_status(struct stmmac_extra_stats *x, x->sa_filter_fail++; if (unlikely(rdes0 & RDES0_OVERFLOW_ERROR)) x->overflow_error++; - if (unlikely(rdes0 & RDES0_IPC_CSUM_ERROR)) - x->ipc_csum_error++; + if (unlikely(rdes0 & RDES0_GIANT_FRAME_ERROR)) + x->rx_length++; if (unlikely(rdes0 & RDES0_COLLISION)) { x->rx_collision++; } @@ -113,6 +112,31 @@ static int ndesc_get_rx_status(struct stmmac_extra_stats *x, return ret; } +static int ndesc_get_rx_status_nocoe(struct stmmac_extra_stats *x, + struct dma_desc *p) +{ + return ndesc_get_rx_basic_status(le32_to_cpu(p->des0), x); +} + +static int ndesc_get_rx_status_coe2(struct stmmac_extra_stats *x, + struct dma_desc *p) +{ + unsigned int rdes0 = le32_to_cpu(p->des0); + int ret; + + ret = ndesc_get_rx_basic_status(rdes0, x); + if (ret & (dma_own | rx_not_ls)) + return ret; + + /* Rx COE type 2 has been available since v3.30a. If it's synthesized + * into the GMAC the Bits 5, 7, and 0 state reflects the Rx COE + * outcome. + */ + return ret | com_desc_rx_coe_rdes0(!!(rdes0 & RDES0_IPC_CSUM_ERROR), + !!(rdes0 & RDES0_FRAME_TYPE), + !!(rdes0 & RDES0_PAYLOAD_CSUM_ERR)); +} + static void ndesc_init_rx_desc(struct dma_desc *p, int disable_rx_ic, int mode, int end, int bfsize) { @@ -279,7 +303,30 @@ static void ndesc_clear(struct dma_desc *p) const struct stmmac_desc_ops ndesc_ops = { .tx_status = ndesc_get_tx_status, - .rx_status = ndesc_get_rx_status, + .rx_status = ndesc_get_rx_status_nocoe, + .get_tx_len = ndesc_get_tx_len, + .init_rx_desc = ndesc_init_rx_desc, + .init_tx_desc = ndesc_init_tx_desc, + .get_tx_owner = ndesc_get_tx_owner, + .release_tx_desc = ndesc_release_tx_desc, + .prepare_tx_desc = ndesc_prepare_tx_desc, + .set_tx_ic = ndesc_set_tx_ic, + .get_tx_ls = ndesc_get_tx_ls, + .set_tx_owner = ndesc_set_tx_owner, + .set_rx_owner = ndesc_set_rx_owner, + .get_rx_frame_len = ndesc_get_rx_frame_len, + .enable_tx_timestamp = ndesc_enable_tx_timestamp, + .get_tx_timestamp_status = ndesc_get_tx_timestamp_status, + .get_timestamp = ndesc_get_timestamp, + .get_rx_timestamp_status = ndesc_get_rx_timestamp_status, + .display_ring = ndesc_display_ring, + .set_addr = ndesc_set_addr, + .clear = ndesc_clear, +}; + +const struct stmmac_desc_ops ndesc_rxcoe2_ops = { + .tx_status = ndesc_get_tx_status, + .rx_status = ndesc_get_rx_status_coe2, .get_tx_len = ndesc_get_tx_len, .init_rx_desc = ndesc_init_rx_desc, .init_tx_desc = ndesc_init_tx_desc, diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 8235401a8e3b9..1042a3657b2ac 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -5556,7 +5556,7 @@ read_again: if (priv->extend_desc) stmmac_rx_extended_status(priv, &priv->xstats, rx_q->dma_erx + entry); - if (unlikely(status == discard_frame)) { + if (unlikely(status & discard_frame)) { dirty++; error = 1; if (!priv->hwts_rx_en) @@ -5741,7 +5741,7 @@ read_again: if (priv->extend_desc) stmmac_rx_extended_status(priv, &priv->xstats, rx_q->dma_erx + entry); - if (unlikely(status == discard_frame)) { + if (unlikely(status & discard_frame)) { error = 1; if (!priv->hwts_rx_en) rx_errors++; -- cgit v1.2.3 From 604f7bd46bf8a5063b2bd6b8bf2c5007866b40c0 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sat, 31 May 2025 22:56:32 +0300 Subject: net: stmmac: dwmac1000: Fix improper Extended Enh descriptor support Indeed. The commit c24602ef8664 ("stmmac: support extend descriptors") added the Extended Enhanced Descriptor support to the STMMAC driver. The problem is that it was done in a nominal way, so the extended status actually reflecting for instance the Rx COE type 2 state has been merely utilized to collect for the incoming traffic statistics. The problem was that even though the driver claimed to support the most modern DW GMAC IP-cores (up to v3.73a) with the Extended Enhanced DMA descriptor in fact it didn't giving an expression that the driver works well in the Rx COE v2 status handling. Due to that the important status flags (like csum_none, etc) will be missed in the STMMAC core driver. Let's fix the inconsistency by taking into account the Rx COE v2 status in the Extended Enhanced Descriptor when it's relevant: DW GMAC IP-core higher than v3.50a and there is the Advanced Time Stamp or Rx COE v2 features. Fixes: c24602ef8664 ("stmmac: support extend descriptors") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/descs.h | 2 + drivers/net/ethernet/stmicro/stmmac/enh_desc.c | 191 +++++++++++++++------- drivers/net/ethernet/stmicro/stmmac/hwif.c | 15 +- drivers/net/ethernet/stmicro/stmmac/hwif.h | 5 +- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 5 - 5 files changed, 143 insertions(+), 75 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/descs.h b/drivers/net/ethernet/stmicro/stmmac/descs.h index acb765fbaee21..22e129e524b7b 100644 --- a/drivers/net/ethernet/stmicro/stmmac/descs.h +++ b/drivers/net/ethernet/stmicro/stmmac/descs.h @@ -164,6 +164,8 @@ struct dma_extended_desc { __le32 des6; /* Tx/Rx Timestamp Low */ __le32 des7; /* Tx/Rx Timestamp High */ }; +#define to_dma_extended_desc(_basic) \ + container_of(_basic, struct dma_extended_desc, basic) /* Enhanced descriptor for TBS */ struct dma_edesc { diff --git a/drivers/net/ethernet/stmicro/stmmac/enh_desc.c b/drivers/net/ethernet/stmicro/stmmac/enh_desc.c index 56f430f69ffc6..5e0ef3558a5e3 100644 --- a/drivers/net/ethernet/stmicro/stmmac/enh_desc.c +++ b/drivers/net/ethernet/stmicro/stmmac/enh_desc.c @@ -75,68 +75,92 @@ static int enh_desc_get_tx_len(struct dma_desc *p) return (le32_to_cpu(p->des1) & ETDES1_BUFFER1_SIZE_MASK); } -static void enh_desc_get_ext_status(struct stmmac_extra_stats *x, - struct dma_extended_desc *p) +static int enh_desc_rx_ext_status(unsigned int rdes4, + struct stmmac_extra_stats *x) { - u32 rdes0 = le32_to_cpu(p->basic.des0); - u32 rdes4 = le32_to_cpu(p->des4); - - if (unlikely(rdes0 & ERDES0_RX_MAC_ADDR)) { - int message_type = FIELD_GET(ERDES4_MSG_TYPE_MASK, rdes4); - - if (rdes4 & ERDES4_IP_HDR_ERR) - x->ip_hdr_err++; - if (rdes4 & ERDES4_IP_PAYLOAD_ERR) - x->ip_payload_err++; - if (rdes4 & ERDES4_IP_CSUM_BYPASSED) - x->ip_csum_bypassed++; - if (rdes4 & ERDES4_IPV4_PKT_RCVD) - x->ipv4_pkt_rcvd++; - if (rdes4 & ERDES4_IPV6_PKT_RCVD) - x->ipv6_pkt_rcvd++; - - if (message_type == RDES_EXT_NO_PTP) - x->no_ptp_rx_msg_type_ext++; - else if (message_type == RDES_EXT_SYNC) - x->ptp_rx_msg_type_sync++; - else if (message_type == RDES_EXT_FOLLOW_UP) - x->ptp_rx_msg_type_follow_up++; - else if (message_type == RDES_EXT_DELAY_REQ) - x->ptp_rx_msg_type_delay_req++; - else if (message_type == RDES_EXT_DELAY_RESP) - x->ptp_rx_msg_type_delay_resp++; - else if (message_type == RDES_EXT_PDELAY_REQ) - x->ptp_rx_msg_type_pdelay_req++; - else if (message_type == RDES_EXT_PDELAY_RESP) - x->ptp_rx_msg_type_pdelay_resp++; - else if (message_type == RDES_EXT_PDELAY_FOLLOW_UP) - x->ptp_rx_msg_type_pdelay_follow_up++; - else if (message_type == RDES_PTP_ANNOUNCE) - x->ptp_rx_msg_type_announce++; - else if (message_type == RDES_PTP_MANAGEMENT) - x->ptp_rx_msg_type_management++; - else if (message_type == RDES_PTP_PKT_RESERVED_TYPE) - x->ptp_rx_msg_pkt_reserved_type++; - - if (rdes4 & ERDES4_PTP_FRAME_TYPE) - x->ptp_frame_type++; - if (rdes4 & ERDES4_PTP_VER) - x->ptp_ver++; - if (rdes4 & ERDES4_TIMESTAMP_DROPPED) - x->timestamp_dropped++; - if (rdes4 & ERDES4_AV_PKT_RCVD) - x->av_pkt_rcvd++; - if (rdes4 & ERDES4_AV_TAGGED_PKT_RCVD) - x->av_tagged_pkt_rcvd++; - if (rdes4 & ERDES4_VLAN_TAG_PRI_VAL_MASK) - x->vlan_tag_priority_val++; - if (rdes4 & ERDES4_L3_FILTER_MATCH) - x->l3_filter_match++; - if (rdes4 & ERDES4_L4_FILTER_MATCH) - x->l4_filter_match++; - if (rdes4 & ERDES4_L3_L4_FILT_NO_MATCH_MASK) - x->l3_l4_filter_no_match++; + int message_type = FIELD_GET(ERDES4_MSG_TYPE_MASK, rdes4); + int ret = good_frame; + + if (rdes4 & ERDES4_IP_HDR_ERR) { + x->ip_hdr_err++; + ret = discard_frame; + } + + if (rdes4 & ERDES4_IP_PAYLOAD_ERR) { + x->ip_payload_err++; + ret = discard_frame; } + + if (rdes4 & ERDES4_IP_CSUM_BYPASSED) { + x->ip_csum_bypassed++; + ret = csum_none; + } + + if (rdes4 & ERDES4_IPV4_PKT_RCVD) + x->ipv4_pkt_rcvd++; + else if (rdes4 & ERDES4_IPV6_PKT_RCVD) + x->ipv6_pkt_rcvd++; + else /* Not an IP packet or COE is disabled */ + ret = csum_none; + + switch (message_type) { + case RDES_EXT_NO_PTP: + x->no_ptp_rx_msg_type_ext++; + break; + case RDES_EXT_SYNC: + x->ptp_rx_msg_type_sync++; + break; + case RDES_EXT_FOLLOW_UP: + x->ptp_rx_msg_type_follow_up++; + break; + case RDES_EXT_DELAY_REQ: + x->ptp_rx_msg_type_delay_req++; + break; + case RDES_EXT_DELAY_RESP: + x->ptp_rx_msg_type_delay_resp++; + break; + case RDES_EXT_PDELAY_REQ: + x->ptp_rx_msg_type_pdelay_req++; + break; + case RDES_EXT_PDELAY_RESP: + x->ptp_rx_msg_type_pdelay_resp++; + break; + case RDES_EXT_PDELAY_FOLLOW_UP: + x->ptp_rx_msg_type_pdelay_follow_up++; + break; + case RDES_PTP_ANNOUNCE: + x->ptp_rx_msg_type_announce++; + break; + case RDES_PTP_MANAGEMENT: + x->ptp_rx_msg_type_management++; + break; + case RDES_PTP_PKT_RESERVED_TYPE: + x->ptp_rx_msg_pkt_reserved_type++; + break; + default: + break; + } + + if (rdes4 & ERDES4_PTP_FRAME_TYPE) + x->ptp_frame_type++; + if (rdes4 & ERDES4_PTP_VER) + x->ptp_ver++; + if (rdes4 & ERDES4_TIMESTAMP_DROPPED) + x->timestamp_dropped++; + if (rdes4 & ERDES4_AV_PKT_RCVD) + x->av_pkt_rcvd++; + if (rdes4 & ERDES4_AV_TAGGED_PKT_RCVD) + x->av_tagged_pkt_rcvd++; + if (rdes4 & ERDES4_VLAN_TAG_PRI_VAL_MASK) + x->vlan_tag_priority_val++; + if (rdes4 & ERDES4_L3_FILTER_MATCH) + x->l3_filter_match++; + if (rdes4 & ERDES4_L4_FILTER_MATCH) + x->l4_filter_match++; + if (rdes4 & ERDES4_L3_L4_FILT_NO_MATCH_MASK) + x->l3_l4_filter_no_match++; + + return ret; } static int enh_desc_get_rx_basic_status(unsigned int rdes0, @@ -228,6 +252,29 @@ static int enh_desc_get_rx_status_noext(struct stmmac_extra_stats *x, !!(rdes0 & RDES0_PAYLOAD_CSUM_ERR)); } +static int enh_desc_get_rx_status_ext(struct stmmac_extra_stats *x, + struct dma_desc *p) +{ + unsigned int rdes4 = le32_to_cpu(to_dma_extended_desc(p)->des4); + unsigned int rdes0 = le32_to_cpu(p->des0); + int ret; + + ret = enh_desc_get_rx_basic_status(rdes0, x); + if (ret & (dma_own | rx_not_ls)) + return ret; + + /* In DW GMAC v3.50a IP-core the Rx COE type 2 status was moved to + * the extended part of the enhanced descriptor (RDES4). The RDES0 + * 5, 7, and 0 bits permutation has been no longer relevant. + */ + if (rdes0 & ERDES0_RX_MAC_ADDR) + ret |= enh_desc_rx_ext_status(rdes4, x); + else /* No extended part so no CSUM/Timestamp processed */ + ret |= csum_none; + + return ret; +} + static void enh_desc_init_rx_desc(struct dma_desc *p, int disable_rx_ic, int mode, int end, int bfsize) { @@ -425,7 +472,6 @@ const struct stmmac_desc_ops enh_desc_ops = { .set_tx_owner = enh_desc_set_tx_owner, .set_rx_owner = enh_desc_set_rx_owner, .get_rx_frame_len = enh_desc_get_rx_frame_len, - .rx_extended_status = enh_desc_get_ext_status, .enable_tx_timestamp = enh_desc_enable_tx_timestamp, .get_tx_timestamp_status = enh_desc_get_tx_timestamp_status, .get_timestamp = enh_desc_get_timestamp, @@ -457,3 +503,26 @@ const struct stmmac_desc_ops enh_desc_noext_ops = { .set_addr = enh_desc_set_addr, .clear = enh_desc_clear, }; + +const struct stmmac_desc_ops enh_desc_ext_ops = { + .tx_status = enh_desc_get_tx_status, + .rx_status = enh_desc_get_rx_status_ext, + .get_tx_len = enh_desc_get_tx_len, + .init_rx_desc = enh_desc_init_rx_desc, + .init_tx_desc = enh_desc_init_tx_desc, + .get_tx_owner = enh_desc_get_tx_owner, + .release_tx_desc = enh_desc_release_tx_desc, + .prepare_tx_desc = enh_desc_prepare_tx_desc, + .set_tx_ic = enh_desc_set_tx_ic, + .get_tx_ls = enh_desc_get_tx_ls, + .set_tx_owner = enh_desc_set_tx_owner, + .set_rx_owner = enh_desc_set_rx_owner, + .get_rx_frame_len = enh_desc_get_rx_frame_len, + .enable_tx_timestamp = enh_desc_enable_tx_timestamp, + .get_tx_timestamp_status = enh_desc_get_tx_timestamp_status, + .get_timestamp = enh_desc_get_timestamp, + .get_rx_timestamp_status = enh_desc_get_rx_timestamp_status, + .display_ring = enh_desc_display_ring, + .set_addr = enh_desc_set_addr, + .clear = enh_desc_clear, +}; diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.c b/drivers/net/ethernet/stmicro/stmmac/hwif.c index 38cebde519304..bc1e585c854d8 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.c +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.c @@ -74,13 +74,18 @@ static int stmmac_dwmac1_quirks(struct stmmac_priv *priv) dev_info(priv->device, "Enhanced/Alternate descriptors\n"); /* GMAC older than 3.50 has no extended descriptors */ - if (priv->synopsys_id >= DWMAC_CORE_3_50) { - dev_info(priv->device, "Enabled extended descriptors\n"); + if (priv->synopsys_id < DWMAC_CORE_3_50) { + dev_warn(priv->device, "Extended descriptors not supported\n"); + mac->desc = priv->plat->rx_coe ? + &enh_desc_noext_ops : &enh_desc_ops; + } else if (priv->dma_cap.atime_stamp || priv->plat->rx_coe) { + dev_info(priv->device, "Extended descriptors enabled\n"); priv->extend_desc = 1; - mac->desc = &enh_desc_ops; + mac->desc = &enh_desc_ext_ops; } else { - dev_warn(priv->device, "Extended descriptors not supported\n"); - mac->desc = &enh_desc_noext_ops; + dev_info(priv->device, "Extended descriptors disabled\n"); + priv->extend_desc = 0; + mac->desc = &enh_desc_ops; } } else { dev_info(priv->device, "Normal descriptors\n"); diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index ca362ab0f61d8..bf8138d75a133 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -74,8 +74,6 @@ struct stmmac_desc_ops { /* Return the reception status looking at the RDES1 */ int (*rx_status)(struct stmmac_extra_stats *x, struct dma_desc *p); - void (*rx_extended_status)(struct stmmac_extra_stats *x, - struct dma_extended_desc *p); /* Set tx timestamp enable bit */ void (*enable_tx_timestamp) (struct dma_desc *p); /* get tx timestamp status */ @@ -137,8 +135,6 @@ struct stmmac_desc_ops { stmmac_do_callback(__priv, desc, get_rx_frame_len, __args) #define stmmac_rx_status(__priv, __args...) \ stmmac_do_callback(__priv, desc, rx_status, __args) -#define stmmac_rx_extended_status(__priv, __args...) \ - stmmac_do_void_callback(__priv, desc, rx_extended_status, __args) #define stmmac_enable_tx_timestamp(__priv, __args...) \ stmmac_do_void_callback(__priv, desc, enable_tx_timestamp, __args) #define stmmac_get_tx_timestamp_status(__priv, __args...) \ @@ -678,6 +674,7 @@ struct stmmac_regs_off { extern const struct stmmac_desc_ops enh_desc_ops; extern const struct stmmac_desc_ops enh_desc_noext_ops; +extern const struct stmmac_desc_ops enh_desc_ext_ops; extern const struct stmmac_desc_ops ndesc_ops; extern const struct stmmac_desc_ops ndesc_rxcoe2_ops; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 1042a3657b2ac..446439f5dbfdf 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -5553,9 +5553,6 @@ read_again: if (!buf->xdp) break; - if (priv->extend_desc) - stmmac_rx_extended_status(priv, &priv->xstats, - rx_q->dma_erx + entry); if (unlikely(status & discard_frame)) { dirty++; error = 1; @@ -5739,8 +5736,6 @@ read_again: prefetch(np); - if (priv->extend_desc) - stmmac_rx_extended_status(priv, &priv->xstats, rx_q->dma_erx + entry); if (unlikely(status & discard_frame)) { error = 1; if (!priv->hwts_rx_en) -- cgit v1.2.3 From 4b09925e77834b182aef2937871e0bf35df01a34 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Tue, 3 Jun 2025 00:04:31 +0300 Subject: net: stmmac: dwxgmac2: Add csum_none Rx-descriptor status support Currently the stmmac_desc_ops::rx_status() method always returns "Good frame" status for any non-erroneous frame including the one for which the IP-checksums weren't verified. Generally speaking this isn't right because it doesn't give an opportunity for the upper software layer to detect the IP csum status for sure. Instead the STMMAC core driver has to implement an heuristic method like stmmac_has_ip_ethertype() which is not only incomplete but also causes a false-positive CSUM verification status passed up to the networking core. Let's fix that in the DW XGMAC module as a preparation before a complete fix of the commit c2945c435c99 ("net: stmmac: Prevent DSA tags from breaking COE") is provided. Fixes: 874dfb65a484 ("net: stmmac: Add descriptor related callbacks for XGMAC2") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h | 1 + drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h index fc15776bd8f6f..3e9d0cf884222 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h @@ -511,6 +511,7 @@ #define XGMAC_RDES3_CDA BIT(27) #define XGMAC_RDES3_RSV BIT(26) #define XGMAC_RDES3_L34T GENMASK(23, 20) +#define XGMAC_L34T_NOTIP 0x0 #define XGMAC_L34T_IP4TCP 0x1 #define XGMAC_L34T_IP4UDP 0x2 #define XGMAC_L34T_IP4ICMP 0x3 diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c index 029fbd24ded87..8109421a0b784 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c @@ -85,14 +85,24 @@ static int dwxgmac2_get_rx_status(struct stmmac_extra_stats *x, l34t = FIELD_GET(XGMAC_RDES3_L34T, rdes3); switch (l34t) { - case XGMAC_L34T_IP4TCP ... XGMAC_L34T_IP4IGMP: case XGMAC_L34T_IP4UNKNOWN: + x->ip_csum_bypassed++; + ret |= csum_none; + fallthrough; + case XGMAC_L34T_IP4TCP ... XGMAC_L34T_IP4IGMP: x->ipv4_pkt_rcvd++; break; - case XGMAC_L34T_IP6TCP ... XGMAC_L34T_IP6ICMP: case XGMAC_L34T_IP6UNKNOWN: + x->ip_csum_bypassed++; + ret |= csum_none; + fallthrough; + case XGMAC_L34T_IP6TCP ... XGMAC_L34T_IP6ICMP: x->ipv6_pkt_rcvd++; break; + case XGMAC_L34T_NOTIP: + x->ip_csum_bypassed++; + ret |= csum_none; + break; } if (unlikely(rdes2 & XGMAC_RDES2_SAF)) -- cgit v1.2.3 From df01b42941eda1c8a2d1685f6e986df5490af762 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Tue, 3 Jun 2025 13:06:46 +0300 Subject: net: stmmac: dwmac4: Add csum Rx-descriptor status check Currently the stmmac_desc_ops::rx_status() method always returns "Good frame" status for the IP-packets with erroneous checksum. Here is what DW QoS Eth databook says about the Rx COE flags in the Rx DMA-descriptor: "RDES0.IPCE IP Payload Error ... Bit 15 (ES) of RDES3 is not set when this bit is set." [1] Thus the "Discard frame" status won't be returned from the stmmac_desc_ops::rx_status() method for the frames failed to pass the control-sum check. Instead such packets will be passed to the upper software layers with the "checksum unnecessary" status, which makes the upper layer being unaware of the packets inconsistency. This is definitely a bug. Moreover the "Good frame" is returned for the unverified frames too (unsupported by the Rx Checksum Offload engine). This isn't that much problematic as the bug but generally speaking this is also incorrect because it doesn't give an opportunity for the upper software layer to detect the IP csum status for sure. Instead the STMMAC core driver has to implement an heuristic method like stmmac_has_ip_ethertype() which is not only incomplete but also causes a false-positive CSUM verification status passed up to the networking core. Let's fix all of the problems above in the DW QoS Eth (GMAC4+) as a preparation before a complete fix of the commit c2945c435c99 ("net: stmmac: Prevent DSA tags from breaking COE") is provided. Fixes: 753a71090f33 ("stmmac: add descriptors function for GMAC 4.xx") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c index 3ff30bc9f8c22..2c741bdae6fa2 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c @@ -106,18 +106,22 @@ static int dwmac4_wrback_get_rx_status(struct stmmac_extra_stats *x, if (rdes1 & RDES1_IP_HDR_ERROR) { x->ip_hdr_err++; - ret |= csum_none; + ret = discard_frame; + } + if (rdes1 & RDES1_IP_PAYLOAD_ERROR) { + x->ip_payload_err++; + ret = discard_frame; } - if (rdes1 & RDES1_IP_CSUM_BYPASSED) + if (rdes1 & RDES1_IP_CSUM_BYPASSED) { x->ip_csum_bypassed++; + ret |= csum_none; + } if (rdes1 & RDES1_IPV4_HEADER) x->ipv4_pkt_rcvd++; - if (rdes1 & RDES1_IPV6_HEADER) + else if (rdes1 & RDES1_IPV6_HEADER) x->ipv6_pkt_rcvd++; - if (rdes1 & RDES1_IP_PAYLOAD_ERROR) { - x->ip_payload_err++; + else /* Not an IP packet or COE is disabled */ ret |= csum_none; - } if (message_type == RDES_EXT_NO_PTP) x->no_ptp_rx_msg_type_ext++; -- cgit v1.2.3 From 6f7c7e07cd279f8d4d1eb3c887b6477df1273c09 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Tue, 3 Jun 2025 16:11:14 +0300 Subject: net: stmmac: Fix incorrect IP-csum status report Currently the IP checksum verification status is incorrectly set to CHECKSUM_UNNECESSARY for all IPv4/IPv6 packets as per the stmmac_has_ip_ethertype() method implementation. It's wrong since the Rx COE embedded into the DW *MAC IP-cores supports the TCP/UDP/ICMP protocols only [1,2,3,4]. From that perspective the commit c2945c435c99 ("net: stmmac: Prevent DSA tags from breaking COE") was close to solving the denoted problem, but didn't provide a comprehensive solution. Since all the low-level driver sub-modules now support reporting the CSUM verification status from Rx DMA-descriptor let's use it to correctly set the IP-sum status in the Rx-frames SKB. [1] DesignWare Cores Ethernet MAC Universal Databook, Revision 3.50a, February 2009, p. 114. [2] DesignWare Cores Ethernet MAC Universal Databook, Revision 3.73a, October 2013, p. 189. [3] DesignWare Cores Ethernet Quality-of-Service Databook, Revision 5.20a, April 2020, pp. 368. [4] DesignWare Cores XGMAC - 10G Ethernet MAC Databook, Revision 3.20a, September 2022, p.253. [n] ... Fixes: c2945c435c99 ("net: stmmac: Prevent DSA tags from breaking COE") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 53 +++++++++++++++++------ 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 446439f5dbfdf..d74927fbf148d 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -4985,6 +4985,42 @@ static void stmmac_rx_vlan(struct net_device *dev, struct sk_buff *skb) } } +/** + * stmmac_get_rx_csum - Retrieve Rx CSUM status + * @priv: driver private structure + * @status: frame Rx-status + * @skb : the socket buffer + * Description : + * This function will initialize the SKB IP summed based on the Rx COE + * outcomes. + */ +static void stmmac_get_rx_csum(struct stmmac_priv *priv, int status, + struct sk_buff *skb) +{ + bool coe = priv->dev->features & NETIF_F_RXCSUM; + + /* If Rx COE wasn't synthesized into the IP-core the status may never + * have the csum_none flag. Skip the status check then to be on a safe + * side. + */ + if (!coe || (status & csum_none)) { + skb_checksum_none_assert(skb); + return; + } + + /* Rx COE feature is relatively similar on the late DW GMAC IP-cores + * (v3.30a and newer) and on the DW QoS Ether and DW XGMAC/XLGMAC/etc + * IP-cores except that the later ones support double tagged + * IP-packets: + * Rx COE type 2: C-(S-)VLAN + IPv4/IPv6 + TCP/UDP/ICMP + * Rx COE enh: 2xC-(S-)VLAN + IPv4/IPv6 + TCP/UDP/ICMP + * + * In any way the outcomes of the Rx COE work must be retrieved from + * the Rx DMA-descriptor. + */ + __skb_incr_checksum_unnecessary(skb); +} + /** * stmmac_rx_refill - refill used skb preallocated buffers * @priv: driver private structure @@ -5357,11 +5393,10 @@ static struct sk_buff *stmmac_construct_skb_zc(struct stmmac_channel *ch, static void stmmac_dispatch_skb_zc(struct stmmac_priv *priv, u32 queue, struct dma_desc *p, struct dma_desc *np, - struct xdp_buff *xdp) + int status, struct xdp_buff *xdp) { struct stmmac_rxq_stats *rxq_stats = &priv->xstats.rxq_stats[queue]; struct stmmac_channel *ch = &priv->channel[queue]; - int coe = priv->dev->features & NETIF_F_RXCSUM; unsigned int len = xdp->data_end - xdp->data; enum pkt_hash_types hash_type; struct sk_buff *skb; @@ -5382,10 +5417,7 @@ static void stmmac_dispatch_skb_zc(struct stmmac_priv *priv, u32 queue, stmmac_rx_vlan(priv->dev, skb); skb->protocol = eth_type_trans(skb, priv->dev); - if (unlikely(!coe) || !stmmac_has_ip_ethertype(skb)) - skb_checksum_none_assert(skb); - else - skb->ip_summed = CHECKSUM_UNNECESSARY; + stmmac_get_rx_csum(priv, status, skb); if (!stmmac_get_rx_hash(priv, p, &hash, &hash_type)) skb_set_hash(skb, hash, hash_type); @@ -5598,7 +5630,7 @@ read_again: switch (res) { case STMMAC_XDP_PASS: - stmmac_dispatch_skb_zc(priv, queue, p, np, buf->xdp); + stmmac_dispatch_skb_zc(priv, queue, p, np, status, buf->xdp); xsk_buff_free(buf->xdp); break; case STMMAC_XDP_CONSUMED: @@ -5659,7 +5691,6 @@ static int stmmac_rx(struct stmmac_priv *priv, int limit, u32 queue) struct stmmac_rxq_stats *rxq_stats = &priv->xstats.rxq_stats[queue]; struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue]; struct stmmac_channel *ch = &priv->channel[queue]; - int coe = priv->dev->features & NETIF_F_RXCSUM; unsigned int count = 0, error = 0, len = 0; unsigned int next_entry = rx_q->cur_rx; enum dma_data_direction dma_dir; @@ -5892,11 +5923,7 @@ drain_data: skb->protocol = eth_type_trans(skb, priv->dev); - if (unlikely(!coe) || !stmmac_has_ip_ethertype(skb) || - (status & csum_none)) - skb_checksum_none_assert(skb); - else - skb->ip_summed = CHECKSUM_UNNECESSARY; + stmmac_get_rx_csum(priv, status, skb); if (!stmmac_get_rx_hash(priv, p, &hash, &hash_type)) skb_set_hash(skb, hash, hash_type); -- cgit v1.2.3 From 2a2b01159b6610a1bf7137f8700921ec46efd846 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 4 Jun 2025 21:44:37 +0300 Subject: net: stmmac: Fix incorrect IP-csum insertion algo Currently the IP checksum insertion on Tx is implied to be performed by HW for any IPv4/IPv6 network protocol types. It's definitely incorrect since based on [1,2,3] Tx COE supports the TCP/UDP/ICMP payloads only. Moreover the IPv6 extension headers are limited to be a single Hop-by-Hop and multiple Destination headers. From that perspective the commit c2945c435c99 ("net: stmmac: Prevent DSA tags from breaking COE") wasn't fully correct in providing just IP-type constraint. Let's fix the problem by providing a comprehensive method which would make sure that the passed frame can be checksummed by the controller Tx Checksum Offload Engine. [1] DesignWare Cores Ethernet MAC Universal Databook, Revision 3.73a, October 2013, p. 187. [2] DesignWare Cores Ethernet Quality-of-Service Databook, Revision 5.20a, April 2020, pp. 366. [3] DesignWare Cores XGMAC - 10G Ethernet MAC Databook, Revision 3.20a, September 2022, p.251. Fixes: c2945c435c99 ("net: stmmac: Prevent DSA tags from breaking COE") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 137 +++++++++++++++------- 1 file changed, 95 insertions(+), 42 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index d74927fbf148d..f0d395b863199 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -41,6 +42,7 @@ #include #include #include +#include #include #include #include @@ -4308,6 +4310,91 @@ static bool stmmac_vlan_insert(struct stmmac_priv *priv, struct sk_buff *skb, return true; } +/** + * stmmac_csum_insert - Insert CSUM into the frame + * @priv: driver private structure + * @skb : the socket buffer + * @queue: TX queue index + * Description : + * This function will either insert all the uninitialized checksum into the + * frame or return a request for the Tx COE to do that if it's supported. + * + * Return: zero if CSUM was inserted into the frame, one if Tx COE is supposed + * to do that, negative error otherwise. + */ +static int stmmac_csum_insert(struct stmmac_priv *priv, struct sk_buff *skb, + int queue) +{ + struct ipv6_opt_hdr *hp; + __be16 l3proto; + int depth = 0; + u8 l4proto; + + /* No checksum required */ + if (!(skb->ip_summed == CHECKSUM_PARTIAL)) + return 0; + + /* Tx COE might be available on a limited set of the queues */ + if (priv->plat->tx_queues_cfg[queue].coe_unsupported) + goto sw_checksum; + + /* Tx COE supports Ethernet II frames only (EtherType >= 0x0600) */ + l3proto = eth_header_parse_protocol(skb); + if (!eth_proto_is_802_3(l3proto)) + goto sw_checksum; + + /* Tx COE supports C-/S-VLAN tagged IP-packets (excl. hwaccel tag?) */ + l3proto = __vlan_get_protocol(skb, l3proto, &depth); + if (depth > (ETH_HLEN + VLAN_HLEN)) + goto sw_checksum; + + /* Tx COE supports IPv4/IPv6 types with TCP/UDP/ICMP protocols */ + if ((priv->dev->features & NETIF_F_IP_CSUM) && + (l3proto == htons(ETH_P_IP))) { + l4proto = ip_hdr(skb)->protocol; + + switch(l4proto) { + case IPPROTO_ICMP: + case IPPROTO_TCP: + case IPPROTO_UDP: + return 1; + default: + goto sw_checksum; + } + } else if ((priv->dev->features & NETIF_F_IPV6_CSUM) && + (l3proto == htons(ETH_P_IPV6))) { + l4proto = ipv6_hdr(skb)->nexthdr; + depth = sizeof(struct ipv6hdr); + + /* Tx COE supports single Hop-by-Hop and multiple Dest headers */ + do { + switch(l4proto) { + case NEXTHDR_TCP: + case NEXTHDR_UDP: + case NEXTHDR_ICMP: + return 1; + case NEXTHDR_HOP: + if (depth != sizeof(struct ipv6hdr)) + goto sw_checksum; + break; + case NEXTHDR_DEST: + break; + default: + goto sw_checksum; + } + + hp = (struct ipv6_opt_hdr *)(skb_network_header(skb) + + depth); + l4proto = hp->nexthdr; + depth += ipv6_optlen(hp); + } while (l4proto != NEXTHDR_NONE); + } + +sw_checksum: + return unlikely(skb_csum_is_sctp(skb)) ? + skb_crc32c_csum_help(skb) : skb_checksum_help(skb); +} + /** * stmmac_tso_allocator - close entry point of the driver * @priv: driver private structure @@ -4662,28 +4749,6 @@ flush_ring: return ret; } -/** - * stmmac_has_ip_ethertype() - Check if packet has IP ethertype - * @skb: socket buffer to check - * - * Check if a packet has an ethertype that will trigger the IP header checks - * and IP/TCP checksum engine of the stmmac core. - * - * Return: true if the ethertype can trigger the checksum engine, false - * otherwise - */ -static bool stmmac_has_ip_ethertype(struct sk_buff *skb) -{ - int depth = 0; - __be16 proto; - - proto = __vlan_get_protocol(skb, eth_header_parse_protocol(skb), - &depth); - - return (depth <= ETH_HLEN) && - (proto == htons(ETH_P_IP) || proto == htons(ETH_P_IPV6)); -} - /** * stmmac_xmit - Tx entry point of the driver * @skb : the socket buffer @@ -4755,22 +4820,11 @@ static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev) first_entry = entry; WARN_ON(tx_q->tx_skbuff[first_entry]); - csum_insertion = (skb->ip_summed == CHECKSUM_PARTIAL); - /* DWMAC IPs can be synthesized to support tx coe only for a few tx - * queues. In that case, checksum offloading for those queues that don't - * support tx coe needs to fallback to software checksum calculation. - * - * Packets that won't trigger the COE e.g. most DSA-tagged packets will - * also have to be checksummed in software. - */ - if (csum_insertion && - (priv->plat->tx_queues_cfg[queue].coe_unsupported || - !stmmac_has_ip_ethertype(skb))) { - if (unlikely(skb_checksum_help(skb))) { - drop = true; - goto flush_ring; - } - csum_insertion = !csum_insertion; + /* Check if CSUM should be inserted by HW */ + csum_insertion = stmmac_csum_insert(priv, skb, queue); + if (csum_insertion < 0) { + drop = true; + goto flush_ring; } if (likely(priv->extend_desc)) @@ -6178,9 +6232,6 @@ static netdev_features_t stmmac_fix_features(struct net_device *dev, { struct stmmac_priv *priv = netdev_priv(dev); - if (!priv->plat->tx_coe) - features &= ~NETIF_F_CSUM_MASK; - /* Some GMAC devices have a bugged Jumbo frame support that * needs to have the Tx COE disabled for oversized frames * (due to limited buffer sizes). In this case we disable @@ -8258,10 +8309,12 @@ static int __stmmac_dvr_probe(struct device *device, ndev->xdp_metadata_ops = &stmmac_xdp_metadata_ops; ndev->xsk_tx_metadata_ops = &stmmac_xsk_tx_metadata_ops; - ndev->hw_features = NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM; ndev->xdp_features = NETDEV_XDP_ACT_BASIC | NETDEV_XDP_ACT_REDIRECT | NETDEV_XDP_ACT_XSK_ZEROCOPY; + if (priv->plat->tx_coe) + ndev->hw_features |= NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM; + if (priv->plat->rx_coe) ndev->hw_features |= NETIF_F_RXCSUM; -- cgit v1.2.3 From c0a6524f98d39f6313d912130e4ebcd444f29d4a Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 15 Aug 2025 23:36:14 +0300 Subject: net: stmmac: Add RXALL feature support All except the ancient DW MAC100 controllers support permitting the erroneous frames to be delivered to the system memory instead of discarding them. The only that needs to be done is to fix the Rx DMA-descriptor status getters to return the error status and to enable the controller' DMA to stop dropping such the frames. It's done by setting up the flags DMA_CONTROL.DT (disable dropping the TCP/UDP/IP frames with incorrect csum), DMA_CONTROL.FEF (forward frames with CRC error, collision error, GMII_ER, giant frame, watchdog timeouted, or cut off due to the FIFO overflow), DMA_CONTROL.FUF (forward undesized good frames). Of course the NETIF_F_RXALL feature will be disabled by default and will be enabled upon the user request by the setting up the denoted flags in the net_device::ndo_set_features() callback. Signed-off-by: Serge Semin --- The DMA_CONTROL.DGF (drop giant frames) flag setup will be added later. Until then the Giant frames won't dropped in anyway unless they exceed the critical size causing the WDT-based truncation. --- drivers/net/ethernet/stmicro/stmmac/common.h | 5 ++ drivers/net/ethernet/stmicro/stmmac/descs_com.h | 6 +-- drivers/net/ethernet/stmicro/stmmac/dwmac1000.h | 4 +- .../net/ethernet/stmicro/stmmac/dwmac1000_dma.c | 9 +++- drivers/net/ethernet/stmicro/stmmac/dwmac4.h | 2 + drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c | 39 ++++++++------ drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c | 11 ++-- drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h | 3 ++ .../net/ethernet/stmicro/stmmac/dwxgmac2_descs.c | 10 +++- drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c | 9 +++- drivers/net/ethernet/stmicro/stmmac/enh_desc.c | 48 ++++++++++------- drivers/net/ethernet/stmicro/stmmac/hwif.h | 5 +- drivers/net/ethernet/stmicro/stmmac/norm_desc.c | 46 +++++++++++----- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 62 ++++++++++++++++++---- 14 files changed, 186 insertions(+), 73 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/common.h b/drivers/net/ethernet/stmicro/stmmac/common.h index 02df8523111ab..82ae61ccfd789 100644 --- a/drivers/net/ethernet/stmicro/stmmac/common.h +++ b/drivers/net/ethernet/stmicro/stmmac/common.h @@ -362,6 +362,11 @@ enum rx_frame_status { llc_snap = 0x4, dma_own = 0x8, rx_not_ls = 0x10, + len_err = 0x20, /* Under/Oversized frames */ + csum_err = 0x40, /* Control sums check failure */ + proto_err = 0x80, /* L1 protocol failures */ + cutoff_err = 0x100, /* Frame' cut off due to HW failures */ + rx_err = len_err | csum_err | proto_err | cutoff_err, }; /* Tx status */ diff --git a/drivers/net/ethernet/stmicro/stmmac/descs_com.h b/drivers/net/ethernet/stmicro/stmmac/descs_com.h index 7cb8c20fbe6f2..d5c6471840469 100644 --- a/drivers/net/ethernet/stmicro/stmmac/descs_com.h +++ b/drivers/net/ethernet/stmicro/stmmac/descs_com.h @@ -154,11 +154,11 @@ static inline int com_desc_rx_coe_rdes0(int ipc_err, int type, int payload_err) case 0x4: return good_frame; case 0x5: - return discard_frame; + return csum_err; case 0x6: - return discard_frame; + return csum_err; case 0x7: - return discard_frame; + return csum_err; default: return csum_none; } diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000.h b/drivers/net/ethernet/stmicro/stmmac/dwmac1000.h index 9fe639fb06bb3..2a51768cf64f2 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac1000.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000.h @@ -172,7 +172,7 @@ enum rx_tx_priority_ratio { /* DMA CRS Control and Status Register Mapping */ /* DMA operation mode defines (start/stop tx/rx are placed in common header)*/ -/* Disable Drop TCP/IP csum error */ +#define DMA_CONTROL_DT 0x04000000 /* Disable Drop TCP/IP csum error */ #define DMA_CONTROL_RSF 0x02000000 /* Receive Store and Forward */ #define DMA_CONTROL_DFF 0x01000000 /* Disaable flushing */ /* Threshold for Activating the FC */ @@ -204,6 +204,8 @@ enum ttc_control { #define DMA_CONTROL_TC_TX_MASK 0xfffe3fff #define DMA_CONTROL_EFC 0x00000100 +#define DMA_CONTROL_FEF 0x00000080 +#define DMA_CONTROL_FUF 0x00000040 /* Receive flow control activation field * RFA field in DMA control register, bits 23,10:9 diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c index a4b5b1a51f0a0..a3052e29c8a2b 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c @@ -125,7 +125,8 @@ static u32 dwmac1000_configure_fc(u32 csr6, int rxfifosz) static void dwmac1000_dma_operation_mode_rx(struct stmmac_priv *priv, void __iomem *ioaddr, int mode, - u32 channel, int fifosz, u8 qmode) + u32 channel, int fifosz, u8 qmode, + bool rxall) { u32 csr6 = readl(ioaddr + DMA_CHAN_CONTROL(channel)); @@ -146,6 +147,12 @@ static void dwmac1000_dma_operation_mode_rx(struct stmmac_priv *priv, csr6 |= DMA_CONTROL_RTC_128; } + /* Permit errorneous frames (IP/TCP/UDP csum, overflow, giant, etc) */ + if (rxall) + csr6 |= DMA_CONTROL_DT | DMA_CONTROL_FEF | DMA_CONTROL_FUF; + else + csr6 &= ~(DMA_CONTROL_DT | DMA_CONTROL_FEF | DMA_CONTROL_FUF); + /* Configure flow control based on rx fifo size */ csr6 = dwmac1000_configure_fc(csr6, fifosz); diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4.h b/drivers/net/ethernet/stmicro/stmmac/dwmac4.h index 25572ac6a3f22..5c611dbc5abc7 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4.h @@ -325,6 +325,8 @@ static inline u32 mtl_chanx_base_addr(const struct dwmac4_addrs *addrs, #define MTL_OP_MODE_EHFC BIT(7) #define MTL_OP_MODE_DIS_TCP_EF BIT(6) +#define MTL_OP_MODE_FEP BIT(4) +#define MTL_OP_MODE_FUP BIT(3) #define MTL_OP_MODE_RTC_MASK GENMASK(1, 0) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c index 2c741bdae6fa2..1b72fa9076100 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c @@ -82,35 +82,46 @@ static int dwmac4_wrback_get_rx_status(struct stmmac_extra_stats *x, return rx_not_ls; if (unlikely(rdes3 & RDES3_ERROR_SUMMARY)) { - if (unlikely(rdes3 & RDES3_GIANT_PACKET)) + if (unlikely(rdes3 & RDES3_GIANT_PACKET)) { x->rx_length++; - if (unlikely(rdes3 & RDES3_OVERFLOW_ERROR)) + ret |= len_err; + } + + if (unlikely(rdes3 & RDES3_OVERFLOW_ERROR)) { x->rx_gmac_overflow++; + ret |= cutoff_err; + } - if (unlikely(rdes3 & RDES3_RECEIVE_WATCHDOG)) + if (unlikely(rdes3 & RDES3_RECEIVE_WATCHDOG)) { x->rx_watchdog++; + ret |= cutoff_err; + } - if (unlikely(rdes3 & RDES3_RECEIVE_ERROR)) + if (unlikely(rdes3 & RDES3_RECEIVE_ERROR)) { x->rx_mii++; + ret |= proto_err; + } - if (unlikely(rdes3 & RDES3_CRC_ERROR)) + if (unlikely(rdes3 & RDES3_CRC_ERROR)) { x->rx_crc_errors++; + ret |= csum_err; + } - if (unlikely(rdes3 & RDES3_DRIBBLE_ERROR)) + if (unlikely(rdes3 & RDES3_DRIBBLE_ERROR)) { x->dribbling_bit++; - - ret = discard_frame; + ret |= proto_err; + } } message_type = FIELD_GET(RDES1_PTP_MSG_TYPE_MASK, rdes1); if (rdes1 & RDES1_IP_HDR_ERROR) { x->ip_hdr_err++; - ret = discard_frame; + ret |= csum_err; } if (rdes1 & RDES1_IP_PAYLOAD_ERROR) { x->ip_payload_err++; - ret = discard_frame; + ret |= csum_err; } if (rdes1 & RDES1_IP_CSUM_BYPASSED) { x->ip_csum_bypassed++; @@ -153,14 +164,10 @@ static int dwmac4_wrback_get_rx_status(struct stmmac_extra_stats *x, if (rdes1 & RDES1_TIMESTAMP_DROPPED) x->timestamp_dropped++; - if (unlikely(rdes2 & RDES2_SA_FILTER_FAIL)) { + if (unlikely(rdes2 & RDES2_SA_FILTER_FAIL)) x->sa_rx_filter_fail++; - ret = discard_frame; - } - if (unlikely(rdes2 & RDES2_DA_FILTER_FAIL)) { + if (unlikely(rdes2 & RDES2_DA_FILTER_FAIL)) x->da_rx_filter_fail++; - ret = discard_frame; - } if (rdes2 & RDES2_L3_FILTER_MATCH) x->l3_filter_match++; diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c index 00a8abd11bf2d..93e9c636d86c9 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c @@ -264,7 +264,8 @@ static void dwmac4_rx_watchdog(struct stmmac_priv *priv, void __iomem *ioaddr, static void dwmac4_dma_rx_chan_op_mode(struct stmmac_priv *priv, void __iomem *ioaddr, int mode, - u32 channel, int fifosz, u8 qmode) + u32 channel, int fifosz, u8 qmode, + bool rxall) { const struct dwmac4_addrs *dwmac4_addrs = priv->plat->dwmac4_addrs; unsigned int rqs = fifosz / 256 - 1; @@ -272,8 +273,6 @@ static void dwmac4_dma_rx_chan_op_mode(struct stmmac_priv *priv, mtl_rx_op = readl(ioaddr + MTL_CHAN_RX_OP_MODE(dwmac4_addrs, channel)); - mtl_rx_op |= MTL_OP_MODE_DIS_TCP_EF; - if (mode == SF_DMA_MODE) { pr_debug("GMAC: enable RX store and forward mode\n"); mtl_rx_op |= MTL_OP_MODE_RSF; @@ -291,6 +290,12 @@ static void dwmac4_dma_rx_chan_op_mode(struct stmmac_priv *priv, mtl_rx_op |= MTL_OP_MODE_RTC_128; } + /* Permit errorneous frames (IP/TCP/UDP csum, overflow, giant, etc) */ + if (rxall) + mtl_rx_op |= MTL_OP_MODE_DIS_TCP_EF | MTL_OP_MODE_FEP | MTL_OP_MODE_FUP; + else + mtl_rx_op &= ~(MTL_OP_MODE_DIS_TCP_EF | MTL_OP_MODE_FEP | MTL_OP_MODE_FUP); + mtl_rx_op &= ~MTL_OP_MODE_RQS_MASK; mtl_rx_op |= FIELD_PREP(MTL_OP_MODE_RQS_MASK, rqs); diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h index 3e9d0cf884222..6391e3d46c8d6 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h @@ -323,7 +323,10 @@ #define XGMAC_MTL_RXQ_OPMODE(x) (0x00001140 + (0x80 * (x))) #define XGMAC_RQS GENMASK(25, 16) #define XGMAC_EHFC BIT(7) +#define XGMAC_DIS_TCP_EF BIT(6) #define XGMAC_RSF BIT(5) +#define XGMAC_FEF BIT(4) +#define XGMAC_FUP BIT(3) #define XGMAC_RTC GENMASK(1, 0) #define XGMAC_MTL_RXQ_MISSED_PKT_CTR(x) (0x00001144 + (0x80 * (x))) #define XGMAC_MISCNTOVF BIT(31) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c index 8109421a0b784..e6f48dd7a5b16 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c @@ -55,31 +55,37 @@ static int dwxgmac2_get_rx_status(struct stmmac_extra_stats *x, switch (et) { case XGMAC_ET_RECEIVE_WATCHDOG: x->rx_watchdog++; + ret |= cutoff_err; break; case XGMAC_ET_XGMII_ERROR: x->rx_mii++; + ret |= proto_err; break; case XGMAC_ET_CRC_ERROR: x->rx_crc_errors++; + ret |= csum_err; break; case XGMAC_ET_GIANT_PACKET: x->rx_length++; + ret |= len_err; break; case XGMAC_ET_IP_HDR_ERROR: x->ip_hdr_err++; + ret |= csum_err; break; case XGMAC_ET_IP_PAYLOAD_ERROR: x->ip_payload_err++; + ret |= csum_err; break; case XGMAC_ET_OVERFLOW_ERROR: x->rx_gmac_overflow++; + ret |= cutoff_err; break; case XGMAC_ET_DESC_ERROR: x->rx_desc++; + ret |= cutoff_err; break; } - - ret = discard_frame; } l34t = FIELD_GET(XGMAC_RDES3_L34T, rdes3); diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c index c5888055c4e65..6b2362910285b 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c @@ -142,7 +142,8 @@ static void dwxgmac2_dma_dump_regs(struct stmmac_priv *priv, } static void dwxgmac2_dma_rx_mode(struct stmmac_priv *priv, void __iomem *ioaddr, - int mode, u32 channel, int fifosz, u8 qmode) + int mode, u32 channel, int fifosz, u8 qmode, + bool rxall) { u32 value = readl(ioaddr + XGMAC_MTL_RXQ_OPMODE(channel)); unsigned int rqs = fifosz / 256 - 1; @@ -163,6 +164,12 @@ static void dwxgmac2_dma_rx_mode(struct stmmac_priv *priv, void __iomem *ioaddr, value = u32_replace_bits(value, rtc, XGMAC_RTC); } + /* Permit errorneous frames (IP/TCP/UDP csum, overflow, giant, etc) */ + if (rxall) + value |= XGMAC_DIS_TCP_EF | XGMAC_FEF | XGMAC_FUP; + else + value &= ~(XGMAC_DIS_TCP_EF | XGMAC_FEF | XGMAC_FUP); + value = u32_replace_bits(value, rqs, XGMAC_RQS); if ((fifosz >= 4096) && (qmode != MTL_QUEUE_AVB)) { diff --git a/drivers/net/ethernet/stmicro/stmmac/enh_desc.c b/drivers/net/ethernet/stmicro/stmmac/enh_desc.c index 5e0ef3558a5e3..34cfe2fd26d2f 100644 --- a/drivers/net/ethernet/stmicro/stmmac/enh_desc.c +++ b/drivers/net/ethernet/stmicro/stmmac/enh_desc.c @@ -83,12 +83,12 @@ static int enh_desc_rx_ext_status(unsigned int rdes4, if (rdes4 & ERDES4_IP_HDR_ERR) { x->ip_hdr_err++; - ret = discard_frame; + ret = csum_err; } if (rdes4 & ERDES4_IP_PAYLOAD_ERR) { x->ip_payload_err++; - ret = discard_frame; + ret = csum_err; } if (rdes4 & ERDES4_IP_CSUM_BYPASSED) { @@ -179,44 +179,52 @@ static int enh_desc_get_rx_basic_status(unsigned int rdes0, if (unlikely(rdes0 & RDES0_ERROR_SUMMARY)) { if (unlikely(rdes0 & RDES0_DESCRIPTOR_ERROR)) { x->rx_desc++; - x->rx_length++; + ret |= cutoff_err; } - if (unlikely(rdes0 & RDES0_OVERFLOW_ERROR)) - x->rx_gmac_overflow++; - if (unlikely(rdes0 & RDES0_COLLISION)) - x->rx_collision++; + if (unlikely(rdes0 & RDES0_OVERFLOW_ERROR)) { + x->rx_gmac_overflow++; + ret |= cutoff_err; + } - if (unlikely(rdes0 & RDES0_GIANT_FRAME_ERROR)) + if (unlikely(rdes0 & RDES0_GIANT_FRAME_ERROR)) { x->rx_length++; + ret |= len_err; + } + + if (unlikely(rdes0 & RDES0_COLLISION)) { + x->rx_collision++; + ret |= proto_err; + } - if (unlikely(rdes0 & RDES0_RECEIVE_WATCHDOG)) + if (unlikely(rdes0 & RDES0_RECEIVE_WATCHDOG)) { x->rx_watchdog++; + ret |= cutoff_err; + } - if (unlikely(rdes0 & RDES0_MII_ERROR)) /* GMII */ + if (unlikely(rdes0 & RDES0_MII_ERROR)) { x->rx_mii++; + ret |= proto_err; + } if (unlikely(rdes0 & RDES0_CRC_ERROR)) { x->rx_crc_errors++; + ret |= csum_err; } - ret = discard_frame; } if (unlikely(rdes0 & RDES0_DRIBBLING)) x->dribbling_bit++; - if (unlikely(rdes0 & RDES0_SA_FILTER_FAIL)) { + if (unlikely(rdes0 & RDES0_SA_FILTER_FAIL)) x->sa_rx_filter_fail++; - ret = discard_frame; - } - if (unlikely(rdes0 & RDES0_DA_FILTER_FAIL)) { + + if (unlikely(rdes0 & RDES0_DA_FILTER_FAIL)) x->da_rx_filter_fail++; - ret = discard_frame; - } - if (unlikely(rdes0 & RDES0_LENGTH_ERROR)) { + + if (unlikely(rdes0 & RDES0_LENGTH_ERROR)) x->rx_length++; - ret = discard_frame; - } + #ifdef STMMAC_VLAN_TAG_USED if (rdes0 & RDES0_VLAN_TAG) x->rx_vlan++; diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index bf8138d75a133..e0495ee19a626 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -187,9 +187,10 @@ struct stmmac_dma_ops { /* Dump DMA registers */ void (*dump_regs)(struct stmmac_priv *priv, void __iomem *ioaddr, u32 *reg_space); + /* Setup DMA Rx/Tx channels */ void (*dma_rx_mode)(struct stmmac_priv *priv, void __iomem *ioaddr, - int mode, u32 channel, - int fifosz, u8 qmode); + int mode, u32 channel, int fifosz, u8 qmode, + bool rxall); void (*dma_tx_mode)(struct stmmac_priv *priv, void __iomem *ioaddr, int mode, u32 channel, int fifosz, u8 qmode); /* To track extra statistic (if supported) */ diff --git a/drivers/net/ethernet/stmicro/stmmac/norm_desc.c b/drivers/net/ethernet/stmicro/stmmac/norm_desc.c index fa95a1eeff5e7..4e0b552aba3bb 100644 --- a/drivers/net/ethernet/stmicro/stmmac/norm_desc.c +++ b/drivers/net/ethernet/stmicro/stmmac/norm_desc.c @@ -78,33 +78,53 @@ static int ndesc_get_rx_basic_status(unsigned int rdes0, } if (unlikely(rdes0 & RDES0_ERROR_SUMMARY)) { - if (unlikely(rdes0 & RDES0_DESCRIPTOR_ERROR)) + if (unlikely(rdes0 & RDES0_DESCRIPTOR_ERROR)) { x->rx_desc++; - if (unlikely(rdes0 & RDES0_SA_FILTER_FAIL)) - x->sa_filter_fail++; - if (unlikely(rdes0 & RDES0_OVERFLOW_ERROR)) + ret |= cutoff_err; + } + + if (unlikely(rdes0 & RDES0_OVERFLOW_ERROR)) { x->overflow_error++; - if (unlikely(rdes0 & RDES0_GIANT_FRAME_ERROR)) + ret |= cutoff_err; + } + + if (unlikely(rdes0 & RDES0_GIANT_FRAME_ERROR)) { x->rx_length++; + ret |= len_err; + } + if (unlikely(rdes0 & RDES0_COLLISION)) { x->rx_collision++; + ret |= proto_err; + } + + if (unlikely(rdes0 & RDES0_RECEIVE_WATCHDOG)) { + x->rx_watchdog++; + ret |= cutoff_err; } + + if (unlikely(rdes0 & RDES0_MII_ERROR)) { + x->rx_mii++; + ret |= proto_err; + } + if (unlikely(rdes0 & RDES0_CRC_ERROR)) { x->rx_crc_errors++; + ret |= csum_err; } - ret = discard_frame; } if (unlikely(rdes0 & RDES0_DRIBBLING)) x->dribbling_bit++; - if (unlikely(rdes0 & RDES0_LENGTH_ERROR)) { + if (unlikely(rdes0 & RDES0_SA_FILTER_FAIL)) + x->sa_filter_fail++; + + if (unlikely(rdes0 & RDES0_DA_FILTER_FAIL)) + x->da_rx_filter_fail++; + + if (unlikely(rdes0 & RDES0_LENGTH_ERROR)) x->rx_length++; - ret = discard_frame; - } - if (unlikely(rdes0 & RDES0_MII_ERROR)) { - x->rx_mii++; - ret = discard_frame; - } + #ifdef STMMAC_VLAN_TAG_USED if (rdes0 & RDES0_VLAN_TAG) x->vlan_tag++; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index f0d395b863199..fc585c562eba7 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -2707,8 +2707,8 @@ static void stmmac_dma_operation_mode(struct stmmac_priv *priv) qmode = priv->plat->rx_queues_cfg[chan].mode_to_use; rx_q->rtc = rxmode; - stmmac_dma_rx_mode(priv, priv->ioaddr, rxmode, chan, - rxfifosz, qmode); + stmmac_dma_rx_mode(priv, priv->ioaddr, rxmode, chan, rxfifosz, + qmode, priv->dev->features & NETIF_F_RXALL); if (rx_q->xsk_pool) { buf_size = xsk_pool_get_rx_frame_size(rx_q->xsk_pool); @@ -5021,6 +5021,28 @@ flush_ring: return ret; } +/** + * stmmac_get_rx_status - Retrieve Rx DMA-descriptor status + * @priv: driver private structure + * @p : descriptor pointer + * Description : + * This function will parse the Rx DMA-descriptor status to get the generic + * status utilized by the Rx packets methods. It shall submit the descriptor + * for dropping if an error detected with no rx-all feature activated. + * Return: Rx DMA-descriptor status (rx_frame_status) + */ +static int stmmac_get_rx_status(struct stmmac_priv *priv, struct dma_desc *p) +{ + bool rxall = priv->dev->features & NETIF_F_RXALL; + int ret; + + ret = stmmac_rx_status(priv, &priv->xstats, p); + if (likely(!(ret & rx_err) || rxall)) + return ret; + + return ret | discard_frame; +} + static void stmmac_rx_vlan(struct net_device *dev, struct sk_buff *skb) { struct vlan_ethhdr *veth = skb_vlan_eth_hdr(skb); @@ -5617,9 +5639,7 @@ read_again: else p = rx_q->dma_rx + entry; - /* read the status of the incoming frame */ - status = stmmac_rx_status(priv, &priv->xstats, p); - /* check if managed by the DMA otherwise go ahead */ + status = stmmac_get_rx_status(priv, p); if (unlikely(status & dma_own)) break; @@ -5642,7 +5662,7 @@ read_again: if (unlikely(status & discard_frame)) { dirty++; error = 1; - if (!priv->hwts_rx_en) + if (status & rx_err) rx_errors++; } @@ -5804,9 +5824,7 @@ read_again: else p = rx_q->dma_rx + entry; - /* read the status of the incoming frame */ - status = stmmac_rx_status(priv, &priv->xstats, p); - /* check if managed by the DMA otherwise go ahead */ + status = stmmac_get_rx_status(priv, p); if (unlikely(status & dma_own)) break; @@ -5823,7 +5841,7 @@ read_again: if (unlikely(status & discard_frame)) { error = 1; - if (!priv->hwts_rx_en) + if (status & rx_err) rx_errors++; } @@ -6259,6 +6277,21 @@ static int stmmac_set_features(struct net_device *netdev, stmmac_rx_ipc(priv, priv->hw, features & NETIF_F_RXCSUM); + for (chan = 0; chan < priv->plat->rx_queues_to_use; chan++) { + struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[chan]; + u8 rxqmode = priv->plat->rx_queues_cfg[chan].mode_to_use; + int rxfifosz = priv->plat->rx_fifo_size; + + if (rxfifosz == 0) + rxfifosz = priv->dma_cap.rx_fifo_size; + + if (dwmac_is_xmac(priv->plat->core_type)) + rxfifosz /= priv->plat->rx_queues_to_use; + + stmmac_dma_rx_mode(priv, priv->ioaddr, rx_q->rtc, chan, + rxfifosz, rxqmode, features & NETIF_F_RXALL); + } + for (chan = 0; chan < priv->plat->tx_queues_to_use; chan++) { struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[chan]; bool tso_en = !(tx_q->tbs & STMMAC_TBS_AVAIL) && @@ -7621,7 +7654,8 @@ static void stmmac_drop_rtc_subtask(struct stmmac_priv *priv, u32 queue) rxqmode = priv->plat->rx_queues_cfg[queue].mode_to_use; - stmmac_dma_rx_mode(priv, priv->ioaddr, rx_q->rtc, queue, rxfifosz, rxqmode); + stmmac_dma_rx_mode(priv, priv->ioaddr, rx_q->rtc, queue, rxfifosz, + rxqmode, priv->dev->features & NETIF_F_RXALL); } /** @@ -8385,6 +8419,12 @@ static int __stmmac_dvr_probe(struct device *device, ndev->hw_features |= NETIF_F_RXHASH; ndev->features |= ndev->hw_features | NETIF_F_SG | NETIF_F_HIGHDMA; + + /* Old DW MAC doesn't seem to provide the pass-error-frames setting */ + if (priv->plat->core_type == DWMAC_CORE_GMAC || + dwmac_is_xmac(priv->plat->core_type)) + ndev->hw_features |= NETIF_F_RXALL; + ndev->watchdog_timeo = msecs_to_jiffies(watchdog); #ifdef STMMAC_VLAN_TAG_USED /* Both mac100 and gmac support receive VLAN tag detection */ -- cgit v1.2.3 From 6573ddf62d8959787bd7323897decb84e503f9a4 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 18 Aug 2025 23:30:06 +0300 Subject: net: stmmac: Add RXFCS feature support The HW-accelerated FCS/Pad stripping was removed in the commit 929d43421ee5 ("net: stmmac: Disable automatic FCS/Pad stripping"). Since then the FCS/Pad cutting of has been done by the driver itselft and for all incoming traffic. Due to that it will be quite easy to add the NETIF_F_RXFCS feature support - just convert the FCS stripping off code to being conditionally called. While at it move it out of the stmmac_rx()/stmmac_rx_zc() methods to the buffers length calculation functions to reduce the amount of the conditionals and to simplify the Rx methods. Also make sure the FCS trimming is applied to the very last Rx DMA-buffer. That is in case if Split Packet Header feature enabled and no header splitting happened both buffers can be utilized for the data reception. Thus the second buffer length must be trimmed of FCS field on that occasion. Of course the NETIF_F_RXALL feature will be disabled by default and will be enabled upon the user request. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 54 +++++++++++++---------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index fc585c562eba7..f68ae9926689c 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -5176,6 +5176,23 @@ static inline void stmmac_rx_refill(struct stmmac_priv *priv, u32 queue) stmmac_enable_dma_reception(priv, priv->ioaddr, queue); } +/** + * stmmac_fcs_trimming - FCS field length + * @priv: driver private structure + * @status: frame Rx-status + * Description : + * Retrieve FCS length required to be trimmed of the Rx frame. + */ +static unsigned int stmmac_fcs_trimming(struct stmmac_priv *priv, int status) +{ + bool rxfcs = priv->dev->features & NETIF_F_RXFCS; + + if (rxfcs) + return 0; + + return ETH_FCS_LEN; +} + static unsigned int stmmac_rx_buf1_len(struct stmmac_priv *priv, struct dma_desc *p, int status, unsigned int len) @@ -5205,10 +5222,13 @@ static unsigned int stmmac_rx_buf1_len(struct stmmac_priv *priv, return len ? plen : plen - stmmac_fs_offset(); } - plen = stmmac_get_rx_frame_len(priv, p); + /* Last descriptor and not split header and secondary buffer used */ + plen = stmmac_get_rx_frame_len(priv, p) - len; + if (plen > priv->dma_conf.dma_buf_sz) + return priv->dma_conf.dma_buf_sz; - /* First descriptor and last descriptor and not split header */ - return min_t(unsigned int, priv->dma_conf.dma_buf_sz, plen); + /* Last descriptor and last buffer with FCS clipped of */ + return plen - stmmac_fcs_trimming(priv, status); } static unsigned int stmmac_rx_buf2_len(struct stmmac_priv *priv, @@ -5239,10 +5259,13 @@ static unsigned int stmmac_rx_buf2_len(struct stmmac_priv *priv, if (priv->plat->core_type != DWMAC_CORE_GMAC4 && (status & rx_not_ls)) return priv->dma_conf.dma_buf_sz; - /* GMAC4 or last descriptor */ - plen = stmmac_get_rx_frame_len(priv, p); + /* GMAC4 or last descriptor and secondary buffer unused */ + plen = stmmac_get_rx_frame_len(priv, p) - len; + if (!plen) + return 0; - return plen - len; + /* Last descriptor and last buffer with FCS clipped of */ + return plen - stmmac_fcs_trimming(priv, status); } static int stmmac_xdp_xmit_xdpf(struct stmmac_priv *priv, int queue, @@ -5689,12 +5712,6 @@ read_again: buf1_len = stmmac_rx_buf1_len(priv, p, status, len); len += buf1_len; - /* ACS is disabled; strip manually. */ - if (likely(!(status & rx_not_ls))) { - buf1_len -= ETH_FCS_LEN; - len -= ETH_FCS_LEN; - } - /* RX buffer is good and fit into a XSK pool buffer */ buf->xdp->data_end = buf->xdp->data + buf1_len; xsk_buff_dma_sync_for_cpu(buf->xdp); @@ -5861,17 +5878,6 @@ read_again: buf2_len = stmmac_rx_buf2_len(priv, p, status, len); len += buf2_len; - /* ACS is disabled; strip manually. */ - if (likely(!(status & rx_not_ls))) { - if (buf2_len) { - buf2_len -= ETH_FCS_LEN; - len -= ETH_FCS_LEN; - } else if (buf1_len) { - buf1_len -= ETH_FCS_LEN; - len -= ETH_FCS_LEN; - } - } - if (!skb) { unsigned int pre_len, sync_len; @@ -8425,6 +8431,8 @@ static int __stmmac_dvr_probe(struct device *device, dwmac_is_xmac(priv->plat->core_type)) ndev->hw_features |= NETIF_F_RXALL; + ndev->hw_features |= NETIF_F_RXFCS; + ndev->watchdog_timeo = msecs_to_jiffies(watchdog); #ifdef STMMAC_VLAN_TAG_USED /* Both mac100 and gmac support receive VLAN tag detection */ -- cgit v1.2.3 From b0e9393ef476879c58f476f1270978c77714ebf3 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Tue, 19 Aug 2025 20:57:53 +0300 Subject: net: stmmac: Add HW-accelerated FCS stripping support Currently the FCS field stripping is done purely by software. That is the driver just reduces the frame length by the FCS size if NETIF_F_RXFCS feature isn't enabled. In the meantime the Pad/FCS stripping can be done by the hardware itself thus speeding up the incoming traffic processing a bit. The Auto Pad/FCS stripping can be enabled by means of the ACS and CST flags in the MAC-control register of all the DW MAC IP-cores. The former one enables the Pad/FCS stripping from the Ethernet 802.3 frames (Length/Type field is less than 1536). The later flag switches on just the CRC stripping for the Ethernet II type frames (Length/Type field is greater or equal to 1536). Ideally both flags could have been enabled to implement the denoted feature and be done with it. But the problems are that first the IP-cores prior DW GMAC v3.50a version don't support CST flag and second the ACS flag causes all pads and FCS truncation on transferring frames from RPE to RFC module up to the _length specified in the Ethertype-field_. If first problem can be quite easy handled in the driver, the second one can't be because it corrupts the DSA frames which have tags placed at the Ethertype-field and looking like a very short Ethernet 802.3 frame for the RPE module of DW MACs. That's why all of the previous commits trying to fix the problem couldn't do that properly. The best solution would be to just switch on CST and never enable the ACS flag on. As a final and the most appropriate solution let's add the HW-accelerated FCS stripping support for the Ethernet II frames only. It will be enabled unless NETIF_F_RXFCS feature is request for DW GMAC v3.50a and higher, DW QoS Ether and DW XGMAC/XLGMAC IP-cores. Fixes: 929d43421ee5 ("net: stmmac: Disable automatic FCS/Pad stripping") Fixes: 8cad443eacf6 ("net: stmmac: Fix reception of Broadcom switches tags") Fixes: 3eeb29972b11 ("stmmac: fix automatic PAD/FCS stripping") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/dwmac1000.h | 1 + .../net/ethernet/stmicro/stmmac/dwmac1000_core.c | 27 +++++++++++++++++++ drivers/net/ethernet/stmicro/stmmac/dwmac4.h | 1 + drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c | 30 ++++++++++++++++++++++ drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c | 12 +++++++++ drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.h | 7 +++++ .../net/ethernet/stmicro/stmmac/dwxgmac2_core.c | 28 ++++++++++++++++++++ .../net/ethernet/stmicro/stmmac/dwxgmac2_descs.c | 3 +++ drivers/net/ethernet/stmicro/stmmac/enh_desc.c | 3 +++ drivers/net/ethernet/stmicro/stmmac/hwif.h | 7 +++++ drivers/net/ethernet/stmicro/stmmac/norm_desc.c | 3 +++ drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 16 ++++++++++++ 12 files changed, 138 insertions(+) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000.h b/drivers/net/ethernet/stmicro/stmmac/dwmac1000.h index 2a51768cf64f2..1e095ba64127c 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac1000.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000.h @@ -85,6 +85,7 @@ enum power_event { /* GMAC Configuration defines */ #define GMAC_CONTROL_2K 0x08000000 /* IEEE 802.3as 2K packets */ +#define GMAC_CONTROL_CST 0x02000000 /* CRC Stripping for Type Frames */ #define GMAC_CONTROL_JD 0x00400000 /* Jabber disable */ #define GMAC_CONTROL_BE 0x00200000 /* Frame Burst Enable */ #define GMAC_CONTROL_JE 0x00100000 /* Jumbo frame */ diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c index 83ec2c00b49a5..1c7562fab3948 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c @@ -72,6 +72,31 @@ static void dwmac1000_irq_modify(struct mac_device_info *hw, u32 disable, spin_unlock_irqrestore(&hw->irq_ctrl_lock, flags); } +static void dwmac1000_rx_fcs_enable(struct mac_device_info *hw, bool enable) +{ + void __iomem *ioaddr = hw->pcsr; + u32 value; + + value = readl(ioaddr + GMAC_CONTROL); + + if (enable) + value &= ~GMAC_CONTROL_CST; + else + value |= GMAC_CONTROL_CST; + + writel(value, ioaddr + GMAC_CONTROL); +} + +static int dwmac1000_rx_fcs_status(struct mac_device_info *hw, int snps_id, + int status) +{ + /* CRC Stripping for Type Frames has been supported since v3.50a */ + if (snps_id < DWMAC_CORE_3_50 || unlikely(status & llc_snap)) + return -ENOTSUPP; + + return 0; +} + static int dwmac1000_rx_ipc_enable(struct mac_device_info *hw, bool enable) { void __iomem *ioaddr = hw->pcsr; @@ -454,6 +479,8 @@ const struct stmmac_ops dwmac1000_ops = { .core_init = dwmac1000_core_init, .irq_modify = dwmac1000_irq_modify, .set_mac = stmmac_set_mac, + .rx_fcs = dwmac1000_rx_fcs_enable, + .rx_fcs_status = dwmac1000_rx_fcs_status, .rx_ipc = dwmac1000_rx_ipc_enable, .dump_regs = dwmac1000_dump_regs, .host_irq_status = dwmac1000_irq_status, diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4.h b/drivers/net/ethernet/stmicro/stmmac/dwmac4.h index 5c611dbc5abc7..e58194fd0c0bf 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4.h @@ -156,6 +156,7 @@ enum power_event { #define GMAC_CONFIG_IPC BIT(27) #define GMAC_CONFIG_IPG GENMASK(26, 24) #define GMAC_CONFIG_2K BIT(22) +#define GMAC_CONFIG_CST BIT(21) #define GMAC_CONFIG_ACS BIT(20) #define GMAC_CONFIG_BE BIT(18) #define GMAC_CONFIG_JD BIT(17) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c index 5337376452a7c..9f53a79aa70cb 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c @@ -318,6 +318,30 @@ static void dwmac4_dump_regs(struct mac_device_info *hw, u32 *reg_space) reg_space[i] = readl(ioaddr + i * 4); } +static void dwmac4_rx_fcs_enable(struct mac_device_info *hw, bool enable) +{ + void __iomem *ioaddr = hw->pcsr; + u32 value; + + value = readl(ioaddr + GMAC_CONFIG); + + if (enable) + value &= ~GMAC_CONFIG_CST; + else + value |= GMAC_CONFIG_CST; + + writel(value, ioaddr + GMAC_CONFIG); +} + +static int dwmac4_rx_fcs_status(struct mac_device_info *hw, int snps_id, + int status) +{ + if (unlikely(status & llc_snap)) + return -ENOTSUPP; + + return 0; +} + static int dwmac4_rx_ipc_enable(struct mac_device_info *hw, bool enable) { void __iomem *ioaddr = hw->pcsr; @@ -894,6 +918,8 @@ const struct stmmac_ops dwmac4_ops = { .irq_modify = dwmac4_irq_modify, .update_caps = dwmac4_update_caps, .set_mac = stmmac_set_mac, + .rx_fcs = dwmac4_rx_fcs_enable, + .rx_fcs_status = dwmac4_rx_fcs_status, .rx_ipc = dwmac4_rx_ipc_enable, .rx_queue_enable = dwmac4_rx_queue_enable, .rx_queue_prio = dwmac4_rx_queue_priority, @@ -930,6 +956,8 @@ const struct stmmac_ops dwmac410_ops = { .irq_modify = dwmac4_irq_modify, .update_caps = dwmac4_update_caps, .set_mac = stmmac_dwmac4_set_mac, + .rx_fcs = dwmac4_rx_fcs_enable, + .rx_fcs_status = dwmac4_rx_fcs_status, .rx_ipc = dwmac4_rx_ipc_enable, .rx_queue_enable = dwmac4_rx_queue_enable, .rx_queue_prio = dwmac4_rx_queue_priority, @@ -968,6 +996,8 @@ const struct stmmac_ops dwmac510_ops = { .irq_modify = dwmac4_irq_modify, .update_caps = dwmac4_update_caps, .set_mac = stmmac_dwmac4_set_mac, + .rx_fcs = dwmac4_rx_fcs_enable, + .rx_fcs_status = dwmac4_rx_fcs_status, .rx_ipc = dwmac4_rx_ipc_enable, .rx_queue_enable = dwmac4_rx_queue_enable, .rx_queue_prio = dwmac4_rx_queue_priority, diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c index 1b72fa9076100..f51da6c80717b 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c @@ -111,6 +111,18 @@ static int dwmac4_wrback_get_rx_status(struct stmmac_extra_stats *x, x->dribbling_bit++; ret |= proto_err; } + } else { + int l2t = FIELD_GET(RDES3_PACKET_LEN_TYPE_MASK, rdes3); + + switch (l2t) { + case RDES3_PACKET_LENGTH: + ret |= llc_snap; + break; + case RDES3_PACKET_VLAN: + case RDES3_PACKET_DVLAN: + x->rx_vlan++; + break; + } } message_type = FIELD_GET(RDES1_PTP_MSG_TYPE_MASK, rdes1); diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.h b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.h index fb1fea5b0e6ed..c089e2fdd901c 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.h @@ -114,6 +114,13 @@ #define RDES3_PACKET_SIZE_MASK GENMASK(14, 0) #define RDES3_ERROR_SUMMARY BIT(15) #define RDES3_PACKET_LEN_TYPE_MASK GENMASK(18, 16) +#define RDES3_PACKET_LENGTH 0 +#define RDES3_PACKET_TYPE 1 +#define RDES3_PACKET_ARP 3 +#define RDES3_PACKET_VLAN 4 +#define RDES3_PACKET_DVLAN 5 +#define RDES3_PACKET_MAC_CONTROL 6 +#define RDES3_PACKET_OAM_PACKET 7 #define RDES3_DRIBBLE_ERROR BIT(19) #define RDES3_RECEIVE_ERROR BIT(20) #define RDES3_OVERFLOW_ERROR BIT(21) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c index 8be75536b284d..13093b7defc58 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c @@ -65,6 +65,30 @@ static void dwxgmac2_set_mac(void __iomem *ioaddr, bool enable) writel(rx, ioaddr + XGMAC_RX_CONFIG); } +static void dwxgmac2_rx_fcs(struct mac_device_info *hw, bool enable) +{ + void __iomem *ioaddr = hw->pcsr; + u32 value; + + value = readl(ioaddr + XGMAC_RX_CONFIG); + + if (enable) + value &= ~XGMAC_CONFIG_CST; + else + value |= XGMAC_CONFIG_CST; + + writel(value, ioaddr + XGMAC_RX_CONFIG); +} + +static int dwxgmac2_rx_fcs_status(struct mac_device_info *hw, int snps_id, + int status) +{ + if (unlikely(status & llc_snap)) + return -ENOTSUPP; + + return 0; +} + static int dwxgmac2_rx_ipc(struct mac_device_info *hw, bool enable) { void __iomem *ioaddr = hw->pcsr; @@ -1533,6 +1557,8 @@ const struct stmmac_ops dwxgmac210_ops = { .irq_modify = dwxgmac2_irq_modify, .update_caps = dwxgmac2_update_caps, .set_mac = dwxgmac2_set_mac, + .rx_fcs = dwxgmac2_rx_fcs, + .rx_fcs_status = dwxgmac2_rx_fcs_status, .rx_ipc = dwxgmac2_rx_ipc, .rx_queue_enable = dwxgmac2_rx_queue_enable, .rx_queue_prio = dwxgmac2_rx_queue_prio, @@ -1588,6 +1614,8 @@ const struct stmmac_ops dwxlgmac2_ops = { .core_init = dwxgmac2_core_init, .irq_modify = dwxgmac2_irq_modify, .set_mac = dwxgmac2_set_mac, + .rx_fcs = dwxgmac2_rx_fcs, + .rx_fcs_status = dwxgmac2_rx_fcs_status, .rx_ipc = dwxgmac2_rx_ipc, .rx_queue_enable = dwxlgmac2_rx_queue_enable, .rx_queue_prio = dwxgmac2_rx_queue_prio, diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c index e6f48dd7a5b16..deb0a787cb045 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c @@ -42,6 +42,9 @@ static int dwxgmac2_get_rx_status(struct stmmac_extra_stats *x, int l2t = FIELD_GET(XGMAC_RDES3_ET_LT, rdes3); switch (l2t) { + case XGMAC_L2T_LENGTH_PACKET: + ret |= llc_snap; + break; case XGMAC_L2T_AV_CONTROL: x->av_pkt_rcvd++; break; diff --git a/drivers/net/ethernet/stmicro/stmmac/enh_desc.c b/drivers/net/ethernet/stmicro/stmmac/enh_desc.c index 34cfe2fd26d2f..5b4bae8c5e89a 100644 --- a/drivers/net/ethernet/stmicro/stmmac/enh_desc.c +++ b/drivers/net/ethernet/stmicro/stmmac/enh_desc.c @@ -176,6 +176,9 @@ static int enh_desc_get_rx_basic_status(unsigned int rdes0, return discard_frame; } + if (unlikely(!(rdes0 & RDES0_FRAME_TYPE))) + ret = llc_snap; + if (unlikely(rdes0 & RDES0_ERROR_SUMMARY)) { if (unlikely(rdes0 & RDES0_DESCRIPTOR_ERROR)) { x->rx_desc++; diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index e0495ee19a626..b1654e86179ad 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -322,6 +322,9 @@ struct stmmac_ops { void (*irq_modify)(struct mac_device_info *hw, u32 disable, u32 enable); /* Enable the MAC RX/TX */ void (*set_mac)(void __iomem *ioaddr, bool enable); + /* Enable and verify the Auto Pad/FCS stripping */ + void (*rx_fcs)(struct mac_device_info *hw, bool enable); + int (*rx_fcs_status)(struct mac_device_info *hw, int snps_id, int status); /* Enable and verify that the IPC module is supported */ int (*rx_ipc)(struct mac_device_info *hw, bool enable); /* Enable RX Queues */ @@ -426,6 +429,10 @@ struct stmmac_ops { stmmac_do_void_callback(__priv, mac, irq_modify, (__priv)->hw, __args) #define stmmac_mac_set(__priv, __args...) \ stmmac_do_void_callback(__priv, mac, set_mac, __args) +#define stmmac_rx_fcs(__priv, __args...) \ + stmmac_do_void_callback(__priv, mac, rx_fcs, __args) +#define stmmac_rx_fcs_status(__priv, __args...) \ + stmmac_do_callback(__priv, mac, rx_fcs_status, __args) #define stmmac_rx_ipc(__priv, __args...) \ stmmac_do_callback(__priv, mac, rx_ipc, __args) #define stmmac_rx_queue_enable(__priv, __args...) \ diff --git a/drivers/net/ethernet/stmicro/stmmac/norm_desc.c b/drivers/net/ethernet/stmicro/stmmac/norm_desc.c index 4e0b552aba3bb..a26b58f9b5732 100644 --- a/drivers/net/ethernet/stmicro/stmmac/norm_desc.c +++ b/drivers/net/ethernet/stmicro/stmmac/norm_desc.c @@ -77,6 +77,9 @@ static int ndesc_get_rx_basic_status(unsigned int rdes0, return discard_frame; } + if (unlikely(!(rdes0 & RDES0_FRAME_TYPE))) + ret = llc_snap; + if (unlikely(rdes0 & RDES0_ERROR_SUMMARY)) { if (unlikely(rdes0 & RDES0_DESCRIPTOR_ERROR)) { x->rx_desc++; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index f68ae9926689c..b4ee92afe8c81 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -3718,6 +3718,9 @@ static int stmmac_hw_setup(struct net_device *dev) /* Initialize Safety Features */ stmmac_safety_feat_configuration(priv); + /* Initialize Auto Pad/FCS Stripping */ + stmmac_rx_fcs(priv, priv->hw, dev->features & NETIF_F_RXFCS); + /* Initialize Rx Checksum Offload Engine */ stmmac_rx_ipc(priv, priv->hw, dev->features & NETIF_F_RXCSUM); @@ -5187,9 +5190,19 @@ static unsigned int stmmac_fcs_trimming(struct stmmac_priv *priv, int status) { bool rxfcs = priv->dev->features & NETIF_F_RXFCS; + /* By default Pad/FCS is left attached to the frame */ if (rxfcs) return 0; + /* Auto Pad/FCS stripping is unavailable on the old controller and it + * is deliberately disabled for Ethernet II frames to prevent the MAC + * confusing some DSA frames with IEEE 802.3 frames and truncating them + * up to the Ethertype-length (see "Receive Protocol Engine Module" + * chapter for details). + */ + if (!stmmac_rx_fcs_status(priv, priv->hw, priv->synopsys_id, status)) + return 0; + return ETH_FCS_LEN; } @@ -6281,6 +6294,8 @@ static int stmmac_set_features(struct net_device *netdev, struct stmmac_priv *priv = netdev_priv(netdev); u32 chan; + stmmac_rx_fcs(priv, priv->hw, features & NETIF_F_RXFCS); + stmmac_rx_ipc(priv, priv->hw, features & NETIF_F_RXCSUM); for (chan = 0; chan < priv->plat->rx_queues_to_use; chan++) { @@ -8431,6 +8446,7 @@ static int __stmmac_dvr_probe(struct device *device, dwmac_is_xmac(priv->plat->core_type)) ndev->hw_features |= NETIF_F_RXALL; + /* Either hardware or software based FCS stripping supported */ ndev->hw_features |= NETIF_F_RXFCS; ndev->watchdog_timeo = msecs_to_jiffies(watchdog); -- cgit v1.2.3 From 754290560513a88af6ca6e6b148a155df8e63851 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 21 Jul 2025 15:20:19 +0300 Subject: net: stmmac: dwmac1000: Fix invalid Tx descriptor chain after TS It's possible to have the DMA-descriptors linked with each other on the DW GMAC IP-cores. It's called the chain mode, when a DMA-descriptor refers to the next one by a pointer. But in case of the IEEE 1588-2002 Timestamp support the chain will be broken by the DMA-engine writing the outbound timestamp to the last descriptor of the transferred frame. So the descriptors chain must be restored afterwards in that case. The restoration has been implemented by means of the stmmac_mode_ops::clean_desc3() function called in the framework of the stmmac_tx_clean() method responsible for the Tx DMA-descriptors preparation for the next transfers. But the procedure has been broken in e3ad57c96715 ("stmmac: review RX/TX ring management"). The clean_desc3() method relies on the stmmac_tx_queue::dirty_rx field state to re-initialize the respective descriptor and find the next one. But the dirty Tx-pointer now is advanced only after all the Tx-descriptors are cleaned up and moved right to the current Tx-pointer. So the intermediate descriptors will be left with the chain broken, meanwhile the first dirty one will point to the first non-dirty. This is definitely wrong. Let's fix that problem. But instead of either getting back the Tx DMA-descriptors cleanup loop semantics or fixing the clean_desc3() method semantics it's better to optimize the chain mode initialization and restoration procedures. It can be done by moving the chain-mode initialization to the stmmac_desc_ops::release_tx_desc() method. So after doing that the Tx DMA-descriptors chaining will be localized in a single method and the problem described above will be fixed since the next descriptor pointer will be passed noew to the release_tx_desc() function. Note the chain-mode flag has been initialized in that function even before this change. Fixes: e3ad57c96715 ("stmmac: review RX/TX ring management") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/chain_mode.c | 18 ------- drivers/net/ethernet/stmicro/stmmac/descs_com.h | 12 ++++- drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c | 3 +- .../net/ethernet/stmicro/stmmac/dwxgmac2_descs.c | 3 +- drivers/net/ethernet/stmicro/stmmac/enh_desc.c | 7 +-- drivers/net/ethernet/stmicro/stmmac/hwif.h | 6 +-- drivers/net/ethernet/stmicro/stmmac/norm_desc.c | 7 +-- drivers/net/ethernet/stmicro/stmmac/ring_mode.c | 13 ----- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 55 +++++++++++----------- 9 files changed, 51 insertions(+), 73 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/chain_mode.c b/drivers/net/ethernet/stmicro/stmmac/chain_mode.c index d7ff4114a7eda..d6118745d681d 100644 --- a/drivers/net/ethernet/stmicro/stmmac/chain_mode.c +++ b/drivers/net/ethernet/stmicro/stmmac/chain_mode.c @@ -148,27 +148,9 @@ static void refill_desc3(struct stmmac_rx_queue *rx_q, struct dma_desc *p) sizeof(struct dma_desc))); } -static void clean_desc3(struct stmmac_tx_queue *tx_q, struct dma_desc *p) -{ - struct stmmac_priv *priv = tx_q->priv_data; - unsigned int entry = tx_q->dirty_tx; - - if (tx_q->tx_skbuff_dma[entry].last_segment && !priv->extend_desc && - priv->hwts_tx_en) - /* NOTE: Device will overwrite des3 with timestamp value if - * 1588-2002 time stamping is enabled, hence reinitialize it - * to keep explicit chaining in the descriptor. - */ - p->des3 = cpu_to_le32((unsigned int)((tx_q->dma_tx_phy + - ((tx_q->dirty_tx + 1) % - priv->dma_conf.dma_tx_size)) - * sizeof(struct dma_desc))); -} - const struct stmmac_mode_ops chain_mode_ops = { .init = init_dma_chain, .is_jumbo_frm = is_jumbo_frm, .jumbo_frm = jumbo_frm, .refill_desc3 = refill_desc3, - .clean_desc3 = clean_desc3, }; diff --git a/drivers/net/ethernet/stmicro/stmmac/descs_com.h b/drivers/net/ethernet/stmicro/stmmac/descs_com.h index d5c6471840469..0019f389181f7 100644 --- a/drivers/net/ethernet/stmicro/stmmac/descs_com.h +++ b/drivers/net/ethernet/stmicro/stmmac/descs_com.h @@ -101,9 +101,13 @@ static inline void ehn_desc_rx_set_on_chain(struct dma_desc *p) p->des1 |= cpu_to_le32(ERDES1_SECOND_ADDRESS_CHAINED); } -static inline void enh_desc_end_tx_desc_on_chain(struct dma_desc *p) +static inline void enh_desc_end_tx_desc_on_chain(struct dma_desc *p, dma_addr_t np, + bool hwts_tx_en) { p->des0 |= cpu_to_le32(ETDES0_SECOND_ADDRESS_CHAINED); + /* des3 is overwritten with timestamp in case of IEEE 1588-2002 */ + if (hwts_tx_en) + p->des3 = cpu_to_le32(lower_32_bits(np)); } static inline void enh_set_tx_desc_len_on_chain(struct dma_desc *p, int len) @@ -117,9 +121,13 @@ static inline void ndesc_rx_set_on_chain(struct dma_desc *p, int end) p->des1 |= cpu_to_le32(RDES1_SECOND_ADDRESS_CHAINED); } -static inline void ndesc_tx_set_on_chain(struct dma_desc *p) +static inline void ndesc_end_tx_desc_on_chain(struct dma_desc *p, dma_addr_t np, + bool hwts_tx_en) { p->des1 |= cpu_to_le32(TDES1_SECOND_ADDRESS_CHAINED); + /* des3 is overwritten with timestamp in case of IEEE 1588-2002 */ + if (hwts_tx_en) + p->des3 = cpu_to_le32(lower_32_bits(np)); } static inline void norm_set_tx_desc_len_on_chain(struct dma_desc *p, int len) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c index f51da6c80717b..7918759369bb9 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c @@ -417,7 +417,8 @@ static void dwmac4_rd_prepare_tso_tx_desc(struct dma_desc *p, int is_fs, p->des3 = cpu_to_le32(tdes3); } -static void dwmac4_release_tx_desc(struct dma_desc *p, int mode) +static void dwmac4_release_tx_desc(struct dma_desc *p, int mode, + dma_addr_t np, bool hwts_tx) { p->des0 = 0; p->des1 = 0; diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c index deb0a787cb045..98135173a321d 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c @@ -323,7 +323,8 @@ static void dwxgmac2_prepare_tso_tx_desc(struct dma_desc *p, int is_fs, p->des3 = cpu_to_le32(tdes3); } -static void dwxgmac2_release_tx_desc(struct dma_desc *p, int mode) +static void dwxgmac2_release_tx_desc(struct dma_desc *p, int mode, + dma_addr_t np, bool hwts_tx) { p->des0 = 0; p->des1 = 0; diff --git a/drivers/net/ethernet/stmicro/stmmac/enh_desc.c b/drivers/net/ethernet/stmicro/stmmac/enh_desc.c index 5b4bae8c5e89a..ed91ffb022722 100644 --- a/drivers/net/ethernet/stmicro/stmmac/enh_desc.c +++ b/drivers/net/ethernet/stmicro/stmmac/enh_desc.c @@ -309,7 +309,7 @@ static void enh_desc_init_tx_desc(struct dma_desc *p, int mode, int end) { memset(p, 0, offsetof(struct dma_desc, des2)); if (mode == STMMAC_CHAIN_MODE) - enh_desc_end_tx_desc_on_chain(p); + enh_desc_end_tx_desc_on_chain(p, 0, false); else enh_desc_end_tx_desc_on_ring(p, end); } @@ -334,13 +334,14 @@ static int enh_desc_get_tx_ls(struct dma_desc *p) return (le32_to_cpu(p->des0) & ETDES0_LAST_SEGMENT) >> 29; } -static void enh_desc_release_tx_desc(struct dma_desc *p, int mode) +static void enh_desc_release_tx_desc(struct dma_desc *p, int mode, + dma_addr_t np, bool hwts_tx) { int ter = (le32_to_cpu(p->des0) & ETDES0_END_RING) >> 21; memset(p, 0, offsetof(struct dma_desc, des2)); if (mode == STMMAC_CHAIN_MODE) - enh_desc_end_tx_desc_on_chain(p); + enh_desc_end_tx_desc_on_chain(p, np, hwts_tx); else enh_desc_end_tx_desc_on_ring(p, ter); } diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index b1654e86179ad..ed961ea1f939a 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -53,7 +53,8 @@ struct stmmac_desc_ops { void (*set_tx_owner)(struct dma_desc *p); int (*get_tx_owner)(struct dma_desc *p); /* Clean the tx descriptor as soon as the tx irq is received */ - void (*release_tx_desc)(struct dma_desc *p, int mode); + void (*release_tx_desc)(struct dma_desc *p, int mode, + dma_addr_t np, bool hwts_tx); /* Clear interrupt on tx frame completion. When this bit is * set an interrupt happens as soon as the frame is transmitted */ void (*set_tx_ic)(struct dma_desc *p); @@ -553,7 +554,6 @@ struct stmmac_mode_ops { int (*set_16kib_bfsize)(int mtu); void (*init_desc3)(struct dma_desc *p); void (*refill_desc3)(struct stmmac_rx_queue *rx_q, struct dma_desc *p); - void (*clean_desc3)(struct stmmac_tx_queue *tx_q, struct dma_desc *p); }; #define stmmac_mode_init(__priv, __args...) \ @@ -568,8 +568,6 @@ struct stmmac_mode_ops { stmmac_do_void_callback(__priv, mode, init_desc3, __args) #define stmmac_refill_desc3(__priv, __args...) \ stmmac_do_void_callback(__priv, mode, refill_desc3, __args) -#define stmmac_clean_desc3(__priv, __args...) \ - stmmac_do_void_callback(__priv, mode, clean_desc3, __args) struct tc_cls_u32_offload; struct tc_cbs_qopt_offload; diff --git a/drivers/net/ethernet/stmicro/stmmac/norm_desc.c b/drivers/net/ethernet/stmicro/stmmac/norm_desc.c index a26b58f9b5732..6f90b0b6f54c6 100644 --- a/drivers/net/ethernet/stmicro/stmmac/norm_desc.c +++ b/drivers/net/ethernet/stmicro/stmmac/norm_desc.c @@ -183,7 +183,7 @@ static void ndesc_init_tx_desc(struct dma_desc *p, int mode, int end) { memset(p, 0, offsetof(struct dma_desc, des2)); if (mode == STMMAC_CHAIN_MODE) - ndesc_tx_set_on_chain(p); + ndesc_end_tx_desc_on_chain(p, 0, false); else ndesc_end_tx_desc_on_ring(p, end); } @@ -208,13 +208,14 @@ static int ndesc_get_tx_ls(struct dma_desc *p) return (le32_to_cpu(p->des1) & TDES1_LAST_SEGMENT) >> 30; } -static void ndesc_release_tx_desc(struct dma_desc *p, int mode) +static void ndesc_release_tx_desc(struct dma_desc *p, int mode, + dma_addr_t np, bool hwts_tx) { int ter = (le32_to_cpu(p->des1) & TDES1_END_RING) >> 25; memset(p, 0, offsetof(struct dma_desc, des2)); if (mode == STMMAC_CHAIN_MODE) - ndesc_tx_set_on_chain(p); + ndesc_end_tx_desc_on_chain(p, np, hwts_tx); else ndesc_end_tx_desc_on_ring(p, ter); } diff --git a/drivers/net/ethernet/stmicro/stmmac/ring_mode.c b/drivers/net/ethernet/stmicro/stmmac/ring_mode.c index 382d94a3b9720..3110b10675cd5 100644 --- a/drivers/net/ethernet/stmicro/stmmac/ring_mode.c +++ b/drivers/net/ethernet/stmicro/stmmac/ring_mode.c @@ -111,18 +111,6 @@ static void init_desc3(struct dma_desc *p) p->des3 = cpu_to_le32(le32_to_cpu(p->des2) + BUF_SIZE_8KiB); } -static void clean_desc3(struct stmmac_tx_queue *tx_q, struct dma_desc *p) -{ - struct stmmac_priv *priv = tx_q->priv_data; - unsigned int entry = tx_q->dirty_tx; - - /* des3 is only used for jumbo frames tx or time stamping */ - if (unlikely(tx_q->tx_skbuff_dma[entry].is_jumbo || - (tx_q->tx_skbuff_dma[entry].last_segment && - !priv->extend_desc && priv->hwts_tx_en))) - p->des3 = 0; -} - static int set_16kib_bfsize(int mtu) { int ret = 0; @@ -136,6 +124,5 @@ const struct stmmac_mode_ops ring_mode_ops = { .jumbo_frm = jumbo_frm, .refill_desc3 = refill_desc3, .init_desc3 = init_desc3, - .clean_desc3 = clean_desc3, .set_16kib_bfsize = set_16kib_bfsize, }; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index b4ee92afe8c81..529a9bf406995 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -2062,38 +2062,32 @@ static int __init_dma_tx_desc_rings(struct stmmac_priv *priv, "(%s) dma_tx_phy=0x%08x\n", __func__, (u32)tx_q->dma_tx_phy); - /* Setup the chained descriptor addresses */ - if (priv->mode == STMMAC_CHAIN_MODE) { - if (priv->extend_desc) - stmmac_mode_init(priv, tx_q->dma_etx, - tx_q->dma_tx_phy, - dma_conf->dma_tx_size, 1); - else if (!(tx_q->tbs & STMMAC_TBS_AVAIL)) - stmmac_mode_init(priv, tx_q->dma_tx, - tx_q->dma_tx_phy, - dma_conf->dma_tx_size, 0); - } - /* This must be performed on each SKB/XSK mode switch */ tx_q->xsk_pool = stmmac_get_xsk_pool(priv, queue); for (i = 0; i < dma_conf->dma_tx_size; i++) { struct dma_desc *p; + dma_addr_t np; - if (priv->extend_desc) - p = &((tx_q->dma_etx + i)->basic); - else if (tx_q->tbs & STMMAC_TBS_AVAIL) - p = &((tx_q->dma_entx + i)->basic); - else - p = tx_q->dma_tx + i; - - stmmac_clear_desc(priv, p); + np = STMMAC_GET_ENTRY(i, dma_conf->dma_tx_size); + if (priv->extend_desc) { + p = &tx_q->dma_etx[i].basic; + np = tx_q->dma_tx_phy + np * sizeof(struct dma_extended_desc); + } else if (tx_q->tbs & STMMAC_TBS_AVAIL) { + p = &tx_q->dma_entx[i].basic; + np = tx_q->dma_tx_phy + np * sizeof(struct dma_edesc); + } else { + p = &tx_q->dma_tx[i]; + np = tx_q->dma_tx_phy + np * sizeof(struct dma_desc); + } tx_q->tx_skbuff_dma[i].buf = 0; tx_q->tx_skbuff_dma[i].map_as_page = false; tx_q->tx_skbuff_dma[i].len = 0; tx_q->tx_skbuff_dma[i].last_segment = false; tx_q->tx_skbuff[i] = NULL; + + stmmac_release_tx_desc(priv, p, priv->mode, np, true); } return 0; @@ -2938,6 +2932,7 @@ static int stmmac_tx_clean(struct stmmac_priv *priv, int budget, u32 queue, struct xdp_frame *xdpf; struct sk_buff *skb; struct dma_desc *p; + dma_addr_t np; int status; if (tx_q->tx_skbuff_dma[entry].buf_type == STMMAC_TXBUF_T_XDP_TX || @@ -2952,12 +2947,17 @@ static int stmmac_tx_clean(struct stmmac_priv *priv, int budget, u32 queue, skb = NULL; } - if (priv->extend_desc) - p = (struct dma_desc *)(tx_q->dma_etx + entry); - else if (tx_q->tbs & STMMAC_TBS_AVAIL) + np = STMMAC_GET_ENTRY(entry, priv->dma_conf.dma_tx_size); + if (priv->extend_desc) { + p = &tx_q->dma_etx[entry].basic; + np = tx_q->dma_tx_phy + np * sizeof(struct dma_extended_desc); + } else if (tx_q->tbs & STMMAC_TBS_AVAIL) { p = &tx_q->dma_entx[entry].basic; - else - p = tx_q->dma_tx + entry; + np = tx_q->dma_tx_phy + np * sizeof(struct dma_edesc); + } else { + p = &tx_q->dma_tx[entry]; + np = tx_q->dma_tx_phy + np * sizeof(struct dma_desc); + } status = stmmac_tx_status(priv, &priv->xstats, p); /* Check if the descriptor is owned by the DMA */ @@ -3011,8 +3011,6 @@ static int stmmac_tx_clean(struct stmmac_priv *priv, int budget, u32 queue, tx_q->tx_skbuff_dma[entry].map_as_page = false; } - stmmac_clean_desc3(priv, tx_q, p); - tx_q->tx_skbuff_dma[entry].last_segment = false; tx_q->tx_skbuff_dma[entry].is_jumbo = false; @@ -3040,7 +3038,8 @@ static int stmmac_tx_clean(struct stmmac_priv *priv, int budget, u32 queue, } } - stmmac_release_tx_desc(priv, p, priv->mode); + stmmac_release_tx_desc(priv, p, priv->mode, np, + priv->hwts_tx_en); entry = STMMAC_GET_ENTRY(entry, priv->dma_conf.dma_tx_size); } -- cgit v1.2.3 From 36658042c830defcf2cc5b3b535827a7742b082b Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Tue, 10 Jun 2025 17:11:13 +0300 Subject: net: stmmac: dwmac1000: Fix long-broken Tx Jumbo-frames support As soon as the Jumbo frames with MTU over 8K enabled it's possible to have a garbage or truncated frame transmitted to a recipient. The problem is multi-dimensional and mainly connected with over-complicated and weakly tested DW GMAC Jumbos implementation. First if all let's clarify the DW GMAC descriptors capabilities crucial for the denoted problems. DW MAC and GMAC support two types of the DMA-descriptors Normal and Enhanced - the former descriptor can have up to two 2K buffers attached, meanwhile the later one can be initialized with up to two 8K buffers. The DMA-descriptors can be allocated either as a continuous array of descriptors (ring mode) or as a set of individually linked descriptors (chain mode). All of that affects the size of the buffers support by each descriptor since in the chain mode the descriptor field responsible for the buffer2 pointer is utilized for linking the adjacent descriptors. Thus here is the maximum buffers size Normal Tx descriptor: 2K (chain), 2x2K (ring) Enhanced Tx descriptor: 8K (chain), 2x8K (ring) The main problem happens when an SKB with a Jumboed fragment is being transmitted. The stmmac_xmit() method allocates only one descriptor in for each segment that case. So if it's a Chained Enhanced descriptor then over 8K fragment just won't fit in there and will be simply truncated. If it's a Ringed Enhanced Descriptor then the buffers length will be properly initialized by the enh_desc_prepare_tx_desc() method, but the descriptor won't have the buffer2 pointer field initialized and a garbage from the DMA address 0x0 will be transmitted. The same problem happens for the Normal descriptors except that it happens for over 2K fragments. Moreover SKBs with Jumboed head won't be properly handled for the Ringed Normal Descriptors mode either. See the buffer2 offset utilized in the stmmac_jumbo_frm() method for the buffer2 pointer. It's always 4K meanwhile the buffer size in this case is 2K. Thus some data will be missed in the sent out frame. So to speak the Jumbos support is almost completely broken for DW MAC/GMAC. The only working case is when an SKB with Jumboed head is submitted and the Enhanced Tx descriptor is utilized. Let's fix the denoted problems. But instead of fixing each part of the affected code and making it even more complicated let's do that in a more elegant way. The jumbos support can be significantly simplified by implementing a allocation-based pattern already developed for the TSO feature. The only difference is that a simple stmmac_desc_ops::prepare_tx_desc() method will be utilized for the Tx DMA-descriptors initialization. Thus a lot of redundant code in the chain_mode.c/ring_mode.c modules will be replaced with a single stmmac_tx_allocator() method utilized for the DMA-descriptors allocation for all SKB head and fragments. As a nice side-effect of this change the stmmac_xmit() and stmmac_tso_xmit() methods now look very much similar which makes the code much more readable and maintainable. Fixes: 286a83721720 ("stmmac: add CHAINED descriptor mode support (V4)") Fixes: 47dd7a540b8a ("net: add support for STMicroelectronics Ethernet controllers.") Signed-off-by: Serge Semin --- Note after this change there is no longer need in the GMAC max MTU 9K constraint for Tx. It can be up to 16K from now. The only thing left to fix is the Rx path. Alas it will be harder to implement. --- drivers/net/ethernet/stmicro/stmmac/chain_mode.c | 91 ---------- drivers/net/ethernet/stmicro/stmmac/descs_com.h | 52 ++++-- drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c | 10 +- .../net/ethernet/stmicro/stmmac/dwxgmac2_descs.c | 10 +- drivers/net/ethernet/stmicro/stmmac/enh_desc.c | 13 +- drivers/net/ethernet/stmicro/stmmac/hwif.h | 11 +- drivers/net/ethernet/stmicro/stmmac/norm_desc.c | 13 +- drivers/net/ethernet/stmicro/stmmac/ring_mode.c | 84 --------- drivers/net/ethernet/stmicro/stmmac/stmmac.h | 1 - drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 200 +++++++++++---------- 10 files changed, 169 insertions(+), 316 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/chain_mode.c b/drivers/net/ethernet/stmicro/stmmac/chain_mode.c index d6118745d681d..fb5749bfb3480 100644 --- a/drivers/net/ethernet/stmicro/stmmac/chain_mode.c +++ b/drivers/net/ethernet/stmicro/stmmac/chain_mode.c @@ -14,95 +14,6 @@ #include "stmmac.h" -static int jumbo_frm(struct stmmac_tx_queue *tx_q, struct sk_buff *skb, - int csum) -{ - unsigned int nopaged_len = skb_headlen(skb); - struct stmmac_priv *priv = tx_q->priv_data; - unsigned int entry = tx_q->cur_tx; - unsigned int bmax, buf_len, des2; - unsigned int i = 1, len; - struct dma_desc *desc; - - if (priv->extend_desc) - desc = (struct dma_desc *)(tx_q->dma_etx + entry); - else - desc = tx_q->dma_tx + entry; - - if (priv->plat->enh_desc) - bmax = BUF_SIZE_8KiB; - else - bmax = BUF_SIZE_2KiB; - - buf_len = min_t(unsigned int, nopaged_len, bmax); - len = nopaged_len - buf_len; - - des2 = dma_map_single(priv->device, skb->data, - buf_len, DMA_TO_DEVICE); - desc->des2 = cpu_to_le32(des2); - if (dma_mapping_error(priv->device, des2)) - return -1; - tx_q->tx_skbuff_dma[entry].buf = des2; - tx_q->tx_skbuff_dma[entry].len = buf_len; - /* do not close the descriptor and do not set own bit */ - stmmac_prepare_tx_desc(priv, desc, 1, buf_len, csum, STMMAC_CHAIN_MODE, - 0, false, skb->len); - - while (len != 0) { - tx_q->tx_skbuff[entry] = NULL; - entry = STMMAC_GET_ENTRY(entry, priv->dma_conf.dma_tx_size); - - if (priv->extend_desc) - desc = (struct dma_desc *)(tx_q->dma_etx + entry); - else - desc = tx_q->dma_tx + entry; - - if (len > bmax) { - des2 = dma_map_single(priv->device, - (skb->data + bmax * i), - bmax, DMA_TO_DEVICE); - desc->des2 = cpu_to_le32(des2); - if (dma_mapping_error(priv->device, des2)) - return -1; - tx_q->tx_skbuff_dma[entry].buf = des2; - tx_q->tx_skbuff_dma[entry].len = bmax; - stmmac_prepare_tx_desc(priv, desc, 0, bmax, csum, - STMMAC_CHAIN_MODE, 1, false, skb->len); - len -= bmax; - i++; - } else { - des2 = dma_map_single(priv->device, - (skb->data + bmax * i), len, - DMA_TO_DEVICE); - desc->des2 = cpu_to_le32(des2); - if (dma_mapping_error(priv->device, des2)) - return -1; - tx_q->tx_skbuff_dma[entry].buf = des2; - tx_q->tx_skbuff_dma[entry].len = len; - /* last descriptor can be set now */ - stmmac_prepare_tx_desc(priv, desc, 0, len, csum, - STMMAC_CHAIN_MODE, 1, - !skb_is_nonlinear(skb), skb->len); - len = 0; - } - } - - tx_q->cur_tx = entry; - - return entry; -} - -static bool is_jumbo_frm(unsigned int len, bool enh_desc) -{ - bool ret = false; - - if ((enh_desc && (len > BUF_SIZE_8KiB)) || - (!enh_desc && (len > BUF_SIZE_2KiB))) - ret = true; - - return ret; -} - static void init_dma_chain(void *des, dma_addr_t phy_addr, unsigned int size, unsigned int extend_desc) { @@ -150,7 +61,5 @@ static void refill_desc3(struct stmmac_rx_queue *rx_q, struct dma_desc *p) const struct stmmac_mode_ops chain_mode_ops = { .init = init_dma_chain, - .is_jumbo_frm = is_jumbo_frm, - .jumbo_frm = jumbo_frm, .refill_desc3 = refill_desc3, }; diff --git a/drivers/net/ethernet/stmicro/stmmac/descs_com.h b/drivers/net/ethernet/stmicro/stmmac/descs_com.h index 0019f389181f7..bedb18538d519 100644 --- a/drivers/net/ethernet/stmicro/stmmac/descs_com.h +++ b/drivers/net/ethernet/stmicro/stmmac/descs_com.h @@ -38,22 +38,25 @@ static inline void enh_desc_end_tx_desc_on_ring(struct dma_desc *p, int end) p->des0 &= cpu_to_le32(~ETDES0_END_RING); } -/* The maximum buffer 1 size is 8KiB - 1. However, we limit to 4KiB. */ static inline void enh_set_tx_desc_len_on_ring(struct dma_desc *p, int len) { - unsigned int buffer1_max_length = BUF_SIZE_4KiB; + if (unlikely(len > ETDES1_BUFFER1_SIZE_MASK)) { + u32 buf2_len = FIELD_PREP(ETDES1_BUFFER2_SIZE_MASK, + len - ETDES1_BUFFER1_SIZE_MASK); - if (unlikely(len > buffer1_max_length)) { - p->des1 |= cpu_to_le32(FIELD_PREP(ETDES1_BUFFER2_SIZE_MASK, - len - buffer1_max_length) | - FIELD_PREP(ETDES1_BUFFER1_SIZE_MASK, - buffer1_max_length)); + p->des1 |= cpu_to_le32(ETDES1_BUFFER1_SIZE_MASK | buf2_len); + p->des3 = cpu_to_le32(le32_to_cpu(p->des2) + ETDES1_BUFFER1_SIZE_MASK); } else { - p->des1 |= cpu_to_le32(FIELD_PREP(ETDES1_BUFFER1_SIZE_MASK, - len)); + p->des1 |= cpu_to_le32(FIELD_PREP(ETDES1_BUFFER1_SIZE_MASK, len)); } } +static inline unsigned int enh_desc_tx_desc_len_on_ring(void) +{ + return FIELD_MAX(ETDES1_BUFFER1_SIZE_MASK) + + FIELD_MAX(ETDES1_BUFFER2_SIZE_MASK); +} + /* Normal descriptors */ static inline void ndesc_rx_set_on_ring(struct dma_desc *p, int end, int bfsize) { @@ -77,22 +80,25 @@ static inline void ndesc_end_tx_desc_on_ring(struct dma_desc *p, int end) p->des1 &= cpu_to_le32(~TDES1_END_RING); } -/* The maximum buffer 1 size is 2KiB - 1, limited by the mask width */ static inline void norm_set_tx_desc_len_on_ring(struct dma_desc *p, int len) { - unsigned int buffer1_max_length = BUF_SIZE_2KiB - 1; + if (unlikely(len > TDES1_BUFFER1_SIZE_MASK)) { + u32 buf2_len = FIELD_PREP(TDES1_BUFFER2_SIZE_MASK, + len - TDES1_BUFFER1_SIZE_MASK); - if (unlikely(len > buffer1_max_length)) { - p->des1 |= cpu_to_le32(FIELD_PREP(TDES1_BUFFER2_SIZE_MASK, - len - buffer1_max_length) | - FIELD_PREP(TDES1_BUFFER1_SIZE_MASK, - buffer1_max_length)); + p->des1 |= cpu_to_le32(TDES1_BUFFER1_SIZE_MASK | buf2_len); + p->des3 = cpu_to_le32(le32_to_cpu(p->des2) + TDES1_BUFFER1_SIZE_MASK); } else { - p->des1 |= cpu_to_le32(FIELD_PREP(TDES1_BUFFER1_SIZE_MASK, - len)); + p->des1 |= cpu_to_le32(FIELD_PREP(TDES1_BUFFER1_SIZE_MASK, len)); } } +static inline unsigned int ndesc_tx_desc_len_on_ring(void) +{ + return FIELD_MAX(TDES1_BUFFER1_SIZE_MASK) + + FIELD_MAX(TDES1_BUFFER2_SIZE_MASK); +} + /* Specific functions used for Chain mode */ /* Enhanced descriptors */ @@ -115,6 +121,11 @@ static inline void enh_set_tx_desc_len_on_chain(struct dma_desc *p, int len) p->des1 |= cpu_to_le32(len & ETDES1_BUFFER1_SIZE_MASK); } +static inline unsigned int enh_desc_tx_desc_len_on_chain(void) +{ + return FIELD_MAX(ETDES1_BUFFER1_SIZE_MASK); +} + /* Normal descriptors */ static inline void ndesc_rx_set_on_chain(struct dma_desc *p, int end) { @@ -135,6 +146,11 @@ static inline void norm_set_tx_desc_len_on_chain(struct dma_desc *p, int len) p->des1 |= cpu_to_le32(len & TDES1_BUFFER1_SIZE_MASK); } +static inline unsigned int ndesc_tx_desc_len_on_chain(void) +{ + return FIELD_MAX(TDES1_BUFFER1_SIZE_MASK); +} + /* Functions used for all ring/chain modes, enhanced/normal descriptors */ static inline int com_desc_rx_coe_rdes0(int ipc_err, int type, int payload_err) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c index 7918759369bb9..90c1567b19b64 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c @@ -191,11 +191,6 @@ static int dwmac4_wrback_get_rx_status(struct stmmac_extra_stats *x, return ret; } -static int dwmac4_rd_get_tx_len(struct dma_desc *p) -{ - return (le32_to_cpu(p->des2) & TDES2_BUFFER1_SIZE_MASK); -} - static int dwmac4_get_tx_owner(struct dma_desc *p) { return (le32_to_cpu(p->des3) & TDES3_OWN) >> TDES3_OWN_SHIFT; @@ -338,6 +333,11 @@ static void dwmac4_rd_init_tx_desc(struct dma_desc *p, int mode, int end) p->des3 = 0; } +static unsigned int dwmac4_rd_get_tx_len(int mode) +{ + return FIELD_MAX(TDES2_BUFFER1_SIZE_MASK); +} + static void dwmac4_rd_prepare_tx_desc(struct dma_desc *p, int is_fs, int len, bool csum_flag, int mode, bool tx_own, bool ls, unsigned int tot_pkt_len) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c index 98135173a321d..a4b40adeda18b 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c @@ -129,11 +129,6 @@ static int dwxgmac2_get_rx_status(struct stmmac_extra_stats *x, return ret; } -static int dwxgmac2_get_tx_len(struct dma_desc *p) -{ - return (le32_to_cpu(p->des2) & XGMAC_TDES2_B1L); -} - static int dwxgmac2_get_tx_owner(struct dma_desc *p) { return (le32_to_cpu(p->des3) & XGMAC_TDES3_OWN) > 0; @@ -247,6 +242,11 @@ static void dwxgmac2_init_tx_desc(struct dma_desc *p, int mode, int end) p->des3 = 0; } +static unsigned int dwxgmac2_get_tx_len(int mode) +{ + return FIELD_MAX(XGMAC_TDES2_B1L); +} + static void dwxgmac2_prepare_tx_desc(struct dma_desc *p, int is_fs, int len, bool csum_flag, int mode, bool tx_own, bool ls, unsigned int tot_pkt_len) diff --git a/drivers/net/ethernet/stmicro/stmmac/enh_desc.c b/drivers/net/ethernet/stmicro/stmmac/enh_desc.c index ed91ffb022722..6e2a9320bfb2e 100644 --- a/drivers/net/ethernet/stmicro/stmmac/enh_desc.c +++ b/drivers/net/ethernet/stmicro/stmmac/enh_desc.c @@ -70,11 +70,6 @@ static int enh_desc_get_tx_status(struct stmmac_extra_stats *x, return ret; } -static int enh_desc_get_tx_len(struct dma_desc *p) -{ - return (le32_to_cpu(p->des1) & ETDES1_BUFFER1_SIZE_MASK); -} - static int enh_desc_rx_ext_status(unsigned int rdes4, struct stmmac_extra_stats *x) { @@ -334,6 +329,14 @@ static int enh_desc_get_tx_ls(struct dma_desc *p) return (le32_to_cpu(p->des0) & ETDES0_LAST_SEGMENT) >> 29; } +static unsigned int enh_desc_get_tx_len(int mode) +{ + if (mode == STMMAC_CHAIN_MODE) + return enh_desc_tx_desc_len_on_chain(); + else + return enh_desc_tx_desc_len_on_ring(); +} + static void enh_desc_release_tx_desc(struct dma_desc *p, int mode, dma_addr_t np, bool hwts_tx) { diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index ed961ea1f939a..085e2b0624785 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -66,8 +66,8 @@ struct stmmac_desc_ops { bool (*get_rx_vlan_valid)(struct dma_desc *p); /* Return the transmit status looking at the TDES1 */ int (*tx_status)(struct stmmac_extra_stats *x, struct dma_desc *p); - /* Get the buffer size from the descriptor */ - int (*get_tx_len)(struct dma_desc *p); + /* Get the buffer(s) size for a single descriptor */ + unsigned int (*get_tx_len)(int mode); /* Handle extra events on specific interrupts hw dependent */ void (*set_rx_owner)(struct dma_desc *p, int disable_rx_ic); /* Get the receive frame size */ @@ -548,9 +548,6 @@ struct stmmac_rx_queue; struct stmmac_mode_ops { void (*init) (void *des, dma_addr_t phy_addr, unsigned int size, unsigned int extend_desc); - bool (*is_jumbo_frm)(unsigned int len, bool enh_desc); - int (*jumbo_frm)(struct stmmac_tx_queue *tx_q, struct sk_buff *skb, - int csum); int (*set_16kib_bfsize)(int mtu); void (*init_desc3)(struct dma_desc *p); void (*refill_desc3)(struct stmmac_rx_queue *rx_q, struct dma_desc *p); @@ -558,10 +555,6 @@ struct stmmac_mode_ops { #define stmmac_mode_init(__priv, __args...) \ stmmac_do_void_callback(__priv, mode, init, __args) -#define stmmac_is_jumbo_frm(__priv, __args...) \ - stmmac_do_callback(__priv, mode, is_jumbo_frm, __args) -#define stmmac_jumbo_frm(__priv, __args...) \ - stmmac_do_callback(__priv, mode, jumbo_frm, __args) #define stmmac_set_16kib_bfsize(__priv, __args...) \ stmmac_do_callback(__priv, mode, set_16kib_bfsize, __args) #define stmmac_init_desc3(__priv, __args...) \ diff --git a/drivers/net/ethernet/stmicro/stmmac/norm_desc.c b/drivers/net/ethernet/stmicro/stmmac/norm_desc.c index 6f90b0b6f54c6..3ca188c934bc5 100644 --- a/drivers/net/ethernet/stmicro/stmmac/norm_desc.c +++ b/drivers/net/ethernet/stmicro/stmmac/norm_desc.c @@ -55,11 +55,6 @@ static int ndesc_get_tx_status(struct stmmac_extra_stats *x, return ret; } -static int ndesc_get_tx_len(struct dma_desc *p) -{ - return (le32_to_cpu(p->des1) & RDES1_BUFFER1_SIZE_MASK); -} - /* This function verifies if each incoming frame has some errors * and, if required, updates the multicast statistics. * In case of success, it returns good_frame because the GMAC device @@ -208,6 +203,14 @@ static int ndesc_get_tx_ls(struct dma_desc *p) return (le32_to_cpu(p->des1) & TDES1_LAST_SEGMENT) >> 30; } +static unsigned int ndesc_get_tx_len(int mode) +{ + if (mode == STMMAC_CHAIN_MODE) + return ndesc_tx_desc_len_on_chain(); + else + return ndesc_tx_desc_len_on_ring(); +} + static void ndesc_release_tx_desc(struct dma_desc *p, int mode, dma_addr_t np, bool hwts_tx) { diff --git a/drivers/net/ethernet/stmicro/stmmac/ring_mode.c b/drivers/net/ethernet/stmicro/stmmac/ring_mode.c index 3110b10675cd5..ff3ddb800d049 100644 --- a/drivers/net/ethernet/stmicro/stmmac/ring_mode.c +++ b/drivers/net/ethernet/stmicro/stmmac/ring_mode.c @@ -14,88 +14,6 @@ #include "stmmac.h" -static int jumbo_frm(struct stmmac_tx_queue *tx_q, struct sk_buff *skb, - int csum) -{ - unsigned int nopaged_len = skb_headlen(skb); - struct stmmac_priv *priv = tx_q->priv_data; - unsigned int entry = tx_q->cur_tx; - unsigned int bmax, len, des2; - struct dma_desc *desc; - - if (priv->extend_desc) - desc = (struct dma_desc *)(tx_q->dma_etx + entry); - else - desc = tx_q->dma_tx + entry; - - if (priv->plat->enh_desc) - bmax = BUF_SIZE_8KiB; - else - bmax = BUF_SIZE_2KiB; - - len = nopaged_len - bmax; - - if (nopaged_len > BUF_SIZE_8KiB) { - - des2 = dma_map_single(priv->device, skb->data, bmax, - DMA_TO_DEVICE); - desc->des2 = cpu_to_le32(des2); - if (dma_mapping_error(priv->device, des2)) - return -1; - - tx_q->tx_skbuff_dma[entry].buf = des2; - tx_q->tx_skbuff_dma[entry].len = bmax; - tx_q->tx_skbuff_dma[entry].is_jumbo = true; - - desc->des3 = cpu_to_le32(des2 + BUF_SIZE_4KiB); - stmmac_prepare_tx_desc(priv, desc, 1, bmax, csum, - STMMAC_RING_MODE, 0, false, skb->len); - tx_q->tx_skbuff[entry] = NULL; - entry = STMMAC_GET_ENTRY(entry, priv->dma_conf.dma_tx_size); - - if (priv->extend_desc) - desc = (struct dma_desc *)(tx_q->dma_etx + entry); - else - desc = tx_q->dma_tx + entry; - - des2 = dma_map_single(priv->device, skb->data + bmax, len, - DMA_TO_DEVICE); - desc->des2 = cpu_to_le32(des2); - if (dma_mapping_error(priv->device, des2)) - return -1; - tx_q->tx_skbuff_dma[entry].buf = des2; - tx_q->tx_skbuff_dma[entry].len = len; - tx_q->tx_skbuff_dma[entry].is_jumbo = true; - - desc->des3 = cpu_to_le32(des2 + BUF_SIZE_4KiB); - stmmac_prepare_tx_desc(priv, desc, 0, len, csum, - STMMAC_RING_MODE, 1, !skb_is_nonlinear(skb), - skb->len); - } else { - des2 = dma_map_single(priv->device, skb->data, - nopaged_len, DMA_TO_DEVICE); - desc->des2 = cpu_to_le32(des2); - if (dma_mapping_error(priv->device, des2)) - return -1; - tx_q->tx_skbuff_dma[entry].buf = des2; - tx_q->tx_skbuff_dma[entry].len = nopaged_len; - tx_q->tx_skbuff_dma[entry].is_jumbo = true; - desc->des3 = cpu_to_le32(des2 + BUF_SIZE_4KiB); - stmmac_prepare_tx_desc(priv, desc, 1, nopaged_len, csum, - STMMAC_RING_MODE, 0, !skb_is_nonlinear(skb), - skb->len); - } - - tx_q->cur_tx = entry; - - return entry; -} - -static bool is_jumbo_frm(unsigned int len, bool enh_desc) -{ - return len >= BUF_SIZE_4KiB; -} - static void refill_desc3(struct stmmac_rx_queue *rx_q, struct dma_desc *p) { struct stmmac_priv *priv = rx_q->priv_data; @@ -120,8 +38,6 @@ static int set_16kib_bfsize(int mtu) } const struct stmmac_mode_ops ring_mode_ops = { - .is_jumbo_frm = is_jumbo_frm, - .jumbo_frm = jumbo_frm, .refill_desc3 = refill_desc3, .init_desc3 = init_desc3, .set_16kib_bfsize = set_16kib_bfsize, diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h index 74f7817b6af4e..1e8ae9b17ca3d 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h @@ -90,7 +90,6 @@ struct stmmac_tx_info { bool map_as_page; unsigned len; bool last_segment; - bool is_jumbo; enum stmmac_txbuf_type buf_type; struct xsk_tx_metadata_compl xsk_meta; }; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 529a9bf406995..7d47b0ea1a2b0 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -2844,7 +2844,6 @@ static bool stmmac_xdp_xmit_zc(struct stmmac_priv *priv, u32 queue, u32 budget) tx_q->tx_skbuff_dma[entry].map_as_page = false; tx_q->tx_skbuff_dma[entry].len = xdp_desc.len; tx_q->tx_skbuff_dma[entry].last_segment = true; - tx_q->tx_skbuff_dma[entry].is_jumbo = false; stmmac_set_desc_addr(priv, tx_desc, dma_addr); @@ -3012,7 +3011,6 @@ static int stmmac_tx_clean(struct stmmac_priv *priv, int budget, u32 queue, } tx_q->tx_skbuff_dma[entry].last_segment = false; - tx_q->tx_skbuff_dma[entry].is_jumbo = false; if (xdpf && tx_q->tx_skbuff_dma[entry].buf_type == STMMAC_TXBUF_T_XDP_TX) { @@ -4751,6 +4749,57 @@ flush_ring: return ret; } +/** + * stmmac_tx_allocator - Tx DMA descriptors allocator for cross-descriptor frames + * @priv: driver private structure + * @des: buffer start address + * @total_len: total length to fill in descriptors + * @last_segment: condition for the last descriptor + * @queue: TX queue index + * Description: + * This function fills descriptor and request new descriptors according to + * buffer length to fill. The method specifically dedicated for Jumbo-frames + * which don't fit to a single descriptor. + */ +static void stmmac_tx_allocator(struct stmmac_priv *priv, dma_addr_t des, + int total_len, bool last_segment, u32 queue) +{ + struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[queue]; + u32 buff_size, buff_max; + struct dma_desc *desc; + int tmp_len; + + buff_max = stmmac_get_tx_len(priv, priv->mode); + tmp_len = total_len; + + while (tmp_len > 0) { + dma_addr_t curr_addr; + + tx_q->cur_tx = STMMAC_GET_ENTRY(tx_q->cur_tx, + priv->dma_conf.dma_tx_size); + WARN_ON(tx_q->tx_skbuff[tx_q->cur_tx]); + + if (likely(priv->extend_desc)) + desc = &tx_q->dma_etx[tx_q->cur_tx].basic; + else if (tx_q->tbs & STMMAC_TBS_AVAIL) + desc = &tx_q->dma_entx[tx_q->cur_tx].basic; + else + desc = &tx_q->dma_tx[tx_q->cur_tx]; + + curr_addr = des + (total_len - tmp_len); + stmmac_set_desc_addr(priv, desc, curr_addr); + + buff_size = tmp_len >= buff_max ? buff_max : tmp_len; + + /* Prepare _not_ first descriptor and set it' own bit */ + stmmac_prepare_tx_desc(priv, desc, false, buff_size, false, + priv->mode, true, + last_segment && (tmp_len <= buff_max), 0); + + tmp_len -= buff_max; + } +} + /** * stmmac_xmit - Tx entry point of the driver * @skb : the socket buffer @@ -4761,21 +4810,19 @@ flush_ring: */ static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev) { - bool enh_desc, has_vlan, set_ic, drop = false, is_jumbo = false; + unsigned int first_entry, tx_packets, nopaged_len, descbuf_len; struct stmmac_priv *priv = netdev_priv(dev); - unsigned int nopaged_len = skb_headlen(skb); u32 queue = skb_get_queue_mapping(skb); int nfrags = skb_shinfo(skb)->nr_frags; - unsigned int first_entry, tx_packets; int gso = skb_shinfo(skb)->gso_type; + bool has_vlan, set_ic, drop = false; struct stmmac_txq_stats *txq_stats; struct dma_edesc *tbs_desc = NULL; + int i, first_tx, csum_insertion; netdev_tx_t ret = NETDEV_TX_OK; struct dma_desc *desc, *first; struct stmmac_tx_queue *tx_q; - int i, csum_insertion = 0; struct netdev_queue *nq; - int entry, first_tx; dma_addr_t des; u32 sdu_len; @@ -4818,8 +4865,7 @@ static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev) /* Check if VLAN can be inserted by HW */ has_vlan = stmmac_vlan_insert(priv, skb, tx_q); - entry = tx_q->cur_tx; - first_entry = entry; + first_entry = tx_q->cur_tx; WARN_ON(tx_q->tx_skbuff[first_entry]); /* Check if CSUM should be inserted by HW */ @@ -4830,46 +4876,45 @@ static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev) } if (likely(priv->extend_desc)) - desc = (struct dma_desc *)(tx_q->dma_etx + entry); + desc = &tx_q->dma_etx[first_entry].basic; else if (tx_q->tbs & STMMAC_TBS_AVAIL) - desc = &tx_q->dma_entx[entry].basic; + desc = &tx_q->dma_entx[first_entry].basic; else - desc = tx_q->dma_tx + entry; + desc = &tx_q->dma_tx[first_entry]; first = desc; if (has_vlan) stmmac_set_desc_vlan(priv, first, STMMAC_VLAN_INSERT); - enh_desc = priv->plat->enh_desc; - /* To program the descriptors according to the size of the frame */ - if (enh_desc) - is_jumbo = stmmac_is_jumbo_frm(priv, skb->len, enh_desc); + des = dma_map_single(priv->device, skb->data, skb_headlen(skb), + DMA_TO_DEVICE); + if (dma_mapping_error(priv->device, des)) { + drop = true; + goto flush_ring; + } - if (unlikely(is_jumbo)) { - entry = stmmac_jumbo_frm(priv, tx_q, skb, csum_insertion); - if (unlikely(entry < 0) && (entry != -EINVAL)) { - drop = true; - goto flush_ring; - } + tx_q->tx_skbuff_dma[first_entry].buf = des; + tx_q->tx_skbuff_dma[first_entry].len = skb_headlen(skb); + tx_q->tx_skbuff_dma[first_entry].map_as_page = false; + tx_q->tx_skbuff_dma[first_entry].buf_type = STMMAC_TXBUF_T_SKB; + + stmmac_set_desc_addr(priv, first, des); + + nopaged_len = skb_headlen(skb); + descbuf_len = stmmac_get_tx_len(priv, priv->mode); + if (nopaged_len > descbuf_len) { + stmmac_tx_allocator(priv, des + descbuf_len, + nopaged_len - descbuf_len, + (nfrags == 0), queue); + nopaged_len = descbuf_len; } for (i = 0; i < nfrags; i++) { const skb_frag_t *frag = &skb_shinfo(skb)->frags[i]; - int len = skb_frag_size(frag); - bool last_segment = (i == (nfrags - 1)); - - entry = STMMAC_GET_ENTRY(entry, priv->dma_conf.dma_tx_size); - WARN_ON(tx_q->tx_skbuff[entry]); - - if (likely(priv->extend_desc)) - desc = (struct dma_desc *)(tx_q->dma_etx + entry); - else if (tx_q->tbs & STMMAC_TBS_AVAIL) - desc = &tx_q->dma_entx[entry].basic; - else - desc = tx_q->dma_tx + entry; - des = skb_frag_dma_map(priv->device, frag, 0, len, + des = skb_frag_dma_map(priv->device, frag, 0, + skb_frag_size(frag), DMA_TO_DEVICE); if (dma_mapping_error(priv->device, des)) { netdev_err(priv->dev, "Frag Tx DMA map failed\n"); @@ -4877,30 +4922,27 @@ static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev) goto flush_ring; /* should reuse desc w/o issues */ } - tx_q->tx_skbuff_dma[entry].buf = des; - - stmmac_set_desc_addr(priv, desc, des); - - tx_q->tx_skbuff_dma[entry].map_as_page = true; - tx_q->tx_skbuff_dma[entry].len = len; - tx_q->tx_skbuff_dma[entry].last_segment = last_segment; - tx_q->tx_skbuff_dma[entry].buf_type = STMMAC_TXBUF_T_SKB; + stmmac_tx_allocator(priv, des, skb_frag_size(frag), + (i == nfrags - 1), queue); - /* Prepare the descriptor and set the own bit too */ - stmmac_prepare_tx_desc(priv, desc, 0, len, csum_insertion, - priv->mode, 1, last_segment, skb->len); + tx_q->tx_skbuff_dma[tx_q->cur_tx].buf = des; + tx_q->tx_skbuff_dma[tx_q->cur_tx].len = skb_frag_size(frag); + tx_q->tx_skbuff_dma[tx_q->cur_tx].map_as_page = true; + tx_q->tx_skbuff_dma[tx_q->cur_tx].buf_type = STMMAC_TXBUF_T_SKB; } + tx_q->tx_skbuff_dma[tx_q->cur_tx].last_segment = true; + /* Only the last descriptor gets to point to the skb. */ - tx_q->tx_skbuff[entry] = skb; - tx_q->tx_skbuff_dma[entry].buf_type = STMMAC_TXBUF_T_SKB; + tx_q->tx_skbuff[tx_q->cur_tx] = skb; + tx_q->tx_skbuff_dma[tx_q->cur_tx].buf_type = STMMAC_TXBUF_T_SKB; /* According to the coalesce parameter the IC bit for the latest * segment is reset and the timer re-started to clean the tx status. * This approach takes care about the fragments: desc is the first * element in case of no SG. */ - tx_packets = (entry + 1) - first_tx; + tx_packets = (tx_q->cur_tx + 1) - first_tx; tx_q->tx_count_frames += tx_packets; if ((skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) && priv->hwts_tx_en) @@ -4917,11 +4959,11 @@ static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev) if (set_ic) { if (likely(priv->extend_desc)) - desc = &tx_q->dma_etx[entry].basic; + desc = &tx_q->dma_etx[tx_q->cur_tx].basic; else if (tx_q->tbs & STMMAC_TBS_AVAIL) - desc = &tx_q->dma_entx[entry].basic; + desc = &tx_q->dma_entx[tx_q->cur_tx].basic; else - desc = &tx_q->dma_tx[entry]; + desc = &tx_q->dma_tx[tx_q->cur_tx]; tx_q->tx_count_frames = 0; stmmac_set_tx_ic(priv, desc); @@ -4932,14 +4974,13 @@ static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev) * ndo_start_xmit will fill this descriptor the next time it's * called and stmmac_tx_clean may clean up to this descriptor. */ - entry = STMMAC_GET_ENTRY(entry, priv->dma_conf.dma_tx_size); - tx_q->cur_tx = entry; + tx_q->cur_tx = STMMAC_GET_ENTRY(tx_q->cur_tx, priv->dma_conf.dma_tx_size); if (netif_msg_pktdata(priv)) { netdev_dbg(priv->dev, - "%s: curr=%d dirty=%d f=%d, e=%d, first=%p, nfrags=%d", + "%s: curr=%d dirty=%d entry=%d, first=%p, nfrags=%d", __func__, tx_q->cur_tx, tx_q->dirty_tx, first_entry, - entry, first, nfrags); + first, nfrags); netdev_dbg(priv->dev, ">>> frame to be transmitted: "); print_pkt(skb->data, skb->len); @@ -4960,41 +5001,11 @@ static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev) if (priv->sarc_type) stmmac_set_desc_sarc(priv, first, priv->sarc_type); - /* Ready to fill the first descriptor and set the OWN bit w/o any - * problems because all the descriptors are actually ready to be - * passed to the DMA engine. - */ - if (likely(!is_jumbo)) { - bool last_segment = (nfrags == 0); - - des = dma_map_single(priv->device, skb->data, - nopaged_len, DMA_TO_DEVICE); - if (dma_mapping_error(priv->device, des)) { - netdev_err(priv->dev, "First Tx DMA map failed\n"); - drop = true; - goto flush_ring; - } - - tx_q->tx_skbuff_dma[first_entry].buf = des; - tx_q->tx_skbuff_dma[first_entry].buf_type = STMMAC_TXBUF_T_SKB; - tx_q->tx_skbuff_dma[first_entry].map_as_page = false; - - stmmac_set_desc_addr(priv, first, des); - - tx_q->tx_skbuff_dma[first_entry].len = nopaged_len; - tx_q->tx_skbuff_dma[first_entry].last_segment = last_segment; - - if (unlikely((skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) && - priv->hwts_tx_en)) { - /* declare that device is doing timestamping */ - skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS; - stmmac_enable_tx_timestamp(priv, first); - } - - /* Prepare the first descriptor setting the OWN bit too */ - stmmac_prepare_tx_desc(priv, first, 1, nopaged_len, - csum_insertion, priv->mode, 0, last_segment, - skb->len); + if (unlikely((skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) && + priv->hwts_tx_en)) { + /* declare that device is doing timestamping */ + skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS; + stmmac_enable_tx_timestamp(priv, first); } if (tx_q->tbs & STMMAC_TBS_EN) { @@ -5004,7 +5015,11 @@ static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev) stmmac_set_desc_tbs(priv, tbs_desc, ts.tv_sec, ts.tv_nsec); } - stmmac_set_tx_owner(priv, first); + /* Complete the first descriptor before granting the DMA */ + stmmac_prepare_tx_desc(priv, first, 1, nopaged_len, + csum_insertion, priv->mode, true, + tx_q->tx_skbuff_dma[first_entry].last_segment, + skb->len); skb_tx_timestamp(skb); @@ -5330,7 +5345,6 @@ static int stmmac_xdp_xmit_xdpf(struct stmmac_priv *priv, int queue, tx_q->tx_skbuff_dma[entry].map_as_page = false; tx_q->tx_skbuff_dma[entry].len = xdpf->len; tx_q->tx_skbuff_dma[entry].last_segment = true; - tx_q->tx_skbuff_dma[entry].is_jumbo = false; tx_q->xdpf[entry] = xdpf; -- cgit v1.2.3 From f5e4e87c3c9c9c1de2a2cd17ce9ecc03d63b1775 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 14 Jul 2025 20:04:28 +0300 Subject: net: stmmac: dwmac1000: Fix inappropriate Rx buffer setup Currently the Rx-buffer is selected based on the interface MTU and is limited to one of the next sizes in the stmmac_set_bfsize() method: BUF_SIZE_16KiB 16368 (0x3FF0) BUF_SIZE_8KiB 8188 (0x1FFC) BUF_SIZE_4KiB 4096 (0x1000) BUF_SIZE_2KiB 2048 (0x800) DEFAULT_BUFSIZE 1536 (0x600) In the meantime the MTU can be no greater than 16368, 9000 or PAGE_SIZE. There are several problems in that. First of all the 8K and 16K sizes are too big for the DW GMAC with Normal descriptor and with Chained Enhanced descriptors. Here is the max size of the Rx buffers depending on the DW MAC version: DW GMAC Normal Rx descriptor: 2K (chain), 2x2K (ring) DW GMAC Enhanced Rx descriptor: 8K (chain), 2x8K (ring) DW QoS Ethernet: 16K (ring) DW XGMAC/XLGMAC: 16k (ring) This has been like that for the Normal descriptors since the initial driver commit and for the Chained Enhanced Descriptors since the b2f3a481c4cd ("net: stmmac: Enable 16KB buffer size"). Moreover the later commit made the stmmac_desc_ops::set_16kib_bfsize() callbacks useless since stmmac_set_bfsize() may setup the 16KiB buffer size anyway. Second is more series problem. The buffer size is determined purely based on MTU with no Ethernet header taken into account. That will definitely cause problems should MTU be specified close to the threshold values listed above. There will be no room for the entire MTU-size frames and they will be just discarded. Thirdly the buffer size alignment is kind of random. It's 16 bytes for the BUF_SIZE_16KiB buffer size, 4 bytes - for the BUF_SIZE_8KiB buffer size, and 4K/2K/1536 bytes - for the rest of the cases. This is also problematic since based on the databooks the "Rx buffer size must be a multiple of 4, 8 or 16 depending on the bus widths, otherwise the transfer might result into undefined behavior." As you can see currently the requirement isn't fully fulfilled since the bus-width isn't taken into account in the driver at all, and the BUF_SIZE_16KiB size only align to the widest data-bus width. Moreover the BUF_SIZE_4KiB and BUF_SIZE_2KiB buf sizes exceed the Normal Descriptor buf size constraint and being converted to just 1 byte aligned sizes, which definitely violate the requirement denoted earlier. So let's fix all the problems above at once since each of them concern the same part of the code in the driver and fixing one would require fixing another. For that to be properly done the set_16kib_bfsize() method must be replaced with stmmac_desc_ops::get_rx_len() since the buffer size depends on the descriptor format and the descriptors linkage mode. Then to be on a safe side the strictest alignment must be applied during the buffer size calculation which must also take into account the Ethernet header size. Note as a side-effect of this change the MTU alignment will be no longer needed, which basically means to revert a part of the commit eaf4fac47807 ("net: stmmac: Do not accept invalid MTU values"). Not that it has been proper implemented there anyway since the alignment requirement is applicable to the Rx buffer size only (as noted above in the log message). Fixes: b2f3a481c4cd ("net: stmmac: Enable 16KB buffer size") Fixes: 47dd7a540b8a ("net: add support for STMicroelectronics Ethernet controllers.") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/common.h | 2 + drivers/net/ethernet/stmicro/stmmac/descs_com.h | 59 +++++++++++++++----- drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c | 20 +++---- drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.h | 1 - .../net/ethernet/stmicro/stmmac/dwxgmac2_descs.c | 6 ++ drivers/net/ethernet/stmicro/stmmac/enh_desc.c | 20 ++++--- drivers/net/ethernet/stmicro/stmmac/hwif.c | 5 -- drivers/net/ethernet/stmicro/stmmac/hwif.h | 6 +- drivers/net/ethernet/stmicro/stmmac/norm_desc.c | 17 ++++-- drivers/net/ethernet/stmicro/stmmac/ring_mode.c | 9 --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 65 ++++++++++++---------- 11 files changed, 125 insertions(+), 85 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/common.h b/drivers/net/ethernet/stmicro/stmmac/common.h index 82ae61ccfd789..c41e151244723 100644 --- a/drivers/net/ethernet/stmicro/stmmac/common.h +++ b/drivers/net/ethernet/stmicro/stmmac/common.h @@ -532,6 +532,8 @@ struct dma_features { #define BUF_SIZE_8KiB 8188 #define BUF_SIZE_4KiB 4096 #define BUF_SIZE_2KiB 2048 +#define STMMAC_RX_BUF_ALIGN(x) ALIGN_DOWN(x, 16) +#define STMMAC_RX_BUF_ADJUST(x) ALIGN(x, 16) /* Power Down and WOL */ #define PMT_NOT_SUPPORTED 0 diff --git a/drivers/net/ethernet/stmicro/stmmac/descs_com.h b/drivers/net/ethernet/stmicro/stmmac/descs_com.h index bedb18538d519..6e71832f5a903 100644 --- a/drivers/net/ethernet/stmicro/stmmac/descs_com.h +++ b/drivers/net/ethernet/stmicro/stmmac/descs_com.h @@ -19,17 +19,29 @@ /* Specific functions used for Ring mode */ /* Enhanced descriptors */ -static inline void ehn_desc_rx_set_on_ring(struct dma_desc *p, int end, +static inline void enh_desc_rx_set_on_ring(struct dma_desc *p, int end, int bfsize) { - if (bfsize == BUF_SIZE_16KiB) - p->des1 |= cpu_to_le32(FIELD_PREP(ERDES1_BUFFER2_SIZE_MASK, - BUF_SIZE_8KiB)); + if (bfsize > STMMAC_RX_BUF_ALIGN(ERDES1_BUFFER1_SIZE_MASK)) { + u32 buf1_len = STMMAC_RX_BUF_ALIGN(ERDES1_BUFFER1_SIZE_MASK); + u32 buf2_len = FIELD_PREP(ERDES1_BUFFER2_SIZE_MASK, + bfsize - buf1_len); + + p->des1 |= cpu_to_le32(buf1_len | buf2_len); + } else { + p->des1 |= cpu_to_le32(FIELD_PREP(ERDES1_BUFFER1_SIZE_MASK, bfsize)); + } if (end) p->des1 |= cpu_to_le32(ERDES1_END_RING); } +static inline unsigned int enh_desc_rx_desc_len_on_ring(void) +{ + return STMMAC_RX_BUF_ALIGN(FIELD_MAX(ERDES1_BUFFER1_SIZE_MASK)) + + STMMAC_RX_BUF_ALIGN(FIELD_MAX(ERDES1_BUFFER2_SIZE_MASK)); +} + static inline void enh_desc_end_tx_desc_on_ring(struct dma_desc *p, int end) { if (end) @@ -60,18 +72,25 @@ static inline unsigned int enh_desc_tx_desc_len_on_ring(void) /* Normal descriptors */ static inline void ndesc_rx_set_on_ring(struct dma_desc *p, int end, int bfsize) { - if (bfsize >= BUF_SIZE_2KiB) { - int bfsize2; + if (bfsize > STMMAC_RX_BUF_ALIGN(RDES1_BUFFER1_SIZE_MASK)) { + u32 buf1_len = STMMAC_RX_BUF_ALIGN(RDES1_BUFFER1_SIZE_MASK); + u32 buf2_len = FIELD_PREP(RDES1_BUFFER2_SIZE_MASK, bfsize - buf1_len); - bfsize2 = min(bfsize - BUF_SIZE_2KiB + 1, BUF_SIZE_2KiB - 1); - p->des1 |= cpu_to_le32(FIELD_PREP(RDES1_BUFFER2_SIZE_MASK, - bfsize2)); + p->des1 |= cpu_to_le32(buf1_len | buf2_len); + } else { + p->des1 |= cpu_to_le32(FIELD_PREP(RDES1_BUFFER1_SIZE_MASK, bfsize)); } if (end) p->des1 |= cpu_to_le32(RDES1_END_RING); } +static inline unsigned int ndesc_rx_desc_len_on_ring(void) +{ + return STMMAC_RX_BUF_ALIGN(FIELD_MAX(RDES1_BUFFER1_SIZE_MASK)) + + STMMAC_RX_BUF_ALIGN(FIELD_MAX(RDES1_BUFFER2_SIZE_MASK)); +} + static inline void ndesc_end_tx_desc_on_ring(struct dma_desc *p, int end) { if (end) @@ -102,9 +121,16 @@ static inline unsigned int ndesc_tx_desc_len_on_ring(void) /* Specific functions used for Chain mode */ /* Enhanced descriptors */ -static inline void ehn_desc_rx_set_on_chain(struct dma_desc *p) +static inline void enh_desc_rx_set_on_chain(struct dma_desc *p, int bfsize) +{ + bfsize = umin(bfsize, STMMAC_RX_BUF_ALIGN(ERDES1_BUFFER1_SIZE_MASK)); + p->des1 |= cpu_to_le32(FIELD_PREP(ERDES1_BUFFER1_SIZE_MASK, bfsize) | + ERDES1_SECOND_ADDRESS_CHAINED); +} + +static inline unsigned int enh_desc_rx_desc_len_on_chain(void) { - p->des1 |= cpu_to_le32(ERDES1_SECOND_ADDRESS_CHAINED); + return STMMAC_RX_BUF_ALIGN(FIELD_MAX(ERDES1_BUFFER1_SIZE_MASK)); } static inline void enh_desc_end_tx_desc_on_chain(struct dma_desc *p, dma_addr_t np, @@ -127,9 +153,16 @@ static inline unsigned int enh_desc_tx_desc_len_on_chain(void) } /* Normal descriptors */ -static inline void ndesc_rx_set_on_chain(struct dma_desc *p, int end) +static inline void ndesc_rx_set_on_chain(struct dma_desc *p, int bfsize) +{ + bfsize = umin(bfsize, STMMAC_RX_BUF_ALIGN(RDES1_BUFFER1_SIZE_MASK)); + p->des1 |= cpu_to_le32(FIELD_PREP(RDES1_BUFFER1_SIZE_MASK, bfsize) | + RDES1_SECOND_ADDRESS_CHAINED); +} + +static inline unsigned int ndesc_rx_desc_len_on_chain(void) { - p->des1 |= cpu_to_le32(RDES1_SECOND_ADDRESS_CHAINED); + return STMMAC_RX_BUF_ALIGN(FIELD_MAX(RDES1_BUFFER1_SIZE_MASK)); } static inline void ndesc_end_tx_desc_on_chain(struct dma_desc *p, dma_addr_t np, diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c index 90c1567b19b64..31e1c2ad90790 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c @@ -11,6 +11,7 @@ #include #include "common.h" #include "dwmac4.h" +#include "dwmac4_dma.h" #include "dwmac4_descs.h" static int dwmac4_wrback_get_tx_status(struct stmmac_extra_stats *x, @@ -333,6 +334,11 @@ static void dwmac4_rd_init_tx_desc(struct dma_desc *p, int mode, int end) p->des3 = 0; } +static unsigned int dwmac4_rd_get_rx_len(int mode) +{ + return STMMAC_RX_BUF_ALIGN(FIELD_MAX(DMA_RBSZ_MASK)); +} + static unsigned int dwmac4_rd_get_tx_len(int mode) { return FIELD_MAX(TDES2_BUFFER1_SIZE_MASK); @@ -509,15 +515,6 @@ static void dwmac4_set_sarc(struct dma_desc *p, u32 sarc_type) sarc_type)); } -static int set_16kib_bfsize(int mtu) -{ - int ret = 0; - - if (unlikely(mtu >= BUF_SIZE_8KiB)) - ret = BUF_SIZE_16KiB; - return ret; -} - static void dwmac4_set_vlan_tag(struct dma_desc *p, u16 tag, u16 inner_tag, u32 inner_type) { @@ -572,6 +569,7 @@ static void dwmac4_set_tbs(struct dma_edesc *p, u32 sec, u32 nsec) const struct stmmac_desc_ops dwmac4_desc_ops = { .tx_status = dwmac4_wrback_get_tx_status, .rx_status = dwmac4_wrback_get_rx_status, + .get_rx_len = dwmac4_rd_get_rx_len, .get_tx_len = dwmac4_rd_get_tx_len, .get_tx_owner = dwmac4_get_tx_owner, .set_tx_owner = dwmac4_set_tx_owner, @@ -601,7 +599,3 @@ const struct stmmac_desc_ops dwmac4_desc_ops = { .set_sec_addr = dwmac4_set_sec_addr, .set_tbs = dwmac4_set_tbs, }; - -const struct stmmac_mode_ops dwmac4_ring_mode_ops = { - .set_16kib_bfsize = set_16kib_bfsize, -}; diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.h b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.h index c089e2fdd901c..e3562fb1ea61c 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.h @@ -143,7 +143,6 @@ /* TDS3 use for both format (read and write back) */ #define RDES3_OWN BIT(31) -extern const struct stmmac_mode_ops dwmac4_ring_mode_ops; extern const struct stmmac_desc_ops dwmac4_desc_ops; #endif /* __DWMAC4_DESCS_H__ */ diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c index a4b40adeda18b..14f2da1e5f027 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c @@ -242,6 +242,11 @@ static void dwxgmac2_init_tx_desc(struct dma_desc *p, int mode, int end) p->des3 = 0; } +static unsigned int dwxgmac2_get_rx_len(int mode) +{ + return STMMAC_RX_BUF_ALIGN(FIELD_MAX(XGMAC_RBSZ)); +} + static unsigned int dwxgmac2_get_tx_len(int mode) { return FIELD_MAX(XGMAC_TDES2_B1L); @@ -445,6 +450,7 @@ static void dwxgmac2_set_tbs(struct dma_edesc *p, u32 sec, u32 nsec) const struct stmmac_desc_ops dwxgmac210_desc_ops = { .tx_status = dwxgmac2_get_tx_status, .rx_status = dwxgmac2_get_rx_status, + .get_rx_len = dwxgmac2_get_rx_len, .get_tx_len = dwxgmac2_get_tx_len, .get_tx_owner = dwxgmac2_get_tx_owner, .set_tx_owner = dwxgmac2_set_tx_owner, diff --git a/drivers/net/ethernet/stmicro/stmmac/enh_desc.c b/drivers/net/ethernet/stmicro/stmmac/enh_desc.c index 6e2a9320bfb2e..0781700a554d9 100644 --- a/drivers/net/ethernet/stmicro/stmmac/enh_desc.c +++ b/drivers/net/ethernet/stmicro/stmmac/enh_desc.c @@ -284,17 +284,12 @@ static int enh_desc_get_rx_status_ext(struct stmmac_extra_stats *x, static void enh_desc_init_rx_desc(struct dma_desc *p, int disable_rx_ic, int mode, int end, int bfsize) { - int bfsize1; - p->des0 |= cpu_to_le32(RDES0_OWN); - bfsize1 = min(bfsize, BUF_SIZE_8KiB); - p->des1 |= cpu_to_le32(bfsize1 & ERDES1_BUFFER1_SIZE_MASK); - if (mode == STMMAC_CHAIN_MODE) - ehn_desc_rx_set_on_chain(p); + enh_desc_rx_set_on_chain(p, bfsize); else - ehn_desc_rx_set_on_ring(p, end, bfsize); + enh_desc_rx_set_on_ring(p, end, bfsize); if (disable_rx_ic) p->des1 |= cpu_to_le32(ERDES1_DISABLE_IC); @@ -324,6 +319,14 @@ static void enh_desc_set_rx_owner(struct dma_desc *p, int disable_rx_ic) p->des0 |= cpu_to_le32(RDES0_OWN); } +static unsigned int enh_desc_get_rx_len(int mode) +{ + if (mode == STMMAC_CHAIN_MODE) + return enh_desc_rx_desc_len_on_chain(); + else + return enh_desc_rx_desc_len_on_ring(); +} + static int enh_desc_get_tx_ls(struct dma_desc *p) { return (le32_to_cpu(p->des0) & ETDES0_LAST_SEGMENT) >> 29; @@ -476,6 +479,7 @@ static void enh_desc_clear(struct dma_desc *p) const struct stmmac_desc_ops enh_desc_ops = { .tx_status = enh_desc_get_tx_status, .rx_status = enh_desc_get_rx_status_nocoe, + .get_rx_len = enh_desc_get_rx_len, .get_tx_len = enh_desc_get_tx_len, .init_rx_desc = enh_desc_init_rx_desc, .init_tx_desc = enh_desc_init_tx_desc, @@ -499,6 +503,7 @@ const struct stmmac_desc_ops enh_desc_ops = { const struct stmmac_desc_ops enh_desc_noext_ops = { .tx_status = enh_desc_get_tx_status, .rx_status = enh_desc_get_rx_status_noext, + .get_rx_len = enh_desc_get_rx_len, .get_tx_len = enh_desc_get_tx_len, .init_rx_desc = enh_desc_init_rx_desc, .init_tx_desc = enh_desc_init_tx_desc, @@ -522,6 +527,7 @@ const struct stmmac_desc_ops enh_desc_noext_ops = { const struct stmmac_desc_ops enh_desc_ext_ops = { .tx_status = enh_desc_get_tx_status, .rx_status = enh_desc_get_rx_status_ext, + .get_rx_len = enh_desc_get_rx_len, .get_tx_len = enh_desc_get_tx_len, .init_rx_desc = enh_desc_init_rx_desc, .init_tx_desc = enh_desc_init_tx_desc, diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.c b/drivers/net/ethernet/stmicro/stmmac/hwif.c index bc1e585c854d8..35d9f058cea29 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.c +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.c @@ -207,7 +207,6 @@ static const struct stmmac_hwif_entry { .vlan = &dwmac_vlan_ops, .hwtimestamp = &stmmac_ptp, .ptp = &stmmac_ptp_clock_ops, - .mode = &dwmac4_ring_mode_ops, .tc = &dwmac510_tc_ops, .mmc = &dwmac_mmc_ops, .est = &dwmac510_est_ops, @@ -228,7 +227,6 @@ static const struct stmmac_hwif_entry { .vlan = &dwmac_vlan_ops, .hwtimestamp = &stmmac_ptp, .ptp = &stmmac_ptp_clock_ops, - .mode = &dwmac4_ring_mode_ops, .tc = &dwmac510_tc_ops, .mmc = &dwmac_mmc_ops, .est = &dwmac510_est_ops, @@ -249,7 +247,6 @@ static const struct stmmac_hwif_entry { .vlan = &dwmac_vlan_ops, .hwtimestamp = &stmmac_ptp, .ptp = &stmmac_ptp_clock_ops, - .mode = &dwmac4_ring_mode_ops, .tc = &dwmac510_tc_ops, .mmc = &dwmac_mmc_ops, .est = &dwmac510_est_ops, @@ -271,7 +268,6 @@ static const struct stmmac_hwif_entry { .vlan = &dwxgmac210_vlan_ops, .hwtimestamp = &stmmac_ptp, .ptp = &stmmac_ptp_clock_ops, - .mode = NULL, .tc = &dwmac510_tc_ops, .mmc = &dwxgmac_mmc_ops, .est = &dwmac510_est_ops, @@ -293,7 +289,6 @@ static const struct stmmac_hwif_entry { .vlan = &dwxlgmac2_vlan_ops, .hwtimestamp = &stmmac_ptp, .ptp = &stmmac_ptp_clock_ops, - .mode = NULL, .tc = &dwmac510_tc_ops, .mmc = &dwxgmac_mmc_ops, .est = &dwmac510_est_ops, diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index 085e2b0624785..2216c25abc668 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -67,6 +67,7 @@ struct stmmac_desc_ops { /* Return the transmit status looking at the TDES1 */ int (*tx_status)(struct stmmac_extra_stats *x, struct dma_desc *p); /* Get the buffer(s) size for a single descriptor */ + unsigned int (*get_rx_len)(int mode); unsigned int (*get_tx_len)(int mode); /* Handle extra events on specific interrupts hw dependent */ void (*set_rx_owner)(struct dma_desc *p, int disable_rx_ic); @@ -128,6 +129,8 @@ struct stmmac_desc_ops { stmmac_do_callback(__priv, desc, get_rx_vlan_valid, __args) #define stmmac_tx_status(__priv, __args...) \ stmmac_do_callback(__priv, desc, tx_status, __args) +#define stmmac_get_rx_len(__priv, __args...) \ + stmmac_do_callback(__priv, desc, get_rx_len, __args) #define stmmac_get_tx_len(__priv, __args...) \ stmmac_do_callback(__priv, desc, get_tx_len, __args) #define stmmac_set_rx_owner(__priv, __args...) \ @@ -548,15 +551,12 @@ struct stmmac_rx_queue; struct stmmac_mode_ops { void (*init) (void *des, dma_addr_t phy_addr, unsigned int size, unsigned int extend_desc); - int (*set_16kib_bfsize)(int mtu); void (*init_desc3)(struct dma_desc *p); void (*refill_desc3)(struct stmmac_rx_queue *rx_q, struct dma_desc *p); }; #define stmmac_mode_init(__priv, __args...) \ stmmac_do_void_callback(__priv, mode, init, __args) -#define stmmac_set_16kib_bfsize(__priv, __args...) \ - stmmac_do_callback(__priv, mode, set_16kib_bfsize, __args) #define stmmac_init_desc3(__priv, __args...) \ stmmac_do_void_callback(__priv, mode, init_desc3, __args) #define stmmac_refill_desc3(__priv, __args...) \ diff --git a/drivers/net/ethernet/stmicro/stmmac/norm_desc.c b/drivers/net/ethernet/stmicro/stmmac/norm_desc.c index 3ca188c934bc5..95d3031f4ab97 100644 --- a/drivers/net/ethernet/stmicro/stmmac/norm_desc.c +++ b/drivers/net/ethernet/stmicro/stmmac/norm_desc.c @@ -158,15 +158,10 @@ static int ndesc_get_rx_status_coe2(struct stmmac_extra_stats *x, static void ndesc_init_rx_desc(struct dma_desc *p, int disable_rx_ic, int mode, int end, int bfsize) { - int bfsize1; - p->des0 |= cpu_to_le32(RDES0_OWN); - bfsize1 = min(bfsize, BUF_SIZE_2KiB - 1); - p->des1 |= cpu_to_le32(bfsize1 & RDES1_BUFFER1_SIZE_MASK); - if (mode == STMMAC_CHAIN_MODE) - ndesc_rx_set_on_chain(p, end); + ndesc_rx_set_on_chain(p, bfsize); else ndesc_rx_set_on_ring(p, end, bfsize); @@ -198,6 +193,14 @@ static void ndesc_set_rx_owner(struct dma_desc *p, int disable_rx_ic) p->des0 |= cpu_to_le32(RDES0_OWN); } +static unsigned int ndesc_get_rx_len(int mode) +{ + if (mode == STMMAC_CHAIN_MODE) + return ndesc_rx_desc_len_on_chain(); + else + return ndesc_rx_desc_len_on_ring(); +} + static int ndesc_get_tx_ls(struct dma_desc *p) { return (le32_to_cpu(p->des1) & TDES1_LAST_SEGMENT) >> 30; @@ -332,6 +335,7 @@ const struct stmmac_desc_ops ndesc_ops = { .tx_status = ndesc_get_tx_status, .rx_status = ndesc_get_rx_status_nocoe, .get_tx_len = ndesc_get_tx_len, + .get_rx_len = ndesc_get_rx_len, .init_rx_desc = ndesc_init_rx_desc, .init_tx_desc = ndesc_init_tx_desc, .get_tx_owner = ndesc_get_tx_owner, @@ -354,6 +358,7 @@ const struct stmmac_desc_ops ndesc_ops = { const struct stmmac_desc_ops ndesc_rxcoe2_ops = { .tx_status = ndesc_get_tx_status, .rx_status = ndesc_get_rx_status_coe2, + .get_rx_len = ndesc_get_rx_len, .get_tx_len = ndesc_get_tx_len, .init_rx_desc = ndesc_init_rx_desc, .init_tx_desc = ndesc_init_tx_desc, diff --git a/drivers/net/ethernet/stmicro/stmmac/ring_mode.c b/drivers/net/ethernet/stmicro/stmmac/ring_mode.c index ff3ddb800d049..916396cb59b85 100644 --- a/drivers/net/ethernet/stmicro/stmmac/ring_mode.c +++ b/drivers/net/ethernet/stmicro/stmmac/ring_mode.c @@ -29,16 +29,7 @@ static void init_desc3(struct dma_desc *p) p->des3 = cpu_to_le32(le32_to_cpu(p->des2) + BUF_SIZE_8KiB); } -static int set_16kib_bfsize(int mtu) -{ - int ret = 0; - if (unlikely(mtu > BUF_SIZE_8KiB)) - ret = BUF_SIZE_16KiB; - return ret; -} - const struct stmmac_mode_ops ring_mode_ops = { .refill_desc3 = refill_desc3, .init_desc3 = init_desc3, - .set_16kib_bfsize = set_16kib_bfsize, }; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 7d47b0ea1a2b0..44f3ef3ff3610 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -63,7 +63,6 @@ */ #define STMMAC_HWTS_ACTIVE (PTP_TCR_TSENA | PTP_TCR_TSCTRLSSR) -#define STMMAC_ALIGN(x) ALIGN(ALIGN(x, SMP_CACHE_BYTES), 16) #define TSO_MAX_LOAD_SIZE SZ_256K #define TSO_MAX_BUFF_SIZE (SZ_16K - 1) @@ -1578,22 +1577,42 @@ static unsigned int stmmac_rx_offset(struct stmmac_priv *priv) return NET_SKB_PAD + stmmac_fs_offset(); } -static int stmmac_set_bfsize(int mtu) +/** + * stmmac_get_bfsize- determine Rx DMA-buffer size + * @priv: driver private structure + * @mtu : requested MTU + * Description: calculate the controller Rx DMA-buffer size suitable + * for the passed MTU value. + * + * Return: the Rx DMA-buffer size. + */ +static unsigned int stmmac_get_bfsize(struct stmmac_priv *priv, int mtu) { - int ret; + unsigned int buff_size, buff_max; - if (mtu >= BUF_SIZE_8KiB) - ret = BUF_SIZE_16KiB; - else if (mtu >= BUF_SIZE_4KiB) - ret = BUF_SIZE_8KiB; - else if (mtu >= BUF_SIZE_2KiB) - ret = BUF_SIZE_4KiB; - else if (mtu > DEFAULT_BUFSIZE) - ret = BUF_SIZE_2KiB; + /* Make sure there is a place for the entire frame including + * the Ethernet header plus possible C/S-VLAN headers. + */ + buff_size = umax(mtu + ETH_HLEN + 2 * VLAN_HLEN + ETH_FCS_LEN, buf_sz); + if (buff_size >= BUF_SIZE_8KiB) + buff_size = BUF_SIZE_16KiB; + else if (buff_size >= BUF_SIZE_4KiB) + buff_size = BUF_SIZE_8KiB; + else if (buff_size >= BUF_SIZE_2KiB) + buff_size = BUF_SIZE_4KiB; + else if (buff_size > DEFAULT_BUFSIZE) + buff_size = BUF_SIZE_2KiB; else - ret = DEFAULT_BUFSIZE; + buff_size = DEFAULT_BUFSIZE; - return ret; + /* Make sure the buffer is bus-width aligned up to fit in all the DMA + * writes and to avoid undefined behaviour (see notes in the databooks). + */ + buff_size = STMMAC_RX_BUF_ADJUST(buff_size); + + /* Apply the max buf size constraint specific to the particular MAC */ + buff_max = stmmac_get_rx_len(priv, priv->mode); + return umin(buff_size, buff_max); } /** @@ -4057,7 +4076,7 @@ static struct stmmac_dma_conf * stmmac_setup_dma_desc(struct stmmac_priv *priv, unsigned int mtu) { struct stmmac_dma_conf *dma_conf; - int chan, bfsize, ret; + int chan, ret; dma_conf = kzalloc_obj(*dma_conf); if (!dma_conf) { @@ -4066,15 +4085,8 @@ stmmac_setup_dma_desc(struct stmmac_priv *priv, unsigned int mtu) return ERR_PTR(-ENOMEM); } - /* Returns 0 or BUF_SIZE_16KiB if mtu > 8KiB and dwmac4 or ring mode */ - bfsize = stmmac_set_16kib_bfsize(priv, mtu); - if (bfsize < 0) - bfsize = 0; + dma_conf->dma_buf_sz = stmmac_get_bfsize(priv, mtu); - if (bfsize < BUF_SIZE_16KiB) - bfsize = stmmac_set_bfsize(max(buf_sz, mtu)); - - dma_conf->dma_buf_sz = bfsize; /* Chose the tx/rx size from the already defined one in the * priv struct. (if defined) */ @@ -6227,7 +6239,6 @@ static int stmmac_change_mtu(struct net_device *dev, int new_mtu) struct stmmac_priv *priv = netdev_priv(dev); int txfifosz = priv->plat->tx_fifo_size; struct stmmac_dma_conf *dma_conf; - const int mtu = new_mtu; int ret; if (txfifosz == 0) @@ -6240,8 +6251,6 @@ static int stmmac_change_mtu(struct net_device *dev, int new_mtu) return -EINVAL; } - new_mtu = STMMAC_ALIGN(new_mtu); - /* If condition true, FIFO is too small or MTU too large */ if ((txfifosz < new_mtu) || (new_mtu > BUF_SIZE_16KiB)) return -EINVAL; @@ -6249,10 +6258,10 @@ static int stmmac_change_mtu(struct net_device *dev, int new_mtu) if (netif_running(dev)) { netdev_dbg(priv->dev, "restarting interface to change its MTU\n"); /* Try to allocate the new DMA conf with the new mtu */ - dma_conf = stmmac_setup_dma_desc(priv, mtu); + dma_conf = stmmac_setup_dma_desc(priv, new_mtu); if (IS_ERR(dma_conf)) { netdev_err(priv->dev, "failed allocating new dma conf for new MTU %d\n", - mtu); + new_mtu); return PTR_ERR(dma_conf); } @@ -6271,7 +6280,7 @@ static int stmmac_change_mtu(struct net_device *dev, int new_mtu) stmmac_set_rx_mode(dev); } - WRITE_ONCE(dev->mtu, mtu); + WRITE_ONCE(dev->mtu, new_mtu); netdev_update_features(dev); return 0; -- cgit v1.2.3 From afa311d89ad518321d4b1a738f31ca6996fbc3ad Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 21 Jul 2025 21:53:20 +0300 Subject: net: stmmac: dwmac1000: Fix invalid Rx descriptor mode setup Currently the Rx DMA-descriptors mode setup is partly broken. First of all the ring mode works for the Enhanced descriptors only. As long as the Normal descriptors support is detected enabling the ring descriptors mode will cause malfunctioned over 2K frames reception. This happens due to the ring-mode init procedure originally being developed for the 8KB buffers specific for the Enhanced descriptors. But since the commit 286a83721720 ("stmmac: add CHAINED descriptor mode support (V4)") it has been converted to be available for both Normal and Enhanced descriptor types without taking into account that the former descriptors type has the buffer of 2KB size. Secondly the commit e3ad57c96715 ("stmmac: review RX/TX ring management") has broken the Rx-descriptors chain and extended ring pointers re-initialization in case of IEEE 1833-2002 Timestamp-ing. The Rx-descriptor refill procedures used to rely on the stmmac_rx_queue::dirty_rx field state to re-init the pointers, but since the blamed commit the field state has no longer being advanced in sync with the descriptors cleanups. It's set only after all the pending Rx DMA-descriptors have been cleaned up. So the intermediate descriptors will have the chain broken, meanwhile the first dirty one will point to the first non-dirty. This is definitely wrong. Let's fix the problems denoted above at once since both of them concern the same part of driver code. The solution is simple - just move the chain and ring mode initialization procedure to the dedicated stmmac_desc_ops::prepare_rx_desc() methods and make sure they are called at the moments where the Rx DMA-descriptors initialization and re-initialization is required. Note as a side-effect of the this change another problem is fixed. The commit bba2556efad6 ("net: stmmac: Enable RX via AF_XDP zero-copy") has missed the chain and ring mode pointers initialization on zero-copy Rx-buffers allocation. Now by calling the stmmac_prepare_rx_desc() method it has that done. Moreover as a result of the denoted conversion there is no longer need in the stmmac_mode_ops-related infrastructure. So it has been fully dropped. Fixes: 286a83721720 ("stmmac: add CHAINED descriptor mode support (V4)") Fixes: e3ad57c96715 ("stmmac: review RX/TX ring management") Fixes: bba2556efad6 ("net: stmmac: Enable RX via AF_XDP zero-copy") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/Makefile | 4 +- drivers/net/ethernet/stmicro/stmmac/chain_mode.c | 65 ----------------- drivers/net/ethernet/stmicro/stmmac/common.h | 1 - drivers/net/ethernet/stmicro/stmmac/descs_com.h | 40 +++++++++++ drivers/net/ethernet/stmicro/stmmac/enh_desc.c | 12 ++++ drivers/net/ethernet/stmicro/stmmac/hwif.c | 28 -------- drivers/net/ethernet/stmicro/stmmac/hwif.h | 23 ++---- drivers/net/ethernet/stmicro/stmmac/norm_desc.c | 11 +++ drivers/net/ethernet/stmicro/stmmac/ring_mode.c | 35 ---------- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 85 +++++++++++++---------- 10 files changed, 119 insertions(+), 185 deletions(-) delete mode 100644 drivers/net/ethernet/stmicro/stmmac/chain_mode.c delete mode 100644 drivers/net/ethernet/stmicro/stmmac/ring_mode.c diff --git a/drivers/net/ethernet/stmicro/stmmac/Makefile b/drivers/net/ethernet/stmicro/stmmac/Makefile index c9263987ef8d2..9f24db4cf7a83 100644 --- a/drivers/net/ethernet/stmicro/stmmac/Makefile +++ b/drivers/net/ethernet/stmicro/stmmac/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_STMMAC_ETH) += stmmac.o -stmmac-objs:= stmmac_main.o stmmac_ethtool.o stmmac_mdio.o ring_mode.o \ - chain_mode.o dwmac_lib.o dwmac1000_core.o dwmac1000_dma.o \ +stmmac-objs:= stmmac_main.o stmmac_ethtool.o stmmac_mdio.o \ + dwmac_lib.o dwmac1000_core.o dwmac1000_dma.o \ dwmac100_core.o dwmac100_dma.o enh_desc.o norm_desc.o \ mmc_core.o stmmac_hwtstamp.o stmmac_ptp.o dwmac4_descs.o \ dwmac4_dma.o dwmac4_lib.o dwmac4_core.o dwmac5.o hwif.o \ diff --git a/drivers/net/ethernet/stmicro/stmmac/chain_mode.c b/drivers/net/ethernet/stmicro/stmmac/chain_mode.c deleted file mode 100644 index fb5749bfb3480..0000000000000 --- a/drivers/net/ethernet/stmicro/stmmac/chain_mode.c +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/******************************************************************************* - Specialised functions for managing Chained mode - - Copyright(C) 2011 STMicroelectronics Ltd - - It defines all the functions used to handle the normal/enhanced - descriptors in case of the DMA is configured to work in chained or - in ring mode. - - - Author: Giuseppe Cavallaro -*******************************************************************************/ - -#include "stmmac.h" - -static void init_dma_chain(void *des, dma_addr_t phy_addr, - unsigned int size, unsigned int extend_desc) -{ - /* - * In chained mode the des3 points to the next element in the ring. - * The latest element has to point to the head. - */ - int i; - dma_addr_t dma_phy = phy_addr; - - if (extend_desc) { - struct dma_extended_desc *p = (struct dma_extended_desc *)des; - for (i = 0; i < (size - 1); i++) { - dma_phy += sizeof(struct dma_extended_desc); - p->basic.des3 = cpu_to_le32((unsigned int)dma_phy); - p++; - } - p->basic.des3 = cpu_to_le32((unsigned int)phy_addr); - - } else { - struct dma_desc *p = (struct dma_desc *)des; - for (i = 0; i < (size - 1); i++) { - dma_phy += sizeof(struct dma_desc); - p->des3 = cpu_to_le32((unsigned int)dma_phy); - p++; - } - p->des3 = cpu_to_le32((unsigned int)phy_addr); - } -} - -static void refill_desc3(struct stmmac_rx_queue *rx_q, struct dma_desc *p) -{ - struct stmmac_priv *priv = rx_q->priv_data; - - if (priv->hwts_rx_en && !priv->extend_desc) - /* NOTE: Device will overwrite des3 with timestamp value if - * 1588-2002 time stamping is enabled, hence reinitialize it - * to keep explicit chaining in the descriptor. - */ - p->des3 = cpu_to_le32((unsigned int)(rx_q->dma_rx_phy + - (((rx_q->dirty_rx) + 1) % - priv->dma_conf.dma_rx_size) * - sizeof(struct dma_desc))); -} - -const struct stmmac_mode_ops chain_mode_ops = { - .init = init_dma_chain, - .refill_desc3 = refill_desc3, -}; diff --git a/drivers/net/ethernet/stmicro/stmmac/common.h b/drivers/net/ethernet/stmicro/stmmac/common.h index c41e151244723..27665c7c5c259 100644 --- a/drivers/net/ethernet/stmicro/stmmac/common.h +++ b/drivers/net/ethernet/stmicro/stmmac/common.h @@ -639,7 +639,6 @@ struct mac_device_info { const struct stmmac_ops *mac; const struct stmmac_desc_ops *desc; const struct stmmac_dma_ops *dma; - const struct stmmac_mode_ops *mode; const struct stmmac_hwtimestamp *ptp; const struct stmmac_tc_ops *tc; const struct stmmac_mmc_ops *mmc; diff --git a/drivers/net/ethernet/stmicro/stmmac/descs_com.h b/drivers/net/ethernet/stmicro/stmmac/descs_com.h index 6e71832f5a903..5eba3182b7a95 100644 --- a/drivers/net/ethernet/stmicro/stmmac/descs_com.h +++ b/drivers/net/ethernet/stmicro/stmmac/descs_com.h @@ -36,6 +36,18 @@ static inline void enh_desc_rx_set_on_ring(struct dma_desc *p, int end, p->des1 |= cpu_to_le32(ERDES1_END_RING); } +static inline void enh_desc_rx_set_buf_on_ring(struct dma_desc *p, int bfsize) +{ + if (bfsize > STMMAC_RX_BUF_ALIGN(ERDES1_BUFFER1_SIZE_MASK)) { + u32 buf1_len = STMMAC_RX_BUF_ALIGN(ERDES1_BUFFER1_SIZE_MASK); + + /* Note DMA will ignore the data-bus unaligned offset of the + * non-first Rx buffers. + */ + p->des3 = cpu_to_le32(le32_to_cpu(p->des2) + buf1_len); + } +} + static inline unsigned int enh_desc_rx_desc_len_on_ring(void) { return STMMAC_RX_BUF_ALIGN(FIELD_MAX(ERDES1_BUFFER1_SIZE_MASK)) + @@ -85,6 +97,18 @@ static inline void ndesc_rx_set_on_ring(struct dma_desc *p, int end, int bfsize) p->des1 |= cpu_to_le32(RDES1_END_RING); } +static inline void ndesc_rx_set_buf_on_ring(struct dma_desc *p, int bfsize) +{ + if (bfsize > STMMAC_RX_BUF_ALIGN(RDES1_BUFFER1_SIZE_MASK)) { + u32 buf1_len = STMMAC_RX_BUF_ALIGN(RDES1_BUFFER1_SIZE_MASK); + + /* Note DMA will ignore the data-bus unaligned offset of the + * non-first Rx buffers. + */ + p->des3 = cpu_to_le32(le32_to_cpu(p->des2) + buf1_len); + } +} + static inline unsigned int ndesc_rx_desc_len_on_ring(void) { return STMMAC_RX_BUF_ALIGN(FIELD_MAX(RDES1_BUFFER1_SIZE_MASK)) + @@ -128,6 +152,14 @@ static inline void enh_desc_rx_set_on_chain(struct dma_desc *p, int bfsize) ERDES1_SECOND_ADDRESS_CHAINED); } +static inline void enh_desc_rx_set_buf_on_chain(struct dma_desc *p, dma_addr_t np, + bool hwts_rx_en) +{ + /* des3 is overwritten with timestamp in case of IEEE 1588-2002 */ + if (hwts_rx_en) + p->des3 = cpu_to_le32(lower_32_bits(np)); +} + static inline unsigned int enh_desc_rx_desc_len_on_chain(void) { return STMMAC_RX_BUF_ALIGN(FIELD_MAX(ERDES1_BUFFER1_SIZE_MASK)); @@ -160,6 +192,14 @@ static inline void ndesc_rx_set_on_chain(struct dma_desc *p, int bfsize) RDES1_SECOND_ADDRESS_CHAINED); } +static inline void ndesc_rx_set_buf_on_chain(struct dma_desc *p, dma_addr_t np, + bool hwts_rx_en) +{ + /* des3 is overwritten with timestamp in case of IEEE 1588-2002 */ + if (hwts_rx_en) + p->des3 = cpu_to_le32(lower_32_bits(np)); +} + static inline unsigned int ndesc_rx_desc_len_on_chain(void) { return STMMAC_RX_BUF_ALIGN(FIELD_MAX(RDES1_BUFFER1_SIZE_MASK)); diff --git a/drivers/net/ethernet/stmicro/stmmac/enh_desc.c b/drivers/net/ethernet/stmicro/stmmac/enh_desc.c index 0781700a554d9..91d63659f108b 100644 --- a/drivers/net/ethernet/stmicro/stmmac/enh_desc.c +++ b/drivers/net/ethernet/stmicro/stmmac/enh_desc.c @@ -327,6 +327,15 @@ static unsigned int enh_desc_get_rx_len(int mode) return enh_desc_rx_desc_len_on_ring(); } +static void enh_desc_prepare_rx_desc(struct dma_desc *p, int mode, + dma_addr_t np, bool hwts_rx, int bfsize) +{ + if (mode == STMMAC_CHAIN_MODE) + enh_desc_rx_set_buf_on_chain(p, np, hwts_rx); + else + enh_desc_rx_set_buf_on_ring(p, bfsize); +} + static int enh_desc_get_tx_ls(struct dma_desc *p) { return (le32_to_cpu(p->des0) & ETDES0_LAST_SEGMENT) >> 29; @@ -484,6 +493,7 @@ const struct stmmac_desc_ops enh_desc_ops = { .init_rx_desc = enh_desc_init_rx_desc, .init_tx_desc = enh_desc_init_tx_desc, .get_tx_owner = enh_desc_get_tx_owner, + .prepare_rx_desc = enh_desc_prepare_rx_desc, .release_tx_desc = enh_desc_release_tx_desc, .prepare_tx_desc = enh_desc_prepare_tx_desc, .set_tx_ic = enh_desc_set_tx_ic, @@ -508,6 +518,7 @@ const struct stmmac_desc_ops enh_desc_noext_ops = { .init_rx_desc = enh_desc_init_rx_desc, .init_tx_desc = enh_desc_init_tx_desc, .get_tx_owner = enh_desc_get_tx_owner, + .prepare_rx_desc = enh_desc_prepare_rx_desc, .release_tx_desc = enh_desc_release_tx_desc, .prepare_tx_desc = enh_desc_prepare_tx_desc, .set_tx_ic = enh_desc_set_tx_ic, @@ -532,6 +543,7 @@ const struct stmmac_desc_ops enh_desc_ext_ops = { .init_rx_desc = enh_desc_init_rx_desc, .init_tx_desc = enh_desc_init_tx_desc, .get_tx_owner = enh_desc_get_tx_owner, + .prepare_rx_desc = enh_desc_prepare_rx_desc, .release_tx_desc = enh_desc_release_tx_desc, .prepare_tx_desc = enh_desc_prepare_tx_desc, .set_tx_ic = enh_desc_set_tx_ic, diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.c b/drivers/net/ethernet/stmicro/stmmac/hwif.c index 35d9f058cea29..fa8b0bf5bdada 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.c +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.c @@ -51,21 +51,6 @@ static void stmmac_get_version(struct stmmac_priv *priv, ver->dev_id = FIELD_GET(DWMAC_USERVER, version); } -static void stmmac_dwmac_mode_quirk(struct stmmac_priv *priv) -{ - struct mac_device_info *mac = priv->hw; - - if (priv->chain_mode) { - dev_info(priv->device, "Chain mode enabled\n"); - priv->mode = STMMAC_CHAIN_MODE; - mac->mode = &chain_mode_ops; - } else { - dev_info(priv->device, "Ring mode enabled\n"); - priv->mode = STMMAC_RING_MODE; - mac->mode = &ring_mode_ops; - } -} - static int stmmac_dwmac1_quirks(struct stmmac_priv *priv) { struct mac_device_info *mac = priv->hw; @@ -92,13 +77,6 @@ static int stmmac_dwmac1_quirks(struct stmmac_priv *priv) mac->desc = priv->plat->rx_coe ? &ndesc_rxcoe2_ops : &ndesc_ops; } - stmmac_dwmac_mode_quirk(priv); - return 0; -} - -static int stmmac_dwmac4_quirks(struct stmmac_priv *priv) -{ - stmmac_dwmac_mode_quirk(priv); return 0; } @@ -129,7 +107,6 @@ static const struct stmmac_hwif_entry { const void *mac; const void *hwtimestamp; const void *ptp; - const void *mode; const void *tc; const void *mmc; const void *est; @@ -150,7 +127,6 @@ static const struct stmmac_hwif_entry { .mac = &dwmac100_ops, .hwtimestamp = &dwmac1000_ptp, .ptp = &dwmac1000_ptp_clock_ops, - .mode = NULL, .tc = NULL, .mmc = &dwmac_mmc_ops, .setup = dwmac100_setup, @@ -167,7 +143,6 @@ static const struct stmmac_hwif_entry { .mac = &dwmac1000_ops, .hwtimestamp = &dwmac1000_ptp, .ptp = &dwmac1000_ptp_clock_ops, - .mode = NULL, .tc = NULL, .mmc = &dwmac_mmc_ops, .setup = dwmac1000_setup, @@ -186,12 +161,10 @@ static const struct stmmac_hwif_entry { .vlan = &dwmac_vlan_ops, .hwtimestamp = &stmmac_ptp, .ptp = &stmmac_ptp_clock_ops, - .mode = NULL, .tc = &dwmac4_tc_ops, .mmc = &dwmac_mmc_ops, .est = &dwmac510_est_ops, .setup = dwmac4_setup, - .quirks = stmmac_dwmac4_quirks, }, { .core_type = DWMAC_CORE_GMAC4, .min_id = DWMAC_CORE_4_00, @@ -380,7 +353,6 @@ int stmmac_hwif_init(struct stmmac_priv *priv) mac->dma = mac->dma ? : entry->dma; mac->mac = mac->mac ? : entry->mac; mac->ptp = mac->ptp ? : entry->hwtimestamp; - mac->mode = mac->mode ? : entry->mode; mac->tc = mac->tc ? : entry->tc; mac->mmc = mac->mmc ? : entry->mmc; mac->est = mac->est ? : entry->est; diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index 2216c25abc668..2eb1e03375f18 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -42,6 +42,9 @@ struct stmmac_desc_ops { int end, int bfsize); /* DMA TX descriptor ring initialization */ void (*init_tx_desc)(struct dma_desc *p, int mode, int end); + /* Invoked by the refill function to prepare the rx descriptor */ + void (*prepare_rx_desc)(struct dma_desc *p, int mode, + dma_addr_t np, bool hwts_rx, int bfsize); /* Invoked by the xmit function to prepare the tx descriptor */ void (*prepare_tx_desc)(struct dma_desc *p, int is_fs, int len, bool csum_flag, int mode, bool tx_own, bool ls, @@ -109,6 +112,8 @@ struct stmmac_desc_ops { stmmac_do_void_callback(__priv, desc, init_rx_desc, __args) #define stmmac_init_tx_desc(__priv, __args...) \ stmmac_do_void_callback(__priv, desc, init_tx_desc, __args) +#define stmmac_prepare_rx_desc(__priv, __args...) \ + stmmac_do_void_callback(__priv, desc, prepare_rx_desc, __args) #define stmmac_prepare_tx_desc(__priv, __args...) \ stmmac_do_void_callback(__priv, desc, prepare_tx_desc, __args) #define stmmac_prepare_tso_tx_desc(__priv, __args...) \ @@ -547,21 +552,6 @@ struct stmmac_hwtimestamp { struct stmmac_tx_queue; struct stmmac_rx_queue; -/* Helpers to manage the descriptors for chain and ring modes */ -struct stmmac_mode_ops { - void (*init) (void *des, dma_addr_t phy_addr, unsigned int size, - unsigned int extend_desc); - void (*init_desc3)(struct dma_desc *p); - void (*refill_desc3)(struct stmmac_rx_queue *rx_q, struct dma_desc *p); -}; - -#define stmmac_mode_init(__priv, __args...) \ - stmmac_do_void_callback(__priv, mode, init, __args) -#define stmmac_init_desc3(__priv, __args...) \ - stmmac_do_void_callback(__priv, mode, init_desc3, __args) -#define stmmac_refill_desc3(__priv, __args...) \ - stmmac_do_void_callback(__priv, mode, refill_desc3, __args) - struct tc_cls_u32_offload; struct tc_cbs_qopt_offload; struct flow_cls_offload; @@ -680,9 +670,6 @@ extern const struct stmmac_desc_ops ndesc_rxcoe2_ops; extern const struct stmmac_hwtimestamp stmmac_ptp; extern const struct stmmac_hwtimestamp dwmac1000_ptp; -extern const struct stmmac_mode_ops ring_mode_ops; -extern const struct stmmac_mode_ops chain_mode_ops; - extern const struct stmmac_ops dwmac100_ops; extern const struct stmmac_dma_ops dwmac100_dma_ops; extern const struct stmmac_ops dwmac1000_ops; diff --git a/drivers/net/ethernet/stmicro/stmmac/norm_desc.c b/drivers/net/ethernet/stmicro/stmmac/norm_desc.c index 95d3031f4ab97..d24324bd20586 100644 --- a/drivers/net/ethernet/stmicro/stmmac/norm_desc.c +++ b/drivers/net/ethernet/stmicro/stmmac/norm_desc.c @@ -201,6 +201,15 @@ static unsigned int ndesc_get_rx_len(int mode) return ndesc_rx_desc_len_on_ring(); } +static void ndesc_prepare_rx_desc(struct dma_desc *p, int mode, + dma_addr_t np, bool hwts_rx, int bfsize) +{ + if (mode == STMMAC_CHAIN_MODE) + ndesc_rx_set_buf_on_chain(p, np, hwts_rx); + else + ndesc_rx_set_buf_on_ring(p, bfsize); +} + static int ndesc_get_tx_ls(struct dma_desc *p) { return (le32_to_cpu(p->des1) & TDES1_LAST_SEGMENT) >> 30; @@ -339,6 +348,7 @@ const struct stmmac_desc_ops ndesc_ops = { .init_rx_desc = ndesc_init_rx_desc, .init_tx_desc = ndesc_init_tx_desc, .get_tx_owner = ndesc_get_tx_owner, + .prepare_rx_desc = ndesc_prepare_rx_desc, .release_tx_desc = ndesc_release_tx_desc, .prepare_tx_desc = ndesc_prepare_tx_desc, .set_tx_ic = ndesc_set_tx_ic, @@ -363,6 +373,7 @@ const struct stmmac_desc_ops ndesc_rxcoe2_ops = { .init_rx_desc = ndesc_init_rx_desc, .init_tx_desc = ndesc_init_tx_desc, .get_tx_owner = ndesc_get_tx_owner, + .prepare_rx_desc = ndesc_prepare_rx_desc, .release_tx_desc = ndesc_release_tx_desc, .prepare_tx_desc = ndesc_prepare_tx_desc, .set_tx_ic = ndesc_set_tx_ic, diff --git a/drivers/net/ethernet/stmicro/stmmac/ring_mode.c b/drivers/net/ethernet/stmicro/stmmac/ring_mode.c deleted file mode 100644 index 916396cb59b85..0000000000000 --- a/drivers/net/ethernet/stmicro/stmmac/ring_mode.c +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/******************************************************************************* - Specialised functions for managing Ring mode - - Copyright(C) 2011 STMicroelectronics Ltd - - It defines all the functions used to handle the normal/enhanced - descriptors in case of the DMA is configured to work in chained or - in ring mode. - - - Author: Giuseppe Cavallaro -*******************************************************************************/ - -#include "stmmac.h" - -static void refill_desc3(struct stmmac_rx_queue *rx_q, struct dma_desc *p) -{ - struct stmmac_priv *priv = rx_q->priv_data; - - /* Fill DES3 in case of RING mode */ - if (priv->dma_conf.dma_buf_sz == BUF_SIZE_16KiB) - p->des3 = cpu_to_le32(le32_to_cpu(p->des2) + BUF_SIZE_8KiB); -} - -/* In ring mode we need to fill the desc3 because it is used as buffer */ -static void init_desc3(struct dma_desc *p) -{ - p->des3 = cpu_to_le32(le32_to_cpu(p->des2) + BUF_SIZE_8KiB); -} - -const struct stmmac_mode_ops ring_mode_ops = { - .refill_desc3 = refill_desc3, - .init_desc3 = init_desc3, -}; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 44f3ef3ff3610..5fbafd8e53ce6 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -1711,7 +1711,7 @@ static void stmmac_clear_descriptors(struct stmmac_priv *priv, */ static int stmmac_init_rx_buffers(struct stmmac_priv *priv, struct stmmac_dma_conf *dma_conf, - struct dma_desc *p, + struct dma_desc *p, dma_addr_t np, int i, gfp_t flags, u32 queue) { struct stmmac_rx_queue *rx_q = &dma_conf->rx_queue[queue]; @@ -1751,8 +1751,8 @@ static int stmmac_init_rx_buffers(struct stmmac_priv *priv, } stmmac_set_desc_addr(priv, p, buf->addr); - if (dma_conf->dma_buf_sz == BUF_SIZE_16KiB) - stmmac_init_desc3(priv, p); + stmmac_prepare_rx_desc(priv, p, priv->mode, np, true, + dma_conf->dma_buf_sz); return 0; } @@ -1861,14 +1861,19 @@ static int stmmac_alloc_rx_buffers(struct stmmac_priv *priv, for (i = 0; i < dma_conf->dma_rx_size; i++) { struct dma_desc *p; + dma_addr_t np; int ret; - if (priv->extend_desc) - p = &((rx_q->dma_erx + i)->basic); - else - p = rx_q->dma_rx + i; + np = STMMAC_GET_ENTRY(i, dma_conf->dma_rx_size); + if (priv->extend_desc) { + p = &rx_q->dma_erx[i].basic; + np = rx_q->dma_rx_phy + np * sizeof(struct dma_extended_desc); + } else { + p = &rx_q->dma_rx[i]; + np = rx_q->dma_rx_phy + np * sizeof(struct dma_desc); + } - ret = stmmac_init_rx_buffers(priv, dma_conf, p, i, flags, + ret = stmmac_init_rx_buffers(priv, dma_conf, p, np, i, flags, queue); if (ret) return ret; @@ -1924,13 +1929,17 @@ static int stmmac_alloc_rx_buffers_zc(struct stmmac_priv *priv, for (i = 0; i < dma_conf->dma_rx_size; i++) { struct stmmac_rx_buffer *buf; - dma_addr_t dma_addr; + dma_addr_t dma_addr, np; struct dma_desc *p; - if (priv->extend_desc) - p = (struct dma_desc *)(rx_q->dma_erx + i); - else - p = rx_q->dma_rx + i; + np = STMMAC_GET_ENTRY(i, dma_conf->dma_rx_size); + if (priv->extend_desc) { + p = &rx_q->dma_erx[i].basic; + np = rx_q->dma_rx_phy + np * sizeof(struct dma_extended_desc); + } else { + p = &rx_q->dma_rx[i]; + np = rx_q->dma_rx_phy + np * sizeof(struct dma_desc); + } buf = &rx_q->buf_pool[i]; @@ -1940,6 +1949,9 @@ static int stmmac_alloc_rx_buffers_zc(struct stmmac_priv *priv, dma_addr = xsk_buff_xdp_get_dma(buf->xdp); stmmac_set_desc_addr(priv, p, dma_addr); + stmmac_prepare_rx_desc(priv, p, priv->mode, np, true, + dma_conf->dma_buf_sz); + rx_q->buf_alloc_num++; } @@ -2010,18 +2022,6 @@ static int __init_dma_rx_desc_rings(struct stmmac_priv *priv, return -ENOMEM; } - /* Setup the chained descriptor addresses */ - if (priv->mode == STMMAC_CHAIN_MODE) { - if (priv->extend_desc) - stmmac_mode_init(priv, rx_q->dma_erx, - rx_q->dma_rx_phy, - dma_conf->dma_rx_size, 1); - else - stmmac_mode_init(priv, rx_q->dma_rx, - rx_q->dma_rx_phy, - dma_conf->dma_rx_size, 0); - } - return 0; } @@ -5148,11 +5148,16 @@ static inline void stmmac_rx_refill(struct stmmac_priv *priv, u32 queue) struct stmmac_rx_buffer *buf = &rx_q->buf_pool[entry]; struct dma_desc *p; bool use_rx_wd; + dma_addr_t np; - if (priv->extend_desc) - p = (struct dma_desc *)(rx_q->dma_erx + entry); - else - p = rx_q->dma_rx + entry; + np = STMMAC_GET_ENTRY(entry, priv->dma_conf.dma_rx_size); + if (priv->extend_desc) { + p = &rx_q->dma_erx[entry].basic; + np = rx_q->dma_rx_phy + np * sizeof(struct dma_extended_desc); + } else { + p = &rx_q->dma_rx[entry]; + np = rx_q->dma_rx_phy + np * sizeof(struct dma_desc); + } if (!buf->page) { size = rx_q->napi_skb_frag_size; @@ -5180,8 +5185,8 @@ static inline void stmmac_rx_refill(struct stmmac_priv *priv, u32 queue) stmmac_set_desc_addr(priv, p, buf->addr); stmmac_set_desc_sec_addr(priv, p, buf->sec_addr, priv->sph_active); - - stmmac_refill_desc3(priv, rx_q, p); + stmmac_prepare_rx_desc(priv, p, priv->mode, np, + priv->hwts_rx_en, priv->dma_conf.dma_buf_sz); rx_q->rx_count_frames++; if (rx_q->rx_count_frames > priv->rx_coal_frames[queue]) @@ -5573,6 +5578,7 @@ static bool stmmac_rx_refill_zc(struct stmmac_priv *priv, u32 queue, u32 budget) struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue]; unsigned int entry = rx_q->dirty_rx; struct dma_desc *rx_desc = NULL; + dma_addr_t rx_next; bool ret = true; budget = min(budget, stmmac_rx_dirty(priv, queue)); @@ -5590,15 +5596,22 @@ static bool stmmac_rx_refill_zc(struct stmmac_priv *priv, u32 queue, u32 budget) } } - if (priv->extend_desc) - rx_desc = (struct dma_desc *)(rx_q->dma_erx + entry); - else - rx_desc = rx_q->dma_rx + entry; + rx_next = STMMAC_GET_ENTRY(entry, priv->dma_conf.dma_rx_size); + if (priv->extend_desc) { + rx_desc = &rx_q->dma_erx[entry].basic; + rx_next = rx_q->dma_rx_phy + + rx_next * sizeof(struct dma_extended_desc); + } else { + rx_desc = &rx_q->dma_rx[entry]; + rx_next = rx_q->dma_rx_phy + + rx_next * sizeof(struct dma_desc); + } dma_addr = xsk_buff_xdp_get_dma(buf->xdp); stmmac_set_desc_addr(priv, rx_desc, dma_addr); stmmac_set_desc_sec_addr(priv, rx_desc, 0, false); - stmmac_refill_desc3(priv, rx_q, rx_desc); + stmmac_prepare_rx_desc(priv, rx_desc, priv->mode, rx_next, + priv->hwts_rx_en, priv->dma_conf.dma_buf_sz); rx_q->rx_count_frames++; if (rx_q->rx_count_frames > priv->rx_coal_frames[queue]) -- cgit v1.2.3 From 6f83bbb0e2136423511b8ffd22e765ddf4937ce9 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Tue, 22 Jul 2025 12:42:48 +0300 Subject: net: stmmac: Add unified Rx descriptor release method Currently the DMA-descriptor release method is implemented for the Tx descriptors and specific for the related code semantics. The Rx DMA-descriptors are being prepared by means of the stmmac_desc_ops::set_rx_owner() methods, which besides to setting the DMA-owner flag also initialize the Interrupt-on-Completion flag and the buffer1-valid flag. This makes the functions body noncoherent and thus the callee code - harder to read/comprehend. Instead let's introduce the stmmac_desc_ops::release_rx_desc() methods for all currently supported DW MAC IP-cores, which would implement the semantics required for the Rx DMA-descriptors being released for DMA. Note this change also moves the DMA-Wr memory barrier to the new release_rx_desc() and makes sure it's executed only ones on the initial descriptor release. This shall speed-up the procedure a bit. Besides the commit happens to fix a problem specific for the DW MAC IP-cores with the fully written back Rx DMA-descriptors (DW GMAC >= v4.x and DW XGMAC >= v2.x). Since the DW MAC DMA-engine of these IP-cores overwrites all the descriptors fields it also clears/sets the Interrupt-on-Completion flag depending on the RDES3.CTX bit state. So the release procedure must correctly restore the IOC flag in accordance with the disable_rx_ic argument value (Rx WDT status) including the possible clearance. Fixes: 753a71090f33 ("stmmac: add descriptors function for GMAC 4.xx") Fixes: 874dfb65a484 ("net: stmmac: Add descriptor related callbacks for XGMAC2") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c | 32 +++++++++++++++++++++- .../net/ethernet/stmicro/stmmac/dwxgmac2_descs.c | 30 +++++++++++++++++++- drivers/net/ethernet/stmicro/stmmac/enh_desc.c | 25 +++++++++++++++++ drivers/net/ethernet/stmicro/stmmac/hwif.h | 5 ++++ drivers/net/ethernet/stmicro/stmmac/norm_desc.c | 24 ++++++++++++++++ drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 16 +++++++---- 6 files changed, 124 insertions(+), 8 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c index 31e1c2ad90790..86eb6c01a7d0b 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c @@ -323,7 +323,12 @@ exit: static void dwmac4_rd_init_rx_desc(struct dma_desc *p, int disable_rx_ic, int mode, int end, int bfsize) { - dwmac4_set_rx_owner(p, disable_rx_ic); + u32 flags = (RDES3_OWN | RDES3_BUFFER1_VALID_ADDR); + + if (!disable_rx_ic) + flags |= RDES3_INT_ON_COMPLETION_EN; + + p->des3 |= cpu_to_le32(flags); } static void dwmac4_rd_init_tx_desc(struct dma_desc *p, int mode, int end) @@ -339,6 +344,30 @@ static unsigned int dwmac4_rd_get_rx_len(int mode) return STMMAC_RX_BUF_ALIGN(FIELD_MAX(DMA_RBSZ_MASK)); } +static void dwmac4_release_rx_desc(struct dma_desc *p, int disable_rx_ic, + int is_fs, bool rx_own) +{ + unsigned int rdes3 = le32_to_cpu(p->des3); + + rdes3 |= RDES3_BUFFER1_VALID_ADDR; + + if (disable_rx_ic) + rdes3 &= cpu_to_le32(~RDES3_INT_ON_COMPLETION_EN); + else + rdes3 |= cpu_to_le32(RDES3_INT_ON_COMPLETION_EN); + + if (rx_own) + rdes3 |= cpu_to_le32(RDES3_OWN); + + /* Before releasing the initial descriptor make sure that all + * the previous writes are visible to the controller. + */ + if (is_fs && rx_own) + dma_wmb(); + + p->des3 = cpu_to_le32(rdes3); +} + static unsigned int dwmac4_rd_get_tx_len(int mode) { return FIELD_MAX(TDES2_BUFFER1_SIZE_MASK); @@ -583,6 +612,7 @@ const struct stmmac_desc_ops dwmac4_desc_ops = { .get_rx_timestamp_status = dwmac4_wrback_get_rx_timestamp_status, .get_timestamp = dwmac4_get_timestamp, .set_tx_ic = dwmac4_rd_set_tx_ic, + .release_rx_desc = dwmac4_release_rx_desc, .prepare_tx_desc = dwmac4_rd_prepare_tx_desc, .prepare_tso_tx_desc = dwmac4_rd_prepare_tso_tx_desc, .release_tx_desc = dwmac4_release_tx_desc, diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c index 14f2da1e5f027..192860b639f66 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c @@ -231,7 +231,12 @@ static int dwxgmac2_get_rx_timestamp_status(void *desc, void *next_desc, static void dwxgmac2_init_rx_desc(struct dma_desc *p, int disable_rx_ic, int mode, int end, int bfsize) { - dwxgmac2_set_rx_owner(p, disable_rx_ic); + u32 flags = XGMAC_RDES3_OWN; + + if (!disable_rx_ic) + flags |= XGMAC_RDES3_IOC; + + p->des3 |= cpu_to_le32(flags); } static void dwxgmac2_init_tx_desc(struct dma_desc *p, int mode, int end) @@ -247,6 +252,28 @@ static unsigned int dwxgmac2_get_rx_len(int mode) return STMMAC_RX_BUF_ALIGN(FIELD_MAX(XGMAC_RBSZ)); } +static void dwxgmac2_release_rx_desc(struct dma_desc *p, int disable_rx_ic, + int is_fs, bool rx_own) +{ + unsigned int rdes3 = le32_to_cpu(p->des3); + + if (disable_rx_ic) + rdes3 &= cpu_to_le32(~XGMAC_RDES3_IOC); + else + rdes3 |= cpu_to_le32(XGMAC_RDES3_IOC); + + if (rx_own) + rdes3 |= cpu_to_le32(XGMAC_RDES3_OWN); + + /* Before releasing the initial descriptor make sure that all + * the previous writes are visible to the controller. + */ + if (is_fs && rx_own) + dma_wmb(); + + p->des3 = cpu_to_le32(rdes3); +} + static unsigned int dwxgmac2_get_tx_len(int mode) { return FIELD_MAX(XGMAC_TDES2_B1L); @@ -464,6 +491,7 @@ const struct stmmac_desc_ops dwxgmac210_desc_ops = { .get_rx_timestamp_status = dwxgmac2_get_rx_timestamp_status, .get_timestamp = dwxgmac2_get_timestamp, .set_tx_ic = dwxgmac2_set_tx_ic, + .release_rx_desc = dwxgmac2_release_rx_desc, .prepare_tx_desc = dwxgmac2_prepare_tx_desc, .prepare_tso_tx_desc = dwxgmac2_prepare_tso_tx_desc, .release_tx_desc = dwxgmac2_release_tx_desc, diff --git a/drivers/net/ethernet/stmicro/stmmac/enh_desc.c b/drivers/net/ethernet/stmicro/stmmac/enh_desc.c index 91d63659f108b..c11a06af7dbfb 100644 --- a/drivers/net/ethernet/stmicro/stmmac/enh_desc.c +++ b/drivers/net/ethernet/stmicro/stmmac/enh_desc.c @@ -327,6 +327,28 @@ static unsigned int enh_desc_get_rx_len(int mode) return enh_desc_rx_desc_len_on_ring(); } +static void enh_desc_release_rx_desc(struct dma_desc *p, int disable_rx_ic, + int is_fs, bool rx_own) +{ + unsigned int rdes1 = le32_to_cpu(p->des1); + + if (disable_rx_ic) + rdes1 |= cpu_to_le32(ERDES1_DISABLE_IC); + else + rdes1 &= cpu_to_le32(~ERDES1_DISABLE_IC); + + p->des1 = cpu_to_le32(rdes1); + + /* Before releasing the initial descriptor make sure that all + * the previous writes are visible to the controller. + */ + if (is_fs && rx_own) + dma_wmb(); + + if (rx_own) + p->des0 = cpu_to_le32(RDES0_OWN); +} + static void enh_desc_prepare_rx_desc(struct dma_desc *p, int mode, dma_addr_t np, bool hwts_rx, int bfsize) { @@ -493,6 +515,7 @@ const struct stmmac_desc_ops enh_desc_ops = { .init_rx_desc = enh_desc_init_rx_desc, .init_tx_desc = enh_desc_init_tx_desc, .get_tx_owner = enh_desc_get_tx_owner, + .release_rx_desc = enh_desc_release_rx_desc, .prepare_rx_desc = enh_desc_prepare_rx_desc, .release_tx_desc = enh_desc_release_tx_desc, .prepare_tx_desc = enh_desc_prepare_tx_desc, @@ -518,6 +541,7 @@ const struct stmmac_desc_ops enh_desc_noext_ops = { .init_rx_desc = enh_desc_init_rx_desc, .init_tx_desc = enh_desc_init_tx_desc, .get_tx_owner = enh_desc_get_tx_owner, + .release_rx_desc = enh_desc_release_rx_desc, .prepare_rx_desc = enh_desc_prepare_rx_desc, .release_tx_desc = enh_desc_release_tx_desc, .prepare_tx_desc = enh_desc_prepare_tx_desc, @@ -543,6 +567,7 @@ const struct stmmac_desc_ops enh_desc_ext_ops = { .init_rx_desc = enh_desc_init_rx_desc, .init_tx_desc = enh_desc_init_tx_desc, .get_tx_owner = enh_desc_get_tx_owner, + .release_rx_desc = enh_desc_release_rx_desc, .prepare_rx_desc = enh_desc_prepare_rx_desc, .release_tx_desc = enh_desc_release_tx_desc, .prepare_tx_desc = enh_desc_prepare_tx_desc, diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index 2eb1e03375f18..f523ef567eab1 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -55,6 +55,9 @@ struct stmmac_desc_ops { /* Set/get the owner of the descriptor */ void (*set_tx_owner)(struct dma_desc *p); int (*get_tx_owner)(struct dma_desc *p); + /* Clean the rx descriptor as soon as the rx flow is handled */ + void (*release_rx_desc)(struct dma_desc *p, int disable_rx_ic, + int is_fs, bool rx_own); /* Clean the tx descriptor as soon as the tx irq is received */ void (*release_tx_desc)(struct dma_desc *p, int mode, dma_addr_t np, bool hwts_tx); @@ -122,6 +125,8 @@ struct stmmac_desc_ops { stmmac_do_void_callback(__priv, desc, set_tx_owner, __args) #define stmmac_get_tx_owner(__priv, __args...) \ stmmac_do_callback(__priv, desc, get_tx_owner, __args) +#define stmmac_release_rx_desc(__priv, __args...) \ + stmmac_do_void_callback(__priv, desc, release_rx_desc, __args) #define stmmac_release_tx_desc(__priv, __args...) \ stmmac_do_void_callback(__priv, desc, release_tx_desc, __args) #define stmmac_set_tx_ic(__priv, __args...) \ diff --git a/drivers/net/ethernet/stmicro/stmmac/norm_desc.c b/drivers/net/ethernet/stmicro/stmmac/norm_desc.c index d24324bd20586..e90184f3f1367 100644 --- a/drivers/net/ethernet/stmicro/stmmac/norm_desc.c +++ b/drivers/net/ethernet/stmicro/stmmac/norm_desc.c @@ -201,6 +201,28 @@ static unsigned int ndesc_get_rx_len(int mode) return ndesc_rx_desc_len_on_ring(); } +static void ndesc_release_rx_desc(struct dma_desc *p, int disable_rx_ic, + int is_fs, bool rx_own) +{ + unsigned int rdes1 = le32_to_cpu(p->des1); + + if (disable_rx_ic) + rdes1 |= cpu_to_le32(RDES1_DISABLE_IC); + else + rdes1 &= cpu_to_le32(~RDES1_DISABLE_IC); + + p->des1 = cpu_to_le32(rdes1); + + /* Before releasing the initial descriptor make sure that all + * the previous writes are visible to the controller. + */ + if (is_fs && rx_own) + dma_wmb(); + + if (rx_own) + p->des0 = cpu_to_le32(RDES0_OWN); +} + static void ndesc_prepare_rx_desc(struct dma_desc *p, int mode, dma_addr_t np, bool hwts_rx, int bfsize) { @@ -348,6 +370,7 @@ const struct stmmac_desc_ops ndesc_ops = { .init_rx_desc = ndesc_init_rx_desc, .init_tx_desc = ndesc_init_tx_desc, .get_tx_owner = ndesc_get_tx_owner, + .release_rx_desc = ndesc_release_rx_desc, .prepare_rx_desc = ndesc_prepare_rx_desc, .release_tx_desc = ndesc_release_tx_desc, .prepare_tx_desc = ndesc_prepare_tx_desc, @@ -373,6 +396,7 @@ const struct stmmac_desc_ops ndesc_rxcoe2_ops = { .init_rx_desc = ndesc_init_rx_desc, .init_tx_desc = ndesc_init_tx_desc, .get_tx_owner = ndesc_get_tx_owner, + .release_rx_desc = ndesc_release_rx_desc, .prepare_rx_desc = ndesc_prepare_rx_desc, .release_tx_desc = ndesc_release_tx_desc, .prepare_tx_desc = ndesc_prepare_tx_desc, diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 5fbafd8e53ce6..0ec2cb760301f 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -5139,11 +5139,13 @@ static inline void stmmac_rx_refill(struct stmmac_priv *priv, u32 queue) int dirty = stmmac_rx_dirty(priv, queue); unsigned int entry = rx_q->dirty_rx; gfp_t gfp = (GFP_ATOMIC | __GFP_NOWARN); - unsigned int size; + unsigned int size, first_rx; if (priv->dma_cap.host_dma_width <= 32) gfp |= GFP_DMA32; + first_rx = rx_q->dirty_rx; + while (dirty-- > 0) { struct stmmac_rx_buffer *buf = &rx_q->buf_pool[entry]; struct dma_desc *p; @@ -5197,8 +5199,8 @@ static inline void stmmac_rx_refill(struct stmmac_priv *priv, u32 queue) if (!priv->use_riwt) use_rx_wd = false; - dma_wmb(); - stmmac_set_rx_owner(priv, p, use_rx_wd); + stmmac_release_rx_desc(priv, p, use_rx_wd, + entry == first_rx, true); entry = STMMAC_GET_ENTRY(entry, priv->dma_conf.dma_rx_size); } @@ -5576,13 +5578,15 @@ static void stmmac_dispatch_skb_zc(struct stmmac_priv *priv, u32 queue, static bool stmmac_rx_refill_zc(struct stmmac_priv *priv, u32 queue, u32 budget) { struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[queue]; - unsigned int entry = rx_q->dirty_rx; + unsigned int entry = rx_q->dirty_rx, first_rx; struct dma_desc *rx_desc = NULL; dma_addr_t rx_next; bool ret = true; budget = min(budget, stmmac_rx_dirty(priv, queue)); + first_rx = rx_q->dirty_rx; + while (budget-- > 0 && entry != rx_q->cur_rx) { struct stmmac_rx_buffer *buf = &rx_q->buf_pool[entry]; dma_addr_t dma_addr; @@ -5622,8 +5626,8 @@ static bool stmmac_rx_refill_zc(struct stmmac_priv *priv, u32 queue, u32 budget) if (!priv->use_riwt) use_rx_wd = false; - dma_wmb(); - stmmac_set_rx_owner(priv, rx_desc, use_rx_wd); + stmmac_release_rx_desc(priv, rx_desc, use_rx_wd, + entry == first_rx, true); entry = STMMAC_GET_ENTRY(entry, priv->dma_conf.dma_rx_size); } -- cgit v1.2.3 From 7cec342ce65a02484b130e49e1903623ff6ac83a Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Tue, 22 Jul 2025 15:31:51 +0300 Subject: net: stmmac: dwmac1000: Fix long-broken Rx Jumbo-frames support As soon as the Jumbo frames with MTU over 2K/4K (for the Normal descriptors) and over 8K (for the chained Enhanced descriptors) enabled the frames exceeding these limits won't be delivered to the networking core and will be just discarded. This happens due to the driver not supporting the cross-descriptors data reception (see the stmmac_desc_ops::rx_status() callbacks handling the non-last segment descriptors). DW GMAC layer of the STMMAC driver currently supports the two types of the Rx DMA-descriptors which besides the different layout differ by the maximum buffers size: Normal Rx descriptor: 2K (chain), 2x2K (ring) Enhanced Rx descriptor: 8K (chain), 2x8K (ring) Since the cross-descriptor data reception is basically disabled the ringed Enhanced Rx-descriptors will only work for the 9K jumbo frames retrieval. The rest of the modes will cause the over the buffer-size frames dropping. This is definitely wrong seeing the driver unconditionally setups max 9K MTU constraint for any DW GMAC devices. Moreover the older non-GMAC IP-cores are supposed to work with the SKB_MAX_HEAD() MTUs which implies up to Page-sized frames reception. In that case depending on the system-wide page size the Jumbo frames reception won't properly work either. Let's fix the problem described above. In fact the driver code has already been prepared for that by the preceding fixes and the only what left to be done is to just return the "rx_not_ls" status from the DW GMAC-specific stmmac_desc_ops::rx_status() callback. The STMMAC core driver will handle the rest as is. Fixes: 286a83721720 ("stmmac: add CHAINED descriptor mode support (V4)") Fixes: 47dd7a540b8a ("net: add support for STMicroelectronics Ethernet controllers.") Signed-off-by: Serge Semin --- Note the patch used to fix the stmmac_rx_buf1_len(). It must take into account that it's possible to have two buffers initialized in the Rx DMA-descriptors. So the very first buffer of the last segment could be not containing the tail of the Rx frame. It's specific for the DW GMAC4/XGMAC IP-cores supporting the split-headers and failing to split the retrieved frame for some reason. --- drivers/net/ethernet/stmicro/stmmac/enh_desc.c | 6 ++---- drivers/net/ethernet/stmicro/stmmac/norm_desc.c | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/enh_desc.c b/drivers/net/ethernet/stmicro/stmmac/enh_desc.c index c11a06af7dbfb..24ac1d2ff164f 100644 --- a/drivers/net/ethernet/stmicro/stmmac/enh_desc.c +++ b/drivers/net/ethernet/stmicro/stmmac/enh_desc.c @@ -166,10 +166,8 @@ static int enh_desc_get_rx_basic_status(unsigned int rdes0, if (unlikely(rdes0 & RDES0_OWN)) return dma_own; - if (unlikely(!(rdes0 & RDES0_LAST_DESCRIPTOR))) { - x->rx_length++; - return discard_frame; - } + if (unlikely(!(rdes0 & RDES0_LAST_DESCRIPTOR))) + return rx_not_ls; if (unlikely(!(rdes0 & RDES0_FRAME_TYPE))) ret = llc_snap; diff --git a/drivers/net/ethernet/stmicro/stmmac/norm_desc.c b/drivers/net/ethernet/stmicro/stmmac/norm_desc.c index e90184f3f1367..389e07f35c6af 100644 --- a/drivers/net/ethernet/stmicro/stmmac/norm_desc.c +++ b/drivers/net/ethernet/stmicro/stmmac/norm_desc.c @@ -67,10 +67,8 @@ static int ndesc_get_rx_basic_status(unsigned int rdes0, if (unlikely(rdes0 & RDES0_OWN)) return dma_own; - if (unlikely(!(rdes0 & RDES0_LAST_DESCRIPTOR))) { - x->rx_length++; - return discard_frame; - } + if (unlikely(!(rdes0 & RDES0_LAST_DESCRIPTOR))) + return rx_not_ls; if (unlikely(!(rdes0 & RDES0_FRAME_TYPE))) ret = llc_snap; -- cgit v1.2.3 From 43b004020cf631086374654966117e81bf41b37a Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 22 Aug 2025 13:39:14 +0300 Subject: net: stmmac: Drop the power-of-2 Rx buffers size constraint The recent commits fixing the Rx DMA-descriptors initialization for the DW MAC100/GMAC switched the code to being agnostic from the power-of-2 Rx buffers size. Instead they fully follow the HW databooks requirements the buffers address and size to be properly aligned. The same has been applicable for the DW QoS Ether, DW XGMAC/XLGMAC devices support from the very initial commit adding them to the driver. So let's drop the pre-defined set of the Rx buffers sizes and just use the one MTU-based Rx buffer size instead. Not sure why hasn't this been done from the very initial driver commit. Sigh... Note this shall improve the driver performance as well since more buffers will be able to fit the orderly allocated pages by the Page Pool means. Thus less more data page-locality, less TLB misses, better CPU performance in handling the incoming traffic. Fixes: 47dd7a540b8a ("net: add support for STMicroelectronics Ethernet controllers.") Signed-off-by: Serge Semin --- Not really a fix, but well... --- drivers/net/ethernet/stmicro/stmmac/common.h | 6 +----- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 16 ++++------------ 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/common.h b/drivers/net/ethernet/stmicro/stmmac/common.h index 27665c7c5c259..95faae938b046 100644 --- a/drivers/net/ethernet/stmicro/stmmac/common.h +++ b/drivers/net/ethernet/stmicro/stmmac/common.h @@ -527,11 +527,7 @@ struct dma_features { u8 actphyif; }; -/* RX Buffer size must be multiple of 4/8/16 bytes */ -#define BUF_SIZE_16KiB 16368 -#define BUF_SIZE_8KiB 8188 -#define BUF_SIZE_4KiB 4096 -#define BUF_SIZE_2KiB 2048 +/* RX Buffer size alignment is of 4/8/16 bytes */ #define STMMAC_RX_BUF_ALIGN(x) ALIGN_DOWN(x, 16) #define STMMAC_RX_BUF_ADJUST(x) ALIGN(x, 16) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 0ec2cb760301f..e4a16757346b9 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -258,8 +258,10 @@ static void stmmac_verify_args(void) { if (unlikely(watchdog < 0)) watchdog = TX_TIMEO; - if (unlikely((buf_sz < DEFAULT_BUFSIZE) || (buf_sz > BUF_SIZE_16KiB))) + + if (unlikely(buf_sz < DEFAULT_BUFSIZE || buf_sz > SZ_16K)) buf_sz = DEFAULT_BUFSIZE; + if (unlikely((pause < 0) || (pause > 0xffff))) pause = PAUSE_TIME; @@ -1594,16 +1596,6 @@ static unsigned int stmmac_get_bfsize(struct stmmac_priv *priv, int mtu) * the Ethernet header plus possible C/S-VLAN headers. */ buff_size = umax(mtu + ETH_HLEN + 2 * VLAN_HLEN + ETH_FCS_LEN, buf_sz); - if (buff_size >= BUF_SIZE_8KiB) - buff_size = BUF_SIZE_16KiB; - else if (buff_size >= BUF_SIZE_4KiB) - buff_size = BUF_SIZE_8KiB; - else if (buff_size >= BUF_SIZE_2KiB) - buff_size = BUF_SIZE_4KiB; - else if (buff_size > DEFAULT_BUFSIZE) - buff_size = BUF_SIZE_2KiB; - else - buff_size = DEFAULT_BUFSIZE; /* Make sure the buffer is bus-width aligned up to fit in all the DMA * writes and to avoid undefined behaviour (see notes in the databooks). @@ -6269,7 +6261,7 @@ static int stmmac_change_mtu(struct net_device *dev, int new_mtu) } /* If condition true, FIFO is too small or MTU too large */ - if ((txfifosz < new_mtu) || (new_mtu > BUF_SIZE_16KiB)) + if ((txfifosz < new_mtu) || (new_mtu > XGMAC_JUMBO_LEN)) return -EINVAL; if (netif_running(dev)) { -- cgit v1.2.3 From 9407bf274087632814b5b337bc0c8bdf9dc89c42 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 24 Jul 2025 00:42:11 +0300 Subject: net: stmmac: dwmac1000: Fix change MTU procedure malfunction Currently the MTU change procedure doesn't work properly on the DW GMAC devices. It does change the Rx buffer size respectively though but as long as there is greater than 2K/9K frames get to be received they will be discarded by the MAC. The problem is that the stmmac_ops::core_init() method relies on the net_device::mtu field value which on the MTU-change procedure contains not new but old MTU. So the GMAC_CONTROL.JE and GMAC_CONTROL.2K flags responsible for the Rx frames length constraints won't be properly initialized. Let's fix that by making sure that the net_device::mtu field contains a new MTU before the interface restart procedure is executed in the stmmac_change_mtu() method. Note even though the commit 347007968744 ("net: ethernet: stmicro: stmmac: permit MTU change with interface up") changed the core part of the STMMAC driver the problem is specific to the DW GMAC only. The rest of stmmac_ops::core_init() implementations have the Giant frames always enabled. So the MTU change procedure just doesn't cause the frames length constraints enabling/disabling. Fixes: 347007968744 ("net: ethernet: stmicro: stmmac: permit MTU change with interface up") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index e4a16757346b9..a34aebb775b45 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -6276,6 +6276,8 @@ static int stmmac_change_mtu(struct net_device *dev, int new_mtu) __stmmac_release(dev); + WRITE_ONCE(dev->mtu, new_mtu); + ret = __stmmac_open(dev, dma_conf); if (ret) { free_dma_desc_resources(priv, dma_conf); @@ -6287,9 +6289,10 @@ static int stmmac_change_mtu(struct net_device *dev, int new_mtu) kfree(dma_conf); stmmac_set_rx_mode(dev); + } else { + WRITE_ONCE(dev->mtu, new_mtu); } - WRITE_ONCE(dev->mtu, new_mtu); netdev_update_features(dev); return 0; -- cgit v1.2.3 From 7dcefeeb505d48a34ca44db31d131c9dc5685a2b Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 13 Aug 2025 20:10:49 +0300 Subject: net: stmmac: dwmac1000: Detect and handle Jumbo frames The driver setups the frame size limits by using the MAC_CONTROL.JD and MAC_CONTROL.JE flags. The former one is responsible for the transmit Jabber engine disabling and the later one enables the Jumbo frames support for both Rx and Tx path. But if the MAC_CONTROL.JD is set MAC_CONTROL.JE won't affect the Tx path and only works for the Rx traffic. Thus the driver currently permits to transmit frames up to 16K bytes irrespective to the MTU value, to receive frames of up to 1518(1522), 2000, 9018(9022) bytes with no packet dropped due to the giant frame status, to get truncated frames over 2048 and 10240 bytes. Moreover giant frames dropping isn't working by default for the DW GMAC controllers of v3.70a and newer when there is no Giant frame status reported via the Rx DMA-descriptors (if Advanced Timestamp or Rx COE v1/v2 features is selected during the IP-core synthesizing). It isn't right to consider the MTU setting that relaxed and to have the device working so much differently depending on the controller version. Moreover based on what is said in [1] the most preferred solution would be to drop the oversized frames instead of passing them up to the networking core subsystem. Also note even though [1] says that the upper layer protocols must not pass a socket buffer (skb) to a device to transmit with more data than the MTU in fact it does at least in case of the pktgen module. In case of DW GMAC the driver can work way better to comply with what the networking subsystem requires. First of all the MAC_CONTROL.JD can be cleared thus enabling the MAC internal watchdog to truncate the oversized frames transmission on 2048 or 10240 outgoing octets. Secondly starting from v3.70a there is a watchdog timeout register which can be used to fine-tune the MAC to truncate the recv frames of the size greater than specified. Finally since the v3.70a IP-core there has been the DMA_CONTROL.DGF flag activating the Giant frames dropping. All the features above can be used to implement a more clever Jumbo frames support so the network interface behaviour would be unified across various DW GMAC IP-core versions (and looking a bit ahead across modern DW QoS Ether/XGMAC/etc). So DW GMAC v3.70a and newer controllers will have implemented the next MTU semantics in the driver: 1. MTU <= 1500: truncate xmit frames greater than 2048, drop recv frames greater than 1518(1522). 2. MTU <= 1978: truncate xmit frames greater than 2048, drop recv frames greater than 1996(2000). 3. 1978 < MTU <= 9000: truncate xmit frames greater than 10240 and recv frames greater than ALIGN_UP(MTU+22, 1024), drop recv frames greater than 9018(9022). 4. 9000 < MTU <= 16383: truncate xmit and recv frames greater than 16K bytes, drop recv frames greater than 9018(9022). This shall not only extend the Jumbo-frames support, but also shall speed up the oversized frames handling since the truncated packets will likely to fit into a single Rx DMA-descriptor, which then will be just dropped as erroneous and re-initialized. In case of the DW GMAC older than v3.70a version the semantics will be as follows: 1. MTU <= 1500: truncate xmit and recv frames greater than 2048, drop recv frames greater than 1518(1522). 2. 1500 < MTU <= 9000: truncate xmit and recv frames greater than 10240, drop recv frames greater than 9018(9022). 3. 9000 < MTU <= 16383: truncate xmit and recv frames greater than 16K, drop recv frames greater than 9018(9022). It's almost the same as before except that the oversized transmitted frames will be truncated from now. Note the driver currently just drops the frames with over Giant frame size so the describes recv truncation seems unnecessary. But it will be utilized in the NETIF_F_RXALL feature implementation. [1] Documentation/networking/netdevices.rst Fixes: 2618abb73c89 ("stmmac: Fix kernel crashes for jumbo frames") Fixes: 84c9f8c41df9 ("net: stmmac: Add ip version to dts bindings") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/dwmac1000.h | 13 ++++++++++-- .../net/ethernet/stmicro/stmmac/dwmac1000_core.c | 24 +++++++++++++++++++--- .../net/ethernet/stmicro/stmmac/dwmac1000_dma.c | 11 +++++++--- 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000.h b/drivers/net/ethernet/stmicro/stmmac/dwmac1000.h index 1e095ba64127c..a424f5884e437 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac1000.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000.h @@ -69,6 +69,9 @@ enum power_event { #define GMAC_PCS_BASE 0x000000c0 /* PCS register base */ #define GMAC_RGSMIIIS 0x000000d8 /* RGMII/SMII status */ +/* GMAC Watchdog timeout for received frames register */ +#define GMAC_WDT 0x000000dc + /* SGMII/RGMII status register */ #define GMAC_RGSMIIIS_LNKMODE BIT(0) #define GMAC_RGSMIIIS_SPEED GENMASK(2, 1) @@ -86,6 +89,7 @@ enum power_event { /* GMAC Configuration defines */ #define GMAC_CONTROL_2K 0x08000000 /* IEEE 802.3as 2K packets */ #define GMAC_CONTROL_CST 0x02000000 /* CRC Stripping for Type Frames */ +#define GMAC_CONTROL_WD 0x00800000 /* Disable Watchdog on receive */ #define GMAC_CONTROL_JD 0x00400000 /* Jabber disable */ #define GMAC_CONTROL_BE 0x00200000 /* Frame Burst Enable */ #define GMAC_CONTROL_JE 0x00100000 /* Jumbo frame */ @@ -101,8 +105,7 @@ enum inter_frame_gap { #define GMAC_CONTROL_DM 0x00000800 /* Duplex Mode */ #define GMAC_CONTROL_IPC 0x00000400 /* Checksum Offload */ -#define GMAC_CORE_INIT (GMAC_CONTROL_JD | GMAC_CONTROL_PS | \ - GMAC_CONTROL_BE | GMAC_CONTROL_DCRS) +#define GMAC_CORE_INIT (GMAC_CONTROL_PS | GMAC_CONTROL_BE | GMAC_CONTROL_DCRS) /* GMAC Frame Filter defines */ #define GMAC_FRAME_FILTER_PR 0x00000001 /* Promiscuous Mode */ @@ -116,6 +119,9 @@ enum inter_frame_gap { #define GMAC_FLOW_CTRL_UP 0x00000008 /* Unicast pause frame enable */ #define GMAC_FLOW_CTRL_RFE 0x00000004 /* Rx Flow Control Enable */ #define GMAC_FLOW_CTRL_TFE 0x00000002 /* Tx Flow Control Enable */ +/* Watchdog timeout for received frames defines */ +#define GMAC_WDT_PWE BIT(16) +#define GMAC_WDT_WTO GENMASK(13, 0) /* DEBUG Register defines */ /* MTL TxStatus FIFO */ @@ -207,6 +213,9 @@ enum ttc_control { #define DMA_CONTROL_EFC 0x00000100 #define DMA_CONTROL_FEF 0x00000080 #define DMA_CONTROL_FUF 0x00000040 +#define DMA_CONTROL_DGF 0x00000020 +#define DMA_CONTROL_PEF_MASK (DMA_CONTROL_DT | DMA_CONTROL_FEF | \ + DMA_CONTROL_FUF | DMA_CONTROL_DGF) /* Receive flow control activation field * RFA field in DMA control register, bits 23,10:9 diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c index 1c7562fab3948..312d22a52ea59 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c @@ -35,6 +35,7 @@ static int dwmac1000_pcs_init(struct stmmac_priv *priv) static void dwmac1000_core_init(struct mac_device_info *hw, struct net_device *dev) { + struct stmmac_priv *priv = netdev_priv(dev); void __iomem *ioaddr = hw->pcsr; int mtu = dev->mtu; u32 value; @@ -42,13 +43,30 @@ static void dwmac1000_core_init(struct mac_device_info *hw, /* Configure GMAC core */ value = readl(ioaddr + GMAC_CONTROL); - if (mtu > 1500) - value |= GMAC_CONTROL_2K; - if (mtu > 2000) + /* Frame length limits (giant status(+VLAN):truncated by watchdog) */ + if (mtu > 9000) /* Rx <= 9018(9022):16383 && Tx <= 16383 */ + value |= GMAC_CONTROL_JE | GMAC_CONTROL_WD | GMAC_CONTROL_JD; + else if (mtu > 1978) /* Rx <= 9018(9022):10240 && Tx <= 10240 */ + value |= GMAC_CONTROL_JE; + else if (priv->synopsys_id < DWMAC_CORE_3_70 && mtu > 1500) value |= GMAC_CONTROL_JE; + else if (mtu > 1500) /* Rx <= 1996(2000):2048 && Tx <= 2048 */ + value |= GMAC_CONTROL_2K; + /* else 64 <= Rx <= 1518(1522):2048 && Tx <= 2048 */ writel(value | GMAC_CORE_INIT, ioaddr + GMAC_CONTROL); + /* Over 2K, 3K, ..., 9K (10K and 16K-1) frames will be truncated on Rx */ + if (mtu > 1500 && mtu <= 9000) { + value = ALIGN(mtu + ETH_HLEN + VLAN_HLEN + ETH_FCS_LEN, SZ_1K); + value = FIELD_PREP(GMAC_WDT_WTO, value) | GMAC_WDT_PWE; + } else { + value = 0; + } + + /* Over giant frame watchdog fine-tuning (available since v3.70a) */ + writel(value, ioaddr + GMAC_WDT); + /* Mask GMAC interrupts */ writel(GMAC_INT_DEFAULT_MASK, ioaddr + GMAC_INT_MASK); diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c index a3052e29c8a2b..3a545442e903a 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c @@ -148,10 +148,15 @@ static void dwmac1000_dma_operation_mode_rx(struct stmmac_priv *priv, } /* Permit errorneous frames (IP/TCP/UDP csum, overflow, giant, etc) */ - if (rxall) + csr6 &= ~DMA_CONTROL_PEF_MASK; + if (rxall) { csr6 |= DMA_CONTROL_DT | DMA_CONTROL_FEF | DMA_CONTROL_FUF; - else - csr6 &= ~(DMA_CONTROL_DT | DMA_CONTROL_FEF | DMA_CONTROL_FUF); + } else { + /* Drop giant frames to unify the GMACs semantics (available + * since v3.70a if no giant frame status reported via Rx descs). + */ + csr6 |= DMA_CONTROL_DGF; + } /* Configure flow control based on rx fifo size */ csr6 = dwmac1000_configure_fc(csr6, fifosz); -- cgit v1.2.3 From 3ff969f9872138167716828c9e992504621a3414 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 14 Aug 2025 16:04:45 +0300 Subject: net: stmmac: dwxgmac2: Detect and handle Jumbo frames The driver currently always enables the Jumbo frames up to 16K bytes to receive and transmit. It's done by setting the MAC_TX_CONFIG.JD flag and initializing the MAC_RX_CONFIG.GPSL field with the 16K frame size. Basically it means the driver completely ignores the MTU settings despite of what was said in the commit 8a488c3f97cd ("net: stmmac: xgmac: Enable RX Jumbo frame support"). It isn't right to disregard the MTU setting since the MTU value might be an important parameter of an Ethernet network segment. Moreover based on what is said in [1] the most preferred way to support MTU would be to drop the oversized frames instead of passing them up to the networking core subsystem. It's also important to note that even though [1] says that the upper layer protocols must not pass a socket buffer (skb) to a device to transmit with more data than the MTU in fact it does at least in case of the pktgen module. So currently the driver doesn't fulfill these requirements and passes/transfers the incoming/outcoming frames further ignoring the MTU value. Let's fix that by activating the Giant frame setting and by setting the internal watchdog up to drop the incoming frames which size is greater then MTU and truncate the outgoing oversized frames with size threshold 2048/16383. Thus the behaviour will get to be closer to what is already implemented for DW GMACs (except a more accurate Giant frame setting and one truncation threshold less of the Tx frames) and follow the networking subsystem recommendations. This shall significantly speed up the oversized frames handling. Note the driver currently just drops the frames with over Giant frame size so the describes recv truncation seems unnecessary. But it will be utilized in the NETIF_F_RXALL feature implementation. [1] Documentation/networking/netdevices.rst Fixes: 8a488c3f97cd ("net: stmmac: xgmac: Enable RX Jumbo frame support") Fixes: 2142754f8b9c ("net: stmmac: Add MAC related callbacks for XGMAC2") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h | 8 ++--- .../net/ethernet/stmicro/stmmac/dwxgmac2_core.c | 36 ++++++++++++++++++++-- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h index 6391e3d46c8d6..65d28dca41851 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h @@ -26,7 +26,6 @@ #define XGMAC_CONFIG_SARC GENMASK(22, 20) #define XGMAC_CONFIG_JD BIT(16) #define XGMAC_CONFIG_TE BIT(0) -#define XGMAC_CORE_INIT_TX (XGMAC_CONFIG_JD) #define XGMAC_RX_CONFIG 0x00000004 #define XGMAC_CONFIG_ARPEN BIT(31) #define XGMAC_CONFIG_GPSL GENMASK(29, 16) @@ -42,10 +41,6 @@ #define XGMAC_CONFIG_CST BIT(2) #define XGMAC_CONFIG_ACS BIT(1) #define XGMAC_CONFIG_RE BIT(0) -#define XGMAC_CORE_INIT_RX (XGMAC_CONFIG_GPSLCE | \ - XGMAC_CONFIG_WD | \ - FIELD_PREP(XGMAC_CONFIG_GPSL, \ - XGMAC_JUMBO_LEN)) #define XGMAC_PACKET_FILTER 0x00000008 #define XGMAC_FILTER_RA BIT(31) #define XGMAC_FILTER_IPFE BIT(20) @@ -55,6 +50,9 @@ #define XGMAC_FILTER_PM BIT(4) #define XGMAC_FILTER_HMC BIT(2) #define XGMAC_FILTER_PR BIT(0) +#define XGMAC_WDT 0x0000000c +#define XGMAC_WDT_PWE BIT(8) +#define XGMAC_WDT_WTO GENMASK(3, 0) #define XGMAC_HASH_TABLE(x) (0x00000010 + (x) * 4) #define XGMAC_MAX_HASH_TABLE 8 #define XGMAC_RXQ_CTRL0 0x000000a0 diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c index 13093b7defc58..c4458b622de97 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c @@ -18,13 +18,43 @@ static void dwxgmac2_core_init(struct mac_device_info *hw, struct net_device *dev) { void __iomem *ioaddr = hw->pcsr; - u32 tx, rx; + int mtu = dev->mtu; + u32 tx, rx, gps; tx = readl(ioaddr + XGMAC_TX_CONFIG); rx = readl(ioaddr + XGMAC_RX_CONFIG); - writel(tx | XGMAC_CORE_INIT_TX, ioaddr + XGMAC_TX_CONFIG); - writel(rx | XGMAC_CORE_INIT_RX, ioaddr + XGMAC_RX_CONFIG); + /* Frame length limits (giant status reported or dropped) */ + gps = ETH_HLEN + ETH_FCS_LEN; + if (mtu > 16357) { /* Rx <= 16375 (+C/SVLAN headers) && Tx <= 16383 */ + tx |= XGMAC_CONFIG_JD; + gps += 16357; + rx |= XGMAC_CONFIG_GPSLCE | FIELD_PREP(XGMAC_CONFIG_GPSL, gps); + } else if (mtu > 2022) { /* Rx <= MTU + Eth (+C/SVLAN headers) && Tx <= 16383 */ + tx |= XGMAC_CONFIG_JD; + gps += mtu; + rx |= XGMAC_CONFIG_GPSLCE | FIELD_PREP(XGMAC_CONFIG_GPSL, gps); + } else if (mtu > 1500) { /* Rx <= MTU + Eth (+C/SVLAN headers) && Tx <= 2048 */ + gps += mtu; + rx |= XGMAC_CONFIG_GPSLCE | FIELD_PREP(XGMAC_CONFIG_GPSL, gps); + } else { /* Rx <= 1500 + Eth (+C/SVLAN headers) && Tx <= 2048 */ + gps += 1500; + } + + writel(tx, ioaddr + XGMAC_TX_CONFIG); + writel(rx, ioaddr + XGMAC_RX_CONFIG); + + /* Over 2K, 3K, ..., 16K-1 frames will be truncated on Rx */ + gps = ALIGN(gps + 2 * VLAN_HLEN, SZ_1K); + if (gps >= SZ_2K) + gps = gps / SZ_1K - 2; + else + gps = 0; + + /* Over giant frame watchdog fine-tuning (available since v2.00a) */ + rx = FIELD_PREP(XGMAC_WDT_WTO, gps) | XGMAC_WDT_PWE; + writel(rx, ioaddr + XGMAC_WDT); + writel(XGMAC_INT_DEFAULT_EN, ioaddr + XGMAC_INT_EN); } -- cgit v1.2.3 From 2e99f6b0a728cf0ff2be3140352d24b0f7b3bc94 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 14 Aug 2025 17:01:31 +0300 Subject: net: stmmac: dwmac4: Detect and handle Jumbo frames Similarly to what has been relevant for DW XGMAC the driver currently always enable the Jumbo frames up to 16K bytes to receive and transmit for DW QoS Ether too (DW GMAC4). It's done by permanently setting the MAC_CONFIG.JD and MAC_CONFIG.JE flags. Basically it means the driver completely ignores the MTU settings permitting up to 16K outgoing frames and no greater than 9K incoming frames. It isn't right to disregard the MTU setting especially such asymmetrically since the MTU value might be an important parameter of an Ethernet network segment. Moreover based on what is said in [1] the most preferred way to support MTU would be to drop the oversized frames instead of passing them up to the networking core subsystem. It's also important to note that even though [1] says that the upper layer protocols must not pass a socket buffer (skb) to a device to transmit with more data than the MTU in fact it does at least in case of the pktgen module. The driver currently doesn't fulfill these requirements and passes/transfers the incoming/outcoming frames further ignoring the MTU value. Let's fix that by activating the Giant frame setting and by setting the internal watchdog up to drop the incoming frames which size is greater then MTU and truncate the outgoing 2048/16383-octets oversized frames. Thus the behaviour will match to what is already implemented for DW GMACs/XGMACs and follow the networking subsystem recommendations. This shall significantly speed up the oversized frames handling. Note the driver currently just drops the frames with over Giant frame size so the describes recv truncation seems unnecessary. But it will be utilized in the NETIF_F_RXALL feature implementation. Also note since this change the driver will support DW QoS Ether controller to send/receive frames up to MTU=16K. The net_device::max_mtu parameter will be accordingly fixed a bit later. [1] Documentation/networking/netdevices.rst Fixes: 41f2a3e6367e ("net: stmmac: dwmac4: Enable RX Jumbo frame support") Fixes: 477286b53f55 ("stmmac: add GMAC4 core support") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/dwmac4.h | 12 ++++++-- drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c | 37 +++++++++++++++++++++-- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4.h b/drivers/net/ethernet/stmicro/stmmac/dwmac4.h index e58194fd0c0bf..53ac45b50e22d 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4.h @@ -16,6 +16,7 @@ #define GMAC_CONFIG 0x00000000 #define GMAC_EXT_CONFIG 0x00000004 #define GMAC_PACKET_FILTER 0x00000008 +#define GMAC_WDT 0x0000000c #define GMAC_HASH_TAB(x) (0x10 + (x) * 4) #define GMAC_RX_FLOW_CTRL 0x00000090 #define GMAC_QX_TX_FLOW_CTRL(x) (0x70 + x * 4) @@ -77,6 +78,10 @@ #define GMAC_MAX_PERFECT_ADDRESSES 128 +/* MAC Watchdog timeout for received frames */ +#define GMAC_WDT_PWE BIT(8) +#define GMAC_WDT_WTO GENMASK(3, 0) + /* MAC RX Queue Enable */ #define GMAC_RX_QUEUE_CLEAR(queue) ~(GENMASK(1, 0) << ((queue) * 2)) #define GMAC_RX_AV_QUEUE_ENABLE(queue) BIT((queue) * 2) @@ -155,9 +160,11 @@ enum power_event { #define GMAC_CONFIG_SARC GENMASK(30, 28) #define GMAC_CONFIG_IPC BIT(27) #define GMAC_CONFIG_IPG GENMASK(26, 24) +#define GMAC_CONFIG_GPSLCE BIT(23) #define GMAC_CONFIG_2K BIT(22) #define GMAC_CONFIG_CST BIT(21) #define GMAC_CONFIG_ACS BIT(20) +#define GMAC_CONFIG_WD BIT(19) #define GMAC_CONFIG_BE BIT(18) #define GMAC_CONFIG_JD BIT(17) #define GMAC_CONFIG_JE BIT(16) @@ -174,6 +181,7 @@ enum power_event { #define GMAC_CONFIG_EIPG_EN BIT(24) #define GMAC_CONFIG_HDSMS GENMASK(22, 20) #define GMAC_CONFIG_HDSMS_256 FIELD_PREP_CONST(GMAC_CONFIG_HDSMS, 0x2) +#define GMAC_CONFIG_GPSL GENMASK(13, 0) /* MAC HW features0 bitmap */ #define GMAC_HW_FEAT_SAVLANINS BIT(27) @@ -472,9 +480,7 @@ static inline u32 mtl_low_credx_base_addr(const struct dwmac4_addrs *addrs, #define MTL_INT_DEFAULT_ENABLE (MTL_RX_OVERFLOW_INT | MTL_TX_UNDERFLOW_INT) /* Default operating mode of the MAC */ -#define GMAC_CORE_INIT (GMAC_CONFIG_JD | GMAC_CONFIG_PS | \ - GMAC_CONFIG_BE | GMAC_CONFIG_DCRS | \ - GMAC_CONFIG_JE) +#define GMAC_CORE_INIT (GMAC_CONFIG_PS | GMAC_CONFIG_BE | GMAC_CONFIG_DCRS) /* To dump the core regs excluding the Address Registers */ #define GMAC_REG_NUM 132 diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c index 9f53a79aa70cb..4d07f421e7afb 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c @@ -37,10 +37,43 @@ static void dwmac4_core_init(struct mac_device_info *hw, struct stmmac_priv *priv = netdev_priv(dev); void __iomem *ioaddr = hw->pcsr; unsigned long clk_rate; - u32 value; + int mtu = dev->mtu; + u32 value, gps; value = readl(ioaddr + GMAC_CONFIG); - writel(value | GMAC_CORE_INIT, ioaddr + GMAC_CONFIG); + value |= GMAC_CORE_INIT; + + /* Frame length limits (giant status reported or dropped) */ + gps = ETH_HLEN + ETH_FCS_LEN; + if (mtu > 16357) { /* Rx <= 16375 (+C/SVLAN headers) && Tx <= 16383 */ + gps += 16357; + value |= GMAC_CONFIG_GPSLCE | GMAC_CONFIG_JD; + } else if (mtu > 2022) { /* Rx <= MTU + Eth (+C/SVLAN headers) && Tx <= 16383 */ + gps += mtu; + value |= GMAC_CONFIG_GPSLCE | GMAC_CONFIG_JD; + } else if (mtu > 1500) { /* Rx <= MTU + Eth (+C/SVLAN headers) && Tx <= 2048 */ + gps += mtu; + value |= GMAC_CONFIG_GPSLCE; + } else { /* Rx <= 1500 + Eth (+C/SVLAN headers) && Tx <= 2048 */ + gps = 1500 + ETH_HLEN + ETH_FCS_LEN; + } + + writel(value, ioaddr + GMAC_CONFIG); + + value = readl(ioaddr + GMAC_EXT_CONFIG); + value |= FIELD_PREP(GMAC_CONFIG_GPSL, gps); + writel(value, ioaddr + GMAC_EXT_CONFIG); + + /* Over 2K, 3K, ..., 16K-1 frames will be truncated on Rx */ + gps = ALIGN(gps + 2 * VLAN_HLEN, SZ_1K); + if (gps >= SZ_2K) + gps = gps / SZ_1K - 2; + else + gps = 0; + + /* Over giant frame watchdog fine-tuning */ + value = FIELD_PREP(GMAC_WDT_WTO, gps) | GMAC_WDT_PWE; + writel(value, ioaddr + GMAC_WDT); /* Configure LPI 1us counter to number of CSR clock ticks in 1us - 1 */ clk_rate = clk_get_rate(priv->plat->stmmac_clk); -- cgit v1.2.3 From 2982a90e35449096b71dfc95f145cc011070d249 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 30 Apr 2025 22:45:13 +0300 Subject: dt-bindings: net: dwmac: Add system interface data bus width property DW *MAC IP-cores can be synthesized with various system interface data bus widths. The bus width normally depends on the SoC system bus capability and is selected by the hardware engineers in a way to deliver the best interface throughput. In accordance with the IP-cores databooks the system bus width is fixed to a single value for all system bus transfers. For instance if it's set to 16 bytes (data bus width of 128 bits) then each DMA bus transfer size will be a multiple of 16 bytes and can't be smaller than that value (that's where the Rx DMA buffer alignment restriction comes from). In addition to that aside with the system interface performance the bus width determines the DW MAC Programmable Burst Length upper limit and Rx descriptors buffer size alignment. Since the system bus width is selected during DW *MAC IP-core synthesize procedure, is specific to each controller and can't be detected on runtime let's add a new property "snps,data-width" to the DW MAC DT-bindings file. The property needs to be placed in the root DW MAC DT-nodes because no matter which system bus interface type has been selected during the IP-core synthesize (AXI, AHB, MTL), the parameter mainly determines the internal MTL core data bus width. The external system/application bus data width is then fixed with that value. Note the system bus width can be of 32, 64 and 128-bits for DW GMAC and DW QoS Ether, while DW xGMAC can have only 64 and 128 bits wide system data bus. Signed-off-by: Serge Semin --- Documentation/devicetree/bindings/net/snps,dwmac.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Documentation/devicetree/bindings/net/snps,dwmac.yaml b/Documentation/devicetree/bindings/net/snps,dwmac.yaml index 38bc34dc4f09b..ae73c7bcf82c5 100644 --- a/Documentation/devicetree/bindings/net/snps,dwmac.yaml +++ b/Documentation/devicetree/bindings/net/snps,dwmac.yaml @@ -546,6 +546,13 @@ properties: Enables the TSO feature otherwise it will be managed by MAC HW capability register. + snps,data-width: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + System data bus width is limited with 8 and 16 bytes for DW + xGMAC and with 4, 8 and 16 for DW GMACs of recent versions. + enum: [4, 8, 16] + mdio: $ref: mdio.yaml# unevaluatedProperties: false -- cgit v1.2.3 From de1c106ece101ececceaef06b13a848283309cdb Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 30 Apr 2025 23:05:28 +0300 Subject: net: stmmac: Add system interface data bus width property support DW *MAC IP-cores can be synthesized with various system interface data bus widths. For instance DW GMACs and DW QoS Ether can work with buses of 32, 64 and 128 bits width, while DW xGMACs can be created with system bus of 64 and 128 bits width. The bus width value affects several very important parameters like DMA transfers granularity and performance, DMA maximum burst length, Rx DMA buffers alignment. So in order to be able to tune and verify these parameters let's add the data bus width device property support. It can be passed either by the platform code or via the DT-node property. For some reason HW capability register doesn't contain that parameter in its fields. Note if the data bus width isn't specified then the driver will use the default value of 16 bytes. That will apply the strongest constraint on the PBL values and Rx DMA buffers alignment (the later one is actually currently hard-coded anyway). In order to get a better HW performance it's preferable to specify the actual value for a particular version of the controller. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 8 ++++++++ drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c | 6 ++++++ include/linux/stmmac.h | 1 + 3 files changed, 15 insertions(+) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index a34aebb775b45..7cacc3aa6b39d 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -7943,6 +7943,14 @@ static int stmmac_hw_init(struct stmmac_priv *priv) dev_info(priv->device, "No HW DMA feature register supported\n"); } + /* Set the data bus width of the system interface to 16 bytes by + * default. It will apply the strongest constraint on the Tx FIFO size + * and the Rx DMA buffers alignment, but at least we'll be on a safe + * side from possible HW UBs. + */ + if (!priv->plat->data_width) + priv->plat->data_width = 16; + if (priv->plat->rx_coe) dev_info(priv->device, "RX Checksum Offload Engine supported\n"); diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c index 617fedff7ef9c..03f1498eca3b1 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c @@ -496,6 +496,12 @@ stmmac_probe_config_dt(struct platform_device *pdev, u8 *mac) of_property_read_u32(np, "rx-fifo-depth", &plat->rx_fifo_size); + /* Default to 16 bytes data bus width to be on a safe side at the + * PBL upper limit and the Rx DMA buffer alignment calculation. + */ + if (of_property_read_u32(np, "snps,data-width", &plat->data_width)) + plat->data_width = 16; + plat->force_sf_dma_mode = of_property_read_bool(np, "snps,force_sf_dma_mode"); diff --git a/include/linux/stmmac.h b/include/linux/stmmac.h index 934a536a3a0f3..d9c9ef8a933f5 100644 --- a/include/linux/stmmac.h +++ b/include/linux/stmmac.h @@ -241,6 +241,7 @@ struct plat_stmmacenet_data { int unicast_filter_entries; int tx_fifo_size; int rx_fifo_size; + u32 data_width; u32 host_dma_width; u32 rx_queues_to_use; u32 tx_queues_to_use; -- cgit v1.2.3 From e43ea4fb081a9595a2dd4a7c708637a87f61d2b1 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 25 Aug 2025 20:30:10 +0300 Subject: net: stmmac: Fix improper max MTU value setup Currently the driver initialize the maximum MTU value as follows: 1. DW XGMAC/XLGMAC: 16KiB 2. DW GMAC with Enhanced DMA-descriptors and DW QoS Ether: 9000 bytes 3. DW MAX100 and DW GMAC with Normal DMA-descriptors: PAGE_SIZE. This is wrong in the aspects 2. and 3. Here is why. First of all DW QoS Ether have always supported Tx and Rx up to 16KiB frames size in the same way as DW XGMAC-like devices. The latest Jumbo-related commit has enabled that in the driver too. Secondly no matter what type of the DMA-descriptor selected DMA GMACs have always supported up to 9K frames at least in the cross-descriptor manner. Thirdly the legacy DW MAC100 controllers have never been able to xmit/recv frames greater than 1500 bytes. There is no any Jumbo-like setup in the MAC config registers. It has been even more wrong to use SKB_MAX_HEAD() macro to calculate max MTU constraint since it's basically converted to PAGE_SIZE which is platform-specific, can be even greater than 16KiB and doesn't determine the actual MTU constraint applicable for the particular DW network controller. Finally the MTU verification procedure isn't complete in the net_device:ndo_change_mtu() method. The upper-limit is specific to the DW XGMACs only (and actually redundant). Moreover the Tx FIFO size-based constraint is also incorrect - it isn't enough to set up the upper MTU-limit based on the Tx-FIFO size (but it will be fixed later). Let's fix all of the inconsistencies above by activating the 16KiB Jumbos for DW QoS Ethernet, setting up 9000 max MTU for any DW GMAC, getting back the normal max MTU for the legacy controllers and dropping the manual max MTU verification in the net_device:ndo_change_mtu() method. Fixes: eaf4fac47807 ("net: stmmac: Do not accept invalid MTU values") Fixes: f748be531d70 ("stmmac: support new GMAC4") Fixes: 45db81e1590c ("stmmac: limit max_mtu in case of 4KiB and use __netdev_alloc_skb (V2)") Fixes: 48febf7e6476 ("stmmac: allow mtu bigger than 1500 in case of normal desc (V4)") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/common.h | 7 ++- drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h | 3 -- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 61 +++++++++++++++-------- 3 files changed, 46 insertions(+), 25 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/common.h b/drivers/net/ethernet/stmicro/stmmac/common.h index 95faae938b046..7b86414006d18 100644 --- a/drivers/net/ethernet/stmicro/stmmac/common.h +++ b/drivers/net/ethernet/stmicro/stmmac/common.h @@ -575,7 +575,12 @@ void stmmac_axi_blen_to_mask(u32 *regval, const u32 *blen, size_t len); #define STMMAC_CHAIN_MODE 0x1 #define STMMAC_RING_MODE 0x2 -#define JUMBO_LEN 9000 +/* MAC MTU constraint */ +#define STMMAC_MTU_GIANT (SZ_16K - ETH_HLEN - 2*VLAN_HLEN - ETH_FCS_LEN - 1) +#define STMMAC_MTU_JUMBO 9000 +#define STMMAC_MTU_NORMAL ETH_DATA_LEN +#define XGMAC_JUMBO_LEN STMMAC_MTU_GIANT +#define JUMBO_LEN STMMAC_MTU_JUMBO /* Receive Side Scaling */ #define STMMAC_RSS_HASH_KEY_SIZE 40 diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h index 65d28dca41851..1798289ef6ab5 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h @@ -9,9 +9,6 @@ #include "common.h" -/* Misc */ -#define XGMAC_JUMBO_LEN 16368 - /* MAC Registers */ #define XGMAC_TX_CONFIG 0x00000000 #define XGMAC_CONFIG_SS_OFF 29 diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 7cacc3aa6b39d..94b05994fe53c 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -1551,6 +1551,43 @@ static void stmmac_display_rings(struct stmmac_priv *priv, stmmac_display_tx_rings(priv, dma_conf); } +/** + * stmmac_get_min_mtu - retrieve minimal supported MTU + * @priv: driver private structure + * Return: minimal MTU. + */ +static unsigned int stmmac_get_min_mtu(struct stmmac_priv *priv) +{ + return ETH_ZLEN - ETH_HLEN; +} + +/** + * stmmac_get_max_mtu - retrieve maximal supported MTU + * @priv: driver private structure + * Description: calculate the maximum MTU supported by DW controller in + * accordance with the device active features. + * Return: maximum MTU. + */ +static unsigned int stmmac_get_max_mtu(struct stmmac_priv *priv) +{ + unsigned int max_mtu; + + /* MTU constraint caused by the MAC implementation */ + if (dwmac_is_xmac(priv->plat->core_type)) + max_mtu = STMMAC_MTU_GIANT; + else if (priv->plat->core_type == DWMAC_CORE_GMAC) + max_mtu = STMMAC_MTU_JUMBO; + else + max_mtu = STMMAC_MTU_NORMAL; + + /* Override only with valid platform-specific max MTU constraint */ + if (priv->plat->maxmtu >= stmmac_get_min_mtu(priv) && + priv->plat->maxmtu < max_mtu) + max_mtu = priv->plat->maxmtu; + + return max_mtu; +} + /** * stmmac_fs_offset - find out platform-specific frame offset * Description: retrieve Rx DMA-buffer offset to align up the incoming @@ -6260,8 +6297,7 @@ static int stmmac_change_mtu(struct net_device *dev, int new_mtu) return -EINVAL; } - /* If condition true, FIFO is too small or MTU too large */ - if ((txfifosz < new_mtu) || (new_mtu > XGMAC_JUMBO_LEN)) + if (new_mtu > txfifosz) return -EINVAL; if (netif_running(dev)) { @@ -8510,25 +8546,8 @@ static int __stmmac_dvr_probe(struct device *device, ndev->vlan_features |= ndev->features; - /* MTU range: 46 - hw-specific max */ - ndev->min_mtu = ETH_ZLEN - ETH_HLEN; - - if (priv->plat->core_type == DWMAC_CORE_XGMAC) - ndev->max_mtu = XGMAC_JUMBO_LEN; - else if (priv->plat->enh_desc || priv->synopsys_id >= DWMAC_CORE_4_00) - ndev->max_mtu = JUMBO_LEN; - else - ndev->max_mtu = SKB_MAX_HEAD(NET_SKB_PAD + NET_IP_ALIGN); - - /* Warn if the platform's maxmtu is smaller than the minimum MTU, - * otherwise clamp the maximum MTU above to the platform's maxmtu. - */ - if (priv->plat->maxmtu < ndev->min_mtu) - dev_warn(priv->device, - "%s: warning: maxmtu having invalid value (%d)\n", - __func__, priv->plat->maxmtu); - else if (priv->plat->maxmtu < ndev->max_mtu) - ndev->max_mtu = priv->plat->maxmtu; + ndev->min_mtu = stmmac_get_min_mtu(priv); + ndev->max_mtu = stmmac_get_max_mtu(priv); ndev->priv_flags |= IFF_LIVE_ADDR_CHANGE; -- cgit v1.2.3 From 26d5628d45bac9aab4fcd82515f0a4a549b0a4d9 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 27 Aug 2025 11:00:50 +0300 Subject: net: stmmac: Move FIFO size calc to dedicated method As a preparation to the PBL and MTU constraint implementation let's introduce a dedicated method which would just calculate the Tx/Rx FIFO sizes based on the currently active queues settings. Thus the driver would have the duplicated code of the FIFO depth calculation moved to one coherent method and be prepare for the announced fixes. Note there is no point in checking whether there is a FIFO size passed via the platform configs storage each time the Tx/Rx FIFO size values are required, since neither the FIFO size platform setting nor the FIFO size DMA-capability get changed after the corresponding devices are probed and added to the system. Thus the total Tx/Rx FIFO size can be set just once on the device probe (HW-initialization) procedure: if non-zero platform FIFO size is detected then the FIFO size DMA-capability will be ignored. Also note this change fixes the Loongson GMAC/GNET device support which the only DW GMAC v3.70a device currently declared to support the multi-channel feature. The commit ad72f783de06 ("net: stmmac: Add multi-channel support") has forgotten to fix stmmac_change_mtu() so the method wouldn't divide the MTL FIFO memory between the queues/channels. The multi-channels DW GMACs have the per-channel non-configurable MTL FIFO memory. So the stmmac_change_mtu() has been wrong in unconditionally dividing the total MTL FIFO memory and using the resultant as the upper limit of MTU. Fixes: ad72f783de06 ("net: stmmac: Add multi-channel support") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 89 +++++++++++++--------- .../net/ethernet/stmicro/stmmac/stmmac_selftests.c | 6 +- 2 files changed, 52 insertions(+), 43 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 94b05994fe53c..f99b189ab571a 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -1551,6 +1551,44 @@ static void stmmac_display_rings(struct stmmac_priv *priv, stmmac_display_tx_rings(priv, dma_conf); } +/** + * stmmac_get_txfifosz - retrieve Tx MTL FIFO size + * @priv: driver private structure + * Description: calculate the Tx MTL FIFO depth in accordance with + * the amount of the activated queues. + * Return: current Tx FIFO depth. + */ +static unsigned int stmmac_get_txfifosz(struct stmmac_priv *priv) +{ + + unsigned int txfifosz = priv->plat->tx_fifo_size; + + /* Tx FIFO is shared between queues on the modern devices */ + if (dwmac_is_xmac(priv->plat->core_type)) + txfifosz /= priv->plat->tx_queues_to_use; + + return txfifosz; +} + +/** + * stmmac_get_rxfifosz - retrieve Rx MTL FIFO size + * @priv: driver private structure + * Description: calculate the Rx MTL FIFO depth in accordance with + * the amount of the activated queues. + * Return: current Rx FIFO depth. + */ +static unsigned int stmmac_get_rxfifosz(struct stmmac_priv *priv) +{ + + unsigned int rxfifosz = priv->plat->rx_fifo_size; + + /* Rx FIFO is shared between queues on the modern devices */ + if (dwmac_is_xmac(priv->plat->core_type)) + rxfifosz /= priv->plat->rx_queues_to_use; + + return rxfifosz; +} + /** * stmmac_get_min_mtu - retrieve minimal supported MTU * @priv: driver private structure @@ -2705,24 +2743,13 @@ static void stmmac_dma_operation_mode(struct stmmac_priv *priv) { u32 rx_channels_count = priv->plat->rx_queues_to_use; u32 tx_channels_count = priv->plat->tx_queues_to_use; - int rxfifosz = priv->plat->rx_fifo_size; - int txfifosz = priv->plat->tx_fifo_size; + int txfifosz = stmmac_get_txfifosz(priv); + int rxfifosz = stmmac_get_rxfifosz(priv); u32 txmode = 0; u32 rxmode = 0; u32 chan = 0; u8 qmode = 0; - if (rxfifosz == 0) - rxfifosz = priv->dma_cap.rx_fifo_size; - if (txfifosz == 0) - txfifosz = priv->dma_cap.tx_fifo_size; - - /* Split up the shared Tx/Rx FIFO memory on DW QoS Eth and DW XGMAC */ - if (dwmac_is_xmac(priv->plat->core_type)) { - rxfifosz /= rx_channels_count; - txfifosz /= tx_channels_count; - } - if (priv->plat->force_thresh_dma_mode) { txmode = ttc; rxmode = rtc; @@ -6283,15 +6310,10 @@ static void stmmac_set_rx_mode(struct net_device *dev) static int stmmac_change_mtu(struct net_device *dev, int new_mtu) { struct stmmac_priv *priv = netdev_priv(dev); - int txfifosz = priv->plat->tx_fifo_size; + int txfifosz = stmmac_get_txfifosz(priv); struct stmmac_dma_conf *dma_conf; int ret; - if (txfifosz == 0) - txfifosz = priv->dma_cap.tx_fifo_size; - - txfifosz /= priv->plat->tx_queues_to_use; - if (stmmac_xdp_is_enabled(priv) && new_mtu > ETH_DATA_LEN) { netdev_dbg(priv->dev, "Jumbo frames not supported for XDP\n"); return -EINVAL; @@ -6371,13 +6393,7 @@ static int stmmac_set_features(struct net_device *netdev, for (chan = 0; chan < priv->plat->rx_queues_to_use; chan++) { struct stmmac_rx_queue *rx_q = &priv->dma_conf.rx_queue[chan]; u8 rxqmode = priv->plat->rx_queues_cfg[chan].mode_to_use; - int rxfifosz = priv->plat->rx_fifo_size; - - if (rxfifosz == 0) - rxfifosz = priv->dma_cap.rx_fifo_size; - - if (dwmac_is_xmac(priv->plat->core_type)) - rxfifosz /= priv->plat->rx_queues_to_use; + int rxfifosz = stmmac_get_rxfifosz(priv); stmmac_dma_rx_mode(priv, priv->ioaddr, rx_q->rtc, chan, rxfifosz, rxqmode, features & NETIF_F_RXALL); @@ -7711,8 +7727,8 @@ static void stmmac_reset_tx_subtask(struct stmmac_priv *priv, u32 queue) */ static void stmmac_drop_rtc_subtask(struct stmmac_priv *priv, u32 queue) { - int rxfifosz = priv->plat->rx_fifo_size; struct stmmac_rx_queue *rx_q; + int rxfifosz; u8 rxqmode; rx_q = &priv->dma_conf.rx_queue[queue]; @@ -7723,11 +7739,7 @@ static void stmmac_drop_rtc_subtask(struct stmmac_priv *priv, u32 queue) return; } - if (rxfifosz == 0) - rxfifosz = priv->dma_cap.rx_fifo_size; - - if (dwmac_is_xmac(priv->plat->core_type)) - rxfifosz /= priv->plat->rx_queues_to_use; + rxfifosz = stmmac_get_rxfifosz(priv); /* Lower limits: DW GMAC/GMAC4 - 32, DW XGMAC/XLGMAC - 64 */ if (rx_q->rtc <= 32) { @@ -7761,9 +7773,9 @@ static void stmmac_drop_rtc_subtask(struct stmmac_priv *priv, u32 queue) */ static void stmmac_bump_ttc_subtask(struct stmmac_priv *priv, u32 queue) { - int txfifosz = priv->plat->tx_fifo_size; struct stmmac_tx_queue *tx_q; u8 txqmode, addend; + int txfifosz; tx_q = &priv->dma_conf.tx_queue[queue]; @@ -7773,11 +7785,7 @@ static void stmmac_bump_ttc_subtask(struct stmmac_priv *priv, u32 queue) return; } - if (txfifosz == 0) - txfifosz = priv->dma_cap.tx_fifo_size; - - if (dwmac_is_xmac(priv->plat->core_type)) - txfifosz /= priv->plat->tx_queues_to_use; + txfifosz = stmmac_get_txfifosz(priv); /* 64-bytes step threshold: DW GMAC - 64, DW QoS/XGMAC/etc - 128. * 128-bytes step threshold: DW QoS/XGMAC/etc - 256. @@ -7962,6 +7970,11 @@ static int stmmac_hw_init(struct stmmac_priv *priv) ilog2(priv->hw->multicast_filter_bins); } + if (!priv->plat->rx_fifo_size) + priv->plat->rx_fifo_size = priv->dma_cap.rx_fifo_size; + if (!priv->plat->tx_fifo_size) + priv->plat->tx_fifo_size = priv->dma_cap.tx_fifo_size; + /* TXCOE doesn't work in thresh DMA mode */ if (priv->plat->force_thresh_dma_mode) priv->plat->tx_coe = 0; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c index e50097d64abc5..c7be1559fc3e2 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c @@ -757,11 +757,7 @@ static int stmmac_test_flowctrl(struct stmmac_priv *priv) dev_add_pack(&tpriv->pt); /* Compute minimum number of packets to make FIFO full */ - pkt_count = priv->plat->rx_fifo_size; - if (!pkt_count) - pkt_count = priv->dma_cap.rx_fifo_size; - pkt_count /= 1400; - pkt_count *= 2; + pkt_count = priv->plat->rx_fifo_size / 1400 * 2; for (i = 0; i < rx_cnt; i++) stmmac_stop_rx(priv, priv->ioaddr, i); -- cgit v1.2.3 From 7f4824d2c5cec98097720165b996936234233a38 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Tue, 26 Aug 2025 19:34:33 +0300 Subject: net: stmmac: Make sure PBLs are valid In accordance with [1,2,3] the PBL (Programmable Burst Length) values are only valid if they belong to the set [1, 2, 4, 8, 16, 32]. Specifying any different value results to Undefined Behaviour. Moreover the maximum value is also limited. The maximum burst length must not exceed the half of the Tx/Rx Queue Depth (Queue Depth = Queue or FIFO size / Data width). Without this requirement being fulfilled the communications will be fully broken. Since the system interface data bus width is now available in the driver private data let's use it to calculate the effective MTL FIFO depth and make sure that the DMA PBL specified by the platform is correct at least for a single activated queue in order to avoid hardware UB-related errors. The best place to do that is to introduce a new stmmac_dma_verify() method, which would be called on the driver probing stage. It will ensure that the PBL is valid at least for the default device setup. Besides the method will contain the rest of the DMA settings verification and adjustment so to be as coherent as possible. [1] DesignWare Cores Ethernet MAC Universal Databook, Revision 3.73a, October 2013, p. 380, Table 6-5. [2] DesignWare Cores Ethernet Quality-of-Service Databook, Revision 5.20a, April 2020, p. 1185, Table 17-303 and p. 1190, Table 17-304. [3] DesignWare Cores XGMAC - 10G Ethernet MAC Databook, Revision 2.11a, September 2015, p. 471, Table 7-17 and p. 474, Table 7-18. Fixes: d6ddfacd95c7 ("net: stmmac: Add DMA related callbacks for XGMAC2") Fixes: f748be531d70 ("stmmac: support new GMAC4") Fixes: 47dd7a540b8a ("net: add support for STMicroelectronics Ethernet controllers.") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac.h | 2 + .../net/ethernet/stmicro/stmmac/stmmac_ethtool.c | 10 +- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 167 ++++++++++++++++++--- 3 files changed, 151 insertions(+), 28 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h index 1e8ae9b17ca3d..cd0531b522579 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h @@ -266,10 +266,12 @@ struct stmmac_dma_conf { unsigned int dma_buf_sz; /* RX Queue */ + unsigned int rx_queue_max; struct stmmac_rx_queue rx_queue[MTL_MAX_RX_QUEUES]; unsigned int dma_rx_size; /* TX Queue */ + unsigned int tx_queue_max; struct stmmac_tx_queue tx_queue[MTL_MAX_TX_QUEUES]; unsigned int dma_tx_size; }; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c index 152c2657da6a7..058b83584e30e 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c @@ -1003,18 +1003,14 @@ static void stmmac_get_channels(struct net_device *dev, chan->rx_count = priv->plat->rx_queues_to_use; chan->tx_count = priv->plat->tx_queues_to_use; - chan->max_rx = priv->dma_cap.number_rx_queues; - chan->max_tx = priv->dma_cap.number_tx_queues; + chan->max_rx = priv->dma_conf.rx_queue_max; + chan->max_tx = priv->dma_conf.tx_queue_max; } static int stmmac_set_channels(struct net_device *dev, struct ethtool_channels *chan) { - struct stmmac_priv *priv = netdev_priv(dev); - - if (chan->rx_count > priv->dma_cap.number_rx_queues || - chan->tx_count > priv->dma_cap.number_tx_queues || - !chan->rx_count || !chan->tx_count) + if (!chan->rx_count || !chan->tx_count) return -EINVAL; return stmmac_reinit_queues(dev, chan->rx_count, chan->tx_count); diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index f99b189ab571a..7fbaa490149b7 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -3391,14 +3391,6 @@ static int stmmac_init_dma_engine(struct stmmac_priv *priv) u32 chan = 0; int ret = 0; - if (!priv->plat->dma_cfg || !priv->plat->dma_cfg->pbl) { - netdev_err(priv->dev, "Invalid DMA configuration\n"); - return -EINVAL; - } - - if (priv->extend_desc) - priv->plat->dma_cfg->atds = 1; - ret = stmmac_prereset_configure(priv); if (ret) return ret; @@ -4142,18 +4134,11 @@ stmmac_setup_dma_desc(struct stmmac_priv *priv, unsigned int mtu) } dma_conf->dma_buf_sz = stmmac_get_bfsize(priv, mtu); - - /* Chose the tx/rx size from the already defined one in the - * priv struct. (if defined) - */ + dma_conf->tx_queue_max = priv->dma_conf.tx_queue_max; dma_conf->dma_tx_size = priv->dma_conf.dma_tx_size; + dma_conf->rx_queue_max = priv->dma_conf.rx_queue_max; dma_conf->dma_rx_size = priv->dma_conf.dma_rx_size; - if (!dma_conf->dma_tx_size) - dma_conf->dma_tx_size = DMA_DEFAULT_TX_SIZE; - if (!dma_conf->dma_rx_size) - dma_conf->dma_rx_size = DMA_DEFAULT_RX_SIZE; - /* Earlier check for TBS */ for (chan = 0; chan < priv->plat->tx_queues_to_use; chan++) { struct stmmac_tx_queue *tx_q = &dma_conf->tx_queue[chan]; @@ -8079,6 +8064,146 @@ static int stmmac_hw_init(struct stmmac_priv *priv) return 0; } +/** + * stmmac_pbl_adjust - DMA PBL adjust and verify. + * @pbl: DMA PBL to verify + * @factor: DMA PBL x8/x4 factor + * @fifodp: MTL FIFO depth + * Description: adjust and verify the passed DMA PBL in accordance with the + * HW requirements. + * Return : 0 on success and errno if invalid PBL specified. + */ +static int stmmac_pbl_adjust(unsigned int *pbl, unsigned int factor, + unsigned int fifodp) +{ + const unsigned int min_pbl = 1, max_pbl = 32; + + /* Make sure PBL is from the set [1, 2, 4, 8, 16, 32] otherwise UB */ + *pbl = rounddown_pow_of_two(*pbl); + if (*pbl < min_pbl || *pbl > max_pbl) + return -EINVAL; + + /* Skip legacy platforms with no Rx/Tx MTL FIFO depth data */ + if (!fifodp) + return 0; + + /* Make sure PBL is within the allowed range - less or equal to half + * the Queue depth (MTL FIFO size in terms of DMA beats). + */ + if (*pbl * factor > fifodp / 2) + return -EINVAL; + + return 0; +} + +/** + * stmmac_dma_verify - verify the DMA settings + * @priv: driver private structure + * Description: make sure the platform-specific DMA-config is valid. If it's + * not then either adjust it in some extent or return an error. + * Return: 0 on success or errno if invalid DMA-config detected. + */ +static int stmmac_dma_verify(struct stmmac_priv *priv) +{ + struct stmmac_dma_cfg *dma_cfg = priv->plat->dma_cfg; + unsigned int factor, rxfifodp, txfifodp; + unsigned int rxmaxq, txmaxq; + int ret; + + /* It's mandatory to have the DMA-config specified */ + if (!dma_cfg) { + dev_err(priv->device, "Missing DMA configuration\n"); + return -EINVAL; + } + + /* Make sure at least 1-beat PBL is setup */ + if (!dma_cfg->pbl) { + dev_warn(priv->device, "No PBL specified, init with minimal 1\n"); + dma_cfg->pbl = 1; + } + + /* Activate extended DMA descriptors if requested */ + if (priv->extend_desc) + priv->plat->dma_cfg->atds = 1; + + /* Only DW QoS Eth v5.20 onwards IPs support DMA-desc prefetch */ + if (priv->synopsys_id < DWMAC_CORE_5_20) + priv->plat->dma_cfg->dche = false; + + /* DMA PBL multiplier had been x4 prior DW GMAC v3.50a */ + if ((priv->plat->core_type == DWMAC_CORE_GMAC && + priv->synopsys_id >= DWMAC_CORE_3_50) || + dwmac_is_xmac(priv->plat->core_type)) { + factor = dma_cfg->pblx8 ? 8 : 1; + } else { + factor = dma_cfg->pblx8 ? 4 : 1; + } + + /* Make sure DMA PBLs are valid and don't exceed HW constraints at + * least for the initially activated queues. + */ + rxfifodp = stmmac_get_rxfifosz(priv) / priv->plat->data_width; + txfifodp = stmmac_get_txfifosz(priv) / priv->plat->data_width; + ret = stmmac_pbl_adjust(&dma_cfg->pbl, factor, min(rxfifodp, txfifodp)); + if (ret) { + dev_err(priv->device, "Invalid DMA PBL specified\n"); + return ret; + } + + if (dma_cfg->txpbl) { + ret = stmmac_pbl_adjust(&dma_cfg->txpbl, factor, txfifodp); + if (ret) { + dev_err(priv->device, "Invalid Tx DMA PBL specified\n"); + return ret; + } + } + + if (dma_cfg->rxpbl) { + ret = stmmac_pbl_adjust(&dma_cfg->rxpbl, factor, rxfifodp); + if (ret) { + dev_err(priv->device, "Invalid Rx DMA PBL specified\n"); + return ret; + } + } + + /* Make sure the amount of the active queues never cause the PBL + * exceeding the half FIFO depth constraint. + */ + priv->dma_conf.rx_queue_max = priv->dma_cap.number_rx_queues; + priv->dma_conf.tx_queue_max = priv->dma_cap.number_tx_queues; + if (dwmac_is_xmac(priv->plat->core_type)) { + rxfifodp = priv->plat->rx_fifo_size / priv->plat->data_width / 2; + rxmaxq = rxfifodp / ((dma_cfg->rxpbl ?: dma_cfg->pbl) * factor); + txfifodp = priv->plat->tx_fifo_size / priv->plat->data_width / 2; + txmaxq = txfifodp / ((dma_cfg->txpbl ?: dma_cfg->pbl) * factor); + } else { + rxmaxq = priv->plat->rx_queues_to_use; + txmaxq = priv->plat->tx_queues_to_use; + } + + if (priv->dma_conf.rx_queue_max > rxmaxq) { + priv->dma_conf.rx_queue_max = rxmaxq; + dev_warn(priv->device, "Reduce Rx queues max to %u due to PBL\n", + rxmaxq); + } else if (!priv->dma_conf.rx_queue_max) { + priv->dma_conf.rx_queue_max = rxmaxq; + } + + if (priv->dma_conf.tx_queue_max > txmaxq) { + priv->dma_conf.tx_queue_max = txmaxq; + dev_warn(priv->device, "Reduce Tx queues max to %u due to PBL\n", + txmaxq); + } else if (!priv->dma_conf.tx_queue_max) { + priv->dma_conf.tx_queue_max = txmaxq; + } + + /* Initialize the default Tx/Rx DMA ring size */ + priv->dma_conf.dma_tx_size = DMA_DEFAULT_TX_SIZE; + priv->dma_conf.dma_rx_size = DMA_DEFAULT_RX_SIZE; + + return 0; +} + static void stmmac_napi_add(struct net_device *dev) { struct stmmac_priv *priv = netdev_priv(dev); @@ -8443,10 +8568,10 @@ static int __stmmac_dvr_probe(struct device *device, if (ret) goto error_hw_init; - /* Only DWMAC core version 5.20 onwards supports HW descriptor prefetch. - */ - if (priv->synopsys_id < DWMAC_CORE_5_20) - priv->plat->dma_cfg->dche = false; + /* Verify DMA config */ + ret = stmmac_dma_verify(priv); + if (ret) + goto error_hw_init; stmmac_check_ether_addr(priv); -- cgit v1.2.3 From 271532680957849a92548f0fb4a57487a5dc6051 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 29 Aug 2025 00:23:23 +0300 Subject: net: stmmac: Fix xmit frames corruption due to out of bounds MTU Tx/Rx COE and TSO features apply additional constraint to DMA-mode and MTU. First of all DMA Store-and-forward mode implies that the frames length mustn't exceed the Tx/Rx FIFO depth. Secondly all DW GMAC, DW QoS Ether and DW XGMAC/XLGMAC demands to apply additional constraint on the Tx frames size if Tx COE is active [1, 2, 3]: "You must make sure that the Transmit FIFO is deep enough to store a complete frame before that frame is transferred to the MAC transmitter. The reason being that when space is not available to accept the programmed burst length of data, then the MTL Tx FIFO starts reading to avoid dead-lock. When reading starts, the COE fails and consequently all succeeding frames may get corrupted because of improper recovery. Therefore, you must enable the checksum insertion only in the frames that are less than the following number of bytes in size (even in the store-and-forward mode): TXFIFO_SIZE - ((PBL + N)*(DATAWIDTH/8))" Thirdly similar but less strict constraint exist if TSO feature is enabled on DW QoS Ether or DW XGMAC/XLGMAC [2, 3]: "The header length plus the MSS size (which is equal to the size of each TCP segment) must not exceed 16383 bytes, otherwise, the MAC transmitter truncates the packet after 16383 bytes causing a CRC error. The header length plus MSS size plus programmed PBL value in register must be lesser than the programmed Tx Queue size." It was surprising to realize that almost none of these constraints are taken into account in the driver, except MTU being less than Tx FIFO size. In the meantime exceeding any of them will cause frames full corruption. That's what happens in case if Jumbo/Giant frames activated. Let's fix the denoted problem by adding the comprehensive procedure to support the net_device::max_mtu field in the correct state. For that the driver now performs the max MTU constraint calculation on: 1. device probe stage so to start device using with a correct constraing. 2. on each change of the active Tx/Rx queues since it causes Tx/Rx FIFO size change on the modern DW network controllers. 3. on each Tx COE feature activation/de-activation due to the feature-specify frame size requirement. Note on each action causing net_device::max_mtu change the driver now has to make sure the current MTU doesn't exceed the new constraint otherwise the action won't be accepted. As a nice consequence of this change the net_device_ops::ndo_change_mtu() callback won't need to have FIFO-specific MTU sanity checks. The respective constraints are now reflected in net_device::max_mtu. [1] DesignWare Cores Ethernet MAC Universal Databook, Revision 3.73a, October 2013, p. 185. [2] DesignWare Cores Ethernet Quality-of-Service Databook, Revision 5.20a, April 2020, p. 364/380. [3] DesignWare Cores XGMAC - 10G Ethernet MAC Databook, Revision 3.20a, October 2022, p. 249/257. Fixes: eaf4fac47807 ("net: stmmac: Do not accept invalid MTU values") Fixes: d6ddfacd95c7 ("net: stmmac: Add DMA related callbacks for XGMAC2") Fixes: f748be531d70 ("stmmac: support new GMAC4") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 135 ++++++++++++++++++++-- 1 file changed, 126 insertions(+), 9 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 7fbaa490149b7..02ba1e965a5ba 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -1551,6 +1551,30 @@ static void stmmac_display_rings(struct stmmac_priv *priv, stmmac_display_tx_rings(priv, dma_conf); } +/** + * stmmac_get_txpbl - retrieve Tx DMA PBL value + * @priv: driver private structure + * Description: calculate the Tx DMA PBL in accordance with the + * DW controller specifics. + * Return: Tx DMA PBL in beats. + */ +static unsigned int stmmac_get_txpbl(struct stmmac_priv *priv) +{ + struct stmmac_dma_cfg *dma_cfg = priv->plat->dma_cfg; + unsigned int factor; + + /* DMA PBL multiplier had been x4 prior DW GMAC v3.50a */ + if ((priv->plat->core_type == DWMAC_CORE_GMAC && + priv->synopsys_id >= DWMAC_CORE_3_50) || + dwmac_is_xmac(priv->plat->core_type)) { + factor = dma_cfg->pblx8 ? 8 : 1; + } else { + factor = dma_cfg->pblx8 ? 4 : 1; + } + + return (dma_cfg->txpbl ?: dma_cfg->pbl) * factor; +} + /** * stmmac_get_txfifosz - retrieve Tx MTL FIFO size * @priv: driver private structure @@ -1602,13 +1626,17 @@ static unsigned int stmmac_get_min_mtu(struct stmmac_priv *priv) /** * stmmac_get_max_mtu - retrieve maximal supported MTU * @priv: driver private structure + * @tx_coe: Tx COE feature state + * @tx_cnt: Number of active Tx queues + * @rx_cnt: Number of active Rx queues * Description: calculate the maximum MTU supported by DW controller in * accordance with the device active features. * Return: maximum MTU. */ -static unsigned int stmmac_get_max_mtu(struct stmmac_priv *priv) +static unsigned int stmmac_get_max_mtu(struct stmmac_priv *priv, bool tx_coe, + u32 tx_cnt, u32 rx_cnt) { - unsigned int max_mtu; + unsigned int max_mtu, tx_mtu, rx_mtu, txfifosz, rxfifosz, addend; /* MTU constraint caused by the MAC implementation */ if (dwmac_is_xmac(priv->plat->core_type)) @@ -1623,7 +1651,38 @@ static unsigned int stmmac_get_max_mtu(struct stmmac_priv *priv) priv->plat->maxmtu < max_mtu) max_mtu = priv->plat->maxmtu; - return max_mtu; + /* MTU constraint caused by DMA SF/CT mode and active Tx COE */ + if (dwmac_is_xmac(priv->plat->core_type)) { + addend = priv->plat->data_width > 4 ? 5 : 7; + txfifosz = priv->plat->tx_fifo_size / tx_cnt; + rxfifosz = priv->plat->rx_fifo_size / rx_cnt; + } else { + addend = 3; + txfifosz = priv->plat->tx_fifo_size; + rxfifosz = priv->plat->rx_fifo_size; + } + + /* DMA Cut-through mode doesn't apply any Rx FIFO-depth constraint */ + if (priv->plat->force_thresh_dma_mode) + rx_mtu = max_mtu; + else + rx_mtu = rxfifosz - ETH_HLEN - 2 * VLAN_HLEN - ETH_FCS_LEN; + + /* DMA Store-and-Forward mode requires frames to be <= FIFO-depth */ + if (priv->plat->force_sf_dma_mode || priv->plat->tx_coe) + tx_mtu = txfifosz - ETH_HLEN - 2 * VLAN_HLEN - ETH_FCS_LEN; + else + tx_mtu = max_mtu; + + /* Tx COE and TSO further reduce the frame length to avoid dead-lock */ + if (tx_coe) { + tx_mtu -= (stmmac_get_txpbl(priv) + addend) * priv->plat->data_width; + /* Subtract two more bytes since MTU must be strictly lesser */ + tx_mtu -= 2; + } + + /* Zero Tx/Rx FIFO size lead to standard max MTU due to subtractions */ + return min3(max_mtu, tx_mtu, rx_mtu); } /** @@ -6281,6 +6340,47 @@ static void stmmac_set_rx_mode(struct net_device *dev) stmmac_set_filter(priv, priv->hw, dev); } +/** + * stmmac_verify_max_mtu - verify net-device maximum MTU constraint + * @priv: driver private structure + * @tx_coe: Tx COE feature state + * @tx_cnt: Number of active Tx queues + * @rx_cnt: Number of active Rx queues + * Description: calculate the maximum MTU constraint corresponding to the + * device capability and passed Tx/Rx parameters, and check whether it can + * be applied so the current MTU wouldn't get out of bounds. + * Return: 0 on success or errno if current MTU gets out of the new max MTU. + */ +static int stmmac_verify_max_mtu(struct stmmac_priv *priv, bool tx_coe, + u32 tx_cnt, u32 rx_cnt) +{ + unsigned int mtu, max_mtu; + + mtu = READ_ONCE(priv->dev->mtu); + max_mtu = stmmac_get_max_mtu(priv, tx_coe, tx_cnt, rx_cnt); + + if (mtu > max_mtu) + return -EINVAL; + + return 0; +} + +/** + * stmmac_set_max_mtu - set net-device maximum MTU constraint + * @priv: driver private structure + * @tx_coe: Tx COE feature state + * @tx_cnt: Number of active Tx queues + * @rx_cnt: Number of active Rx queues + * Description: calculate and apply the maximum MTU constraint corresponding to the + * device capability and passed Tx/Rx parameters. + * Return: 0 on success or errno if current MTU gets out of the new max MTU. + */ +static void stmmac_set_max_mtu(struct stmmac_priv *priv, bool tx_coe, + u32 tx_cnt, u32 rx_cnt) +{ + priv->dev->max_mtu = stmmac_get_max_mtu(priv, tx_coe, tx_cnt, rx_cnt); +} + /** * stmmac_change_mtu - entry point to change MTU size for the device. * @dev : device pointer. @@ -6295,7 +6395,6 @@ static void stmmac_set_rx_mode(struct net_device *dev) static int stmmac_change_mtu(struct net_device *dev, int new_mtu) { struct stmmac_priv *priv = netdev_priv(dev); - int txfifosz = stmmac_get_txfifosz(priv); struct stmmac_dma_conf *dma_conf; int ret; @@ -6304,9 +6403,6 @@ static int stmmac_change_mtu(struct net_device *dev, int new_mtu) return -EINVAL; } - if (new_mtu > txfifosz) - return -EINVAL; - if (netif_running(dev)) { netdev_dbg(priv->dev, "restarting interface to change its MTU\n"); /* Try to allocate the new DMA conf with the new mtu */ @@ -6354,6 +6450,12 @@ static netdev_features_t stmmac_fix_features(struct net_device *dev, if (priv->plat->bugged_jumbo && (dev->mtu > ETH_DATA_LEN)) features &= ~NETIF_F_CSUM_MASK; + /* MTU constraint depends on Tx COE/TSO features */ + if (stmmac_verify_max_mtu(priv, features & NETIF_F_CSUM_MASK, + priv->plat->tx_queues_to_use, + priv->plat->rx_queues_to_use)) + features &= ~NETIF_F_CSUM_MASK; + /* SPH feature depends on Rx COE */ if (priv->sph_active) features |= NETIF_F_RXCSUM; @@ -6371,6 +6473,10 @@ static int stmmac_set_features(struct net_device *netdev, struct stmmac_priv *priv = netdev_priv(netdev); u32 chan; + stmmac_set_max_mtu(priv, features & NETIF_F_CSUM_MASK, + priv->plat->tx_queues_to_use, + priv->plat->rx_queues_to_use); + stmmac_rx_fcs(priv, priv->hw, features & NETIF_F_RXFCS); stmmac_rx_ipc(priv, priv->hw, features & NETIF_F_RXCSUM); @@ -8277,7 +8383,12 @@ static void stmmac_napi_del(struct net_device *dev) int stmmac_reinit_queues(struct net_device *dev, u32 rx_cnt, u32 tx_cnt) { struct stmmac_priv *priv = netdev_priv(dev); - int ret = 0, i; + int ret, i; + + ret = stmmac_verify_max_mtu(priv, dev->features & NETIF_F_CSUM_MASK, + tx_cnt, rx_cnt); + if (ret) + return ret; if (netif_running(dev)) stmmac_release(dev); @@ -8286,6 +8397,10 @@ int stmmac_reinit_queues(struct net_device *dev, u32 rx_cnt, u32 tx_cnt) priv->plat->rx_queues_to_use = rx_cnt; priv->plat->tx_queues_to_use = tx_cnt; + + stmmac_set_max_mtu(priv, dev->features & NETIF_F_CSUM_MASK, + tx_cnt, rx_cnt); + if (!netif_is_rxfh_configured(dev)) for (i = 0; i < ARRAY_SIZE(priv->rss.table); i++) priv->rss.table[i] = ethtool_rxfh_indir_default(i, @@ -8685,7 +8800,9 @@ static int __stmmac_dvr_probe(struct device *device, ndev->vlan_features |= ndev->features; ndev->min_mtu = stmmac_get_min_mtu(priv); - ndev->max_mtu = stmmac_get_max_mtu(priv); + ndev->max_mtu = stmmac_get_max_mtu(priv, ndev->features & NETIF_F_CSUM_MASK, + priv->plat->tx_queues_to_use, + priv->plat->rx_queues_to_use); ndev->priv_flags |= IFF_LIVE_ADDR_CHANGE; -- cgit v1.2.3 From 91f9e9a3b7d78e3ad06ce928f6518cc304991244 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 14 Mar 2025 00:38:13 +0300 Subject: net: stmmac: dwxgmac2: Fix VLAN packets filtering on promiscuous mode Currently the promiscuous mode doesn't work for VLAN frames. So if there is a VLAN interface created on top of an DW XGMAC native interface only VLAN-frames with the specified VIDs will be received despite of having the promiscuous mode activated. That's because the stmmac_ops::set_filter() callback doesn't disable the VLAN-filters when the IFF_PROMISC mode is detected. Let's fix the problem the way it has already been done for DW Ether QoS GMAC in commit Fixes: a7602e7332b9 ("net: stmmac: don't reject VLANs when IFF_PROMISC is set"). The only thing that needs to be done is moving the PACKET_FILTER.VTFE flag toggling to the denoted set_filter() callback. Thus if the IFF_PROMISC flag is set the PACKET_FILTER.VTFE flag will be cleared and VLAN-filter will be de-activated. Otherwise the flag will be left set permitting VLAN-filters to work (if any was configured). Fixes: 3cd1cfcba26e ("net: stmmac: Implement VLAN Hash Filtering in XGMAC") Signed-off-by: Serge Semin --- .../net/ethernet/stmicro/stmmac/dwxgmac2_core.c | 6 +++++ drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c | 27 +++------------------- drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.h | 1 - 3 files changed, 9 insertions(+), 25 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c index c4458b622de97..ea1252730f6c1 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c @@ -620,6 +620,12 @@ static void dwxgmac2_set_filter(struct mac_device_info *hw, } } + /* VLAN filtering */ + if (dev->flags & IFF_PROMISC) + value &= ~XGMAC_FILTER_VTFE; + else if (dev->features & NETIF_F_HW_VLAN_CTAG_FILTER) + value |= XGMAC_FILTER_VTFE; + writel(value, ioaddr + XGMAC_PACKET_FILTER); } diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c index e24efe3bfedbe..59469805edc26 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c @@ -254,18 +254,13 @@ static void dwxgmac2_update_vlan_hash(struct mac_device_info *hw, u32 hash, u16 perfect_match, bool is_double) { void __iomem *ioaddr = hw->pcsr; + u32 value; writel(hash, ioaddr + VLAN_HASH_TABLE); - if (hash) { - u32 value = readl(ioaddr + XGMAC_PACKET_FILTER); - - value |= XGMAC_FILTER_VTFE; - - writel(value, ioaddr + XGMAC_PACKET_FILTER); - - value = readl(ioaddr + VLAN_TAG); + value = readl(ioaddr + VLAN_TAG); + if (hash) { value |= VLAN_VTHM | VLAN_ETV; if (is_double) { value |= VLAN_EDVLP; @@ -280,14 +275,6 @@ static void dwxgmac2_update_vlan_hash(struct mac_device_info *hw, u32 hash, value &= ~VLAN_VID; writel(value, ioaddr + VLAN_TAG); } else if (perfect_match) { - u32 value = readl(ioaddr + XGMAC_PACKET_FILTER); - - value |= XGMAC_FILTER_VTFE; - - writel(value, ioaddr + XGMAC_PACKET_FILTER); - - value = readl(ioaddr + VLAN_TAG); - value &= ~VLAN_VTHM; value |= VLAN_ETV; if (is_double) { @@ -303,14 +290,6 @@ static void dwxgmac2_update_vlan_hash(struct mac_device_info *hw, u32 hash, value &= ~VLAN_VID; writel(value | perfect_match, ioaddr + VLAN_TAG); } else { - u32 value = readl(ioaddr + XGMAC_PACKET_FILTER); - - value &= ~XGMAC_FILTER_VTFE; - - writel(value, ioaddr + XGMAC_PACKET_FILTER); - - value = readl(ioaddr + VLAN_TAG); - value &= ~(VLAN_VTHM | VLAN_ETV); value &= ~(VLAN_EDVLP | VLAN_ESVL); value &= ~VLAN_DOVLTC; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.h b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.h index c24f89a9049b8..514f28f246d96 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.h +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.h @@ -8,7 +8,6 @@ #define __STMMAC_VLAN_H__ #include -#include "dwxgmac2.h" #define VLAN_TAG 0x00000050 #define VLAN_TAG_DATA 0x00000054 -- cgit v1.2.3 From b677e3eaaede2a55acdefa38b100d4be6d4348b2 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 18 Apr 2025 15:42:38 +0300 Subject: net: stmmac: dwxgmac2: Fix control packets recv after promiscous mode off The commit 8c5f48d99de2 ("net: stmmac: dwxgmac2: Also pass control frames while in promisc mode") made sure that the Ethernet flow-control packets get to be passed to the system memory when IFF_PROMISC by setting the MAC_Packet_Filter.PCF flag. But it forgot to have the bit cleared after the promiscuous mode was disabled. Let's fix that. Fixes: 8c5f48d99de2 ("net: stmmac: dwxgmac2: Also pass control frames while in promisc mode") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c index ea1252730f6c1..7a7c3c91fc526 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c @@ -572,11 +572,15 @@ static void dwxgmac2_set_filter(struct mac_device_info *hw, struct net_device *dev) { void __iomem *ioaddr = (void __iomem *)dev->base_addr; - u32 value = readl(ioaddr + XGMAC_PACKET_FILTER); int mcbitslog2 = hw->mcast_bits_log2; - u32 mc_filter[8]; + u32 value, mc_filter[8]; + + value = readl(ioaddr + XGMAC_PACKET_FILTER); + value &= ~XGMAC_FILTER_PCF; + value &= ~XGMAC_FILTER_PM; + value &= ~XGMAC_FILTER_HMC; + value &= ~XGMAC_FILTER_PR; - value &= ~(XGMAC_FILTER_PR | XGMAC_FILTER_HMC | XGMAC_FILTER_PM); value |= XGMAC_FILTER_HPF; memset(mc_filter, 0, sizeof(mc_filter)); -- cgit v1.2.3 From e0f507d2d6e578e7209a9796641b1bfa81bc6b72 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 20 Mar 2025 12:57:35 +0300 Subject: net: stmmac: vlan: Add VLAN S-TAG perfect filtering The blamed commit added the Extended VLAN Tag Perfect filtering support to the DW Ether QoS part of the driver. Then the implementation has been reused for DW XGMACs in commit 534df0c1724b ("net: stmmac: dwxgmac2: Add support for HW-accelerated VLAN stripping"). Originally it was intended for the VLAN TAG perfect filters to be used orthogonally with the VLAN Tag Hash filtering. So one functionality would work irrespective to another one being available. Well, with some flaws but it has worked. The problem comes when you get to have a device with no VLAN Tag Hash filter but with the Extended VLAN Tag filter support. In such case by default the S-TAG filter won't be activated causing C-TAG frames filtering instead, because no ERSVLM or DOVLTC flag set in the MAC_VLAN_Tag_Filter register. These flags are responsible for VLAN S-TAG tag activating or disabling VLAN tag type checking. Anyway in case if the VLAN Tag Hash filtering is supported it will be _responsible_ for permitting both C- and S-Tags of the same VID, which at least isn't secure. But the VLAN perfect filter S-TAG entries will be misconfigured. Let's fix the problem denoted above by setting up the ERSVLM flag in case if the 802.1ad VLAN-protocol requested. The flag will be preserved in the private-data VIDs cache so to be properly restored after system resume and to differentiate the tags with the same VIDs but different types. Fixes: ed64639bc1e0 ("net: stmmac: Add support for VLAN Rx filtering") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c | 68 ++++++++++++++++------- drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.h | 2 + 2 files changed, 49 insertions(+), 21 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c index 59469805edc26..1550843446a7c 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c @@ -7,14 +7,15 @@ #include "stmmac.h" #include "stmmac_vlan.h" -static void vlan_write_single(struct net_device *dev, u16 vid) +static void vlan_write_single(struct net_device *dev, u32 data) { void __iomem *ioaddr = (void __iomem *)dev->base_addr; - u32 val; + u32 val, mask; - val = readl(ioaddr + VLAN_TAG); - val &= ~VLAN_TAG_VID; - val |= VLAN_TAG_ETV | vid; + mask = VLAN_TAG_ERSVLM | VLAN_TAG_VID; + + val = readl(ioaddr + VLAN_TAG) & ~mask; + val |= VLAN_TAG_ETV | (data & mask); writel(val, ioaddr + VLAN_TAG); } @@ -56,12 +57,15 @@ static int vlan_add_hw_rx_fltr(struct net_device *dev, __be16 proto, u16 vid) { int index = -1; + bool is_stag; u32 val = 0; int i, ret; if (vid > 4095) return -EINVAL; + is_stag = proto == htons(ETH_P_8021AD); + /* Single Rx VLAN Filter */ if (hw->num_vlan == 1) { /* For single VLAN filter, VID 0 means VLAN promiscuous */ @@ -70,21 +74,30 @@ static int vlan_add_hw_rx_fltr(struct net_device *dev, return -EPERM; } + val = vid; + if (is_stag) + val |= VLAN_TAG_ERSVLM; + + if (hw->vlan_filter[0] == val) + return 0; + if (hw->vlan_filter[0] & VLAN_TAG_VID) { netdev_err(dev, "Only single VLAN ID supported\n"); return -EPERM; } - hw->vlan_filter[0] = vid; - if (netif_running(dev)) - vlan_write_single(dev, vid); + vlan_write_single(dev, val); + + hw->vlan_filter[0] = val; return 0; } /* Extended Rx VLAN Filter Enable */ val |= VLAN_TAG_DATA_ETV | VLAN_TAG_DATA_VEN | vid; + if (is_stag) + val |= VLAN_TAG_DATA_ERSVLM; for (i = 0; i < hw->num_vlan; i++) { if (hw->vlan_filter[i] == val) @@ -115,31 +128,44 @@ static int vlan_del_hw_rx_fltr(struct net_device *dev, __be16 proto, u16 vid) { int i, ret = 0; + bool is_stag; + + is_stag = proto == htons(ETH_P_8021AD); /* Single Rx VLAN Filter */ if (hw->num_vlan == 1) { - if ((hw->vlan_filter[0] & VLAN_TAG_VID) == vid) { - hw->vlan_filter[0] = 0; + if ((hw->vlan_filter[0] & VLAN_TAG_VID) != vid) + return 0; + + if (is_stag != !!(hw->vlan_filter[0] & VLAN_TAG_ERSVLM)) + return 0; + + if (netif_running(dev)) + vlan_write_single(dev, 0); + + hw->vlan_filter[0] = 0; - if (netif_running(dev)) - vlan_write_single(dev, 0); - } return 0; } /* Extended Rx VLAN Filter Enable */ for (i = 0; i < hw->num_vlan; i++) { - if ((hw->vlan_filter[i] & VLAN_TAG_DATA_VEN) && - ((hw->vlan_filter[i] & VLAN_TAG_DATA_VID) == vid)) { + if (!(hw->vlan_filter[i] & VLAN_TAG_DATA_VEN)) + continue; - if (netif_running(dev)) { - ret = vlan_write_filter(dev, hw, i, 0); - if (ret) - return ret; - } + if ((hw->vlan_filter[i] & VLAN_TAG_DATA_VID) != vid) + continue; - hw->vlan_filter[i] = 0; + if (is_stag != !!(hw->vlan_filter[i] & VLAN_TAG_DATA_ERSVLM)) + continue; + + if (netif_running(dev)) { + ret = vlan_write_filter(dev, hw, i, 0); + if (ret) + return ret; } + + hw->vlan_filter[i] = 0; } return 0; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.h b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.h index 514f28f246d96..5143c0fbda268 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.h +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.h @@ -30,6 +30,7 @@ /* MAC VLAN Tag */ #define VLAN_TAG_VID GENMASK(15, 0) #define VLAN_TAG_ETV BIT(16) +#define VLAN_TAG_ERSVLM BIT(19) /* MAC VLAN Tag Control */ #define VLAN_TAG_CTRL_OB BIT(0) @@ -49,6 +50,7 @@ #define VLAN_TAG_DATA_VID GENMASK(15, 0) #define VLAN_TAG_DATA_VEN BIT(16) #define VLAN_TAG_DATA_ETV BIT(17) +#define VLAN_TAG_DATA_ERSVLM BIT(19) /* MAC VLAN HW FEAT */ #define HW_FEATURE3 0x00000128 -- cgit v1.2.3 From 28b7ceecaa507209e6e7224241f253d0c6887f08 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Tue, 25 Mar 2025 21:50:49 +0300 Subject: net: stmmac: vlan: Fix VLAN filter basically disabled by default If no Extended VLAN filter Filtering supported, then a basic perfect VLAN-filter will be available. That filter regards VID=0 as a special value which if specified in the filter register basically means to accept any VLAN frame irrespective to the hash-based VLAN-filter settings. Here is the MAC_VLAN_Tag.VID field description actual for both DW QoS Eth and DW XGMAC IP-cores: > If this field ([11:0] if ETV is set) is all zeros, the MAC does not > check the 15th and 16th bytes for VLAN tag comparison and > declares all packets with Type field value of 0x8100 or 0x88a8 > as VLAN packets. This is also documented by the table "VLAN Match Status" in the IP-cores databooks: VID | VLAN Perfect Filter | VTHM | VLAN Hash Filter | Final VLAN | | Match Result | Bit | Match Result | Match Status | ----+---------------------+------+------------------+--------------+ 0 | Pass | 0 | Any | Pass | 0 | Pass | 1 | Any | Pass | So in order to have the VLAN-filtering actually working for hardware with no Extended VLAN filter support by default let's always initialize the MAC_VLAN_Tag.VID with 0xffff's if no real VID specified. Thus no real VLAN frames would be permitted except the packets with the reserved VID, which is better than permitting all VIDs and making VLAN hash filter basically useless. Fixes: 3cd1cfcba26e ("net: stmmac: Implement VLAN Hash Filtering in XGMAC") Fixes: ed64639bc1e0 ("net: stmmac: Add support for VLAN Rx filtering") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c index 1550843446a7c..7f1e34e62eee7 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c @@ -13,6 +13,8 @@ static void vlan_write_single(struct net_device *dev, u32 data) u32 val, mask; mask = VLAN_TAG_ERSVLM | VLAN_TAG_VID; + if (!(data & VLAN_TAG_VID)) + data |= VLAN_TAG_VID; val = readl(ioaddr + VLAN_TAG) & ~mask; val |= VLAN_TAG_ETV | (data & mask); @@ -209,6 +211,9 @@ static void vlan_update_hash(struct mac_device_info *hw, u32 hash, value &= ~VLAN_DOVLTC; } + if (!hw->vlan_filter[0]) + value |= VLAN_VID; + writel(value, ioaddr + VLAN_TAG); } else if (perfect_match) { u32 value = VLAN_ETV; @@ -228,7 +233,8 @@ static void vlan_update_hash(struct mac_device_info *hw, u32 hash, value &= ~(VLAN_VTHM | VLAN_ETV); value &= ~(VLAN_EDVLP | VLAN_ESVL); value &= ~VLAN_DOVLTC; - value &= ~VLAN_VID; + if (!hw->vlan_filter[0]) + value |= VLAN_VID; writel(value, ioaddr + VLAN_TAG); } @@ -298,7 +304,9 @@ static void dwxgmac2_update_vlan_hash(struct mac_device_info *hw, u32 hash, value &= ~VLAN_DOVLTC; } - value &= ~VLAN_VID; + if (!hw->vlan_filter[0]) + value |= VLAN_VID; + writel(value, ioaddr + VLAN_TAG); } else if (perfect_match) { value &= ~VLAN_VTHM; @@ -319,7 +327,8 @@ static void dwxgmac2_update_vlan_hash(struct mac_device_info *hw, u32 hash, value &= ~(VLAN_VTHM | VLAN_ETV); value &= ~(VLAN_EDVLP | VLAN_ESVL); value &= ~VLAN_DOVLTC; - value &= ~VLAN_VID; + if (!hw->vlan_filter[0]) + value |= VLAN_VID; writel(value, ioaddr + VLAN_TAG); } -- cgit v1.2.3 From d6cd34229a19ff94da813d53bc5845748a590f22 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 16 Apr 2025 21:01:02 +0300 Subject: net: stmmac: vlan: Fix VLAN_TAG register override on perfect-match The stmmac_vlan_update() method falls back to the basic perfect filter case if no VLAN hash filter detected. In that case the method completely overrides the VLAN_TAG register clearing the settings like EVLRXS or EVLS, which are required for the VLAN interface to work correctly. For instance EVLRXS activates VLAN Tag in Rx status getting the Rx DMA-descriptors. So let's fix that in a way it has been done for DW XGMAC VLAN in commit 907a076881f1 ("net: stmmac: xgmac: fix incorrect XGMAC_VLAN_TAG register writting") - by using the already read value of the VLAN_TAG register. Fixes: c7ab0b8088d7 ("net: stmmac: Fallback to VLAN Perfect filtering if HASH is not available") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c index 7f1e34e62eee7..c553cf6b772e8 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c @@ -216,8 +216,8 @@ static void vlan_update_hash(struct mac_device_info *hw, u32 hash, writel(value, ioaddr + VLAN_TAG); } else if (perfect_match) { - u32 value = VLAN_ETV; - + value &= ~VLAN_VTHM; + value |= VLAN_ETV; if (is_double) { value |= VLAN_EDVLP; value |= VLAN_ESVL; @@ -228,6 +228,7 @@ static void vlan_update_hash(struct mac_device_info *hw, u32 hash, value &= ~VLAN_DOVLTC; } + value &= ~VLAN_VID; writel(value | perfect_match, ioaddr + VLAN_TAG); } else { value &= ~(VLAN_VTHM | VLAN_ETV); -- cgit v1.2.3 From f535f3cdfde211611ea6b0a48a975f97f570a233 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sat, 29 Mar 2025 01:26:23 +0300 Subject: net: stmmac: vlan: Fix VLAN Stripping always enabled in ndo_set_features() Commit 8eb301bd7b0f ("net: stmmac: enable HW-accelerated VLAN stripping for gmac4 only") attempted to fix the problem of the HW-accelerated VLAN tag stripping feature being enabled for all MAC cores even despite of having it supported by the DW GMAC4 driver only. But the solution only works up until the ndo_set_features() method is called. When it's done the mac_device_info::hw_vlan_en field will be set to true thus disabling the software-based VLAN tag stripping implementation and relying on the MAC to do that. Of course that won't be done on the DW GMAC- nor DW XLGMAC-based devices since the driver currently doesn't support the feature on these IP-cores. Let's completely fix the denoted problem just by changing the HW-offloaded VLAN tag detection flag only if the NETIF_F_HW_VLAN_CTAG_RX flag is set in ndt_device::hw_features. The later condition is only met for the DW GMAC4 and DW XGMAC IP-cores currently. Fixes: 8eb301bd7b0f ("net: stmmac: enable HW-accelerated VLAN stripping for gmac4 only") Fixes: 750011e239a5 ("net: stmmac: Add support for HW-accelerated VLAN stripping") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 02ba1e965a5ba..ca9608f08c94b 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -6502,10 +6502,8 @@ static int stmmac_set_features(struct net_device *netdev, features & NETIF_F_RXHASH, priv->plat->rx_queues_to_use); - if (features & NETIF_F_HW_VLAN_CTAG_RX) - priv->hw->hw_vlan_en = true; - else - priv->hw->hw_vlan_en = false; + if (netdev->hw_features & NETIF_F_HW_VLAN_CTAG_RX) + priv->hw->hw_vlan_en = !!(features & NETIF_F_HW_VLAN_CTAG_RX); phylink_rx_clk_stop_block(priv->phylink); stmmac_set_hw_vlan_mode(priv, priv->hw); -- cgit v1.2.3 From 7af0d9110c1cb46f69add1b36d7b2327ba03b688 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Tue, 8 Apr 2025 19:21:43 +0300 Subject: net: stmmac: vlan: Fix VLAN S-TAG stripping In fact the always strip mode enabled in the vlan_set_hw_mode() method means stripping both VLAN tag types C and S ones. Thus both of these NET-device features must be enabled/disabled simultaneously if requested by ethtool. Fix that by adding the respective conditionals to the ndo_fix_feature() method and making sure that both of these features are synchronously switchable. Fixes: 750011e239a5 ("net: stmmac: Add support for HW-accelerated VLAN stripping") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index ca9608f08c94b..a23e9e3a05eab 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -6464,6 +6464,14 @@ static netdev_features_t stmmac_fix_features(struct net_device *dev, if (!(features & NETIF_F_RXCSUM)) features &= ~NETIF_F_RXHASH; + /* VLAN-stripping implies removing any VLAN tag type: CTAG and STAG */ + if (dwmac_is_xmac(priv->plat->core_type)) { + if (features & NETIF_F_HW_VLAN_CTAG_RX) + features |= NETIF_F_HW_VLAN_STAG_RX; + else if (features & NETIF_F_HW_VLAN_STAG_RX) + features |= NETIF_F_HW_VLAN_CTAG_RX; + } + return features; } @@ -8783,7 +8791,7 @@ static int __stmmac_dvr_probe(struct device *device, /* Both mac100 and gmac support receive VLAN tag detection */ ndev->features |= NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_HW_VLAN_STAG_RX; if (dwmac_is_xmac(priv->plat->core_type)) { - ndev->hw_features |= NETIF_F_HW_VLAN_CTAG_RX; + ndev->hw_features |= NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_HW_VLAN_STAG_RX; priv->hw->hw_vlan_en = true; } if (priv->dma_cap.vlhash) { -- cgit v1.2.3 From 5eeb71f19acb81724c6ba962bd32e599691b4faf Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 8 Sep 2025 18:56:40 +0300 Subject: net: stmmac: vlan: Report correct number of Ext VLAN filters The amount of the Extended Rx Perfect Filter slots is reported via the FEATURE3.NRVF field on the DW IP-core supporting that features: DW QoS Ether and DW XGMAC/XLGMAC/etc. The field encrypts the size of the filter as follows: - 000: No Extended Rx VLAN Filters - 001: 4 Extended Rx VLAN Filters - 010: 8 Extended Rx VLAN Filters - 011: 16 Extended Rx VLAN Filters - 100: 24 Extended Rx VLAN Filters - 101: 32 Extended Rx VLAN Filters - 110-111: Reserved In the meantime the dma_cap DebugFS node incorrectly decrypts the field as (nrvf << 1) and only for the DW XGMAC IP-core. Let's fix that by appropriately parsing the FEATURE3.NRVF field on the both IP-cores supporting the Extended Rx Perfect Filter feature. Fixes: ed64639bc1e0 ("net: stmmac: Add support for VLAN Rx filtering") Fixes: 669a55560e4b ("net: stmmac: Check more MAC HW features for XGMAC Core 3.20") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c | 17 +++++++++++++++++ drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c | 16 ++++++++++++++++ drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 5 ++--- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c index 93e9c636d86c9..a75206682c413 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_dma.c @@ -485,6 +485,23 @@ static int dwmac4_get_hw_feature(void __iomem *ioaddr, dma_cap->frpsel = (hw_cap & GMAC_HW_FEAT_FRPSEL) >> 10; dma_cap->dvlan = (hw_cap & GMAC_HW_FEAT_DVLAN) >> 5; + /* Number of Extended VLAN Tag Filters */ + dma_cap->nrvf_num = (hw_cap & GMAC_HW_FEAT_NRVF) >> 0; + switch (dma_cap->nrvf_num) { + case 1 ... 3: + dma_cap->nrvf_num = 1 << (dma_cap->nrvf_num + 1); + break; + case 4: + dma_cap->nrvf_num = 24; + break; + case 5: + dma_cap->nrvf_num = 32; + break; + default: + dma_cap->nrvf_num = 0; + break; + } + return 0; } diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c index 6b2362910285b..a50f79a47266c 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c @@ -499,7 +499,23 @@ static int dwxgmac2_get_hw_feature(void __iomem *ioaddr, dma_cap->frppipe_num = ((hw_cap & XGMAC_HWFEAT_FRPPIPE) >> 5) + 1; dma_cap->cbtisel = (hw_cap & XGMAC_HWFEAT_CBTISEL) >> 4; dma_cap->frpsel = (hw_cap & XGMAC_HWFEAT_FRPSEL) >> 3; + + /* Number of Extended VLAN Tag Filters */ dma_cap->nrvf_num = (hw_cap & XGMAC_HWFEAT_NRVF) >> 0; + switch (dma_cap->nrvf_num) { + case 1 ... 3: + dma_cap->nrvf_num = 1 << (dma_cap->nrvf_num + 1); + break; + case 4: + dma_cap->nrvf_num = 24; + break; + case 5: + dma_cap->nrvf_num = 32; + break; + default: + dma_cap->nrvf_num = 0; + break; + } /* MAC HW feature 4 */ hw_cap = readl(ioaddr + XGMAC_HW_FEATURE4); diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index a23e9e3a05eab..921aa3231d2df 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -7025,9 +7025,8 @@ static int stmmac_dma_cap_show(struct seq_file *seq, void *v) BIT(priv->dma_cap.frpbs) << 6); seq_printf(seq, "\tParallel Instruction Processor Engines: %d\n", priv->dma_cap.frppipe_num); - seq_printf(seq, "\tNumber of Extended VLAN Tag Filters: %lu\n", - priv->dma_cap.nrvf_num ? - (BIT(priv->dma_cap.nrvf_num) << 1) : 0); + seq_printf(seq, "\tNumber of Extended VLAN Tag Filters: %u\n", + priv->dma_cap.nrvf_num); seq_printf(seq, "\tWidth of the Time Interval Field in GCL: %d\n", priv->dma_cap.estwid ? 4 * priv->dma_cap.estwid + 12 : 0); seq_printf(seq, "\tDepth of GCL: %lu\n", -- cgit v1.2.3 From a69d2bad1218cae53e09c671a610c7d6c719b61a Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 7 Mar 2025 14:03:09 +0300 Subject: net: stmmac: dwxgmac2: Add Double VLAN processing feature detection The MAC_HW_Feature3.DVLAN flag has been available since the DW XGMAC IP-core v3.00a, but the Double VLAN Processing feature can be enable since the v2.00a version (or later than v1.20a). Thus it's incorrect to determine the DVLAN feature availability based on the flag state on the early versions of the IP-core. Since we don't know since what particular IP-core version the Double VLAN Processing feature is available let's provide the auto-detection procedure for any of them if DVLAN flag has been met cleared. The auto-detection procedure is created by checking the MAC_VLAN_Tag.EIVLRXS (Enable Inner VLAN Tag in Rx Status) flag writability. The IP-cores databooks define the flag as 0x0 after reset and available only if the DWCXG_DOUBLE_VLAN_EN parameter is enabled. Otherwise it's reserved, RO and zero. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c | 20 ++++++++++++++++++++ drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.h | 1 + 2 files changed, 21 insertions(+) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c index a50f79a47266c..a05f448bfd7a4 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c @@ -6,6 +6,7 @@ #include #include "stmmac.h" +#include "stmmac_vlan.h" #include "dwxgmac2.h" static int dwxgmac2_dma_reset(void __iomem *ioaddr) @@ -388,6 +389,20 @@ static int dwxgmac2_dma_interrupt(struct stmmac_priv *priv, return ret; } +static bool dwxgmac2_det_hw_dvlan(void __iomem *ioaddr) +{ + u32 tmp; + + tmp = readl(ioaddr + VLAN_TAG); + writel(tmp ^ VLAN_EIVLRXS, ioaddr + VLAN_TAG); + if (tmp != readl(ioaddr + VLAN_TAG)) { + writel(tmp, ioaddr + VLAN_TAG); + return true; + } + + return false; +} + static int dwxgmac2_get_hw_feature(void __iomem *ioaddr, struct dma_features *dma_cap) { @@ -492,7 +507,12 @@ static int dwxgmac2_get_hw_feature(void __iomem *ioaddr, dma_cap->estsel = (hw_cap & XGMAC_HWFEAT_ESTSEL) >> 19; dma_cap->ttsfd = (hw_cap & XGMAC_HWFEAT_TTSFD) >> 16; dma_cap->asp = (hw_cap & XGMAC_HWFEAT_ASP) >> 14; + + /* Double VLAN Tagging (flag is unavailable prior DW XGMAC v3.00a) */ dma_cap->dvlan = (hw_cap & XGMAC_HWFEAT_DVLAN) >> 13; + if (!dma_cap->dvlan) + dma_cap->dvlan = dwxgmac2_det_hw_dvlan(ioaddr); + dma_cap->frpes = (hw_cap & XGMAC_HWFEAT_FRPES) >> 11; dma_cap->frpbs = (hw_cap & XGMAC_HWFEAT_FRPPB) >> 9; dma_cap->pou_ost_en = (hw_cap & XGMAC_HWFEAT_POUOST) >> 8; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.h b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.h index 5143c0fbda268..a138f4fdc4d80 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.h +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.h @@ -15,6 +15,7 @@ #define VLAN_INCL 0x00000060 /* MAC VLAN */ +#define VLAN_EIVLRXS BIT(31) #define VLAN_EDVLP BIT(26) #define VLAN_VTHM BIT(25) #define VLAN_DOVLTC BIT(20) -- cgit v1.2.3 From 930a50372b6d057ed30ffd622e4f16c94aa21e74 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Tue, 11 Aug 2026 12:37:51 +0300 Subject: net: stmmac: vlan: Use single ops desc for GMAC4 and XGMAC The dwxgmac2_update_vlan_hash() method now completely match to vlan_update_hash(). In the meantime the rest of the callbacks utilized in dwmac_vlan_ops and dwxgmac210_vlan_ops are the same. Thus drop the dwxgmac210_vlan_ops descriptor and use now common dwmac_vlan_ops descriptor instead. Rename it to dwmac4_vlan_ops so not to be accidentally utilized for the older IP-cores which have a bit different VLAN engine and isn't compatible with current VLAN implementation in the driver. Note the current VLAN module can be fully utilized for DW XLGMAC IP-cores too. But leave it be as is for the engineers with the compatible hardware at hand. Don't bother with that right now. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/hwif.c | 10 ++-- drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c | 66 +---------------------- drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.h | 3 +- 3 files changed, 8 insertions(+), 71 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.c b/drivers/net/ethernet/stmicro/stmmac/hwif.c index fa8b0bf5bdada..c8025504e5afa 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.c +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.c @@ -158,7 +158,7 @@ static const struct stmmac_hwif_entry { .desc = &dwmac4_desc_ops, .dma = &dwmac4_dma_ops, .mac = &dwmac4_ops, - .vlan = &dwmac_vlan_ops, + .vlan = &dwmac4_vlan_ops, .hwtimestamp = &stmmac_ptp, .ptp = &stmmac_ptp_clock_ops, .tc = &dwmac4_tc_ops, @@ -177,7 +177,7 @@ static const struct stmmac_hwif_entry { .desc = &dwmac4_desc_ops, .dma = &dwmac4_dma_ops, .mac = &dwmac410_ops, - .vlan = &dwmac_vlan_ops, + .vlan = &dwmac4_vlan_ops, .hwtimestamp = &stmmac_ptp, .ptp = &stmmac_ptp_clock_ops, .tc = &dwmac510_tc_ops, @@ -197,7 +197,7 @@ static const struct stmmac_hwif_entry { .desc = &dwmac4_desc_ops, .dma = &dwmac410_dma_ops, .mac = &dwmac410_ops, - .vlan = &dwmac_vlan_ops, + .vlan = &dwmac4_vlan_ops, .hwtimestamp = &stmmac_ptp, .ptp = &stmmac_ptp_clock_ops, .tc = &dwmac510_tc_ops, @@ -217,7 +217,7 @@ static const struct stmmac_hwif_entry { .desc = &dwmac4_desc_ops, .dma = &dwmac410_dma_ops, .mac = &dwmac510_ops, - .vlan = &dwmac_vlan_ops, + .vlan = &dwmac4_vlan_ops, .hwtimestamp = &stmmac_ptp, .ptp = &stmmac_ptp_clock_ops, .tc = &dwmac510_tc_ops, @@ -238,7 +238,7 @@ static const struct stmmac_hwif_entry { .desc = &dwxgmac210_desc_ops, .dma = &dwxgmac210_dma_ops, .mac = &dwxgmac210_ops, - .vlan = &dwxgmac210_vlan_ops, + .vlan = &dwmac4_vlan_ops, .hwtimestamp = &stmmac_ptp, .ptp = &stmmac_ptp_clock_ops, .tc = &dwmac510_tc_ops, diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c index c553cf6b772e8..2dcdbf78dd44c 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c @@ -283,59 +283,7 @@ static void vlan_set_hw_mode(struct mac_device_info *hw) writel(value, ioaddr + VLAN_TAG); } -static void dwxgmac2_update_vlan_hash(struct mac_device_info *hw, u32 hash, - u16 perfect_match, bool is_double) -{ - void __iomem *ioaddr = hw->pcsr; - u32 value; - - writel(hash, ioaddr + VLAN_HASH_TABLE); - - value = readl(ioaddr + VLAN_TAG); - - if (hash) { - value |= VLAN_VTHM | VLAN_ETV; - if (is_double) { - value |= VLAN_EDVLP; - value |= VLAN_ESVL; - value |= VLAN_DOVLTC; - } else { - value &= ~VLAN_EDVLP; - value &= ~VLAN_ESVL; - value &= ~VLAN_DOVLTC; - } - - if (!hw->vlan_filter[0]) - value |= VLAN_VID; - - writel(value, ioaddr + VLAN_TAG); - } else if (perfect_match) { - value &= ~VLAN_VTHM; - value |= VLAN_ETV; - if (is_double) { - value |= VLAN_EDVLP; - value |= VLAN_ESVL; - value |= VLAN_DOVLTC; - } else { - value &= ~VLAN_EDVLP; - value &= ~VLAN_ESVL; - value &= ~VLAN_DOVLTC; - } - - value &= ~VLAN_VID; - writel(value | perfect_match, ioaddr + VLAN_TAG); - } else { - value &= ~(VLAN_VTHM | VLAN_ETV); - value &= ~(VLAN_EDVLP | VLAN_ESVL); - value &= ~VLAN_DOVLTC; - if (!hw->vlan_filter[0]) - value |= VLAN_VID; - - writel(value, ioaddr + VLAN_TAG); - } -} - -const struct stmmac_vlan_ops dwmac_vlan_ops = { +const struct stmmac_vlan_ops dwmac4_vlan_ops = { .update_vlan_hash = vlan_update_hash, .enable_vlan = vlan_enable, .add_hw_vlan_rx_fltr = vlan_add_hw_rx_fltr, @@ -346,18 +294,8 @@ const struct stmmac_vlan_ops dwmac_vlan_ops = { }; const struct stmmac_vlan_ops dwxlgmac2_vlan_ops = { - .update_vlan_hash = dwxgmac2_update_vlan_hash, - .enable_vlan = vlan_enable, -}; - -const struct stmmac_vlan_ops dwxgmac210_vlan_ops = { - .update_vlan_hash = dwxgmac2_update_vlan_hash, + .update_vlan_hash = vlan_update_hash, .enable_vlan = vlan_enable, - .add_hw_vlan_rx_fltr = vlan_add_hw_rx_fltr, - .del_hw_vlan_rx_fltr = vlan_del_hw_rx_fltr, - .restore_hw_vlan_rx_fltr = vlan_restore_hw_rx_fltr, - .rx_hw_vlan = vlan_rx_hw, - .set_hw_vlan_mode = vlan_set_hw_mode, }; u32 stmmac_get_num_vlan(void __iomem *ioaddr) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.h b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.h index a138f4fdc4d80..3ee0022282f16 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.h +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.h @@ -57,8 +57,7 @@ #define HW_FEATURE3 0x00000128 #define VLAN_HW_FEAT_NRVF GENMASK(2, 0) -extern const struct stmmac_vlan_ops dwmac_vlan_ops; -extern const struct stmmac_vlan_ops dwxgmac210_vlan_ops; +extern const struct stmmac_vlan_ops dwmac4_vlan_ops; extern const struct stmmac_vlan_ops dwxlgmac2_vlan_ops; u32 stmmac_get_num_vlan(void __iomem *ioaddr); -- cgit v1.2.3 From 20747923ecfc7d104a4820859e036d822033a6ef Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 10 Apr 2025 20:53:16 +0300 Subject: net: stmmac: vlan: Drop VLAN basic perfect-filter fallback Originally that functionality was added in the commit c7ab0b8088d7 ("net: stmmac: Fallback to VLAN Perfect filtering if HASH is not available") in order to initialize at least some filter if no HASH-based VLAN filter available since at least one slot of the VLAN perfect filter always available. But earlier another commit 3cd1cfcba26e ("net: stmmac: Implement VLAN Hash Filtering in XGMAC") had made sure that no HW-offloaded VLAN frames filter utilized if no HASH-based VLAN filter provided in a controller. Thus that functionality has never been actually used in practice. Moreover the later commit ed64639bc1e0 ("net: stmmac: Add support for VLAN Rx filtering") implied using that perfect-filter register if no Extended VLAN filter feature is available thus partly duplicating what has already been implemented in the driver. So to speak let's drop the unused code especially seeing the respective device feature is utilized in a framework of the perfect-filter initialization procedure already. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/hwif.h | 2 +- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 13 +--------- drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c | 31 +++++------------------ 3 files changed, 8 insertions(+), 38 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index f523ef567eab1..7328791e236fe 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -629,7 +629,7 @@ struct stmmac_est_ops { struct stmmac_vlan_ops { /* VLAN */ void (*update_vlan_hash)(struct mac_device_info *hw, u32 hash, - u16 perfect_match, bool is_double); + bool is_double); void (*enable_vlan)(struct mac_device_info *hw, u32 type); void (*rx_hw_vlan)(struct mac_device_info *hw, struct dma_desc *rx_desc, struct sk_buff *skb); diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 921aa3231d2df..f154a0037d8da 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -7130,29 +7130,18 @@ static u32 stmmac_vid_crc32_le(__le16 vid_le) static int stmmac_vlan_update(struct stmmac_priv *priv, bool is_double) { u32 crc, hash = 0; - u16 pmatch = 0; - int count = 0; u16 vid = 0; for_each_set_bit(vid, priv->active_vlans, VLAN_N_VID) { __le16 vid_le = cpu_to_le16(vid); crc = bitrev32(~stmmac_vid_crc32_le(vid_le)) >> 28; hash |= (1 << crc); - count++; - } - - if (!priv->dma_cap.vlhash) { - if (count > 2) /* VID = 0 always passes filter */ - return -EOPNOTSUPP; - - pmatch = vid; - hash = 0; } if (!netif_running(priv->dev)) return 0; - return stmmac_update_vlan_hash(priv, priv->hw, hash, pmatch, is_double); + return stmmac_update_vlan_hash(priv, priv->hw, hash, is_double); } /* FIXME: This may need RXC to be running, but it may be called with BH diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c index 2dcdbf78dd44c..4ed7f7f031f49 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c @@ -190,7 +190,7 @@ static void vlan_restore_hw_rx_fltr(struct net_device *dev, } static void vlan_update_hash(struct mac_device_info *hw, u32 hash, - u16 perfect_match, bool is_double) + bool is_double) { void __iomem *ioaddr = hw->pcsr; u32 value; @@ -210,35 +210,16 @@ static void vlan_update_hash(struct mac_device_info *hw, u32 hash, value &= ~VLAN_ESVL; value &= ~VLAN_DOVLTC; } - - if (!hw->vlan_filter[0]) - value |= VLAN_VID; - - writel(value, ioaddr + VLAN_TAG); - } else if (perfect_match) { - value &= ~VLAN_VTHM; - value |= VLAN_ETV; - if (is_double) { - value |= VLAN_EDVLP; - value |= VLAN_ESVL; - value |= VLAN_DOVLTC; - } else { - value &= ~VLAN_EDVLP; - value &= ~VLAN_ESVL; - value &= ~VLAN_DOVLTC; - } - - value &= ~VLAN_VID; - writel(value | perfect_match, ioaddr + VLAN_TAG); } else { value &= ~(VLAN_VTHM | VLAN_ETV); value &= ~(VLAN_EDVLP | VLAN_ESVL); value &= ~VLAN_DOVLTC; - if (!hw->vlan_filter[0]) - value |= VLAN_VID; - - writel(value, ioaddr + VLAN_TAG); } + + if (!hw->vlan_filter[0]) + value |= VLAN_VID; + + writel(value, ioaddr + VLAN_TAG); } static void vlan_enable(struct mac_device_info *hw, u32 type) -- cgit v1.2.3 From 8275a29fddf22f5a588347585b9cc6ea2ee91b7a Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 12 Mar 2025 16:51:21 +0300 Subject: net: stmmac: vlan: Drop double VLAN enabling for 802.1ad Rx-frames MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The commit c657f86106c8 ("net: stmmac: vlan: Disable 802.1AD tag insertion offload") has dropped the double VLAN erroneous insertion for Tx frames. The Rx VLAN-frames filtering code also suffers from that feature being enabled but with much less harm. The feature is enabled if VLAN S-TAG filtering is requested, but it never actually activated since the ERIVLT and EIVLS flags aren't touched by the code. Presumably the EDVLP flag setting up used to be working in conjunction with the VLAN-insertion feature, since it enables the double VLAN processing for both Rx and _Tx_ paths. So without it no inner VLAN would be inserted if requested. But that feature has been incorrectly enabled for the S-VLAN frames and dropped in the framework of the commit c657f86106c8 ("net: stmmac: vlan: Disable 802.1AD tag insertion offload") thus fixing a completely broken 802.1ad tags support added in the initial commit 30d932279dc2 ("net: stmmac: Add support for VLAN Insertion Offload"). So let's drop the code currently handling the EDVLP flag setting up. Also fix the naming around the just dropped code to describing the actual feature the entities are utilized for - enable S-VLAN filtering. After this change there will be almost no the double VLAN feature trace left in the driver. The only leftovers are stmmac_desc_ops::set_vlan_tag() callbacks but the respective part doesn't effect the controller state, so be it for now. Note though based on [1, 2, 3] Double VLAN processing feature must be available and enabled so the *GMACs could properly perform the L2/L3 checksum verification of the frames with one-level inner VLAN tagged. Let's unconditionally enable it thus speeding up the incoming Double VLAN tagged frames handling. [1] DesignWare Cores XLGMAC - Enterprise Ethernet MAC, Revision 2.00a, September 2017, p.181. [1] DesignWare Cores XGMAC - 10G Ethernet MAC Databook, Revision 3.20a, September 2022, p.253. [2] DesignWare® Cores Ethernet Quality-of-Service, Revision 5.20a, April 2020, p.368. Fixes: 3cd1cfcba26e ("net: stmmac: Implement VLAN Hash Filtering in XGMAC") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/hwif.h | 2 +- drivers/net/ethernet/stmicro/stmmac/stmmac.h | 2 +- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 34 +++++++++++------------ drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c | 23 +++++++++++---- 4 files changed, 37 insertions(+), 24 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index 7328791e236fe..e4b28b3a62ccf 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -629,7 +629,7 @@ struct stmmac_est_ops { struct stmmac_vlan_ops { /* VLAN */ void (*update_vlan_hash)(struct mac_device_info *hw, u32 hash, - bool is_double); + bool is_svlan); void (*enable_vlan)(struct mac_device_info *hw, u32 type); void (*rx_hw_vlan)(struct mac_device_info *hw, struct dma_desc *rx_desc, struct sk_buff *skb); diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h index cd0531b522579..b1188d897936a 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h @@ -375,7 +375,7 @@ struct stmmac_priv { void __iomem *ptpaddr; void __iomem *estaddr; unsigned long active_vlans[BITS_TO_LONGS(VLAN_N_VID)]; - unsigned int num_double_vlans; + unsigned int num_svlans; int sfty_irq; int sfty_ce_irq; int sfty_ue_irq; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index f154a0037d8da..14027a0e34d6a 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -7127,7 +7127,7 @@ static u32 stmmac_vid_crc32_le(__le16 vid_le) return crc; } -static int stmmac_vlan_update(struct stmmac_priv *priv, bool is_double) +static int stmmac_vlan_update(struct stmmac_priv *priv, bool is_svlan) { u32 crc, hash = 0; u16 vid = 0; @@ -7141,7 +7141,7 @@ static int stmmac_vlan_update(struct stmmac_priv *priv, bool is_double) if (!netif_running(priv->dev)) return 0; - return stmmac_update_vlan_hash(priv, priv->hw, hash, is_double); + return stmmac_update_vlan_hash(priv, priv->hw, hash, is_svlan); } /* FIXME: This may need RXC to be running, but it may be called with BH @@ -7150,8 +7150,8 @@ static int stmmac_vlan_update(struct stmmac_priv *priv, bool is_double) static int stmmac_vlan_rx_add_vid(struct net_device *ndev, __be16 proto, u16 vid) { struct stmmac_priv *priv = netdev_priv(ndev); - unsigned int num_double_vlans; - bool is_double = false; + unsigned int num_svlans; + bool is_svlan = false; int ret; ret = pm_runtime_resume_and_get(priv->device); @@ -7159,11 +7159,11 @@ static int stmmac_vlan_rx_add_vid(struct net_device *ndev, __be16 proto, u16 vid return ret; if (be16_to_cpu(proto) == ETH_P_8021AD) - is_double = true; + is_svlan = true; set_bit(vid, priv->active_vlans); - num_double_vlans = priv->num_double_vlans + is_double; - ret = stmmac_vlan_update(priv, num_double_vlans); + num_svlans = priv->num_svlans + is_svlan; + ret = stmmac_vlan_update(priv, num_svlans); if (ret) { clear_bit(vid, priv->active_vlans); goto err_pm_put; @@ -7173,12 +7173,12 @@ static int stmmac_vlan_rx_add_vid(struct net_device *ndev, __be16 proto, u16 vid ret = stmmac_add_hw_vlan_rx_fltr(priv, ndev, priv->hw, proto, vid); if (ret) { clear_bit(vid, priv->active_vlans); - stmmac_vlan_update(priv, priv->num_double_vlans); + stmmac_vlan_update(priv, priv->num_svlans); goto err_pm_put; } } - priv->num_double_vlans = num_double_vlans; + priv->num_svlans = num_svlans; err_pm_put: pm_runtime_put(priv->device); @@ -7192,8 +7192,8 @@ err_pm_put: static int stmmac_vlan_rx_kill_vid(struct net_device *ndev, __be16 proto, u16 vid) { struct stmmac_priv *priv = netdev_priv(ndev); - unsigned int num_double_vlans; - bool is_double = false; + unsigned int num_svlans; + bool is_svlan = false; int ret; ret = pm_runtime_resume_and_get(priv->device); @@ -7201,11 +7201,11 @@ static int stmmac_vlan_rx_kill_vid(struct net_device *ndev, __be16 proto, u16 vi return ret; if (be16_to_cpu(proto) == ETH_P_8021AD) - is_double = true; + is_svlan = true; clear_bit(vid, priv->active_vlans); - num_double_vlans = priv->num_double_vlans - is_double; - ret = stmmac_vlan_update(priv, num_double_vlans); + num_svlans = priv->num_svlans - is_double; + ret = stmmac_vlan_update(priv, num_svlans); if (ret) { set_bit(vid, priv->active_vlans); goto del_vlan_error; @@ -7215,12 +7215,12 @@ static int stmmac_vlan_rx_kill_vid(struct net_device *ndev, __be16 proto, u16 vi ret = stmmac_del_hw_vlan_rx_fltr(priv, ndev, priv->hw, proto, vid); if (ret) { set_bit(vid, priv->active_vlans); - stmmac_vlan_update(priv, priv->num_double_vlans); + stmmac_vlan_update(priv, priv->num_svlans); goto del_vlan_error; } } - priv->num_double_vlans = num_double_vlans; + priv->num_svlans = num_svlans; del_vlan_error: pm_runtime_put(priv->device); @@ -7236,7 +7236,7 @@ static void stmmac_vlan_restore(struct stmmac_priv *priv) if (priv->hw->num_vlan) stmmac_restore_hw_vlan_rx_fltr(priv, priv->dev, priv->hw); - stmmac_vlan_update(priv, priv->num_double_vlans); + stmmac_vlan_update(priv, priv->num_svlans); } static int stmmac_bpf(struct net_device *dev, struct netdev_bpf *bpf) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c index 4ed7f7f031f49..a6ade06a7ae09 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c @@ -190,7 +190,7 @@ static void vlan_restore_hw_rx_fltr(struct net_device *dev, } static void vlan_update_hash(struct mac_device_info *hw, u32 hash, - bool is_double) + bool is_svlan) { void __iomem *ioaddr = hw->pcsr; u32 value; @@ -201,18 +201,16 @@ static void vlan_update_hash(struct mac_device_info *hw, u32 hash, if (hash) { value |= VLAN_VTHM | VLAN_ETV; - if (is_double) { - value |= VLAN_EDVLP; + if (is_svlan) { value |= VLAN_ESVL; value |= VLAN_DOVLTC; } else { - value &= ~VLAN_EDVLP; value &= ~VLAN_ESVL; value &= ~VLAN_DOVLTC; } } else { value &= ~(VLAN_VTHM | VLAN_ETV); - value &= ~(VLAN_EDVLP | VLAN_ESVL); + value &= ~VLAN_ESVL; value &= ~VLAN_DOVLTC; } @@ -259,11 +257,25 @@ static void vlan_set_hw_mode(struct mac_device_info *hw) /* Do not strip VLAN on Receive */ value |= VLAN_TAG_STRIP_NONE; + /* Activate Double VLAN for Rx COE */ + value |= VLAN_EDVLP; + /* Enable outer VLAN Tag in Rx DMA descriptor */ value |= VLAN_TAG_CTRL_EVLRXS; writel(value, ioaddr + VLAN_TAG); } +static void dwxlgmac2_vlan_set_hw_mode(struct mac_device_info *hw) +{ + void __iomem *ioaddr = hw->pcsr; + u32 value = readl(ioaddr + VLAN_TAG); + + /* Activate Double VLAN for Rx COE */ + value |= VLAN_EDVLP; + + writel(value, ioaddr + VLAN_TAG); +} + const struct stmmac_vlan_ops dwmac4_vlan_ops = { .update_vlan_hash = vlan_update_hash, .enable_vlan = vlan_enable, @@ -276,6 +288,7 @@ const struct stmmac_vlan_ops dwmac4_vlan_ops = { const struct stmmac_vlan_ops dwxlgmac2_vlan_ops = { .update_vlan_hash = vlan_update_hash, + .set_hw_vlan_mode = dwxlgmac2_vlan_set_hw_mode, .enable_vlan = vlan_enable, }; -- cgit v1.2.3 From 13f0c6abd92b8c772492b68e511e9bb58e92e5eb Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 12 Aug 2026 10:24:04 +0300 Subject: net: stmmac: vlan: Avoid writing to MAC_VLAN_Tag_Filter on iface down It was found out that the problem with the VLAN filters setting up is in the missing PHY RXC clock when EEE is enabled and the interface being down. As a result any attempt to create a virtual VLAN interface causes the error like: # ip link add link end1 name end1.5 type vlan id 5 15c40000.ethernet end1: Timeout accessing MAC_VLAN_Tag_Filter RTNETLINK answers: Device or resource busy # ip link set end1 down renesas-gbeth 15c40000.ethernet end1: Timeout accessing MAC_VLAN_Tag_Filter renesas-gbeth 15c40000.ethernet end1: failed to kill vid 0081/0 This was fixed in the commit c171e679ee66 ("net: stmmac: Disable EEE RX clock stop when VLAN is enabled") and commit 2cd70e3968f5 ("net: stmmac: Defer VLAN HW configuration when interface is down"). So the problem is no longer exist. But the later commit has turned to be too aggressive in fixing it by adding the netif_running() check on each VLAN CSRs access. In the meantime writing to the normal MAC VLAN CSRs won't cause any problem, the timeout happens only in the MAC_VLAN_Tag_Filter indirect access. See the log messages above. So let's drop the redundant netif_running() checks and add a single one to the vlan_write_filter() method. The method will return -EAGAIN error in case if the access could be performed right now so the callee would do that again later when the interface is brought up. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 3 --- drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c | 30 ++++++++++++----------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 14027a0e34d6a..d746064011ab8 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -7138,9 +7138,6 @@ static int stmmac_vlan_update(struct stmmac_priv *priv, bool is_svlan) hash |= (1 << crc); } - if (!netif_running(priv->dev)) - return 0; - return stmmac_update_vlan_hash(priv, priv->hw, hash, is_svlan); } diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c index a6ade06a7ae09..293589addf2d7 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c @@ -33,6 +33,14 @@ static int vlan_write_filter(struct net_device *dev, if (index >= hw->num_vlan) return -EINVAL; + /* Indirect VLAN Tag filter access inteface requires PHY RXC clock to + * be running. Retry when the interface is up. + */ + if (!netif_running(dev)) { + netdev_dbg(dev, "Skip writing Extended VLAN filter entry\n"); + return -EAGAIN; + } + writel(data, ioaddr + VLAN_TAG_DATA); val = readl(ioaddr + VLAN_TAG); @@ -88,8 +96,7 @@ static int vlan_add_hw_rx_fltr(struct net_device *dev, return -EPERM; } - if (netif_running(dev)) - vlan_write_single(dev, val); + vlan_write_single(dev, val); hw->vlan_filter[0] = val; @@ -114,11 +121,9 @@ static int vlan_add_hw_rx_fltr(struct net_device *dev, return -EPERM; } - if (netif_running(dev)) { - ret = vlan_write_filter(dev, hw, index, val); - if (ret) - return ret; - } + ret = vlan_write_filter(dev, hw, index, val); + if (ret && ret != -EAGAIN) + return ret; hw->vlan_filter[index] = val; @@ -142,8 +147,7 @@ static int vlan_del_hw_rx_fltr(struct net_device *dev, if (is_stag != !!(hw->vlan_filter[0] & VLAN_TAG_ERSVLM)) return 0; - if (netif_running(dev)) - vlan_write_single(dev, 0); + vlan_write_single(dev, 0); hw->vlan_filter[0] = 0; @@ -161,11 +165,9 @@ static int vlan_del_hw_rx_fltr(struct net_device *dev, if (is_stag != !!(hw->vlan_filter[i] & VLAN_TAG_DATA_ERSVLM)) continue; - if (netif_running(dev)) { - ret = vlan_write_filter(dev, hw, i, 0); - if (ret) - return ret; - } + ret = vlan_write_filter(dev, hw, i, 0); + if (ret && ret != -EAGAIN) + return ret; hw->vlan_filter[i] = 0; } -- cgit v1.2.3 From 62c9bd56dddf42b4f97dd3e10d4276e6d9169c92 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 26 Mar 2025 21:59:18 +0300 Subject: net: stmmac: vlan: Restore VLAN filters in set_rx_mode() The commit bd7ad51253a7 ("net: stmmac: Fix VLAN HW state restore") fixed the problem with the VLAN filters not being restored if the device was closed or after resume. That could have been done in a clearer and more coherent way just by calling the stmmac_restore_hw_vlan_rx_fltr() method in the stmmac_set_filter() method. The later in its turn is called in the net_device_ops::ndo_set_rx_mode() callback of the driver, which in its turn is called on device open and resumes. The only problematic part is to store the HASH/perfect filters context. But it can be done by adding two more mac_device_info fields aside with the Extended VLAN perfect filter settings. A similar approach with cacheing and using ndo_set_rx_mode() to restore VLAN-filters is implemented in some another network drivers. Let's do that then. As a nice side-effect the stmmac_vlan_restore() will be no longer needed. Also the stmmac_ops::set_filter() methods now look more coherent setting up the all MAC and VLAN filters. Note the vlan_write_filter() method must be altered to call the atomic-version of the CSR polling function, since it's caller - vlan_restore_hw_vlan_rx_fltr() is now called from the BH-disabled context (see dev_set_rx_mode()). bd7ad51253a7 ("net: stmmac: Fix VLAN HW state restore") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/common.h | 2 + drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c | 7 +++- .../net/ethernet/stmicro/stmmac/dwxgmac2_core.c | 7 +++- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 16 -------- drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c | 45 +++++++++++++--------- 5 files changed, 38 insertions(+), 39 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/common.h b/drivers/net/ethernet/stmicro/stmmac/common.h index 7b86414006d18..8403a3e172110 100644 --- a/drivers/net/ethernet/stmicro/stmmac/common.h +++ b/drivers/net/ethernet/stmicro/stmmac/common.h @@ -656,6 +656,8 @@ struct mac_device_info { unsigned int pcs; unsigned int xlgmac; unsigned int num_vlan; + u16 vlan_hash; + bool vlan_stag; u32 vlan_filter[32]; bool vlan_fail_q_en; u8 vlan_fail_q; diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c index 4d07f421e7afb..c7a3097472e79 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c @@ -511,6 +511,7 @@ static void dwmac4_set_filter(struct mac_device_info *hw, { void __iomem *ioaddr = (void __iomem *)dev->base_addr; int numhashregs = (hw->multicast_filter_bins >> 5); + struct stmmac_priv *priv = netdev_priv(dev); int mcbitslog2 = hw->mcast_bits_log2; unsigned int value; u32 mc_filter[8]; @@ -593,10 +594,12 @@ static void dwmac4_set_filter(struct mac_device_info *hw, } /* VLAN filtering */ - if (dev->flags & IFF_PROMISC && !hw->vlan_fail_q_en) + if (dev->flags & IFF_PROMISC && !hw->vlan_fail_q_en) { value &= ~GMAC_PACKET_FILTER_VTFE; - else if (dev->features & NETIF_F_HW_VLAN_CTAG_FILTER) + } else if (dev->features & NETIF_F_HW_VLAN_CTAG_FILTER) { value |= GMAC_PACKET_FILTER_VTFE; + stmmac_restore_hw_vlan_rx_fltr(priv, dev, hw); + } writel(value, ioaddr + GMAC_PACKET_FILTER); } diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c index 7a7c3c91fc526..9cd58b0d83062 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c @@ -572,6 +572,7 @@ static void dwxgmac2_set_filter(struct mac_device_info *hw, struct net_device *dev) { void __iomem *ioaddr = (void __iomem *)dev->base_addr; + struct stmmac_priv *priv = netdev_priv(dev); int mcbitslog2 = hw->mcast_bits_log2; u32 value, mc_filter[8]; @@ -625,10 +626,12 @@ static void dwxgmac2_set_filter(struct mac_device_info *hw, } /* VLAN filtering */ - if (dev->flags & IFF_PROMISC) + if (dev->flags & IFF_PROMISC) { value &= ~XGMAC_FILTER_VTFE; - else if (dev->features & NETIF_F_HW_VLAN_CTAG_FILTER) + } else if (dev->features & NETIF_F_HW_VLAN_CTAG_FILTER) { value |= XGMAC_FILTER_VTFE; + stmmac_restore_hw_vlan_rx_fltr(priv, dev, hw); + } writel(value, ioaddr + XGMAC_PACKET_FILTER); } diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index d746064011ab8..308800906fbcd 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -160,7 +160,6 @@ static void stmmac_reset_tx_queue(struct stmmac_priv *priv, u32 queue); static void stmmac_reset_queues_param(struct stmmac_priv *priv); static void stmmac_tx_timer_arm(struct stmmac_priv *priv, u32 queue); static void stmmac_flush_tx_descriptors(struct stmmac_priv *priv, int queue); -static void stmmac_vlan_restore(struct stmmac_priv *priv); #ifdef CONFIG_DEBUG_FS static const struct net_device_ops stmmac_netdev_ops; @@ -4268,8 +4267,6 @@ static int __stmmac_open(struct net_device *dev, phylink_start(priv->phylink); - stmmac_vlan_restore(priv); - ret = stmmac_request_irq(dev); if (ret) goto irq_error; @@ -7225,17 +7222,6 @@ del_vlan_error: return ret; } -static void stmmac_vlan_restore(struct stmmac_priv *priv) -{ - if (!(priv->dev->features & NETIF_F_VLAN_FEATURES)) - return; - - if (priv->hw->num_vlan) - stmmac_restore_hw_vlan_rx_fltr(priv, priv->dev, priv->hw); - - stmmac_vlan_update(priv, priv->num_svlans); -} - static int stmmac_bpf(struct net_device *dev, struct netdev_bpf *bpf) { struct stmmac_priv *priv = netdev_priv(dev); @@ -9124,8 +9110,6 @@ int stmmac_resume(struct device *dev) stmmac_set_rx_mode(ndev); phylink_rx_clk_stop_unblock(priv->phylink); - stmmac_vlan_restore(priv); - stmmac_enable_all_queues(priv); stmmac_enable_all_dma_irq(priv); diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c index 293589addf2d7..24246de499a62 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c @@ -51,9 +51,9 @@ static int vlan_write_filter(struct net_device *dev, writel(val, ioaddr + VLAN_TAG); - ret = readl_poll_timeout(ioaddr + VLAN_TAG, val, - !(val & VLAN_TAG_CTRL_OB), - 1000, 500000); + ret = readl_poll_timeout_atomic(ioaddr + VLAN_TAG, val, + !(val & VLAN_TAG_CTRL_OB), + 1000, 500000); if (ret) { netdev_err(dev, "Timeout accessing MAC_VLAN_Tag_Filter\n"); return -EBUSY; @@ -175,28 +175,13 @@ static int vlan_del_hw_rx_fltr(struct net_device *dev, return 0; } -static void vlan_restore_hw_rx_fltr(struct net_device *dev, - struct mac_device_info *hw) -{ - int i; - - /* Single Rx VLAN Filter */ - if (hw->num_vlan == 1) { - vlan_write_single(dev, hw->vlan_filter[0]); - return; - } - - /* Extended Rx VLAN Filter Enable */ - for (i = 0; i < hw->num_vlan; i++) - vlan_write_filter(dev, hw, i, hw->vlan_filter[i]); -} - static void vlan_update_hash(struct mac_device_info *hw, u32 hash, bool is_svlan) { void __iomem *ioaddr = hw->pcsr; u32 value; + hw->vlan_hash = hash; writel(hash, ioaddr + VLAN_HASH_TABLE); value = readl(ioaddr + VLAN_TAG); @@ -204,13 +189,16 @@ static void vlan_update_hash(struct mac_device_info *hw, u32 hash, if (hash) { value |= VLAN_VTHM | VLAN_ETV; if (is_svlan) { + hw->vlan_stag = true; value |= VLAN_ESVL; value |= VLAN_DOVLTC; } else { + hw->vlan_stag = false; value &= ~VLAN_ESVL; value &= ~VLAN_DOVLTC; } } else { + hw->vlan_stag = false; value &= ~(VLAN_VTHM | VLAN_ETV); value &= ~VLAN_ESVL; value &= ~VLAN_DOVLTC; @@ -222,6 +210,25 @@ static void vlan_update_hash(struct mac_device_info *hw, u32 hash, writel(value, ioaddr + VLAN_TAG); } +static void vlan_restore_hw_rx_fltr(struct net_device *dev, + struct mac_device_info *hw) +{ + int i; + + /* Hash-based Rx VLAN Filter */ + vlan_update_hash(hw, hw->vlan_hash, hw->vlan_stag); + + /* Single Rx VLAN Filter */ + if (hw->num_vlan == 1) { + vlan_write_single(dev, hw->vlan_filter[0]); + return; + } + + /* Extended Rx VLAN Filter Enable */ + for (i = 0; i < hw->num_vlan; i++) + vlan_write_filter(dev, hw, i, hw->vlan_filter[i]); +} + static void vlan_enable(struct mac_device_info *hw, u32 type) { void __iomem *ioaddr = hw->pcsr; -- cgit v1.2.3 From b7f24fb3203e66c18e6e166bb6afdc37d010ca0c Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 28 Mar 2025 21:34:15 +0300 Subject: net: stmmac: vlan: Add VLAN S-TAG Insertion Offload support Originally the VLAN Insertion Offload feature was configured to always insert S-VLAN type due to the MAC_VLAN_Incl.CSVL flag being always set. So as long as there was at least one 802.1ad virtual interface created any other virtual VLAN-interface would have generated S-TAG'ed frames, which was caused by having both MAC_VLAN_Incl.CSVL and MAC_VLAN_Tag.ESVL flags set. The former flag wouldn't work without the later one - that's why at least one 802.1ad virtual interface needed to be created to meet the problem. That has been fixed in the commit c657f86106c8 ("net: stmmac: vlan: Disable 802.1AD tag insertion offload") just by removing the VLAN S-TAG Insertion support from the driver with justification that the controller doesn't support inserting both VLAN C-TAG and S-TAG at a time. That is only one VLAN tag type can be enabled at a time. That's true but it doesn't mean the feature should have been just deleted. The driver can be enabled to support only one of these tags type insertion at a time. That can be easily achieved by having the networking core net_device_ops::{ndo_fix_features(),ndo_set_features()} callbacks utilized. So let's use them to make sure that only one of VLAN tags type insertion is enabled at a time - NETIF_F_HW_VLAN_CTAG_TX or NETIF_F_HW_VLAN_STAG_TX. The MAC_VLAN_Incl.CSVL flag will be enabled only if the NETIF_F_HW_VLAN_STAG_TX feature is requested. Please note if there is no 802.1ad virtual interface (basically no S-TAG filter enabled) it won't be possible to generate S-TAG'ed frames no mater what due to the VLAN_TAG.ESVL flag cleared. This will be fixed in another commit sometime later. Fixes: c657f86106c8 ("net: stmmac: vlan: Disable 802.1AD tag insertion offload") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/hwif.h | 5 +-- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 23 +++++++++----- drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c | 37 ++++++----------------- 3 files changed, 27 insertions(+), 38 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index e4b28b3a62ccf..45111ec4902f4 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -630,10 +630,9 @@ struct stmmac_vlan_ops { /* VLAN */ void (*update_vlan_hash)(struct mac_device_info *hw, u32 hash, bool is_svlan); - void (*enable_vlan)(struct mac_device_info *hw, u32 type); void (*rx_hw_vlan)(struct mac_device_info *hw, struct dma_desc *rx_desc, struct sk_buff *skb); - void (*set_hw_vlan_mode)(struct mac_device_info *hw); + void (*set_hw_vlan_mode)(struct mac_device_info *hw, bool tx_stag); int (*add_hw_vlan_rx_fltr)(struct net_device *dev, struct mac_device_info *hw, __be16 proto, u16 vid); @@ -646,8 +645,6 @@ struct stmmac_vlan_ops { #define stmmac_update_vlan_hash(__priv, __args...) \ stmmac_do_void_callback(__priv, vlan, update_vlan_hash, __args) -#define stmmac_enable_vlan(__priv, __args...) \ - stmmac_do_void_callback(__priv, vlan, enable_vlan, __args) #define stmmac_rx_hw_vlan(__priv, __args...) \ stmmac_do_void_callback(__priv, vlan, rx_hw_vlan, __args) #define stmmac_set_hw_vlan_mode(__priv, __args...) \ diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 308800906fbcd..0c963a4601f4c 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -3873,10 +3873,6 @@ static int stmmac_hw_setup(struct net_device *dev) for (chan = 0; chan < rx_cnt; chan++) stmmac_enable_sph(priv, priv->ioaddr, priv->sph_active, chan); - /* VLAN Tag Insertion */ - if (priv->dma_cap.vlins) - stmmac_enable_vlan(priv, priv->hw, STMMAC_VLAN_INSERT); - /* Enable TSO and/or TBS */ for (chan = 0; chan < tx_cnt; chan++) { struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[chan]; @@ -3896,7 +3892,8 @@ static int stmmac_hw_setup(struct net_device *dev) stmmac_start_all_dma(priv); phylink_rx_clk_stop_block(priv->phylink); - stmmac_set_hw_vlan_mode(priv, priv->hw); + stmmac_set_hw_vlan_mode(priv, priv->hw, + dev->features & NETIF_F_HW_VLAN_STAG_TX); phylink_rx_clk_stop_unblock(priv->phylink); return 0; @@ -6469,6 +6466,12 @@ static netdev_features_t stmmac_fix_features(struct net_device *dev, features |= NETIF_F_HW_VLAN_CTAG_RX; } + /* VLAN type insertion is mutually exclusive: either CTAG or STAG */ + if (features & NETIF_F_HW_VLAN_CTAG_TX) + features &= ~NETIF_F_HW_VLAN_STAG_TX; + else if (features & NETIF_F_HW_VLAN_STAG_TX) + features &= ~NETIF_F_HW_VLAN_CTAG_TX; + return features; } @@ -6511,7 +6514,8 @@ static int stmmac_set_features(struct net_device *netdev, priv->hw->hw_vlan_en = !!(features & NETIF_F_HW_VLAN_CTAG_RX); phylink_rx_clk_stop_block(priv->phylink); - stmmac_set_hw_vlan_mode(priv, priv->hw); + stmmac_set_hw_vlan_mode(priv, priv->hw, + features & NETIF_F_HW_VLAN_STAG_TX); phylink_rx_clk_stop_unblock(priv->phylink); return 0; @@ -8769,8 +8773,13 @@ static int __stmmac_dvr_probe(struct device *device, ndev->features |= NETIF_F_HW_VLAN_CTAG_FILTER; ndev->features |= NETIF_F_HW_VLAN_STAG_FILTER; } - if (priv->dma_cap.vlins) + + /* VLAN Insertion feature supports only one type at a time */ + if (priv->dma_cap.vlins) { ndev->features |= NETIF_F_HW_VLAN_CTAG_TX; + ndev->hw_features |= NETIF_F_HW_VLAN_CTAG_TX | + NETIF_F_HW_VLAN_STAG_TX; + } #endif priv->msg_enable = netif_msg_init(debug, default_msg_level); diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c index 24246de499a62..c7cc2ee22366a 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c @@ -229,19 +229,6 @@ static void vlan_restore_hw_rx_fltr(struct net_device *dev, vlan_write_filter(dev, hw, i, hw->vlan_filter[i]); } -static void vlan_enable(struct mac_device_info *hw, u32 type) -{ - void __iomem *ioaddr = hw->pcsr; - u32 value; - - value = readl(ioaddr + VLAN_INCL); - value |= VLAN_VLTI; - value &= ~VLAN_CSVL; /* Only use CVLAN */ - value &= ~VLAN_VLC; - value |= (type << VLAN_VLC_SHIFT) & VLAN_VLC; - writel(value, ioaddr + VLAN_INCL); -} - static void vlan_rx_hw(struct mac_device_info *hw, struct dma_desc *rx_desc, struct sk_buff *skb) { @@ -252,7 +239,7 @@ static void vlan_rx_hw(struct mac_device_info *hw, } } -static void vlan_set_hw_mode(struct mac_device_info *hw) +static void vlan_set_hw_mode(struct mac_device_info *hw, bool tx_stag) { void __iomem *ioaddr = hw->pcsr; u32 value = readl(ioaddr + VLAN_TAG); @@ -272,22 +259,19 @@ static void vlan_set_hw_mode(struct mac_device_info *hw) /* Enable outer VLAN Tag in Rx DMA descriptor */ value |= VLAN_TAG_CTRL_EVLRXS; writel(value, ioaddr + VLAN_TAG); -} -static void dwxlgmac2_vlan_set_hw_mode(struct mac_device_info *hw) -{ - void __iomem *ioaddr = hw->pcsr; - u32 value = readl(ioaddr + VLAN_TAG); - - /* Activate Double VLAN for Rx COE */ - value |= VLAN_EDVLP; - - writel(value, ioaddr + VLAN_TAG); + /* Set Tx VLAN Insertion feature (might be unavailable) */ + value = readl(ioaddr + VLAN_INCL); + value |= VLAN_VLTI; + if (tx_stag) + value |= VLAN_CSVL; + else + value &= ~VLAN_CSVL; + writel(value, ioaddr + VLAN_INCL); } const struct stmmac_vlan_ops dwmac4_vlan_ops = { .update_vlan_hash = vlan_update_hash, - .enable_vlan = vlan_enable, .add_hw_vlan_rx_fltr = vlan_add_hw_rx_fltr, .del_hw_vlan_rx_fltr = vlan_del_hw_rx_fltr, .restore_hw_vlan_rx_fltr = vlan_restore_hw_rx_fltr, @@ -297,8 +281,7 @@ const struct stmmac_vlan_ops dwmac4_vlan_ops = { const struct stmmac_vlan_ops dwxlgmac2_vlan_ops = { .update_vlan_hash = vlan_update_hash, - .set_hw_vlan_mode = dwxlgmac2_vlan_set_hw_mode, - .enable_vlan = vlan_enable, + .set_hw_vlan_mode = vlan_set_hw_mode, }; u32 stmmac_get_num_vlan(void __iomem *ioaddr) -- cgit v1.2.3 From 95abad53e336074aed686ad64f37281ae1114e61 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 10 Apr 2025 10:43:56 +0300 Subject: net: stmmac: vlan: Enable VLAN Tags Insertion Offload in TSO The commit 041cc86b3653 ("net: stmmac: Enable TSO on VLANs") stated that the TSO feature was malfunction on DW QoS Eth and DW XGMAC for the VLAN tagged frames. In particular the commit log claims that the very first TCP segment is VLAN tagged, but the proceeding ones aren't. This hasn't been confirmed at least on a DW XGMAC 2.11a device. All the transferred TCP/IPv4 segments are VLAN tagged. Moreover the blamed commit stated that it enabled TSO for VLANs while it actually disabled it. Instead the driver now just inserts the VLAN TPID/TCI fields to the payload. It's also strange to see that the HW-accelerated VLAN IDs were actually specified for the TSO SKBs since before that commit the NETIF_F_TSO flag had been cleared for the VLAN interfaces. All of that makes me thinking that if there was a problem it has been purely investigated. Revert the change until the actually broken hardware is met. Fixes: 041cc86b3653 ("net: stmmac: Enable TSO on VLANs") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 0c963a4601f4c..f120c64a8bf35 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -4609,9 +4609,9 @@ static void stmmac_flush_tx_descriptors(struct stmmac_priv *priv, int queue) */ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev) { + bool has_vlan, set_ic, is_last_segment, drop = false; struct dma_desc *desc, *first, *mss_desc = NULL; struct stmmac_priv *priv = netdev_priv(dev); - bool set_ic, is_last_segment, drop = false; unsigned int first_entry, tx_packets; struct stmmac_txq_stats *txq_stats; netdev_tx_t ret = NETDEV_TX_OK; @@ -4622,19 +4622,6 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev) u8 proto_hdr_len, hdr; dma_addr_t des; - /* Always insert VLAN tag to SKB payload for TSO frames. - * - * Never insert VLAN tag by HW, since segments split by - * TSO engine will be un-tagged by mistake. - */ - if (skb_vlan_tag_present(skb)) { - skb = __vlan_hwaccel_push_inside(skb); - if (unlikely(!skb)) { - priv->xstats.tx_dropped++; - return NETDEV_TX_OK; - } - } - nfrags = skb_shinfo(skb)->nr_frags; queue = skb_get_queue_mapping(skb); @@ -4685,6 +4672,8 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev) skb->data_len); } + has_vlan = stmmac_vlan_insert(priv, skb, tx_q); + first_entry = tx_q->cur_tx; WARN_ON(tx_q->tx_skbuff[first_entry]); @@ -4694,6 +4683,9 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev) desc = &tx_q->dma_tx[first_entry]; first = desc; + if (has_vlan) + stmmac_set_desc_vlan(priv, first, STMMAC_VLAN_INSERT); + /* first descriptor: fill Headers on Buf1 */ des = dma_map_single(priv->device, skb->data, skb_headlen(skb), DMA_TO_DEVICE); -- cgit v1.2.3 From 58cf7d6e3073700c2c34186a2fec3afcac332411 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 27 Mar 2025 14:40:42 +0300 Subject: net: stmmac: vlan: Convert to being agnostic from hw::num_vlan Indeed there has been no need in adding the stmmac_vlan_ops::add_hw_vlan_rx_fltr() and stmmac_vlan_ops::del_hw_vlan_rx_fltr() execution dependent from the mac_device_info::num_vlan field value. If the VLAN perfect filters feature is supported by the driver (DW QoS Ether and DW XGMAC) then mac_device_info::num_vlan will be always initialized at least with one. That's because the modern IP-cores (DW QoS Ether and DW XGMAC) always support at least a Basic VLAN perfect filter engine. If the driver doesn't support the VLAN perfect filters functionality for the particular IP-core then the denoted callbacks just won't be available and their execution attempt will cause the -EINVAL error returned. Thus let's just correctly parse the return value of the methods call and drop the mac_device_info::num_vlan conditional statement. This change is a short preparation before adding a comprehensive VLAN-engine support to the driver. Signed-off-by: Serge Semin --- Note if the dma_features::nrvf_num was accessible in the VLAN-related callbacks the mac_device_info::num_vlan field could have been completely dropped as redundant. Do that when it is. --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 24 ++++++++++------------- drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c | 4 ++-- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index f120c64a8bf35..ae2dea6b3d3c0 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -7159,13 +7159,11 @@ static int stmmac_vlan_rx_add_vid(struct net_device *ndev, __be16 proto, u16 vid goto err_pm_put; } - if (priv->hw->num_vlan) { - ret = stmmac_add_hw_vlan_rx_fltr(priv, ndev, priv->hw, proto, vid); - if (ret) { - clear_bit(vid, priv->active_vlans); - stmmac_vlan_update(priv, priv->num_svlans); - goto err_pm_put; - } + ret = stmmac_add_hw_vlan_rx_fltr(priv, ndev, priv->hw, proto, vid); + if (ret && ret != -EINVAL) { + clear_bit(vid, priv->active_vlans); + stmmac_vlan_update(priv, priv->num_svlans); + goto err_pm_put; } priv->num_svlans = num_svlans; @@ -7201,13 +7199,11 @@ static int stmmac_vlan_rx_kill_vid(struct net_device *ndev, __be16 proto, u16 vi goto del_vlan_error; } - if (priv->hw->num_vlan) { - ret = stmmac_del_hw_vlan_rx_fltr(priv, ndev, priv->hw, proto, vid); - if (ret) { - set_bit(vid, priv->active_vlans); - stmmac_vlan_update(priv, priv->num_svlans); - goto del_vlan_error; - } + ret = stmmac_del_hw_vlan_rx_fltr(priv, ndev, priv->hw, proto, vid); + if (ret && ret != -EINVAL) { + set_bit(vid, priv->active_vlans); + stmmac_vlan_update(priv, priv->num_svlans); + goto del_vlan_error; } priv->num_svlans = num_svlans; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c index c7cc2ee22366a..77fe0ea31c331 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c @@ -31,7 +31,7 @@ static int vlan_write_filter(struct net_device *dev, u32 val; if (index >= hw->num_vlan) - return -EINVAL; + return -ENOSPC; /* Indirect VLAN Tag filter access inteface requires PHY RXC clock to * be running. Retry when the interface is up. @@ -72,7 +72,7 @@ static int vlan_add_hw_rx_fltr(struct net_device *dev, int i, ret; if (vid > 4095) - return -EINVAL; + return -ENXIO; is_stag = proto == htons(ETH_P_8021AD); -- cgit v1.2.3 From 523a423a3ec0ad5725f1fa881e6986339f3e7a38 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Tue, 8 Apr 2025 23:52:37 +0300 Subject: net: stmmac: vlan: Drop redundant mac_device_info::hw_vlan_en flag The flag has been introduced in the commit 750011e239a5 ("net: stmmac: Add support for HW-accelerated VLAN stripping") in order to logically split up the HW-offloaded VLAN tag stripping and the stripping implemented in the driver. As the commit log says it's supposed to be in sync with the NETIF_F_HW_VLAN_CTAG_RX feature-flag, otherwise the software-based VLAN tag stripping will be performed. First of all the denoted semantics is incorrect, since the MAC and driver-based VLAN tag stripping are mutually exclusive features and are supposed to be enabled/disabled by the NETIF_F_HW_VLAN_CTAG_RX/NETIF_F_HW_VLAN_STAG_RX feature flags. Secondly the flags can be directly retrieved from the net_device::features field anytime in the driver. So introducing another mirror-flag for that was unnecessary. Finally the MAC-based VLAN tag stripping is always persistent in the DW *MAC IP-cores supporting VLANs (at least DW Ether QoS and DW XGMAC). Thus as long as the HW-offloaded VLAN tag stripping is implemented in the respective VLAN driver it can be utilized instead of the software-based tag stripping code. So to speak let's drop the mac_device_info::hw_vlan_en flag and just switch on/off the VLAN tags stripping feature based on the respective flags set in the net_device::features field. Please note that since the VLAN-based features are available on the kernel with the CONFIG_VLAN_8021Q config enabled it is pointless to parse VLAN Ethernet-header if the config is disable. In that case the entire stmmac_get_rx_vlan() body can be compiled off thus speeding up the Rx fast-path of the driver. Note as a nice side effect of this change the VLAN tag stripping feature is now capable to be switched on/off by the system administrator irrespective whether it's HW-offloaded or not. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/common.h | 1 - drivers/net/ethernet/stmicro/stmmac/hwif.h | 3 +- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 58 ++++++++++++++--------- drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c | 5 +- 4 files changed, 40 insertions(+), 27 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/common.h b/drivers/net/ethernet/stmicro/stmmac/common.h index 8403a3e172110..dff7a12047a2b 100644 --- a/drivers/net/ethernet/stmicro/stmmac/common.h +++ b/drivers/net/ethernet/stmicro/stmmac/common.h @@ -661,7 +661,6 @@ struct mac_device_info { u32 vlan_filter[32]; bool vlan_fail_q_en; u8 vlan_fail_q; - bool hw_vlan_en; bool reverse_sgmii_enable; /* This spinlock protects read-modify-write of the interrupt diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index 45111ec4902f4..6996fe2e13361 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -632,7 +632,8 @@ struct stmmac_vlan_ops { bool is_svlan); void (*rx_hw_vlan)(struct mac_device_info *hw, struct dma_desc *rx_desc, struct sk_buff *skb); - void (*set_hw_vlan_mode)(struct mac_device_info *hw, bool tx_stag); + void (*set_hw_vlan_mode)(struct mac_device_info *hw, bool rx_strip, + bool tx_stag); int (*add_hw_vlan_rx_fltr)(struct net_device *dev, struct mac_device_info *hw, __be16 proto, u16 vid); diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index ae2dea6b3d3c0..47de06bbde2ff 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -3893,6 +3893,8 @@ static int stmmac_hw_setup(struct net_device *dev) phylink_rx_clk_stop_block(priv->phylink); stmmac_set_hw_vlan_mode(priv, priv->hw, + dev->features & (NETIF_F_HW_VLAN_CTAG_RX | + NETIF_F_HW_VLAN_STAG_RX), dev->features & NETIF_F_HW_VLAN_STAG_TX); phylink_rx_clk_stop_unblock(priv->phylink); @@ -5158,22 +5160,43 @@ static int stmmac_get_rx_status(struct stmmac_priv *priv, struct dma_desc *p) return ret | discard_frame; } -static void stmmac_rx_vlan(struct net_device *dev, struct sk_buff *skb) +/** + * stmmac_get_rx_vlan - Extract VLAN ID from the frame + * @priv: driver private structure + * @p : descriptor pointer + * @skb : the socket buffer + * Description : + * This function will either read the VLAN ID and TCI from the DMA-descriptor + * (if it's supported the VLAN stripping has been enabled) or just perform + * the VLAN header stripping directly from the payload. + */ +static void stmmac_get_rx_vlan(struct stmmac_priv *priv, struct dma_desc *p, + struct sk_buff *skb) { +#ifdef STMMAC_VLAN_TAG_USED struct vlan_ethhdr *veth = skb_vlan_eth_hdr(skb); - __be16 vlan_proto = veth->h_vlan_proto; + __be16 vlan_proto; u16 vlanid; + int ret; + + /* MAC level stripping */ + ret = stmmac_rx_hw_vlan(priv, priv->hw, p, skb); + if (!ret) + return; + /* Driver level stripping */ + vlan_proto = veth->h_vlan_proto; if ((vlan_proto == htons(ETH_P_8021Q) && - dev->features & NETIF_F_HW_VLAN_CTAG_RX) || + priv->dev->features & NETIF_F_HW_VLAN_CTAG_RX) || (vlan_proto == htons(ETH_P_8021AD) && - dev->features & NETIF_F_HW_VLAN_STAG_RX)) { + priv->dev->features & NETIF_F_HW_VLAN_STAG_RX)) { /* pop the vlan tag */ vlanid = ntohs(veth->h_vlan_TCI); memmove(skb->data + VLAN_HLEN, veth, ETH_ALEN * 2); skb_pull(skb, VLAN_HLEN); __vlan_hwaccel_put_tag(skb, vlan_proto, vlanid); } +#endif } /** @@ -5639,12 +5662,9 @@ static void stmmac_dispatch_skb_zc(struct stmmac_priv *priv, u32 queue, } stmmac_get_rx_hwtstamp(priv, p, np, skb); - if (priv->hw->hw_vlan_en) - /* MAC level stripping. */ - stmmac_rx_hw_vlan(priv, priv->hw, p, skb); - else - /* Driver level stripping. */ - stmmac_rx_vlan(priv->dev, skb); + + stmmac_get_rx_vlan(priv, p, skb); + skb->protocol = eth_type_trans(skb, priv->dev); stmmac_get_rx_csum(priv, status, skb); @@ -6133,12 +6153,7 @@ drain_data: stmmac_get_rx_hwtstamp(priv, p, np, skb); - if (priv->hw->hw_vlan_en) - /* MAC level stripping. */ - stmmac_rx_hw_vlan(priv, priv->hw, p, skb); - else - /* Driver level stripping. */ - stmmac_rx_vlan(priv->dev, skb); + stmmac_get_rx_vlan(priv, p, skb); skb->protocol = eth_type_trans(skb, priv->dev); @@ -6502,11 +6517,10 @@ static int stmmac_set_features(struct net_device *netdev, features & NETIF_F_RXHASH, priv->plat->rx_queues_to_use); - if (netdev->hw_features & NETIF_F_HW_VLAN_CTAG_RX) - priv->hw->hw_vlan_en = !!(features & NETIF_F_HW_VLAN_CTAG_RX); - phylink_rx_clk_stop_block(priv->phylink); stmmac_set_hw_vlan_mode(priv, priv->hw, + features & (NETIF_F_HW_VLAN_CTAG_RX | + NETIF_F_HW_VLAN_STAG_RX), features & NETIF_F_HW_VLAN_STAG_TX); phylink_rx_clk_stop_unblock(priv->phylink); @@ -8753,10 +8767,8 @@ static int __stmmac_dvr_probe(struct device *device, #ifdef STMMAC_VLAN_TAG_USED /* Both mac100 and gmac support receive VLAN tag detection */ ndev->features |= NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_HW_VLAN_STAG_RX; - if (dwmac_is_xmac(priv->plat->core_type)) { - ndev->hw_features |= NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_HW_VLAN_STAG_RX; - priv->hw->hw_vlan_en = true; - } + ndev->hw_features |= NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_HW_VLAN_STAG_RX; + if (priv->dma_cap.vlhash) { ndev->features |= NETIF_F_HW_VLAN_CTAG_FILTER; ndev->features |= NETIF_F_HW_VLAN_STAG_FILTER; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c index 77fe0ea31c331..bb05219784534 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c @@ -239,14 +239,15 @@ static void vlan_rx_hw(struct mac_device_info *hw, } } -static void vlan_set_hw_mode(struct mac_device_info *hw, bool tx_stag) +static void vlan_set_hw_mode(struct mac_device_info *hw, bool rx_strip, + bool tx_stag) { void __iomem *ioaddr = hw->pcsr; u32 value = readl(ioaddr + VLAN_TAG); value &= ~VLAN_TAG_CTRL_EVLS_MASK; - if (hw->hw_vlan_en) + if (rx_strip) /* Always strip VLAN on Receive */ value |= VLAN_TAG_STRIP_ALL; else -- cgit v1.2.3 From 3c449f1bfdeca7713316f15fbac70ab1fd0b4156 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 9 Apr 2025 14:24:36 +0300 Subject: net: stmmac: vlan: Enable S-VLAN feature on device open The MAC_VLAN_Tag.ESVL flag is responsible for the S-VLAN feature activation in the MAC. Here is the bit description: > When this bit is set, the MAC transmitter and receiver consider the > S-VLAN packets (Type = 0x88A8) as valid VLAN tagged packets. From that perspective the flag must be set if S-TAG is supposed to be met in the incoming or outcoming traffic. Thus it must be set if the HW-offloaded S-VLAN is required on any path. But currently it is set on the Rx S-VLAN filter activation only. It's not that a big problem, since normally the S-TAG-ed frames are expected in both directions. Thus if the S-TAG filter feature isn't enabled no S-TAGed outbound frames will be generated. But from the scalability and maintainability perspectives it's better to set the flag in a centralized way if any of the HW-offloaded S-VLAN feature is requested. Let's do that by moving the bit handling to the stmmac_set_hw_vlan_mode() method. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/hwif.h | 2 +- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 2 ++ drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c | 11 +++++++---- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index 6996fe2e13361..a46e819aec296 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -633,7 +633,7 @@ struct stmmac_vlan_ops { void (*rx_hw_vlan)(struct mac_device_info *hw, struct dma_desc *rx_desc, struct sk_buff *skb); void (*set_hw_vlan_mode)(struct mac_device_info *hw, bool rx_strip, - bool tx_stag); + bool rx_stag, bool tx_stag); int (*add_hw_vlan_rx_fltr)(struct net_device *dev, struct mac_device_info *hw, __be16 proto, u16 vid); diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 47de06bbde2ff..1a6d2f6335b57 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -3895,6 +3895,7 @@ static int stmmac_hw_setup(struct net_device *dev) stmmac_set_hw_vlan_mode(priv, priv->hw, dev->features & (NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_HW_VLAN_STAG_RX), + dev->features & NETIF_F_HW_VLAN_STAG_FILTER, dev->features & NETIF_F_HW_VLAN_STAG_TX); phylink_rx_clk_stop_unblock(priv->phylink); @@ -6521,6 +6522,7 @@ static int stmmac_set_features(struct net_device *netdev, stmmac_set_hw_vlan_mode(priv, priv->hw, features & (NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_HW_VLAN_STAG_RX), + features & NETIF_F_HW_VLAN_STAG_FILTER, features & NETIF_F_HW_VLAN_STAG_TX); phylink_rx_clk_stop_unblock(priv->phylink); diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c index bb05219784534..64270920de918 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c @@ -190,17 +190,14 @@ static void vlan_update_hash(struct mac_device_info *hw, u32 hash, value |= VLAN_VTHM | VLAN_ETV; if (is_svlan) { hw->vlan_stag = true; - value |= VLAN_ESVL; value |= VLAN_DOVLTC; } else { hw->vlan_stag = false; - value &= ~VLAN_ESVL; value &= ~VLAN_DOVLTC; } } else { hw->vlan_stag = false; value &= ~(VLAN_VTHM | VLAN_ETV); - value &= ~VLAN_ESVL; value &= ~VLAN_DOVLTC; } @@ -240,7 +237,7 @@ static void vlan_rx_hw(struct mac_device_info *hw, } static void vlan_set_hw_mode(struct mac_device_info *hw, bool rx_strip, - bool tx_stag) + bool rx_stag, bool tx_stag) { void __iomem *ioaddr = hw->pcsr; u32 value = readl(ioaddr + VLAN_TAG); @@ -254,6 +251,12 @@ static void vlan_set_hw_mode(struct mac_device_info *hw, bool rx_strip, /* Do not strip VLAN on Receive */ value |= VLAN_TAG_STRIP_NONE; + /* Activate S-VLAN feature on MAC Tx and Rx */ + if (rx_strip || rx_stag || tx_stag) + value |= VLAN_ESVL; + else + value &= ~VLAN_ESVL; + /* Activate Double VLAN for Rx COE */ value |= VLAN_EDVLP; -- cgit v1.2.3 From f685ed0a3ac7e1f32a16b5eb0db14be201519473 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 9 Apr 2025 16:20:38 +0300 Subject: net: stmmac: vlan: Prevent redundant desc allocation for VLAN tags Both DW GMAC4 and DW XGMAC HW-manuals claim that the VLAN tags specified via the context descriptors are stored in the DMA-engine and re-used on the next packet marked as VLAN tagged: > The VLAN Tag IDs and MSS values, provided by the application in a > context descriptor with their corresponding Valid bits set, are stored > internally by the DMA. When the outer or inner VLAN tag is provided > with the Valid bit set, the DMA always passes the last valid VLAN tag to > the MTL. The application cannot invalidate the valid VLAN tag stored by > the DMA. The VLAN tag is inserted or replaced based on the control > inputs provided for the packet. Thus it's redundant to allocate the context DMA-descriptor each time a VLAN tagged packet is transmitted in case if it' tag has already been stored by the DMA-engine of the respective queue. Let's cache the VLAN tag then and skip the allocation if a frame with the same tag is specified. This shall improve the xfer performance and reduce the DMA-descriptors consumption rate. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac.h | 1 + drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h index b1188d897936a..7ac58e01d846b 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h @@ -119,6 +119,7 @@ struct stmmac_tx_queue { dma_addr_t dma_tx_phy; dma_addr_t tx_tail_addr; u32 mss; + u16 tci; u16 ttc; }; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 1a6d2f6335b57..9dad81d3ffea8 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -4406,6 +4406,13 @@ static bool stmmac_vlan_insert(struct stmmac_priv *priv, struct sk_buff *skb, tag = skb_vlan_tag_get(skb); + /* The previously set outer VLAN Tag is stored in the DMA-engine. + * Skip setting it again if it is the same is in the previous xfers. + * Note TPID is selected by the activated Tx CTAG/STAG feature. + */ + if (tx_q->tci == tag) + return true; + if (tx_q->tbs & STMMAC_TBS_AVAIL) p = &tx_q->dma_entx[tx_q->cur_tx].basic; else @@ -4416,6 +4423,7 @@ static bool stmmac_vlan_insert(struct stmmac_priv *priv, struct sk_buff *skb, stmmac_set_tx_owner(priv, p); tx_q->cur_tx = STMMAC_GET_ENTRY(tx_q->cur_tx, priv->dma_conf.dma_tx_size); + tx_q->tci = tag; return true; } @@ -9025,6 +9033,7 @@ static void stmmac_reset_tx_queue(struct stmmac_priv *priv, u32 queue) tx_q->cur_tx = 0; tx_q->dirty_tx = 0; tx_q->mss = 0; + tx_q->tci = 0; netdev_tx_reset_queue(netdev_get_tx_queue(priv->dev, queue)); } -- cgit v1.2.3 From dd88704ca02f19b5a755b3cfd1ea8d4ffb16f234 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 16 Apr 2025 16:38:57 +0300 Subject: net: stmmac: vlan: Improve hash and perfect VLAN filters security By default all DW GMAC v3.70, DW QoS Ether and DW XGMAC IP-cores are equipped with a single perfect VLAN-tag filter tunable to filter C- and/or S-Tags. Optionally a hash-based VLAN-filter can be synthesized into the IP-cores. But both the basic perfect filter and the hash-based filter are configured by using the same CSR - MAC_VLAN_Tag. So if S-VLAN type tags filtering enabled, it will be applicable for both basic and hash-based filters. From that perspective it would be better to synchronize both types of the filters utilization. Moreover currently the filters configuration is someway insecure. If at least one S-VLAN tags is added to the hash-based filter, then the VLAN type checking will be fully disabled. Thus both C- and S-VLAN tags would be permitted even if S-VLAN tags filtering is only requested. In addition to that the hash-based filter implementation keeps track a united list of the C- and S- tags. So if a tag of both of these tags added and some of the them is removed, then both of the tags filtering will be disabled. Even though it seems unlikely to have both C- and S-VLANs on the same wire, it will be still inappropriate behaviour. So let's fix all the misbehaviours above by improving the basic perfect and hash-based VLAN-filters configuration procedure. First the added/removed C- and S-Tags must be kept tracked in order to properly setup the filters. Second the hash-based filter configuration method must be altered to take these counters state into account. Third both the filters re-configuration must be done synchronously in case if any basic perfect or hash-based state is updated. Finally the core driver VLANs bitmap must be extended to preserve S-Tags too so not to disable the tags pair on one of the tags removal. Thus this shall provide the most optimal and secure basic and hash-based filters utilization. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/common.h | 3 +- drivers/net/ethernet/stmicro/stmmac/hwif.h | 2 +- drivers/net/ethernet/stmicro/stmmac/stmmac.h | 6 +- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 90 +++++++++++++---------- drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c | 44 ++++++----- drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.h | 1 + 6 files changed, 84 insertions(+), 62 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/common.h b/drivers/net/ethernet/stmicro/stmmac/common.h index dff7a12047a2b..4a811516474bc 100644 --- a/drivers/net/ethernet/stmicro/stmmac/common.h +++ b/drivers/net/ethernet/stmicro/stmmac/common.h @@ -657,7 +657,8 @@ struct mac_device_info { unsigned int xlgmac; unsigned int num_vlan; u16 vlan_hash; - bool vlan_stag; + int vlan_ctags; + int vlan_stags; u32 vlan_filter[32]; bool vlan_fail_q_en; u8 vlan_fail_q; diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index a46e819aec296..b8fd4b5273939 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -629,7 +629,7 @@ struct stmmac_est_ops { struct stmmac_vlan_ops { /* VLAN */ void (*update_vlan_hash)(struct mac_device_info *hw, u32 hash, - bool is_svlan); + int add_ctags, int add_stags); void (*rx_hw_vlan)(struct mac_device_info *hw, struct dma_desc *rx_desc, struct sk_buff *skb); void (*set_hw_vlan_mode)(struct mac_device_info *hw, bool rx_strip, diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h index 7ac58e01d846b..2053f62c67f2b 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h @@ -375,8 +375,10 @@ struct stmmac_priv { void __iomem *mmcaddr; void __iomem *ptpaddr; void __iomem *estaddr; - unsigned long active_vlans[BITS_TO_LONGS(VLAN_N_VID)]; - unsigned int num_svlans; + + DECLARE_BITMAP(active_cvlans, VLAN_N_VID); + DECLARE_BITMAP(active_svlans, VLAN_N_VID); + int sfty_irq; int sfty_ce_irq; int sfty_ue_irq; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 9dad81d3ffea8..d2006d90e7e96 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -7144,18 +7144,56 @@ static u32 stmmac_vid_crc32_le(__le16 vid_le) return crc; } -static int stmmac_vlan_update(struct stmmac_priv *priv, bool is_svlan) +static int stmmac_update_hw_vlan_rx_hash(struct stmmac_priv *priv, bool add, + __be16 proto, u16 vid) { + int add_ctags = 0, add_stags = 0; + unsigned long *active_vlans; u32 crc, hash = 0; - u16 vid = 0; + u16 vid_cpu; + int ret; + + if (proto == htons(ETH_P_8021Q)) { + active_vlans = priv->active_cvlans; + add_ctags = add ? 1 : -1; + } else { + active_vlans = priv->active_svlans; + add_stags = add ? 1 : -1; + } + + if (add) + ret = test_and_set_bit(vid, active_vlans); + else + ret = test_and_clear_bit(vid, active_vlans); + + if (ret == add) + return 0; - for_each_set_bit(vid, priv->active_vlans, VLAN_N_VID) { + for_each_set_bit(vid, priv->active_cvlans, VLAN_N_VID) { __le16 vid_le = cpu_to_le16(vid); crc = bitrev32(~stmmac_vid_crc32_le(vid_le)) >> 28; hash |= (1 << crc); } - return stmmac_update_vlan_hash(priv, priv->hw, hash, is_svlan); + for_each_set_bit(vid_cpu, priv->active_svlans, VLAN_N_VID) { + __le16 vid_le = cpu_to_le16(vid_cpu); + crc = bitrev32(~stmmac_vid_crc32_le(vid_le)) >> 28; + hash |= (1 << crc); + } + + ret = stmmac_update_vlan_hash(priv, priv->hw, hash, add_ctags, add_stags); + if (ret) + goto err_revert_bit; + + return 0; + +err_revert_bit: + if (add) + clear_bit(vid, active_vlans); + else + set_bit(vid, active_vlans); + + return ret; } /* FIXME: This may need RXC to be running, but it may be called with BH @@ -7164,33 +7202,19 @@ static int stmmac_vlan_update(struct stmmac_priv *priv, bool is_svlan) static int stmmac_vlan_rx_add_vid(struct net_device *ndev, __be16 proto, u16 vid) { struct stmmac_priv *priv = netdev_priv(ndev); - unsigned int num_svlans; - bool is_svlan = false; int ret; ret = pm_runtime_resume_and_get(priv->device); if (ret < 0) return ret; - if (be16_to_cpu(proto) == ETH_P_8021AD) - is_svlan = true; - - set_bit(vid, priv->active_vlans); - num_svlans = priv->num_svlans + is_svlan; - ret = stmmac_vlan_update(priv, num_svlans); - if (ret) { - clear_bit(vid, priv->active_vlans); + ret = stmmac_update_hw_vlan_rx_hash(priv, true, proto, vid); + if (ret) goto err_pm_put; - } ret = stmmac_add_hw_vlan_rx_fltr(priv, ndev, priv->hw, proto, vid); - if (ret && ret != -EINVAL) { - clear_bit(vid, priv->active_vlans); - stmmac_vlan_update(priv, priv->num_svlans); - goto err_pm_put; - } - - priv->num_svlans = num_svlans; + if (ret && ret != -EINVAL) + stmmac_update_hw_vlan_rx_hash(priv, false, proto, vid); err_pm_put: pm_runtime_put(priv->device); @@ -7204,33 +7228,19 @@ err_pm_put: static int stmmac_vlan_rx_kill_vid(struct net_device *ndev, __be16 proto, u16 vid) { struct stmmac_priv *priv = netdev_priv(ndev); - unsigned int num_svlans; - bool is_svlan = false; int ret; ret = pm_runtime_resume_and_get(priv->device); if (ret < 0) return ret; - if (be16_to_cpu(proto) == ETH_P_8021AD) - is_svlan = true; - - clear_bit(vid, priv->active_vlans); - num_svlans = priv->num_svlans - is_double; - ret = stmmac_vlan_update(priv, num_svlans); - if (ret) { - set_bit(vid, priv->active_vlans); - goto del_vlan_error; - } - ret = stmmac_del_hw_vlan_rx_fltr(priv, ndev, priv->hw, proto, vid); - if (ret && ret != -EINVAL) { - set_bit(vid, priv->active_vlans); - stmmac_vlan_update(priv, priv->num_svlans); + if (ret && ret != -EINVAL) goto del_vlan_error; - } - priv->num_svlans = num_svlans; + ret = stmmac_update_hw_vlan_rx_hash(priv, false, proto, vid); + if (ret) + stmmac_add_hw_vlan_rx_fltr(priv, ndev, priv->hw, proto, vid); del_vlan_error: pm_runtime_put(priv->device); diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c index 64270920de918..b6d4b2c7a2d3d 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c @@ -7,17 +7,19 @@ #include "stmmac.h" #include "stmmac_vlan.h" +static void vlan_update_hash(struct mac_device_info *hw, u32 hash, + int add_ctags, int add_stags); + static void vlan_write_single(struct net_device *dev, u32 data) { void __iomem *ioaddr = (void __iomem *)dev->base_addr; - u32 val, mask; + u32 val; - mask = VLAN_TAG_ERSVLM | VLAN_TAG_VID; if (!(data & VLAN_TAG_VID)) data |= VLAN_TAG_VID; - val = readl(ioaddr + VLAN_TAG) & ~mask; - val |= VLAN_TAG_ETV | (data & mask); + val = readl(ioaddr + VLAN_TAG) & ~VLAN_TAG_VID; + val |= VLAN_TAG_ETV | (data & VLAN_TAG_VID); writel(val, ioaddr + VLAN_TAG); } @@ -96,6 +98,8 @@ static int vlan_add_hw_rx_fltr(struct net_device *dev, return -EPERM; } + vlan_update_hash(hw, hw->vlan_hash, !is_stag, is_stag); + vlan_write_single(dev, val); hw->vlan_filter[0] = val; @@ -147,6 +151,8 @@ static int vlan_del_hw_rx_fltr(struct net_device *dev, if (is_stag != !!(hw->vlan_filter[0] & VLAN_TAG_ERSVLM)) return 0; + vlan_update_hash(hw, hw->vlan_hash, -!is_stag, -is_stag); + vlan_write_single(dev, 0); hw->vlan_filter[0] = 0; @@ -176,7 +182,7 @@ static int vlan_del_hw_rx_fltr(struct net_device *dev, } static void vlan_update_hash(struct mac_device_info *hw, u32 hash, - bool is_svlan) + int add_ctags, int add_stags) { void __iomem *ioaddr = hw->pcsr; u32 value; @@ -184,21 +190,23 @@ static void vlan_update_hash(struct mac_device_info *hw, u32 hash, hw->vlan_hash = hash; writel(hash, ioaddr + VLAN_HASH_TABLE); - value = readl(ioaddr + VLAN_TAG); + hw->vlan_ctags += add_ctags; + hw->vlan_stags += add_stags; + value = readl(ioaddr + VLAN_TAG) | VLAN_ETV; - if (hash) { - value |= VLAN_VTHM | VLAN_ETV; - if (is_svlan) { - hw->vlan_stag = true; - value |= VLAN_DOVLTC; - } else { - hw->vlan_stag = false; - value &= ~VLAN_DOVLTC; - } + if (hash) + value |= VLAN_VTHM; + else + value &= ~VLAN_VTHM; + + if (hw->vlan_ctags && hw->vlan_stags) { + value |= VLAN_DOVLTC; + } else if (hw->vlan_stags) { + value &= ~VLAN_DOVLTC; + value |= VLAN_ERSVLM; } else { - hw->vlan_stag = false; - value &= ~(VLAN_VTHM | VLAN_ETV); value &= ~VLAN_DOVLTC; + value &= ~VLAN_ERSVLM; } if (!hw->vlan_filter[0]) @@ -213,7 +221,7 @@ static void vlan_restore_hw_rx_fltr(struct net_device *dev, int i; /* Hash-based Rx VLAN Filter */ - vlan_update_hash(hw, hw->vlan_hash, hw->vlan_stag); + vlan_update_hash(hw, hw->vlan_hash, 0, 0); /* Single Rx VLAN Filter */ if (hw->num_vlan == 1) { diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.h b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.h index 3ee0022282f16..e38f570e3297e 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.h +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.h @@ -19,6 +19,7 @@ #define VLAN_EDVLP BIT(26) #define VLAN_VTHM BIT(25) #define VLAN_DOVLTC BIT(20) +#define VLAN_ERSVLM BIT(19) #define VLAN_ESVL BIT(18) #define VLAN_ETV BIT(16) #define VLAN_VID GENMASK(15, 0) -- cgit v1.2.3 From 2c6ad51635c5ca38a674bf33659db1ffb266dfa9 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 18 Apr 2025 18:56:39 +0300 Subject: net: stmmac: vlan: Convert VLAN filters feature to being switchable Currently if the kernel is built with the VLAN_8021Q config enabled the HW-offloaded VLAN packets filtering is fixed to be enabled and can't be disabled unless the Promiscuous mode is on. This can be fixed so the system administrators could switch the feature on and off when required by means of the ethtool -K command. The denoted functionality can be implemented by moving the MAC_Packet_Filter.VTFE flag switching to the stmmac_ops::set_hw_vlan_mode() callbacks and making sure it's toggled depending on the NETIF_F_HW_VLAN_(C|S)TAG_FILTER feature flag state. But the stmmac_ops::set_filter() callback will still need to be able to permit the VLAN traffic on the Promiscuous mode. Sadly the MAC_Packet_Filter.PR flag doesn't do that, but the MAC_Packet_Filter.RA flag do. Basically both of these flags are responsible for the same functionality - to make the Address Filtering module passing all incoming packets irrespective of the destination or source address. But the MAC_Packet_Filter.RA flag also let the packets to bypass all the filters including the VLAN one preserving the filters status. That fact is utilized by the MAC to deliver the filtering status to the DMA-descriptor and to decide to which Fail-queue the packets must be routed. Here is a table [1] describing the semantics of the denoted flags: +----+-------+--------------+-------------+-------+------------+ | | | SA/DA Filter | VLAN Filter | | Queue | | RA | VTFE | result | result | VFFQE | Routing | +----+-------+--------------+-------------+-------+------------+ | 0 | 0 | PASS | FAIL | 1 | VFFQ | | 0 | 1 | PASS | FAIL | X | DROP | | 1 | X | FAIL | X | 1 | UFFQ*/VFFQ | | 1 | X | PASS | FAIL | 1 | VFFQ | +----+-------+--------------+-------------+-------+------------+ * where X - don't care condition. So by setting the RA flag not only the Promiscouse mode is permitted but the VLAN Fail-queue feature is left supported (currently for the DW QoS Ether devices). So to speak the implemented change not only converts the HW-offloaded VLAN tags filter being switchable, but also preserve the VLAN Fail-queue feature supported in the driver, about which the commit a7602e7332b9 ("net: stmmac: don't reject VLANs when IFF_PROMISC is set") author worried. Note surprisingly the MAC_Packet_Filter CSR layout completely match on all the currently supported IP-cores: DW QoS Ether, DW XGMAC and DW XLGMAC. [1] DesignWare Cores Ethernet Quality-of-Service Databook, Revision 5.20a, April 2020, pp. 241-242. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c | 13 ++++--------- drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c | 8 +++----- drivers/net/ethernet/stmicro/stmmac/hwif.h | 2 +- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 14 ++++++++++++-- drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c | 15 +++++++++++++-- 5 files changed, 33 insertions(+), 19 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c index c7a3097472e79..5d95acfc748f6 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c @@ -534,11 +534,10 @@ static void dwmac4_set_filter(struct mac_device_info *hw, value |= GMAC_RXQCTRL_VFFQE | (hw->vlan_fail_q << GMAC_RXQCTRL_VFFQ_SHIFT); writel(value, ioaddr + GMAC_RXQ_CTRL4); - value = GMAC_PACKET_FILTER_PR | GMAC_PACKET_FILTER_RA; - } else { - value = GMAC_PACKET_FILTER_PR | GMAC_PACKET_FILTER_PCF; } - + value |= GMAC_PACKET_FILTER_RA; + value |= GMAC_PACKET_FILTER_PR; + value |= GMAC_PACKET_FILTER_PCF; } else if ((dev->flags & IFF_ALLMULTI) || (netdev_mc_count(dev) > hw->multicast_filter_bins)) { /* Pass all multi */ @@ -594,12 +593,8 @@ static void dwmac4_set_filter(struct mac_device_info *hw, } /* VLAN filtering */ - if (dev->flags & IFF_PROMISC && !hw->vlan_fail_q_en) { - value &= ~GMAC_PACKET_FILTER_VTFE; - } else if (dev->features & NETIF_F_HW_VLAN_CTAG_FILTER) { - value |= GMAC_PACKET_FILTER_VTFE; + if (dev->features & NETIF_F_HW_VLAN_CTAG_FILTER) stmmac_restore_hw_vlan_rx_fltr(priv, dev, hw); - } writel(value, ioaddr + GMAC_PACKET_FILTER); } diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c index 9cd58b0d83062..197e9d5dd6db7 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c @@ -577,6 +577,7 @@ static void dwxgmac2_set_filter(struct mac_device_info *hw, u32 value, mc_filter[8]; value = readl(ioaddr + XGMAC_PACKET_FILTER); + value &= ~XGMAC_FILTER_RA; value &= ~XGMAC_FILTER_PCF; value &= ~XGMAC_FILTER_PM; value &= ~XGMAC_FILTER_HMC; @@ -587,6 +588,7 @@ static void dwxgmac2_set_filter(struct mac_device_info *hw, memset(mc_filter, 0, sizeof(mc_filter)); if (dev->flags & IFF_PROMISC) { + value |= XGMAC_FILTER_RA; value |= XGMAC_FILTER_PR; value |= XGMAC_FILTER_PCF; } else if ((dev->flags & IFF_ALLMULTI) || @@ -626,12 +628,8 @@ static void dwxgmac2_set_filter(struct mac_device_info *hw, } /* VLAN filtering */ - if (dev->flags & IFF_PROMISC) { - value &= ~XGMAC_FILTER_VTFE; - } else if (dev->features & NETIF_F_HW_VLAN_CTAG_FILTER) { - value |= XGMAC_FILTER_VTFE; + if (dev->features & NETIF_F_HW_VLAN_CTAG_FILTER) stmmac_restore_hw_vlan_rx_fltr(priv, dev, hw); - } writel(value, ioaddr + XGMAC_PACKET_FILTER); } diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index b8fd4b5273939..b332d2a3bf270 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -633,7 +633,7 @@ struct stmmac_vlan_ops { void (*rx_hw_vlan)(struct mac_device_info *hw, struct dma_desc *rx_desc, struct sk_buff *skb); void (*set_hw_vlan_mode)(struct mac_device_info *hw, bool rx_strip, - bool rx_stag, bool tx_stag); + bool rx_ctag, bool rx_stag, bool tx_stag); int (*add_hw_vlan_rx_fltr)(struct net_device *dev, struct mac_device_info *hw, __be16 proto, u16 vid); diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index d2006d90e7e96..58a4ce67fe19c 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -3895,6 +3895,7 @@ static int stmmac_hw_setup(struct net_device *dev) stmmac_set_hw_vlan_mode(priv, priv->hw, dev->features & (NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_HW_VLAN_STAG_RX), + dev->features & NETIF_F_HW_VLAN_CTAG_FILTER, dev->features & NETIF_F_HW_VLAN_STAG_FILTER, dev->features & NETIF_F_HW_VLAN_STAG_TX); phylink_rx_clk_stop_unblock(priv->phylink); @@ -6474,6 +6475,12 @@ static netdev_features_t stmmac_fix_features(struct net_device *dev, if (!(features & NETIF_F_RXCSUM)) features &= ~NETIF_F_RXHASH; + /* VLAN-filters work for both VLAN tag types */ + if (features & NETIF_F_HW_VLAN_CTAG_FILTER) + features |= NETIF_F_HW_VLAN_STAG_FILTER; + else if (features & NETIF_F_HW_VLAN_STAG_FILTER) + features |= NETIF_F_HW_VLAN_CTAG_FILTER; + /* VLAN-stripping implies removing any VLAN tag type: CTAG and STAG */ if (dwmac_is_xmac(priv->plat->core_type)) { if (features & NETIF_F_HW_VLAN_CTAG_RX) @@ -6530,6 +6537,7 @@ static int stmmac_set_features(struct net_device *netdev, stmmac_set_hw_vlan_mode(priv, priv->hw, features & (NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_HW_VLAN_STAG_RX), + features & NETIF_F_HW_VLAN_CTAG_FILTER, features & NETIF_F_HW_VLAN_STAG_FILTER, features & NETIF_F_HW_VLAN_STAG_TX); phylink_rx_clk_stop_unblock(priv->phylink); @@ -8790,8 +8798,10 @@ static int __stmmac_dvr_probe(struct device *device, ndev->hw_features |= NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_HW_VLAN_STAG_RX; if (priv->dma_cap.vlhash) { - ndev->features |= NETIF_F_HW_VLAN_CTAG_FILTER; - ndev->features |= NETIF_F_HW_VLAN_STAG_FILTER; + ndev->features |= NETIF_F_HW_VLAN_CTAG_FILTER | + NETIF_F_HW_VLAN_STAG_FILTER; + ndev->hw_features |= NETIF_F_HW_VLAN_CTAG_FILTER | + NETIF_F_HW_VLAN_STAG_FILTER; } /* VLAN Insertion feature supports only one type at a time */ diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c index b6d4b2c7a2d3d..2cd79672c739e 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c @@ -4,6 +4,7 @@ * stmmac VLAN (802.1Q) handling */ +#include "dwmac4.h" #include "stmmac.h" #include "stmmac_vlan.h" @@ -245,11 +246,21 @@ static void vlan_rx_hw(struct mac_device_info *hw, } static void vlan_set_hw_mode(struct mac_device_info *hw, bool rx_strip, - bool rx_stag, bool tx_stag) + bool rx_ctag, bool rx_stag, bool tx_stag) { void __iomem *ioaddr = hw->pcsr; - u32 value = readl(ioaddr + VLAN_TAG); + u32 value; + + /* Activate VLAN Tag Rx filters */ + value = readl(ioaddr + GMAC_PACKET_FILTER); + if (rx_ctag || rx_stag) + value |= GMAC_PACKET_FILTER_VTFE; + else + value &= ~GMAC_PACKET_FILTER_VTFE; + writel(value, ioaddr + GMAC_PACKET_FILTER); + /* Setup Rx VLAN Tag stripping */ + value = readl(ioaddr + VLAN_TAG); value &= ~VLAN_TAG_CTRL_EVLS_MASK; if (rx_strip) -- cgit v1.2.3 From d456306f0decc3143ce273af33af5bed0d8cc033 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 17 Apr 2025 01:20:45 +0300 Subject: net: stmmac: vlan: Use hash and perfect VLAN filters complementarily Current hash-based VLAN-filters and extended VLAN-filters implementations are orthogonal. That is they duplicate each other filtering the same VLAN tags. It's not that optimal since the controller supports them to work complementarily. It means to use one filter resources if only the resources of another one have been fully used up. Particularly the Extended VLAN-Filters as being the perfect filters would be utilised first. Only if no free perfect VLAN-filter slots left, the hash-based VLAN-filter will be configured. Let's implement the denoted functionality then. It's not that complicated since the preceding commits have smoothly prepared the code for that. So just invert the filters configuration order and regard the error-values of the extended filter configuration as a hint to fallback to the hash-based filter utilization. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 25 +++++++++--------- drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c | 32 +++++++++-------------- 2 files changed, 24 insertions(+), 33 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 58a4ce67fe19c..b8bdaeab32b73 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -7161,6 +7161,9 @@ static int stmmac_update_hw_vlan_rx_hash(struct stmmac_priv *priv, bool add, u16 vid_cpu; int ret; + if (!priv->dma_cap.vlhash) + return -ENOSPC; + if (proto == htons(ETH_P_8021Q)) { active_vlans = priv->active_cvlans; add_ctags = add ? 1 : -1; @@ -7216,15 +7219,13 @@ static int stmmac_vlan_rx_add_vid(struct net_device *ndev, __be16 proto, u16 vid if (ret < 0) return ret; - ret = stmmac_update_hw_vlan_rx_hash(priv, true, proto, vid); - if (ret) - goto err_pm_put; - ret = stmmac_add_hw_vlan_rx_fltr(priv, ndev, priv->hw, proto, vid); - if (ret && ret != -EINVAL) - stmmac_update_hw_vlan_rx_hash(priv, false, proto, vid); + if (!ret) + goto ok_pm_put; -err_pm_put: + ret = stmmac_update_hw_vlan_rx_hash(priv, true, proto, vid); + +ok_pm_put: pm_runtime_put(priv->device); return ret; @@ -7243,14 +7244,12 @@ static int stmmac_vlan_rx_kill_vid(struct net_device *ndev, __be16 proto, u16 vi return ret; ret = stmmac_del_hw_vlan_rx_fltr(priv, ndev, priv->hw, proto, vid); - if (ret && ret != -EINVAL) - goto del_vlan_error; + if (!ret) + goto ok_pm_put; ret = stmmac_update_hw_vlan_rx_hash(priv, false, proto, vid); - if (ret) - stmmac_add_hw_vlan_rx_fltr(priv, ndev, priv->hw, proto, vid); -del_vlan_error: +ok_pm_put: pm_runtime_put(priv->device); return ret; @@ -8797,7 +8796,7 @@ static int __stmmac_dvr_probe(struct device *device, ndev->features |= NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_HW_VLAN_STAG_RX; ndev->hw_features |= NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_HW_VLAN_STAG_RX; - if (priv->dma_cap.vlhash) { + if (priv->dma_cap.vlhash || priv->dma_cap.nrvf_num) { ndev->features |= NETIF_F_HW_VLAN_CTAG_FILTER | NETIF_F_HW_VLAN_STAG_FILTER; ndev->hw_features |= NETIF_F_HW_VLAN_CTAG_FILTER | diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c index 2cd79672c739e..4cf1096a119ab 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c @@ -82,10 +82,8 @@ static int vlan_add_hw_rx_fltr(struct net_device *dev, /* Single Rx VLAN Filter */ if (hw->num_vlan == 1) { /* For single VLAN filter, VID 0 means VLAN promiscuous */ - if (vid == 0) { - netdev_warn(dev, "Adding VLAN ID 0 is not supported\n"); - return -EPERM; - } + if (vid == 0) + return -EINVAL; val = vid; if (is_stag) @@ -94,10 +92,8 @@ static int vlan_add_hw_rx_fltr(struct net_device *dev, if (hw->vlan_filter[0] == val) return 0; - if (hw->vlan_filter[0] & VLAN_TAG_VID) { - netdev_err(dev, "Only single VLAN ID supported\n"); - return -EPERM; - } + if (hw->vlan_filter[0] & VLAN_TAG_VID) + return -ENOSPC; vlan_update_hash(hw, hw->vlan_hash, !is_stag, is_stag); @@ -120,11 +116,8 @@ static int vlan_add_hw_rx_fltr(struct net_device *dev, index = i; } - if (index == -1) { - netdev_err(dev, "MAC_VLAN_Tag_Filter full (size: %0u)\n", - hw->num_vlan); - return -EPERM; - } + if (index == -1) + return -ENOSPC; ret = vlan_write_filter(dev, hw, index, val); if (ret && ret != -EAGAIN) @@ -139,18 +132,18 @@ static int vlan_del_hw_rx_fltr(struct net_device *dev, struct mac_device_info *hw, __be16 proto, u16 vid) { - int i, ret = 0; bool is_stag; + int i, ret; is_stag = proto == htons(ETH_P_8021AD); /* Single Rx VLAN Filter */ if (hw->num_vlan == 1) { if ((hw->vlan_filter[0] & VLAN_TAG_VID) != vid) - return 0; + return -ENOENT; if (is_stag != !!(hw->vlan_filter[0] & VLAN_TAG_ERSVLM)) - return 0; + return -ENOENT; vlan_update_hash(hw, hw->vlan_hash, -!is_stag, -is_stag); @@ -177,9 +170,11 @@ static int vlan_del_hw_rx_fltr(struct net_device *dev, return ret; hw->vlan_filter[i] = 0; + + return 0; } - return 0; + return -ENOENT; } static void vlan_update_hash(struct mac_device_info *hw, u32 hash, @@ -210,9 +205,6 @@ static void vlan_update_hash(struct mac_device_info *hw, u32 hash, value &= ~VLAN_ERSVLM; } - if (!hw->vlan_filter[0]) - value |= VLAN_VID; - writel(value, ioaddr + VLAN_TAG); } -- cgit v1.2.3 From d2591835849e6e252e8e3e5df8623878b551c9fc Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sat, 19 Apr 2025 01:44:10 +0300 Subject: net: stmmac: vlan: Extract VLAN TPID from DMA-descriptors DW QoS Eth and DW XGMAC IP-cores are always equipped with the VLAN tag stripping feature. That is the MAC are capable to extract VLAN tag from the L2 header and place it into the dedicated DMA-descriptor field. This feature is already supported by the STMMAC driver. But what is missing is the VLAN tag type extraction in the meantime at least DW XGMAC can determined and report the L2 packet type including VLAN-frame Tag protocol ID. So in order to support the feature denoted above let's introduce the stmmac_desc_ops::get_rx_vlan_tpid() callback which would provide a VLAN TPID of the received frame of course if the frame is detected to be VLAN. Define it for both DW QoS Eth (GMAC4) and DW XGMAC IP-cores which are currently supported by the driver. In case of DW QoS Eth IP-core this callback shall return just 802.1Q tag type since at the state of v5.20a IP-core the wrote-back receive descriptor doesn't provide such information. That's why all the VLAN frames received reported as C-Tagged. Note for a VLAN tag and VLAN Tag protocol ID being valid they must be extracted from the last and not erroneous descriptor. Thus the dwxgmac2_wrback_get_rx_vlan_valid() method fixed accordingly. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c | 7 +++++ .../net/ethernet/stmicro/stmmac/dwxgmac2_descs.c | 30 +++++++++++++++++++--- drivers/net/ethernet/stmicro/stmmac/hwif.h | 2 ++ drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c | 3 ++- 4 files changed, 37 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c index 86eb6c01a7d0b..4287c381e5304 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c @@ -218,6 +218,12 @@ static int dwmac4_get_tx_ls(struct dma_desc *p) >> TDES3_LAST_DESCRIPTOR_SHIFT; } +static u16 dwmac4_wrback_get_rx_vlan_tpid(struct dma_desc *p) +{ + /* Alas at least up to v5.20 there is no TPID in wrback desc */ + return ETH_P_8021Q; +} + static u16 dwmac4_wrback_get_rx_vlan_tci(struct dma_desc *p) { return (le32_to_cpu(p->des0) & RDES0_VLAN_TAG_MASK); @@ -604,6 +610,7 @@ const struct stmmac_desc_ops dwmac4_desc_ops = { .set_tx_owner = dwmac4_set_tx_owner, .set_rx_owner = dwmac4_set_rx_owner, .get_tx_ls = dwmac4_get_tx_ls, + .get_rx_vlan_tpid = dwmac4_wrback_get_rx_vlan_tpid, .get_rx_vlan_tci = dwmac4_wrback_get_rx_vlan_tci, .get_rx_vlan_valid = dwmac4_wrback_get_rx_vlan_valid, .get_rx_frame_len = dwmac4_wrback_get_rx_frame_len, diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c index 192860b639f66..7301df6a6d344 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c @@ -154,6 +154,26 @@ static int dwxgmac2_get_tx_ls(struct dma_desc *p) return (le32_to_cpu(p->des3) & XGMAC_RDES3_LD) > 0; } +static u16 dwxgmac2_wrback_get_rx_vlan_tpid(struct dma_desc *p) +{ + u32 l2t; + + l2t = FIELD_GET(XGMAC_RDES3_ET_LT, le32_to_cpu(p->des3)); + + switch (l2t) { + case XGMAC_L2T_VLAN_STAG: + case XGMAC_L2T_DVLAN_SSTAG: + case XGMAC_L2T_DVLAN_SCTAG: + return ETH_P_8021AD; + case XGMAC_L2T_VLAN_CTAG: + case XGMAC_L2T_DVLAN_CCTAG: + case XGMAC_L2T_DVLAN_CSTAG: + return ETH_P_8021Q; + default: + return 0; + } +} + static u16 dwxgmac2_wrback_get_rx_vlan_tci(struct dma_desc *p) { return le32_to_cpu(p->des0) & XGMAC_RDES0_VLAN_TAG_MASK; @@ -161,12 +181,13 @@ static u16 dwxgmac2_wrback_get_rx_vlan_tci(struct dma_desc *p) static bool dwxgmac2_wrback_get_rx_vlan_valid(struct dma_desc *p) { - u32 et_lt; + u32 des3 = le32_to_cpu(p->des3); + u32 l2t; - et_lt = FIELD_GET(XGMAC_RDES3_ET_LT, le32_to_cpu(p->des3)); + l2t = FIELD_GET(XGMAC_RDES3_ET_LT, des3); - return et_lt >= XGMAC_ET_LT_VLAN_STAG && - et_lt <= XGMAC_ET_LT_DVLAN_STAG_CTAG; + return (des3 & XGMAC_RDES3_LD) && !(des3 & XGMAC_RDES3_ES) && + (l2t >= XGMAC_L2T_VLAN_STAG) && (l2t <= XGMAC_L2T_DVLAN_CSTAG); } static int dwxgmac2_get_rx_frame_len(struct dma_desc *p) @@ -483,6 +504,7 @@ const struct stmmac_desc_ops dwxgmac210_desc_ops = { .set_tx_owner = dwxgmac2_set_tx_owner, .set_rx_owner = dwxgmac2_set_rx_owner, .get_tx_ls = dwxgmac2_get_tx_ls, + .get_rx_vlan_tpid = dwxgmac2_wrback_get_rx_vlan_tpid, .get_rx_vlan_tci = dwxgmac2_wrback_get_rx_vlan_tci, .get_rx_vlan_valid = dwxgmac2_wrback_get_rx_vlan_valid, .get_rx_frame_len = dwxgmac2_get_rx_frame_len, diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index b332d2a3bf270..63e926a5583a2 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -66,6 +66,8 @@ struct stmmac_desc_ops { void (*set_tx_ic)(struct dma_desc *p); /* Last tx segment reports the transmit status */ int (*get_tx_ls)(struct dma_desc *p); + /* Get the protocol of the descriptor */ + u16 (*get_rx_vlan_tpid)(struct dma_desc *p); /* Get the tag of the descriptor */ u16 (*get_rx_vlan_tci)(struct dma_desc *p); /* Get the valid status of descriptor */ diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c index 4cf1096a119ab..4ce60c5a93ae8 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_vlan.c @@ -231,9 +231,10 @@ static void vlan_rx_hw(struct mac_device_info *hw, struct dma_desc *rx_desc, struct sk_buff *skb) { if (hw->desc->get_rx_vlan_valid(rx_desc)) { + u16 tpid = hw->desc->get_rx_vlan_tpid(rx_desc); u16 vid = hw->desc->get_rx_vlan_tci(rx_desc); - __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), vid); + __vlan_hwaccel_put_tag(skb, htons(tpid), vid); } } -- cgit v1.2.3 From 5276846cf534820b1956283d16157f010301438c Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 21 Apr 2025 17:05:04 +0300 Subject: net: stmmac: dwmac1000: Add VLAN Rx filtering support DW GMAC IP-core supports two types of the Rx VLAN tag filters. First one is a basic perfect filter consisting a single CSR to place a being allowed VID. Second one is a Hash-based filter. It implies to pass the only VLAN tags which the most significant four bits of their CRC-32 point to a set bit in the VLAN Hash Table register. So basically the filters functionality is the same as could be found in the DW QoS Ether and DW XGMAC IP-cores except there is no Extended VLAN Perfect filter support and the S-VLAN filtering capability can't be enabled independently from the C-VLAN tags filtering - it's either both tag types allowed or just C-Tags. The CSRs layout and their mapping is different. Based on the description provided above let's introduce the VLAN Rx Tags filtering support to the DW GMAC module of the STMMAC driver. It mainly looks similar to what is found in the rest of the VLAN-capable modules except the tags counter isn't utilized since the S-VLAN tags filtering is switched on/off based on the HW-features flag. That leads to passing both C- and S-Tags if the STAG HW-offloaded filtering is enabled. Also note that the VLAN Tag Hash-based filtering availability isn't indicated in the HW-features register. Instead an auto-detection procedure has been implemented based on the fact that the VLAN Hash register is RO if feature is unavailable. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/dwmac1000.h | 10 ++ .../net/ethernet/stmicro/stmmac/dwmac1000_core.c | 180 +++++++++++++++++++-- .../net/ethernet/stmicro/stmmac/dwmac1000_dma.c | 17 ++ drivers/net/ethernet/stmicro/stmmac/hwif.c | 1 + drivers/net/ethernet/stmicro/stmmac/hwif.h | 1 + 5 files changed, 200 insertions(+), 9 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000.h b/drivers/net/ethernet/stmicro/stmmac/dwmac1000.h index a424f5884e437..3f44df41d446c 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac1000.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000.h @@ -19,6 +19,7 @@ #define GMAC_MII_DATA 0x00000014 /* MII Data */ #define GMAC_FLOW_CTRL 0x00000018 /* Flow Control */ #define GMAC_VLAN_TAG 0x0000001c /* VLAN Tag */ +#define GMAC_VERSION 0x00000020 /* GMAC version register */ #define GMAC_DEBUG 0x00000024 /* GMAC debug register */ #define GMAC_INT_STATUS 0x00000038 /* interrupt status register */ @@ -72,6 +73,9 @@ enum power_event { /* GMAC Watchdog timeout for received frames register */ #define GMAC_WDT 0x000000dc +/* VLAN Hash register */ +#define GMAC_VLAN_HASH_TABLE 0x00000588 + /* SGMII/RGMII status register */ #define GMAC_RGSMIIIS_LNKMODE BIT(0) #define GMAC_RGSMIIIS_SPEED GENMASK(2, 1) @@ -113,12 +117,18 @@ enum inter_frame_gap { #define GMAC_FRAME_FILTER_PM 0x00000010 /* Pass all multicast */ #define GMAC_FRAME_FILTER_PCF 0x00000080 /* Pass Control frames */ #define GMAC_FRAME_FILTER_HPF 0x00000400 /* Hash or perfect Filter */ +#define GMAC_FRAME_FILTER_VTFE 0x00010000 /* VLAN Tag Filter */ #define GMAC_FRAME_FILTER_RA 0x80000000 /* Receive all mode */ /* GMAC FLOW CTRL defines */ #define GMAC_FLOW_CTRL_PT_MASK GENMASK(31, 16) /* Pause Time Mask */ #define GMAC_FLOW_CTRL_UP 0x00000008 /* Unicast pause frame enable */ #define GMAC_FLOW_CTRL_RFE 0x00000004 /* Rx Flow Control Enable */ #define GMAC_FLOW_CTRL_TFE 0x00000002 /* Tx Flow Control Enable */ +/* VLAN Tag defines */ +#define GMAC_VLAN_VTHM BIT(19) +#define GMAC_VLAN_ESVL BIT(18) +#define GMAC_VLAN_ETV BIT(16) +#define GMAC_VLAN_VID GENMASK(15, 0) /* Watchdog timeout for received frames defines */ #define GMAC_WDT_PWE BIT(16) #define GMAC_WDT_WTO GENMASK(13, 0) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c index 312d22a52ea59..7ed350fbc8af9 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c @@ -69,11 +69,6 @@ static void dwmac1000_core_init(struct mac_device_info *hw, /* Mask GMAC interrupts */ writel(GMAC_INT_DEFAULT_MASK, ioaddr + GMAC_INT_MASK); - -#ifdef STMMAC_VLAN_TAG_USED - /* Tag detection without filtering */ - writel(0x0, ioaddr + GMAC_VLAN_TAG); -#endif } static void dwmac1000_irq_modify(struct mac_device_info *hw, u32 disable, @@ -184,6 +179,150 @@ static void dwmac1000_set_mchash(void __iomem *ioaddr, u32 *mcfilterbits, ioaddr + GMAC_EXTHASH_BASE + regs * 4); } +static void dwmac1000_vlan_write_single(struct net_device *dev, u32 data) +{ + void __iomem *ioaddr = (void __iomem *)dev->base_addr; + u32 val; + + if (!(data & GMAC_VLAN_VID)) + data |= GMAC_VLAN_VID; + + val = readl(ioaddr + GMAC_VLAN_TAG) & ~GMAC_VLAN_VID; + val |= GMAC_VLAN_ETV | (data & GMAC_VLAN_VID); + + writel(val, ioaddr + GMAC_VLAN_TAG); +} + +static int dwmac1000_vlan_add_hw_rx_fltr(struct net_device *dev, + struct mac_device_info *hw, + __be16 proto, u16 vid) +{ + bool is_stag; + u32 val; + + if (vid > 4095) + return -EINVAL; + + is_stag = proto == htons(ETH_P_8021AD); + + /* For single VLAN filter, VID 0 means VLAN promiscuous */ + if (vid == 0) + return -EINVAL; + + /* Note ESVL is utilized to virtually distinguish the C- and S-Tags + * only. The bit is set/cleared in the set_hw_vlan_mode() depending + * on the HW-offloaded features requested. + */ + val = vid; + if (is_stag) + val |= GMAC_VLAN_ESVL; + + if (hw->vlan_filter[0] == val) + return 0; + + if (hw->vlan_filter[0] & GMAC_VLAN_VID) + return -ENOSPC; + + if (netif_running(dev)) + dwmac1000_vlan_write_single(dev, val); + + hw->vlan_filter[0] = val; + + return 0; +} + +static int dwmac1000_vlan_del_hw_rx_fltr(struct net_device *dev, + struct mac_device_info *hw, + __be16 proto, u16 vid) +{ + bool is_stag; + + is_stag = proto == htons(ETH_P_8021AD); + + /* Single Rx VLAN Filter */ + if ((hw->vlan_filter[0] & GMAC_VLAN_VID) != vid) + return -ENOENT; + + if (is_stag != !!(hw->vlan_filter[0] & GMAC_VLAN_ESVL)) + return -ENOENT; + + if (netif_running(dev)) + dwmac1000_vlan_write_single(dev, 0); + + hw->vlan_filter[0] = 0; + + return 0; +} + +static void dwmac1000_vlan_update_hash(struct mac_device_info *hw, u32 hash, + int add_ctags, int add_stags) +{ + void __iomem *ioaddr = hw->pcsr; + u32 value; + + hw->vlan_hash = hash; + writel(hash, ioaddr + GMAC_VLAN_HASH_TABLE); + + value = readl(ioaddr + GMAC_VLAN_TAG) | GMAC_VLAN_ETV; + + if (hash) + value |= GMAC_VLAN_VTHM; + else + value &= ~GMAC_VLAN_VTHM; + + /* Note DW GMAC VLAN filter doesn't have a filter-specific flag to + * toggle the S-Tags support. Instead if ESVL flag is set then both + * tag types will be considered as valid. But since the bit also + * affects the MAC transmitter it is handled in set_hw_vlan_mode(). + */ + + writel(value, ioaddr + GMAC_VLAN_TAG); +} + +static void dwmac1000_vlan_restore_hw_rx_fltr(struct net_device *dev, + struct mac_device_info *hw) +{ + /* Hash-based Rx VLAN Filter */ + dwmac1000_vlan_update_hash(hw, hw->vlan_hash, 0, 0); + + /* Single Rx VLAN Filter */ + dwmac1000_vlan_write_single(dev, hw->vlan_filter[0]); +} + +static void dwmac1000_vlan_set_hw_mode(struct mac_device_info *hw, bool rx_strip, + bool rx_ctag, bool rx_stag, bool tx_stag) +{ + void __iomem *ioaddr = hw->pcsr; + u32 value; + + /* Activate VLAN Tag Rx filters */ + value = readl(ioaddr + GMAC_FRAME_FILTER); + if (rx_ctag || rx_stag) + value |= GMAC_FRAME_FILTER_VTFE; + else + value &= ~GMAC_FRAME_FILTER_VTFE; + writel(value, ioaddr + GMAC_FRAME_FILTER); + + /* Activate S-VLAN feature on MAC Tx and Rx */ + value = readl(ioaddr + GMAC_VLAN_TAG); + if (rx_stag) + value |= GMAC_VLAN_ESVL; + else + value &= ~GMAC_VLAN_ESVL; + writel(value, ioaddr + GMAC_VLAN_TAG); +} + +static u32 dwmac1000_get_num_vlan(void __iomem *ioaddr) +{ + u32 val; + + val = readl(ioaddr + GMAC_VERSION); + if ((val & GENMASK(7, 0)) >= DWMAC_CORE_3_70) + return 1; + + return 0; +} + static void dwmac1000_set_filter(struct mac_device_info *hw, struct net_device *dev) { @@ -195,20 +334,30 @@ static void dwmac1000_set_filter(struct mac_device_info *hw, pr_debug("%s: # mcasts %d, # unicast %d\n", __func__, netdev_mc_count(dev), netdev_uc_count(dev)); + value = readl(ioaddr + GMAC_FRAME_FILTER); + value &= ~GMAC_FRAME_FILTER_RA; + value &= ~GMAC_FRAME_FILTER_HPF; + value &= ~GMAC_FRAME_FILTER_PCF; + value &= ~GMAC_FRAME_FILTER_PM; + value &= ~GMAC_FRAME_FILTER_HMC; + value &= ~GMAC_FRAME_FILTER_PR; + memset(mc_filter, 0, sizeof(mc_filter)); if (dev->flags & IFF_PROMISC) { - value = GMAC_FRAME_FILTER_PR | GMAC_FRAME_FILTER_PCF; + value |= GMAC_FRAME_FILTER_RA; + value |= GMAC_FRAME_FILTER_PR; + value |= GMAC_FRAME_FILTER_PCF; } else if (dev->flags & IFF_ALLMULTI) { value = GMAC_FRAME_FILTER_PM; /* pass all multi */ } else if (!netdev_mc_empty(dev) && (mcbitslog2 == 0)) { /* Fall back to all multicast if we've no filter */ - value = GMAC_FRAME_FILTER_PM; + value |= GMAC_FRAME_FILTER_PM; } else if (!netdev_mc_empty(dev) && dev->flags & IFF_MULTICAST) { struct netdev_hw_addr *ha; /* Hash filter for multicast */ - value = GMAC_FRAME_FILTER_HMC; + value |= GMAC_FRAME_FILTER_HMC; netdev_for_each_mc_addr(ha, dev) { /* The upper n bits of the calculated CRC are used to @@ -257,10 +406,14 @@ static void dwmac1000_set_filter(struct mac_device_info *hw, /* Enable Receive all mode (to debug filtering_fail errors) */ value |= GMAC_FRAME_FILTER_RA; #endif + + /* VLAN filtering */ + if (dev->features & NETIF_F_HW_VLAN_CTAG_FILTER) + dwmac1000_vlan_restore_hw_rx_fltr(dev, hw); + writel(value, ioaddr + GMAC_FRAME_FILTER); } - static void dwmac1000_flow_ctrl(struct mac_device_info *hw, unsigned int duplex, unsigned int fc, unsigned int pause_time, u32 tx_cnt) @@ -515,6 +668,14 @@ const struct stmmac_ops dwmac1000_ops = { .set_mac_loopback = dwmac1000_set_mac_loopback, }; +const struct stmmac_vlan_ops dwmac1000_vlan_ops = { + .update_vlan_hash = dwmac1000_vlan_update_hash, + .add_hw_vlan_rx_fltr = dwmac1000_vlan_add_hw_rx_fltr, + .del_hw_vlan_rx_fltr = dwmac1000_vlan_del_hw_rx_fltr, + .restore_hw_vlan_rx_fltr = dwmac1000_vlan_restore_hw_rx_fltr, + .set_hw_vlan_mode = dwmac1000_vlan_set_hw_mode, +}; + int dwmac1000_setup(struct stmmac_priv *priv) { struct mac_device_info *mac = priv->hw; @@ -545,6 +706,7 @@ int dwmac1000_setup(struct stmmac_priv *priv) mac->mii.reg_mask = 0x000007C0; mac->mii.clk_csr_shift = 2; mac->mii.clk_csr_mask = GENMASK(5, 2); + mac->num_vlan = dwmac1000_get_num_vlan(priv->ioaddr); return 0; } diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c index 3a545442e903a..17fa072b829e0 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c @@ -209,6 +209,20 @@ static void dwmac1000_dump_dma_regs(struct stmmac_priv *priv, readl(ioaddr + DMA_BUS_MODE + i * 4); } +static bool dwmac1000_det_hw_vlhash(void __iomem *ioaddr) +{ + u32 tmp; + + writel(0xffff, ioaddr + GMAC_VLAN_HASH_TABLE); + tmp = readl(ioaddr + GMAC_VLAN_HASH_TABLE); + if (tmp == 0xffff) { + writel(0x0, ioaddr + GMAC_VLAN_HASH_TABLE); + return true; + } + + return false; +} + static int dwmac1000_get_hw_feature(void __iomem *ioaddr, struct dma_features *dma_cap) { @@ -253,6 +267,9 @@ static int dwmac1000_get_hw_feature(void __iomem *ioaddr, dma_cap->actphyif = FIELD_GET(DMA_HW_FEAT_ACTPHYIF, hw_cap); + /* 16-bit VLAN Hash Filter (flag is unavailable in the CSR) */ + dma_cap->vlhash = dwmac1000_det_hw_vlhash(ioaddr); + return 0; } diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.c b/drivers/net/ethernet/stmicro/stmmac/hwif.c index c8025504e5afa..46b8cf7345a86 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.c +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.c @@ -141,6 +141,7 @@ static const struct stmmac_hwif_entry { .desc = NULL, .dma = &dwmac1000_dma_ops, .mac = &dwmac1000_ops, + .vlan = &dwmac1000_vlan_ops, .hwtimestamp = &dwmac1000_ptp, .ptp = &dwmac1000_ptp_clock_ops, .tc = NULL, diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index 63e926a5583a2..7e43630c6e87c 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -679,6 +679,7 @@ extern const struct stmmac_ops dwmac100_ops; extern const struct stmmac_dma_ops dwmac100_dma_ops; extern const struct stmmac_ops dwmac1000_ops; extern const struct stmmac_dma_ops dwmac1000_dma_ops; +extern const struct stmmac_vlan_ops dwmac1000_vlan_ops; extern const struct stmmac_ops dwmac4_ops; extern const struct stmmac_dma_ops dwmac4_dma_ops; extern const struct stmmac_ops dwmac410_ops; -- cgit v1.2.3 From 090df3f59c6054fe486e443666e9d49a092bb6d6 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 21 Apr 2025 22:40:24 +0300 Subject: net: stmmac: vlan: Add VLAN Tag core setter DW MAC IP-cores can be equipped with the "SA, VLAN, and CRC Insertion on TX" feature. In case of VLAN Tag insertion the actual tag can be specified either via a dedicated setup register field or via a context DMA-descriptor. The last option is available on the DW QoS Ether and DW XGMAC IP-cores only, but not on the DW GMAC controllers. As a preparation before adding the VLAN Tag insertion support to the DW GMAC submodule let's add a callback method which would be responsible for initializing the Tx VLAN Tags of the device MAC. Note after this change if none of the VLAN Tx Tag setting up callbacks were successful the driver will try to push the tag inside the payload. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/hwif.h | 3 +++ drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 21 +++++++++++++++------ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index 7e43630c6e87c..a7f73b039fdf6 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -630,6 +630,7 @@ struct stmmac_est_ops { struct stmmac_vlan_ops { /* VLAN */ + int (*set_vlan_tag)(struct mac_device_info *hw, u32 queue, u16 vid); void (*update_vlan_hash)(struct mac_device_info *hw, u32 hash, int add_ctags, int add_stags); void (*rx_hw_vlan)(struct mac_device_info *hw, struct dma_desc *rx_desc, @@ -646,6 +647,8 @@ struct stmmac_vlan_ops { struct mac_device_info *hw); }; +#define stmmac_set_core_vlan_tag(__priv, __args...) \ + stmmac_do_callback(__priv, vlan, set_vlan_tag, __args) #define stmmac_update_vlan_hash(__priv, __args...) \ stmmac_do_void_callback(__priv, vlan, update_vlan_hash, __args) #define stmmac_rx_hw_vlan(__priv, __args...) \ diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index b8bdaeab32b73..163ab3f0ad2b0 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -4419,13 +4419,22 @@ static bool stmmac_vlan_insert(struct stmmac_priv *priv, struct sk_buff *skb, else p = &tx_q->dma_tx[tx_q->cur_tx]; - if (stmmac_set_desc_vlan_tag(priv, p, tag, 0x0, 0x0)) - return false; + if (!stmmac_set_desc_vlan_tag(priv, p, tag, 0x0, 0x0)) { + stmmac_set_tx_owner(priv, p); + tx_q->cur_tx = STMMAC_GET_ENTRY(tx_q->cur_tx, + priv->dma_conf.dma_tx_size); + tx_q->tci = tag; + return true; + } else if (!stmmac_set_core_vlan_tag(priv, priv->hw, tx_q->queue_index, tag)) { + tx_q->tci = tag; + return true; + } - stmmac_set_tx_owner(priv, p); - tx_q->cur_tx = STMMAC_GET_ENTRY(tx_q->cur_tx, priv->dma_conf.dma_tx_size); - tx_q->tci = tag; - return true; + skb = __vlan_hwaccel_push_inside(skb); + if (IS_ERR(skb)) + netdev_err(priv->dev, "failed to push VLAN inside, skipping\n"); + + return false; } /** -- cgit v1.2.3 From bd15404789142bf23777cd1f431029e06572ab5e Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 21 Apr 2025 23:35:56 +0300 Subject: net: stmmac: dwmac1000: Add VLAN tag insertion support DW GMAC supports the VLAN Tag insertion in the framework of the "SA, VLAN, and CRC Insertion on TX" IP-core option. It' implementation looks very similar to what can be found in the DW QoS Ether and DW XGMAC controllers except it doesn't provide a way to re-initialize the VLAN tag via the specially formed DMA-descriptors. The only way to set the outbound VLAN Tag is to initialize the respective register. But in order to prevent the outbound frames corruption that can be done only if there is no packets pending to be fetched and transmitted via the device DMA engine. Based on the notes above let's add the VLAN Tag insertion support to the DW GMAC submodule of the driver. Basically it means to provide two callbacks: 1. dwmac1000_set_vlan_tag() - setup VLAN tag for the ongoing outbound traffic. 2. enh_desc_set_vlan() - enable VLAN tag insertion for the frame of the passed descriptor. It's also required to selectively enable/disable SVLAN Tags depending on the current network device HW-features state. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/descs.h | 1 + drivers/net/ethernet/stmicro/stmmac/dwmac1000.h | 7 +++++ .../net/ethernet/stmicro/stmmac/dwmac1000_core.c | 35 +++++++++++++++++++++- .../net/ethernet/stmicro/stmmac/dwmac1000_dma.c | 2 ++ drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h | 1 + drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c | 18 +++++++++++ drivers/net/ethernet/stmicro/stmmac/enh_desc.c | 8 +++++ 7 files changed, 71 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/descs.h b/drivers/net/ethernet/stmicro/stmmac/descs.h index 22e129e524b7b..48fb1cee663f5 100644 --- a/drivers/net/ethernet/stmicro/stmmac/descs.h +++ b/drivers/net/ethernet/stmicro/stmmac/descs.h @@ -102,6 +102,7 @@ #define ETDES0_ERROR_SUMMARY BIT(15) #define ETDES0_IP_HEADER_ERROR BIT(16) #define ETDES0_TIME_STAMP_STATUS BIT(17) +#define ETDES0_VLIC_MASK GENMASK(19, 18) #define ETDES0_SECOND_ADDRESS_CHAINED BIT(20) #define ETDES0_END_RING BIT(21) #define ETDES0_CHECKSUM_INSERTION_MASK GENMASK(23, 22) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000.h b/drivers/net/ethernet/stmicro/stmmac/dwmac1000.h index 3f44df41d446c..6d69ec596fb96 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac1000.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000.h @@ -73,6 +73,9 @@ enum power_event { /* GMAC Watchdog timeout for received frames register */ #define GMAC_WDT 0x000000dc +/* VLAN Tag Inclusion or Replacement register */ +#define GMAC_VLAN_INCL 0x00000584 + /* VLAN Hash register */ #define GMAC_VLAN_HASH_TABLE 0x00000588 @@ -132,6 +135,10 @@ enum inter_frame_gap { /* Watchdog timeout for received frames defines */ #define GMAC_WDT_PWE BIT(16) #define GMAC_WDT_WTO GENMASK(13, 0) +/* VLAN Tag inclusing defines */ +#define GMAC_VLAN_CSVL BIT(19) +#define GMAC_VLAN_VLC GENMASK(17, 16) +#define GMAC_VLAN_VLT GENMASK(15, 0) /* DEBUG Register defines */ /* MTL TxStatus FIFO */ diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c index 7ed350fbc8af9..38032786b2214 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include "stmmac.h" #include "stmmac_pcs.h" @@ -179,6 +180,29 @@ static void dwmac1000_set_mchash(void __iomem *ioaddr, u32 *mcfilterbits, ioaddr + GMAC_EXTHASH_BASE + regs * 4); } +static int dwmac1000_vlan_set_tag(struct mac_device_info *hw, u32 queue, u16 vid) +{ + void __iomem *ioaddr = hw->pcsr; + bool ready; + u32 value; + int ret; + + /* Wait for any transfer being finished so not to corrupt the pending + * outbound frames. + */ + ret = read_poll_timeout_atomic(dwmac_dma_suspended, ready, ready, + 100, 50000, false, + ioaddr, queue, DMA_DIR_TX); + if (ret) + return ret; + + value = readl(ioaddr + GMAC_VLAN_INCL) & ~GMAC_VLAN_VLT; + value |= FIELD_PREP(GMAC_VLAN_VLT, vid); + writel(value, ioaddr + GMAC_VLAN_INCL); + + return 0; +} + static void dwmac1000_vlan_write_single(struct net_device *dev, u32 data) { void __iomem *ioaddr = (void __iomem *)dev->base_addr; @@ -305,11 +329,19 @@ static void dwmac1000_vlan_set_hw_mode(struct mac_device_info *hw, bool rx_strip /* Activate S-VLAN feature on MAC Tx and Rx */ value = readl(ioaddr + GMAC_VLAN_TAG); - if (rx_stag) + if (rx_stag || tx_stag) value |= GMAC_VLAN_ESVL; else value &= ~GMAC_VLAN_ESVL; writel(value, ioaddr + GMAC_VLAN_TAG); + + /* Set Tx VLAN Insertion feature (might be unavailable) */ + value = readl(ioaddr + GMAC_VLAN_INCL); + if (tx_stag) + value |= GMAC_VLAN_CSVL; + else + value &= ~GMAC_VLAN_CSVL; + writel(value, ioaddr + GMAC_VLAN_INCL); } static u32 dwmac1000_get_num_vlan(void __iomem *ioaddr) @@ -669,6 +701,7 @@ const struct stmmac_ops dwmac1000_ops = { }; const struct stmmac_vlan_ops dwmac1000_vlan_ops = { + .set_vlan_tag = dwmac1000_vlan_set_tag, .update_vlan_hash = dwmac1000_vlan_update_hash, .add_hw_vlan_rx_fltr = dwmac1000_vlan_add_hw_rx_fltr, .del_hw_vlan_rx_fltr = dwmac1000_vlan_del_hw_rx_fltr, diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c index 17fa072b829e0..8a6a6f79bde61 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c @@ -264,6 +264,8 @@ static int dwmac1000_get_hw_feature(void __iomem *ioaddr, dma_cap->number_tx_channel = (hw_cap & DMA_HW_FEAT_TXCHCNT) >> 22; /* Alternate (enhanced) DESC mode */ dma_cap->enh_desc = (hw_cap & DMA_HW_FEAT_ENHDESSEL) >> 24; + /* Source address/VLAN Insertion/Replacement/Deletion Engine */ + dma_cap->vlins = (hw_cap & DMA_HW_FEAT_SAVLANINS) >> 27; dma_cap->actphyif = FIELD_GET(DMA_HW_FEAT_ACTPHYIF, hw_cap); diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h b/drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h index a4e034c9fecf5..c34e4ccb3b68c 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac_dma.h @@ -167,6 +167,7 @@ void dwmac_dma_start_rx(struct stmmac_priv *priv, void __iomem *ioaddr, u32 chan); void dwmac_dma_stop_rx(struct stmmac_priv *priv, void __iomem *ioaddr, u32 chan); +bool dwmac_dma_suspended(void __iomem *ioaddr, u32 chan, u32 dir); int dwmac_dma_interrupt(struct stmmac_priv *priv, void __iomem *ioaddr, struct stmmac_extra_stats *x, u32 chan, u32 dir); void dwmac_dma_diagnostic_fr(struct stmmac_priv *priv, void __iomem *ioaddr, diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c b/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c index 75dd1d370f641..90d2d93c322fc 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c @@ -158,6 +158,24 @@ static void show_rx_process_state(unsigned int status) } #endif +bool dwmac_dma_suspended(void __iomem *ioaddr, u32 chan, u32 dir) +{ + u32 status = readl(ioaddr + DMA_CHAN_STATUS(chan)); + u32 ts, rs; + + if (dir == DMA_DIR_TX) { + ts = FIELD_GET(DMA_STATUS_TS_MASK, status); + return ts == 6; + } else if (dir == DMA_DIR_RX) { + rs = FIELD_GET(DMA_STATUS_RS_MASK, status); + return rs == 4; + } + + ts = FIELD_GET(DMA_STATUS_TS_MASK, status); + rs = FIELD_GET(DMA_STATUS_RS_MASK, status); + return ts == 6 && rs == 4; +} + int dwmac_dma_interrupt(struct stmmac_priv *priv, void __iomem *ioaddr, struct stmmac_extra_stats *x, u32 chan, u32 dir) { diff --git a/drivers/net/ethernet/stmicro/stmmac/enh_desc.c b/drivers/net/ethernet/stmicro/stmmac/enh_desc.c index 24ac1d2ff164f..01ac6cc72fa68 100644 --- a/drivers/net/ethernet/stmicro/stmmac/enh_desc.c +++ b/drivers/net/ethernet/stmicro/stmmac/enh_desc.c @@ -505,6 +505,11 @@ static void enh_desc_clear(struct dma_desc *p) p->des2 = 0; } +static void enh_desc_set_vlan(struct dma_desc *p, u32 type) +{ + p->des0 |= cpu_to_le32(FIELD_PREP(ETDES0_VLIC_MASK, type)); +} + const struct stmmac_desc_ops enh_desc_ops = { .tx_status = enh_desc_get_tx_status, .rx_status = enh_desc_get_rx_status_nocoe, @@ -529,6 +534,7 @@ const struct stmmac_desc_ops enh_desc_ops = { .display_ring = enh_desc_display_ring, .set_addr = enh_desc_set_addr, .clear = enh_desc_clear, + .set_vlan = enh_desc_set_vlan, }; const struct stmmac_desc_ops enh_desc_noext_ops = { @@ -555,6 +561,7 @@ const struct stmmac_desc_ops enh_desc_noext_ops = { .display_ring = enh_desc_display_ring, .set_addr = enh_desc_set_addr, .clear = enh_desc_clear, + .set_vlan = enh_desc_set_vlan, }; const struct stmmac_desc_ops enh_desc_ext_ops = { @@ -581,4 +588,5 @@ const struct stmmac_desc_ops enh_desc_ext_ops = { .display_ring = enh_desc_display_ring, .set_addr = enh_desc_set_addr, .clear = enh_desc_clear, + .set_vlan = enh_desc_set_vlan, }; -- cgit v1.2.3 From 50fc57124e412b937af7fc3d7bd43ddc6ba0a649 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 21 Aug 2025 16:24:53 +0300 Subject: net: stmmac: vlan: Extend VLAN filters devices support DW GMAC, DW QoS Ether and DW XGMAC/XLGMAC IP-core supports three types of the VLAN filters: 1. Basic Rx VLAN perfect filter. In some extent it's available on all DW controllers. But in case of DW GMAC the S-VLAN tagged frames filtering has been supported since v3.70a. 2. Extended Rx VLAN perfect filter. It's optionally available on the modern IP-cores: DW QoS Ether, DW XGMAC, DW XLGMAC, etc. The feature has been introduced as a substitution to the Basic Rx VLAn perfect filter to extend the amount of the VLAN tags to filter. 3. Rx VLAN hash filter. It's optionally available on all modern IP-cores and on DW GMACs since v3.70a. As it can be inferred from the name the filter implies to use hash-table as a set of the allowed the VLAN-tags. The notable particularity of this filter is that it' S-VLAN capability is synchronized with the Basic Rx VLAN perfect filter. So as can be seen from the description at least one VLAN filter is always available on the modern DW network controllers, meanwhile the extended Rx VLAN and hash-based filters are optional. Moreover the current VLAN tag filters implementation in the driver support all of these filters working complementarily. Thus the driver can be freely converted to advertise the HW-accelerated VLAN C/S-Tags filtering for all the modern DW network controllers: DW GMAC v3.7x, DW QoS Ether and DW XGMAC/XLGMAC. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 163ab3f0ad2b0..04ffac7339479 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -8805,7 +8805,10 @@ static int __stmmac_dvr_probe(struct device *device, ndev->features |= NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_HW_VLAN_STAG_RX; ndev->hw_features |= NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_HW_VLAN_STAG_RX; - if (priv->dma_cap.vlhash || priv->dma_cap.nrvf_num) { + /* VLAN filtering is implemented for modern IP-cores only */ + if ((priv->plat->core_type == DWMAC_CORE_GMAC && + priv->synopsys_id >= DWMAC_CORE_3_70) || + dwmac_is_xmac(priv->plat->core_type)) { ndev->features |= NETIF_F_HW_VLAN_CTAG_FILTER | NETIF_F_HW_VLAN_STAG_FILTER; ndev->hw_features |= NETIF_F_HW_VLAN_CTAG_FILTER | -- cgit v1.2.3 From a84655e43fb891b723ee9c8beacbc9af4dd3eed1 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 1 Aug 2025 18:26:17 +0300 Subject: net: stmmac: dwxgmac2: Fix secondary buffer left enabled In case of DW XGMAC/XLGMAC IP-cores the secondary Rx DMA-buffer can be specified in the framework of the Split Packet Header feature implementation. But the feature can be disabled in runtime if an XDP BPF program is installed meanwhile the secondary buffer address won't be properly cleaned up. As a result the secondary buffer will be left enabled in the Rx DMA-descriptors even though it isn't utilized in the driver. In the worst-case scenario a part of the Rx-frames will be received to the already long freed memory page thus corrupting it' content. It's possible in case of a sudden Jumbo frame reception. [1] DesignWare Cores XGMAC - 10G Ethernet MAC Databook, Revision 3.20a, September 2022, p.251. Fixes: 67afd6d1cfdf ("net: stmmac: Add Split Header support and enable it in XGMAC cores") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c | 9 +++++++-- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c index 7301df6a6d344..c61b143443004 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c @@ -448,8 +448,13 @@ static void dwxgmac2_get_rx_header_len(struct dma_desc *p, unsigned int *len) static void dwxgmac2_set_sec_addr(struct dma_desc *p, dma_addr_t addr, bool is_valid) { - p->des2 = cpu_to_le32(lower_32_bits(addr)); - p->des3 = cpu_to_le32(upper_32_bits(addr)); + if (is_valid) { + p->des2 = cpu_to_le32(lower_32_bits(addr)); + p->des3 = cpu_to_le32(upper_32_bits(addr)); + } else { + p->des2 = 0; + p->des3 = 0; + } } static void dwxgmac2_set_sarc(struct dma_desc *p, u32 sarc_type) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 04ffac7339479..4caa0b5210ceb 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -1872,6 +1872,7 @@ static int stmmac_init_rx_buffers(struct stmmac_priv *priv, stmmac_set_desc_sec_addr(priv, p, buf->sec_addr, true); } else { buf->sec_page = NULL; + buf->sec_addr = 0; stmmac_set_desc_sec_addr(priv, p, buf->sec_addr, false); } -- cgit v1.2.3 From 83c9a3b84b8f576e5890029029c60b993c0c9305 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 21 Aug 2025 22:30:52 +0300 Subject: net: stmmac: Fix XDP transmission paths inconsistency The stmmac_dma_ops::enable_dma_transmission() callback implements the Tx Poll Demand command implying to charge Tx DMA to be seeking for the next DMA-own descriptor and start transmitting the respective frames to the wire. The stmmac_dma_ops::set_tx_tail_ptr() implements the same logic except that it also updates the Tx tail pointer and if the former method is specific for the DW MAC100/GMAC IP-cores the later callback is available on the most modern IP-cores: DW QoS Ether, DW XGMAC, etc. Thus both callbacks must be called in sync otherwise the respective xmit procedures will work differently on different controllers. Currently it's correctly implemented for the simple net_device_ops::stmmac_xmit() method. But for some mysterious reason the XDP ZC (stmmac_xdp_xmit_zc()) and XDP Tx/Redirect (stmmac_xdp_xmit_xdpf()) features get to call the stmmac_enable_dma_transmission() method right after each Tx-frame submission to Tx DMA engine way before the stmmac_flush_tx_descriptors() invocation thus delivering poorer XDP-performance on DW MAC100/GMAC devices. Let's fix that by moving the stmmac_enable_dma_transmission() method call to stmmac_flush_tx_descriptors() where the stmmac_set_tx_tail_ptr() function invocation resides. Thus both Tx DMA charging methods will be called in sync delivering the same Tx procedure semantics on all the supported DW network controllers. Fixes: 132c32ee5bc0 ("net: stmmac: Add TX via XDP zero-copy socket") Fixes: be8b38a722e6 ("net: stmmac: Add support for XDP_TX action") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 4caa0b5210ceb..b1c5090352761 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -3007,8 +3007,6 @@ static bool stmmac_xdp_xmit_zc(struct stmmac_priv *priv, u32 queue, u32 budget) csum, priv->mode, true, true, xdp_desc.len); - stmmac_enable_dma_transmission(priv, priv->ioaddr, queue); - xsk_tx_metadata_to_compl(meta, &tx_q->tx_skbuff_dma[entry].xsk_meta); @@ -4588,8 +4586,16 @@ static void stmmac_flush_tx_descriptors(struct stmmac_priv *priv, int queue) */ wmb(); + /* Update Tx desc tail pointer and issue the Tx poll demand cmd + * (specific for the most modern IP-cores: DW QoS Ether, XGMAC, etc). + */ tx_q->tx_tail_addr = tx_q->dma_tx_phy + (tx_q->cur_tx * desc_size); stmmac_set_tx_tail_ptr(priv, priv->ioaddr, tx_q->tx_tail_addr, queue); + + /* Just issue the Tx poll demand cmd (specific to the older DW + * MAC100/GMAC IP-cores). + */ + stmmac_enable_dma_transmission(priv, priv->ioaddr, queue); } /** @@ -5150,7 +5156,6 @@ flush_ring: dev_kfree_skb(skb); priv->xstats.tx_dropped++; } - stmmac_enable_dma_transmission(priv, priv->ioaddr, queue); stmmac_flush_tx_descriptors(priv, queue); stmmac_tx_timer_arm(priv, queue); } @@ -5517,8 +5522,6 @@ static int stmmac_xdp_xmit_xdpf(struct stmmac_priv *priv, int queue, u64_stats_update_end(&txq_stats->q_syncp); } - stmmac_enable_dma_transmission(priv, priv->ioaddr, queue); - entry = STMMAC_GET_ENTRY(entry, priv->dma_conf.dma_tx_size); tx_q->cur_tx = entry; -- cgit v1.2.3 From f5217b89a59fae16008e37532068e71b51bd2b5a Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 22 Sep 2025 15:26:29 +0300 Subject: net: stmmac: Fix spurious MAC IRQs on device open The MAC IRQs are enabled early on the NET-device open stage by means of the stmmac_core_init() method execution. But the IRQ-line is requested only after the main part of the DMA/MAC blocks are initialized and even after the PHY-link is set up. That causes the unhandled IRQs being caught in case if the MAC IRQ is really shared. Let's fix that by performing the IRQ-line request procedure earlier - before the PHY-link is brought up thus making sure that all the link-related IRQs will happen afterwards and won't be missed. Note this change is also required for the DW MAC GPI IRQs handling in the shared IRQ-line manner. Fixes: 523f11b5d4fd ("net: stmmac: move hardware setup for stmmac_open to new function") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index b1c5090352761..e0d94e7d7a00e 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -4265,12 +4265,15 @@ static int __stmmac_open(struct net_device *dev, stmmac_init_coalesce(priv); - phylink_start(priv->phylink); - + /* Request IRQs before any PHY/DMA/etc IRQs are activated to prevent + * the spurious IRQs getting by the handlers of the shared MAC IRQ. + */ ret = stmmac_request_irq(dev); if (ret) goto irq_error; + phylink_start(priv->phylink); + stmmac_enable_all_queues(priv); netif_tx_start_all_queues(priv->dev); stmmac_enable_all_dma_irq(priv); @@ -4278,8 +4281,6 @@ static int __stmmac_open(struct net_device *dev, return 0; irq_error: - phylink_stop(priv->phylink); - for (chan = 0; chan < priv->plat->tx_queues_to_use; chan++) hrtimer_cancel(&priv->dma_conf.tx_queue[chan].txtimer); -- cgit v1.2.3 From a176b9aa714e180c333ca366d3a7b8dc4a9f087a Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 22 Sep 2025 14:43:16 +0300 Subject: net: stmmac: Enable LPI IRQs on the link up event The MAC IRQs are enabled early on the NET-device open stage by means of the stmmac_core_init() method execution. This also concerns the MAC LPI IRQ. There is no point in handling the event while the link is down, since the device won't track LPI events anyway. Moreover having the LPI IRQ unmasked before the respective IRQ-handler is installed will cause the early LPI IRQ being considered as spurious on the shared-IRQ case thus force-converting the IRQ-line to being polled. Considering all the above let's enable the LPI IRQ only on the MAC Link Up event. Thus preventing the IRQ from happening before the respective IRQ-handler is installed and the link is established. This change is also required for the DW MAC GPI IRQs handling in the shared IRQ-line manner. Fixes: d765955d2ae0 ("stmmac: add the Energy Efficient Ethernet support") Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/dwmac1000.h | 4 +++- drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c | 13 ++++++++----- drivers/net/ethernet/stmicro/stmmac/dwmac4.h | 3 +-- drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c | 13 ++++++++----- drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h | 2 +- drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c | 11 ++++++++--- 6 files changed, 29 insertions(+), 17 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000.h b/drivers/net/ethernet/stmicro/stmmac/dwmac1000.h index 6d69ec596fb96..809a09a2a4d42 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac1000.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000.h @@ -35,10 +35,12 @@ #define GMAC_INT_DISABLE_PCSAN BIT(2) #define GMAC_INT_DISABLE_PMT BIT(3) #define GMAC_INT_DISABLE_TIMESTAMP BIT(9) +#define GMAC_INT_DISABLE_LPI BIT(10) #define GMAC_INT_DEFAULT_MASK (GMAC_INT_DISABLE_RGMII | \ GMAC_INT_DISABLE_PCSLINK | \ GMAC_INT_DISABLE_PCSAN | \ - GMAC_INT_DISABLE_TIMESTAMP) + GMAC_INT_DISABLE_TIMESTAMP | \ + GMAC_INT_DISABLE_LPI) /* PMT Control and Status */ #define GMAC_PMT 0x0000002c diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c index 38032786b2214..29fb465d81920 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c @@ -559,15 +559,18 @@ static int dwmac1000_set_lpi_mode(struct mac_device_info *hw, static void dwmac1000_set_eee_pls(struct mac_device_info *hw, int link) { void __iomem *ioaddr = hw->pcsr; - u32 value; + u32 value, intr_mask; value = readl(ioaddr + LPI_CTRL_STATUS); - - if (link) + intr_mask = readl(ioaddr + GMAC_INT_MASK); + if (link) { value |= LPI_CTRL_STATUS_PLS; - else + intr_mask &= ~GMAC_INT_DISABLE_LPI; + } else { + intr_mask |= GMAC_INT_DISABLE_LPI; value &= ~LPI_CTRL_STATUS_PLS; - + } + writel(intr_mask, ioaddr + GMAC_INT_MASK); writel(value, ioaddr + LPI_CTRL_STATUS); } diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4.h b/drivers/net/ethernet/stmicro/stmmac/dwmac4.h index 53ac45b50e22d..a59fab217db21 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4.h @@ -111,8 +111,7 @@ #define GMAC_INT_LPI_EN BIT(5) #define GMAC_INT_TSIE BIT(12) -#define GMAC_INT_DEFAULT_ENABLE (GMAC_INT_PMT_EN | GMAC_INT_LPI_EN | \ - GMAC_INT_TSIE) +#define GMAC_INT_DEFAULT_ENABLE (GMAC_INT_PMT_EN | GMAC_INT_TSIE) enum dwmac4_irq_status { time_stamp_irq = 0x00001000, diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c index 5d95acfc748f6..5ca647fe1c453 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c @@ -479,15 +479,18 @@ static int dwmac4_set_lpi_mode(struct mac_device_info *hw, static void dwmac4_set_eee_pls(struct mac_device_info *hw, int link) { void __iomem *ioaddr = hw->pcsr; - u32 value; + u32 value, intr_enable; value = readl(ioaddr + GMAC4_LPI_CTRL_STATUS); - - if (link) + intr_enable = readl(ioaddr + GMAC_INT_EN); + if (link) { value |= LPI_CTRL_STATUS_PLS; - else + intr_enable |= GMAC_INT_LPI_EN; + } else { + intr_enable &= ~GMAC_INT_LPI_EN; value &= ~LPI_CTRL_STATUS_PLS; - + } + writel(intr_enable, ioaddr + GMAC_INT_EN); writel(value, ioaddr + GMAC4_LPI_CTRL_STATUS); } diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h index 1798289ef6ab5..549574454a7d9 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h @@ -82,7 +82,7 @@ #define XGMAC_TSIE BIT(12) #define XGMAC_LPIIE BIT(5) #define XGMAC_PMTIE BIT(4) -#define XGMAC_INT_DEFAULT_EN (XGMAC_LPIIE | XGMAC_PMTIE) +#define XGMAC_INT_DEFAULT_EN (XGMAC_PMTIE) #define XGMAC_Qx_TX_FLOW_CTRL(x) (0x00000070 + (x) * 4) #define XGMAC_PT GENMASK(31, 16) #define XGMAC_TFE BIT(1) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c index 197e9d5dd6db7..7e4457bfa86f2 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_core.c @@ -526,13 +526,18 @@ static int dwxgmac2_set_lpi_mode(struct mac_device_info *hw, static void dwxgmac2_set_eee_pls(struct mac_device_info *hw, int link) { void __iomem *ioaddr = hw->pcsr; - u32 value; + u32 value, en; value = readl(ioaddr + XGMAC_LPI_CTRL); - if (link) + en = readl(ioaddr + XGMAC_INT_EN); + if (link) { value |= LPI_CTRL_STATUS_PLS; - else + en |= XGMAC_LPIIE; + } else { + en &= ~XGMAC_LPIIE; value &= ~LPI_CTRL_STATUS_PLS; + } + writel(en, ioaddr + XGMAC_INT_EN); writel(value, ioaddr + XGMAC_LPI_CTRL); } -- cgit v1.2.3 From 9919ac0db02eb8175d599f5cac8c76b2d2a3785e Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 18 Sep 2025 12:16:30 +0300 Subject: net: stmmac: dwmac1000: Convert GPI and PCS IRQs to being expected DW GMAC low-level code considers the DMA_STATUS.GLI flag being set as unexpected and prints an ugly warning to the system log. The bit indicates an PCS/RGMII/GPI interrupt. Since the RGMII PCS IRQs handling has been already added and the GPI IRQ handling is about to be added let's remove that semantics since the respective IRQs are now expected. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c b/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c index 90d2d93c322fc..031deffde9d9e 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c @@ -254,8 +254,7 @@ int dwmac_dma_interrupt(struct stmmac_priv *priv, void __iomem *ioaddr, x->rx_early_irq++; } /* Optional hardware blocks, interrupts should be disabled */ - if (unlikely(intr_status & - (DMA_STATUS_GPI | DMA_STATUS_GMI | DMA_STATUS_GLI))) + if (unlikely(intr_status & (DMA_STATUS_GPI | DMA_STATUS_GMI))) pr_warn("%s: unexpected status %08x\n", __func__, intr_status); /* Clear the interrupt by writing a logic 1 to the CSR5[15-0] */ -- cgit v1.2.3 From 223d668bdffc91eb82ac267586a58de603e8e61c Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 11 Sep 2025 15:38:29 +0300 Subject: dt-bindings: net: dwmac: Add embedded GPIO-chip properties Synopsys DesignWare Ethernet controllers can be synthesized with General-Purpose IOs support. GPIOs can work either as inputs or as outputs depending on the gpi_i/gpo_o ports they belong. The ports width (number of possible inputs/outputs) and the configuration registers layout depend on the IP-core version and on the GIW/GOW (GP I/O port diwth) IP-core synthesize parameter. For instance, DW GMAC can have from 0 to 4 GPIs and from 0 to 4 GPOs, meanwhile DW QoS/xGMAC have wider ports width up to 16 pins of each type. Since these GPIOs can be used as generic platform GPIOs the DW MAC DT-node is supposed to describe them accordingly. So the DW MAC DT-bindings are extended to permit the "ngpios" property, which can't have a value greater than 32, standard GPIO-related properties like "gpio-controller" and "#gpio-cells", and, if GPIs are supposed to be detected, IRQ-controller related properties. Signed-off-by: Serge Semin --- Documentation/devicetree/bindings/net/snps,dwmac.yaml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Documentation/devicetree/bindings/net/snps,dwmac.yaml b/Documentation/devicetree/bindings/net/snps,dwmac.yaml index ae73c7bcf82c5..80e0a83de9ca3 100644 --- a/Documentation/devicetree/bindings/net/snps,dwmac.yaml +++ b/Documentation/devicetree/bindings/net/snps,dwmac.yaml @@ -185,6 +185,23 @@ properties: power-domains: maxItems: 1 + ngpios: + description: + Total number of GPIOs the MAC supports. The property shall include both + the GPI and GPO ports width. + minimum: 1 + maximum: 32 + + gpio-controller: true + + "#gpio-cells": + const: 2 + + interrupt-controller: true + + "#interrupt-cells": + const: 2 + mac-mode: $ref: ethernet-controller.yaml#/properties/phy-connection-type description: -- cgit v1.2.3 From 8d149a427127d12b7ed259c824641968e705b231 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 11 Sep 2025 15:12:37 +0300 Subject: net: stmmac: Add GPIO low-level infrastructure Synopsys DesignWare Ethernet controllers can be synthesized with General-Purpose IOs support. In general these GPIOs can be used for anything (but most likely for the network-specific stuff). Thus the subsequent commits will add the STMMAC sub-module registering a respective GPIO-chip. But before it's done the STMMAC low-level programming interface must be extended. First of all the GPIO data accessors is added. Even though all DW *MAC GPIOs are semantically equivalent their CSR-wise implementation might be different. That is the GPI type set/get, GPI IRQ en/dis, GPI get and GPO set/get functions are either exposed via a single CSR (DW GMAC v3.7x case) or as two control/status registers (DW QoS/XGMAC/etc case). So in order to implement a generic GPIO-chip sub-module in the STMMAC driver these differences will be abstracted out by the stmmac_gpio_ops accessors. Secondly there is an issue connected with the controller resets. The GPIOs embedded into the DW MACs are a subject of the reset-related actions. That is if any hard- or soft-reset of the controller is performed the GPIO settings will be reset as well. If the hard-reset is happening on the device probing stage the soft-reset is executed on each network device open. So should the GPIOs support is added as is to the driver, their hardware implementation will be reset on each network device open cycle. This is definitely not how generic GPIO-chip supposed to work. The only way to fix that is to avoid the soft-reset procedure anyhow. But the rest of the driver code relies on the controller being in a default state on each device opening. Thus the best approach to solve the denoted issue is to replace the controller soft-reset with a proper device cleanup procedure. That's what the second part of the change about - to provide such infrastructure. So the provided programming interface is dedicated to solve the denoted issues above and to prepare the driver for further GPIO-related extensions. It's left unused for now up until the DW MAC GPIO-chip support is added to the driver. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/common.h | 1 + drivers/net/ethernet/stmicro/stmmac/hwif.c | 2 ++ drivers/net/ethernet/stmicro/stmmac/hwif.h | 25 +++++++++++++++++++++++++ 3 files changed, 28 insertions(+) diff --git a/drivers/net/ethernet/stmicro/stmmac/common.h b/drivers/net/ethernet/stmicro/stmmac/common.h index 4a811516474bc..ce383f012220b 100644 --- a/drivers/net/ethernet/stmicro/stmmac/common.h +++ b/drivers/net/ethernet/stmicro/stmmac/common.h @@ -645,6 +645,7 @@ struct mac_device_info { const struct stmmac_mmc_ops *mmc; const struct stmmac_est_ops *est; const struct stmmac_vlan_ops *vlan; + const struct stmmac_gpio_ops *gpio; struct dw_xpcs *xpcs; struct phylink_pcs *phylink_pcs; struct mii_regs mii; /* MII register Addresses */ diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.c b/drivers/net/ethernet/stmicro/stmmac/hwif.c index 46b8cf7345a86..7d443c2749503 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.c +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.c @@ -111,6 +111,7 @@ static const struct stmmac_hwif_entry { const void *mmc; const void *est; const void *vlan; + const void *gpio; int (*setup)(struct stmmac_priv *priv); int (*quirks)(struct stmmac_priv *priv); } stmmac_hw[] = { @@ -358,6 +359,7 @@ int stmmac_hwif_init(struct stmmac_priv *priv) mac->mmc = mac->mmc ? : entry->mmc; mac->est = mac->est ? : entry->est; mac->vlan = mac->vlan ? : entry->vlan; + mac->gpio = mac->gpio ? : entry->gpio; priv->hw = mac; priv->fpe_cfg.reg = entry->regs.fpe_reg; diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index a7f73b039fdf6..c8b64b6d47ea0 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -198,6 +198,8 @@ struct stmmac_dma_ops { void (*init_tx_chan)(struct stmmac_priv *priv, void __iomem *ioaddr, struct stmmac_dma_cfg *dma_cfg, dma_addr_t phy, u32 chan); + /* DMA core cleanup */ + void (*clean)(struct stmmac_priv *priv, void __iomem *ioaddr); /* Configure the AXI Bus Mode Register */ void (*axi)(void __iomem *ioaddr, struct stmmac_axi *axi); /* Dump DMA registers */ @@ -262,6 +264,8 @@ struct stmmac_dma_ops { stmmac_do_void_callback(__priv, dma, init_rx_chan, __priv, __args) #define stmmac_init_tx_chan(__priv, __args...) \ stmmac_do_void_callback(__priv, dma, init_tx_chan, __priv, __args) +#define stmmac_dma_clean(__priv, __args...) \ + stmmac_do_void_callback(__priv, dma, clean, __priv, __args) #define stmmac_axi(__priv, __args...) \ stmmac_do_void_callback(__priv, dma, axi, __args) #define stmmac_dump_dma_regs(__priv, __args...) \ @@ -332,6 +336,10 @@ struct stmmac_ops { int (*pcs_init)(struct stmmac_priv *priv); /* MAC core initialization */ void (*core_init)(struct mac_device_info *hw, struct net_device *dev); + /* MAC core stop */ + int (*core_stop)(struct mac_device_info *hw); + /* MAC core cleanup */ + void (*core_clean)(struct mac_device_info *hw); /* Update MAC capabilities */ void (*update_caps)(struct stmmac_priv *priv); /* Change the interrupt enable setting. Enable takes precedence. */ @@ -439,6 +447,10 @@ struct stmmac_ops { stmmac_do_callback(__priv, mac, pcs_init, __priv) #define stmmac_core_init(__priv, __args...) \ stmmac_do_void_callback(__priv, mac, core_init, __args) +#define stmmac_core_stop(__priv, __args...) \ + stmmac_do_callback(__priv, mac, core_stop, __args) +#define stmmac_core_clean(__priv, __args...) \ + stmmac_do_void_callback(__priv, mac, core_clean, __args) #define stmmac_mac_update_caps(__priv) \ stmmac_do_void_callback(__priv, mac, update_caps, __priv) #define stmmac_mac_irq_modify(__priv, __args...) \ @@ -662,6 +674,19 @@ struct stmmac_vlan_ops { #define stmmac_restore_hw_vlan_rx_fltr(__priv, __args...) \ stmmac_do_void_callback(__priv, vlan, restore_hw_vlan_rx_fltr, __args) +struct stmmac_gpio_ops { + void (*set_ctrl)(struct stmmac_priv *priv, u32 gpie, u32 gpit, u32 gpo); + void (*get_ctrl)(struct stmmac_priv *priv, u32 *gpie, u32 *gpit, u32 *gpo); + int (*get_gpis)(struct stmmac_priv *priv); +}; + +#define stmmac_gpio_set_ctrl(__priv, __args...) \ + stmmac_do_void_callback(__priv, gpio, set_ctrl, __priv, __args) +#define stmmac_gpio_get_ctrl(__priv, __args...) \ + stmmac_do_void_callback(__priv, gpio, get_ctrl, __priv, __args) +#define stmmac_gpio_get_gpis(__priv) \ + stmmac_do_callback(__priv, gpio, get_gpis, __priv) + struct stmmac_regs_off { const struct stmmac_fpe_reg *fpe_reg; u32 ptp_off; -- cgit v1.2.3 From a8c19a421893fca887060cd525f3e86371fd1801 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 12 Sep 2025 13:33:57 +0300 Subject: net: stmmac: Add GPIO-chip driver sub-module Synopsys DesignWare Ethernet controllers can be synthesized with General-Purpose IOs support. GPIOs are divided into two ports: gpi_i - inputs and gpo_o - outputs. While the GPIOs semantics is identical on all DW Ethernet controllers the ports width (number of possible inputs/outputs) and the configuration registers layout depend on the IP-core version. For instance, DW GMAC can have from 0 to 4 GPIs and from 0 to 4 GPOs, while DW QoS Ether/xGMAC/etc have a wider ports width up to 16 pins of each one. In the framework of provided implementation any DesignWare Ethernet controllers GPIO block can be supported as soon as the GPIO registers accessors and the cleanup methods are defined for the respective IP-core. Total number of GPIOs MAC supports is supposed to be passed via the platform descriptor. If it's an OF-based platform, then the standard "ngpios" DT-property will be parsed for it as well. Before registering the GPIO-chip in the kernel, the driver will try to auto-detect the number of GPIs and GPOs by writing 1s into the GPI type config register. Reading the written value back and calculating the number of actually set bits will give the GPI port width the device has been synthesized with. If GPIs have been detected then GPIO IRQ-chip will be also initialized and only in that case the GPIO IRQs handling will be activated. Since the pending events are cleared just by reading from the GPI event status register, only the edged IRQs type can be implemented. For the same reason and for the reason of having the rest of GPIO configs reside in the same CSR, the GPI type, GPI mask and GPO state caches have been added. So the driver would avoid RMW CSR update code to minimize the pending GPI events clearing. Note in case of GPIOs being available the core soft-resetting mustn't be executed otherwise the GPIO configs will be reset to the initial state. Instead MAC/DMA stop and CSRs cleanup has been added. Signed-off-by: Serge Semin --- .../device_drivers/ethernet/stmicro/stmmac.rst | 4 + drivers/net/ethernet/stmicro/stmmac/Kconfig | 2 + drivers/net/ethernet/stmicro/stmmac/Makefile | 2 +- drivers/net/ethernet/stmicro/stmmac/hwif.c | 14 + drivers/net/ethernet/stmicro/stmmac/stmmac.h | 5 + drivers/net/ethernet/stmicro/stmmac/stmmac_gpio.c | 519 +++++++++++++++++++++ drivers/net/ethernet/stmicro/stmmac/stmmac_gpio.h | 39 ++ drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 9 + .../net/ethernet/stmicro/stmmac/stmmac_platform.c | 5 + include/linux/stmmac.h | 1 + 10 files changed, 599 insertions(+), 1 deletion(-) create mode 100644 drivers/net/ethernet/stmicro/stmmac/stmmac_gpio.c create mode 100644 drivers/net/ethernet/stmicro/stmmac/stmmac_gpio.h diff --git a/Documentation/networking/device_drivers/ethernet/stmicro/stmmac.rst b/Documentation/networking/device_drivers/ethernet/stmicro/stmmac.rst index 5d46e50361294..c0e6f1e7538c9 100644 --- a/Documentation/networking/device_drivers/ethernet/stmicro/stmmac.rst +++ b/Documentation/networking/device_drivers/ethernet/stmicro/stmmac.rst @@ -495,6 +495,10 @@ is used to configure the AMBA bridge to generate more efficient STBus traffic:: int has_xgmac; +37) Total number of supported GPIOs:: + + u32 ngpios; + :: } diff --git a/drivers/net/ethernet/stmicro/stmmac/Kconfig b/drivers/net/ethernet/stmicro/stmmac/Kconfig index 07088d03dbab5..71c1a21ba9690 100644 --- a/drivers/net/ethernet/stmicro/stmmac/Kconfig +++ b/drivers/net/ethernet/stmicro/stmmac/Kconfig @@ -10,6 +10,8 @@ config STMMAC_ETH select PHYLINK select CRC32 select RESET_CONTROLLER + select GPIOLIB + select GPIOLIB_IRQCHIP select NET_DEVLINK help This is the driver for the Ethernet IPs built around a diff --git a/drivers/net/ethernet/stmicro/stmmac/Makefile b/drivers/net/ethernet/stmicro/stmmac/Makefile index 9f24db4cf7a83..cd3c62b4a9646 100644 --- a/drivers/net/ethernet/stmicro/stmmac/Makefile +++ b/drivers/net/ethernet/stmicro/stmmac/Makefile @@ -7,7 +7,7 @@ stmmac-objs:= stmmac_main.o stmmac_ethtool.o stmmac_mdio.o \ dwmac4_dma.o dwmac4_lib.o dwmac4_core.o dwmac5.o hwif.o \ stmmac_tc.o dwxgmac2_core.o dwxgmac2_dma.o dwxgmac2_descs.o \ stmmac_xdp.o stmmac_est.o stmmac_fpe.o stmmac_vlan.o \ - stmmac_pcs.o $(stmmac-y) + stmmac_pcs.o stmmac_gpio.o $(stmmac-y) stmmac-$(CONFIG_STMMAC_SELFTESTS) += stmmac_selftests.o diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.c b/drivers/net/ethernet/stmicro/stmmac/hwif.c index 7d443c2749503..ddf1f201969bc 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.c +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.c @@ -7,6 +7,7 @@ #include "common.h" #include "stmmac.h" #include "stmmac_fpe.h" +#include "stmmac_gpio.h" #include "stmmac_ptp.h" #include "stmmac_est.h" #include "stmmac_vlan.h" @@ -90,10 +91,23 @@ int stmmac_reset(struct stmmac_priv *priv) { struct plat_stmmacenet_data *plat = priv->plat; void __iomem *ioaddr = priv->ioaddr; + int ret; if (plat && plat->fix_soc_reset) return plat->fix_soc_reset(priv); + if (stmmac_gpio_is_available(priv)) { + ret = stmmac_core_stop(priv, priv->hw); + if (ret) + return ret; + + ret = stmmac_core_clean(priv, priv->hw); + if (ret) + return ret; + + return stmmac_dma_clean(priv, ioaddr); + } + return stmmac_do_callback(priv, dma, reset, ioaddr); } diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h index 2053f62c67f2b..9ebfd26d44b85 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h @@ -27,6 +27,8 @@ #include #include +#include "stmmac_gpio.h" + struct stmmac_pcs; struct stmmac_resources { @@ -418,6 +420,9 @@ struct stmmac_priv { /* Receive Side Scaling */ struct stmmac_rss rss; + /* General Purpose IO */ + struct stmmac_gpio gpio; + /* XDP BPF Program */ unsigned long *af_xdp_zc_qps; struct bpf_prog *xdp_prog; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_gpio.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_gpio.c new file mode 100644 index 0000000000000..80df614a2662c --- /dev/null +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_gpio.c @@ -0,0 +1,519 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC + * stmmac GPIO handling + */ + +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "hwif.h" +#include "stmmac.h" +#include "stmmac_gpio.h" +#include "stmmac_platform.h" + +/* Synopsys DesignWare Ethernet controllers can be synthesized with + * General-Purpose IOs support. GPIOs can work either as inputs or as + * outputs depending on whether they belong to the gpi_i and gpo_o ports + * respectively. The ports width (number of possible inputs/outputs) and + * the configuration registers layout depend on the IP-core version and + * the GIW/GOW synthesize parameters. For instance, DW GMAC can have from + * 0 to 4 GPIs and from 0 to 4 GPOs, while DW QoS/xGMAC have a wider ports + * width up to 16 pins of each one. Here are the DW MAC GPIO block + * implementation specifics: + * + * Note 1. The GPIs state is multiplexed with the edge-triggered interrupt + * status in the GPIO control/status register. That is in case of the falling + * or rising edge event the GPI status register field preserves the latched + * state of the input pin until the next read from the register. So in order to + * read the actual state of the input pin we'd need to read the GPIO status + * register twice. But that also causes the race condition between the ordinary + * GPI state reads and the GPI IRQ handler. In order to fix that the + * IRQ-pending value cache is implemented. + * + * Note 2. The GPIs state configuration fields are mapped either to the + * GPIO control or the GPIO status register, which other than that may + * also provide the settings like GPOs state, GPIs IRQ type/mask/unmask. + * Due to that we have no choice but to cache the registers state and use + * the cached values to update the denoted settings, so to prevent the + * racy reads of the GPIs. + * + * Note 3. Due to the multiplexed GPIs state and interrupt status, the + * driver may experience false repeated IRQs detection. That is if after + * reading the GPI status register and calling the interrupt handler a client + * device doesn't revert the IRQ lane state and some other GPI raises an + * interrupt, the driver will get to detect the previous IRQ again. So the + * GPIO client device must return the GPI state back to idling after the + * IRQ is handled. + * + * Note 4. The GPIOs state is cleared together with the DW *MAC controller + * reset. So if IP-core isn't fixed to prevent that behavior the core + * resets mustn't be performed while the GPIO-chip is being registered in + * the system to have a stable GPIs/GPOs interface. + */ + +/* IRQs are acked in the parental ISR, but the irq_ack() callback is still + * required for the edge-triggered IRQ handling by the IRQ-chip core. + */ +static void stmmac_gpio_irq_ack(struct irq_data *data) +{ +} + +static void stmmac_gpio_irq_mask(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct stmmac_priv *priv = gpiochip_get_data(gc); + unsigned long flags; + u32 mask; + + mask = ~BIT(irqd_to_hwirq(d)); + + spin_lock_irqsave(&priv->gpio.lock, flags); + + priv->gpio.cache.gpie &= mask; + stmmac_gpio_set_ctrl(priv, priv->gpio.cache.gpie, + priv->gpio.cache.gpit, priv->gpio.cache.gpo); + + priv->gpio.cache.gpip &= mask; + + spin_unlock_irqrestore(&priv->gpio.lock, flags); +} + +static void stmmac_gpio_irq_unmask(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct stmmac_priv *priv = gpiochip_get_data(gc); + unsigned long flags; + u32 mask; + + mask = BIT(irqd_to_hwirq(d)); + + spin_lock_irqsave(&priv->gpio.lock, flags); + + priv->gpio.cache.gpie |= mask; + stmmac_gpio_set_ctrl(priv, priv->gpio.cache.gpie, + priv->gpio.cache.gpit, priv->gpio.cache.gpo); + + spin_unlock_irqrestore(&priv->gpio.lock, flags); +} + +static int stmmac_gpio_irq_set_wake(struct irq_data *d, unsigned int on) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct stmmac_priv *priv = gpiochip_get_data(gc); + + return irq_set_irq_wake(priv->dev->irq, on); +} + +static int stmmac_gpio_irq_set_type(struct irq_data *d, u32 type) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct stmmac_priv *priv = gpiochip_get_data(gc); + irq_hw_number_t offset = irqd_to_hwirq(d); + unsigned long flags; + int ret = 0; + u32 mask; + + if (offset >= priv->gpio.ngpis) + return -EINVAL; + + mask = BIT(offset); + + spin_lock_irqsave(&priv->gpio.lock, flags); + + switch (type) { + case IRQ_TYPE_EDGE_RISING: + priv->gpio.cache.gpit &= ~mask; + break; + case IRQ_TYPE_EDGE_FALLING: + priv->gpio.cache.gpit |= mask; + break; + default: + ret = -EINVAL; + goto err_unlock; + } + + stmmac_gpio_set_ctrl(priv, priv->gpio.cache.gpie, + priv->gpio.cache.gpit, priv->gpio.cache.gpo); + + priv->gpio.cache.gpip &= ~mask; + +err_unlock: + spin_unlock_irqrestore(&priv->gpio.lock, flags); + + return ret; +} + +static void stmmac_gpio_irq_print_chip(struct irq_data *d, struct seq_file *p) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + + seq_printf(p, dev_name(gc->parent)); +} + +static irqreturn_t stmmac_gpio_interrupt(int irq, void *dev_id) +{ + struct stmmac_gpio *gpio = dev_id; + struct gpio_chip *gc = &gpio->gc; + struct stmmac_priv *priv; + irq_hw_number_t hwirq; + unsigned long status; + + priv = container_of(gpio, struct stmmac_priv, gpio); + + /* Make sure we get to handle only the GPIs with unmasked + * latched-low/-high events and the GPI is latched in accordance with + * the type of the event. If for some reason the IRQ cause hasn't been + * cleared on the previous IRQ handler execution or the client device + * hasn't got the GPI state back, here we'll get a repeated false IRQ. + */ + spin_lock(&gpio->lock); + + status = stmmac_gpio_get_gpis(priv); + status = (status ^ gpio->cache.gpit) | gpio->cache.gpip; + status &= gpio->cache.gpie; + + gpio->cache.gpip = 0; + + spin_unlock(&gpio->lock); + + for_each_set_bit(hwirq, &status, gpio->ngpis) + generic_handle_irq(irq_find_mapping(gc->irq.domain, hwirq)); + + return IRQ_RETVAL(status); +} + +static const struct irq_chip stmmac_gpio_irq_chip = { + .name = "dwmac", + .irq_ack = stmmac_gpio_irq_ack, + .irq_mask = stmmac_gpio_irq_mask, + .irq_unmask = stmmac_gpio_irq_unmask, + .irq_set_wake = stmmac_gpio_irq_set_wake, + .irq_set_type = stmmac_gpio_irq_set_type, + .irq_print_chip = stmmac_gpio_irq_print_chip, + .flags = IRQCHIP_IMMUTABLE, + GPIOCHIP_IRQ_RESOURCE_HELPERS, +}; + +static int stmmac_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) +{ + struct stmmac_priv *priv = gpiochip_get_data(gc); + + if (offset < priv->gpio.ngpis) + return GPIO_LINE_DIRECTION_IN; + else if (offset < priv->plat->ngpios) + return GPIO_LINE_DIRECTION_OUT; + + return -EINVAL; +} + +static int stmmac_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct stmmac_priv *priv = gpiochip_get_data(gc); + unsigned long flags; + int ret; + + if (offset >= priv->plat->ngpios) + return -EINVAL; + + spin_lock_irqsave(&priv->gpio.lock, flags); + + if (offset < priv->gpio.ngpis) { + /* Read twice to clear the latched state out and get the + * current input pin state. + */ + ret = stmmac_gpio_get_gpis(priv); + priv->gpio.cache.gpip |= (ret ^ priv->gpio.cache.gpit) & + priv->gpio.cache.gpie; + + ret = stmmac_gpio_get_gpis(priv); + priv->gpio.cache.gpip |= (ret ^ priv->gpio.cache.gpit) & + priv->gpio.cache.gpie; + } else { + ret = priv->gpio.cache.gpo << priv->gpio.ngpis; + } + + spin_unlock_irqrestore(&priv->gpio.lock, flags); + + return !!(ret & BIT(offset)); +} + +static int stmmac_gpio_get_multiple(struct gpio_chip *gc, unsigned long *mask, + unsigned long *bits) +{ + struct stmmac_priv *priv = gpiochip_get_data(gc); + unsigned long flags, fs, val = 0; + + fs = __ffs(*mask); + + spin_lock_irqsave(&priv->gpio.lock, flags); + + if (fs <= priv->gpio.ngpis) { + /* Read twice to clear the latched state out and get the + * current input pin state. + */ + val = stmmac_gpio_get_gpis(priv); + priv->gpio.cache.gpip |= (val ^ priv->gpio.cache.gpit) & + priv->gpio.cache.gpie; + + val = stmmac_gpio_get_gpis(priv); + priv->gpio.cache.gpip |= (val ^ priv->gpio.cache.gpit) & + priv->gpio.cache.gpie; + } + + val |= (priv->gpio.cache.gpo << priv->gpio.ngpis); + + spin_unlock_irqrestore(&priv->gpio.lock, flags); + + bitmap_replace(bits, bits, &val, mask, priv->plat->ngpios); + + return 0; +} + +static int stmmac_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) +{ + struct stmmac_priv *priv = gpiochip_get_data(gc); + unsigned long flags; + + if (offset < priv->gpio.ngpis || offset >= priv->plat->ngpios) + return -EINVAL; + + offset -= priv->gpio.ngpis; + + spin_lock_irqsave(&priv->gpio.lock, flags); + + if (value) + priv->gpio.cache.gpo |= BIT(offset); + else + priv->gpio.cache.gpo &= ~BIT(offset); + + stmmac_gpio_set_ctrl(priv, priv->gpio.cache.gpie, + priv->gpio.cache.gpit, priv->gpio.cache.gpo); + + spin_unlock_irqrestore(&priv->gpio.lock, flags); + + return 0; +} + +static int stmmac_gpio_set_multiple(struct gpio_chip *gc, unsigned long *mask, + unsigned long *bits) +{ + struct stmmac_priv *priv = gpiochip_get_data(gc); + unsigned long flags, gpom, gpob; + + gpom = *mask >> priv->gpio.ngpis; + gpob = *bits >> priv->gpio.ngpis; + + spin_lock_irqsave(&priv->gpio.lock, flags); + + priv->gpio.cache.gpo = (priv->gpio.cache.gpo & ~gpom) | gpob; + + stmmac_gpio_set_ctrl(priv, priv->gpio.cache.gpie, + priv->gpio.cache.gpit, priv->gpio.cache.gpo); + + spin_unlock_irqrestore(&priv->gpio.lock, flags); + + return 0; +} + +static int stmmac_gpio_set_config(struct gpio_chip *gc, unsigned int offset, + unsigned long cfg) +{ + enum pin_config_param mode = pinconf_to_config_param(cfg); + + /* All output pins operate as open source */ + if (mode != PIN_CONFIG_DRIVE_OPEN_SOURCE) + return -ENOTSUPP; + + return gpiochip_generic_config(gc, offset, cfg); +} + +static int stmmac_gpio_data_init(struct stmmac_priv *priv) +{ + u32 tmp; + + if (priv->plat->ngpios > STMMAC_GPI_MAX) { + dev_err(priv->device, "Invalid ngpios specified\n"); + return -EINVAL; + } + + /* GPIs auto-detection: save GPO state, set as many GPI type bits + * as possible, then read the written value back. The number of set + * bits will be the actual number of GPIs. Leave the GPI type field + * being initialized with ones (i.e. the inputs having latched-low + * type) to have the falling-edge IRQs by default. + */ + priv->gpio.cache.gpit = ~0; + stmmac_gpio_get_ctrl(priv, &tmp, &tmp, &priv->gpio.cache.gpo); + stmmac_gpio_set_ctrl(priv, priv->gpio.cache.gpie, + priv->gpio.cache.gpit, priv->gpio.cache.gpo); + stmmac_gpio_get_ctrl(priv, &priv->gpio.cache.gpie, + &priv->gpio.cache.gpit, &priv->gpio.cache.gpo); + + priv->gpio.ngpis = hweight_long(priv->gpio.cache.gpit); + + if (priv->gpio.ngpis > priv->plat->ngpios) { + dev_err(priv->device, "ngpios less than detected\n"); + return -EINVAL; + } + + priv->gpio.ngpos = priv->plat->ngpios - priv->gpio.ngpis; + + spin_lock_init(&priv->gpio.lock); + + dev_info(priv->device, "GPI: %u, GPO: %u\n", priv->gpio.ngpis, + priv->gpio.ngpos); + + return 0; +} + +static void stmmac_gpio_chip_init(struct stmmac_priv *priv) +{ + struct gpio_chip *gc = &priv->gpio.gc; + + gc->label = dev_name(priv->device); + gc->parent = priv->device; + gc->owner = THIS_MODULE; + gc->request = gpiochip_generic_request, + gc->free = gpiochip_generic_free, + gc->get_direction = stmmac_gpio_get_direction; + gc->get = stmmac_gpio_get; + gc->get_multiple = stmmac_gpio_get_multiple; + gc->set = stmmac_gpio_set; + gc->set_multiple = stmmac_gpio_set_multiple; + gc->set_config = stmmac_gpio_set_config; + gc->base = -1; + gc->ngpio = priv->plat->ngpios; + gc->can_sleep = false; +} + +static int stmmac_gpio_irq_init(struct stmmac_priv *priv) +{ + struct gpio_chip *gc = &priv->gpio.gc; + struct gpio_irq_chip *girq = &gc->irq; + int ret; + + /* Skip GPIO IRQ-chip initialization if no GPI detected */ + if (!priv->gpio.ngpis) + return 0; + + /* Make sure no MAC activity enabled before requesting IRQ so not + * to end up with spurious IRQs flood left pending after boot up. + */ + ret = stmmac_core_stop(priv, priv->hw); + if (ret) { + dev_err(priv->device, "Failed to stop MAC before GPIO init\n"); + return ret; + } + + girq->handler = handle_edge_irq; + girq->default_type = IRQ_TYPE_NONE; + girq->num_parents = 0; + girq->parents = NULL; + girq->parent_handler = NULL; + + gpio_irq_chip_set_chip(girq, &stmmac_gpio_irq_chip); + + ret = request_irq(priv->dev->irq, stmmac_gpio_interrupt, + IRQF_SHARED, priv->dev->name, &priv->gpio); + if (ret) { + dev_err(priv->device, "Failed to request GPIO IRQ\n"); + return ret; + } + + return 0; +} + +static void stmmac_gpio_irq_free(struct stmmac_priv *priv) +{ + if (!priv->gpio.ngpis) + return; + + free_irq(priv->dev->irq, &priv->gpio); +} + +static int stmmac_gpio_chip_register(struct stmmac_priv *priv) +{ + struct gpio_chip *gc = &priv->gpio.gc; + int ret; + + ret = gpiochip_add_data(gc, priv); + if (ret) + dev_err(priv->device, "Failed to register GPIO-chip\n"); + + return ret; +} + +static void stmmac_gpio_chip_remove(struct stmmac_priv *priv) +{ + gpiochip_remove(&priv->gpio.gc); +} + +/** + * stmmac_gpio_is_available - test whether DW MAC GPIO is detected + * @priv: driver private structure + * Description: check whether the DW MAC has been synthesized with some + * GPIOs enabled. + * Return: + * true if the DW MAC has GPIO capability, otherwise - false + */ +bool stmmac_gpio_is_available(struct stmmac_priv *priv) +{ + return !!priv->plat->ngpios; +} + +/** + * stmmac_gpio_add - add DW MAC GPIO chip + * @priv: driver private structure + * Description: register GPIO-chip handling the DW *MAC GPIs and GPOs + * Return: + * returns 0 on success, otherwise negative errno. + */ +int stmmac_gpio_setup(struct stmmac_priv *priv) +{ + int ret; + + if (!priv->plat->ngpios) + return 0; + + ret = stmmac_gpio_data_init(priv); + if (ret) + return ret; + + stmmac_gpio_chip_init(priv); + + ret = stmmac_gpio_irq_init(priv); + if (ret) + return ret; + + ret = stmmac_gpio_chip_register(priv); + if (ret) + goto err_irq_free; + + return 0; + +err_irq_free: + stmmac_gpio_irq_free(priv); + + return ret; +} + +/** + * stmmac_gpio_clean - remove DW MAC GPIO-chip + * @priv: driver private structure + * Description: remove GPIO-chip registered for the DW *MAC GPIs and GPOs + */ +void stmmac_gpio_clean(struct stmmac_priv *priv) +{ + if (!priv->plat->ngpios) + return; + + stmmac_gpio_chip_remove(priv); + + stmmac_gpio_irq_free(priv); +} diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_gpio.h b/drivers/net/ethernet/stmicro/stmmac/stmmac_gpio.h new file mode 100644 index 0000000000000..17888e0b64d56 --- /dev/null +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_gpio.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC + * stmmac GPIO handling + */ + +#ifndef __STMMAC_GPIO_H__ +#define __STMMAC_GPIO_H__ + +#include +#include +#include + +#include "stmmac.h" + +#define STMMAC_GPI_MAX 16 +#define STMMAC_GPO_MAX 16 +#define STMMAC_GPIO_MAX (STMMAC_GPI_MAX + STMMAC_GPO_MAX) + +struct stmmac_gpio { + struct gpio_chip gc; + + u32 ngpis; + u32 ngpos; + + struct { + u32 gpie; + u32 gpit; + u32 gpip; + u32 gpo; + } cache; + spinlock_t lock; +}; + +bool stmmac_gpio_is_available(struct stmmac_priv *priv); +int stmmac_gpio_setup(struct stmmac_priv *priv); +void stmmac_gpio_clean(struct stmmac_priv *priv); + +#endif /* __STMMAC_GPIO_H__ */ diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index e0d94e7d7a00e..fd8bffe9e7d1b 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -49,6 +49,7 @@ #include "stmmac_ptp.h" #include "stmmac_fpe.h" #include "stmmac.h" +#include "stmmac_gpio.h" #include "stmmac_pcs.h" #include "stmmac_xdp.h" #include @@ -8852,6 +8853,10 @@ static int __stmmac_dvr_probe(struct device *device, if (!pm_runtime_enabled(device)) pm_runtime_enable(device); + ret = stmmac_gpio_setup(priv); + if (ret) + goto error_gpio_setup; + ret = stmmac_mdio_register(ndev); if (ret < 0) { dev_err_probe(priv->device, ret, @@ -8904,6 +8909,8 @@ error_phy_setup: error_pcs_setup: stmmac_mdio_unregister(ndev); error_mdio_register: + stmmac_gpio_clean(priv); +error_gpio_setup: pm_runtime_put_noidle(device); pm_runtime_disable(device); stmmac_napi_del(ndev); @@ -8975,6 +8982,8 @@ void stmmac_dvr_remove(struct device *dev) stmmac_pcs_clean(ndev); stmmac_mdio_unregister(ndev); + stmmac_gpio_clean(priv); + stmmac_state_clear(priv); if (priv->plat->stmmac_rst) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c index 03f1498eca3b1..b02a557c67e5d 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c @@ -492,6 +492,11 @@ stmmac_probe_config_dt(struct platform_device *pdev, u8 *mac) goto error_put_phy; } + /* Retrieve total number of supported GPIOs from the STMMAC DT-node. + * Amount of GPIs and GPOs will be auto-detected by the driver later. + */ + of_property_read_u32(np, "ngpios", &plat->ngpios); + of_property_read_u32(np, "tx-fifo-depth", &plat->tx_fifo_size); of_property_read_u32(np, "rx-fifo-depth", &plat->rx_fifo_size); diff --git a/include/linux/stmmac.h b/include/linux/stmmac.h index d9c9ef8a933f5..4e53b946d63e9 100644 --- a/include/linux/stmmac.h +++ b/include/linux/stmmac.h @@ -226,6 +226,7 @@ struct plat_stmmacenet_data { struct device_node *mdio_node; struct stmmac_dma_cfg *dma_cfg; struct stmmac_safety_feature_cfg *safety_feat_cfg; + u32 ngpios; int clk_csr; int enh_desc; int tx_coe; -- cgit v1.2.3 From 995f7a1b5ff5b86db25d2cd484cf565bce7b1e65 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 12 Sep 2025 14:08:44 +0300 Subject: net: stmmac: dwmac1000: Add GPIO module support Synopsys DW GMAC v3.7x can be synthesized with up to four GPIs and four GPOs support, which in case if enabled can be configured via a MAC CSR 0xe0. In order to have the DW GMAC GPIO interface supported in the STMMAC GPIO driver the GPIO CSR accessors and MAC/DMA gentle stop/cleanup methods are introduced. These are utilized as the low-level feature implementation abstracting out the DW GMAC GPIO-specifics. The GPIO-chip related code using that infrastructure has already been added to the driver. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/dwmac1000.h | 14 ++ .../net/ethernet/stmicro/stmmac/dwmac1000_core.c | 162 +++++++++++++++++++++ .../net/ethernet/stmicro/stmmac/dwmac1000_dma.c | 24 +++ drivers/net/ethernet/stmicro/stmmac/hwif.c | 1 + drivers/net/ethernet/stmicro/stmmac/hwif.h | 1 + 5 files changed, 202 insertions(+) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000.h b/drivers/net/ethernet/stmicro/stmmac/dwmac1000.h index 809a09a2a4d42..1de5d07b64ad2 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac1000.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000.h @@ -27,6 +27,7 @@ #define GMAC_INT_STATUS_MMCTIS BIT(6) #define GMAC_INT_STATUS_MMCCSUM BIT(7) #define GMAC_INT_STATUS_LPIIS BIT(10) +#define GMAC_INT_STATUS_GPIIS BIT(11) /* interrupt mask register */ #define GMAC_INT_MASK 0x0000003c @@ -75,6 +76,14 @@ enum power_event { /* GMAC Watchdog timeout for received frames register */ #define GMAC_WDT 0x000000dc +/* General Purpose IO register */ +#define GMAC_GPIO 0x000000e0 /* General Purpose IO */ + +/* MMC registers */ +#define GMAC_MMC_CTRL 0x00000100 +#define GMAC_MMC_RX_INTR 0x00000104 +#define GMAC_MMC_TX_INTR 0x00000108 + /* VLAN Tag Inclusion or Replacement register */ #define GMAC_VLAN_INCL 0x00000584 @@ -137,6 +146,11 @@ enum inter_frame_gap { /* Watchdog timeout for received frames defines */ #define GMAC_WDT_PWE BIT(16) #define GMAC_WDT_WTO GENMASK(13, 0) +/* General Purpose IO defines */ +#define GMAC_GPIO_GPIS GENMASK(3, 0) /* General Purpose Input Sts */ +#define GMAC_GPIO_GPO GENMASK(11, 8) /* General Purpose Output */ +#define GMAC_GPIO_GPIE GENMASK(19, 16) /* GPI Interrupt Enable */ +#define GMAC_GPIO_GPIT GENMASK(27, 24) /* GPI Type */ /* VLAN Tag inclusing defines */ #define GMAC_VLAN_CSVL BIT(19) #define GMAC_VLAN_VLC GENMASK(17, 16) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c index 29fb465d81920..31fbaa93aa2eb 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c @@ -12,6 +12,7 @@ Author: Giuseppe Cavallaro *******************************************************************************/ +#include #include #include #include @@ -22,6 +23,7 @@ #include "stmmac_pcs.h" #include "stmmac_ptp.h" #include "dwmac1000.h" +#include "dwmac_dma.h" static int dwmac1000_pcs_init(struct stmmac_priv *priv) { @@ -86,6 +88,125 @@ static void dwmac1000_irq_modify(struct mac_device_info *hw, u32 disable, spin_unlock_irqrestore(&hw->irq_ctrl_lock, flags); } +/* Stop the DMA and MAC engines (as described in the Stopping and + * Starting Transmission chapter of the databook) + */ +static int dwmac1000_core_stop(struct mac_device_info *hw) +{ + void __iomem *ioaddr = hw->pcsr; + const unsigned int maxch = 8; + int ret, ch; + u32 value; + + /* Disable and clear DMA IRQ */ + for (ch = 0; ch < maxch; ch++) { + writel(0, ioaddr + DMA_CHAN_INTR_ENA(ch)); + writel(0x0001FFFF, ioaddr + DMA_CHAN_STATUS(ch)); + } + + /* Disable MAC interrupts. Note the MAC interrupts are enabled by + * default after reset, but mask them out anyway so to avoid any + * spurious IRQ in the clean up procedure + */ + writel(0x7FF, ioaddr + GMAC_INT_MASK); + + /* Disable DMA transmitter */ + for (ch = 0; ch < maxch; ch++) { + value = readl(ioaddr + DMA_CHAN_CONTROL(ch)); + value &= ~DMA_CONTROL_ST; + writel(value, ioaddr + DMA_CHAN_CONTROL(ch)); + } + + /* Wait for MAC to stop transmitting the data */ + ret = readl_poll_timeout(ioaddr + GMAC_DEBUG, value, + !(value & GMAC_DEBUG_TPESTS), + 1000, 20000); + if (ret) + return ret; + + /* Disable MAC transmitter and receiver */ + writel(0, ioaddr + GMAC_CONTROL); + + /* Make sure Tx FIFOs is empty */ + ret = readl_poll_timeout(ioaddr + GMAC_DEBUG, value, + !(value & GMAC_DEBUG_TXFSTS), + 1000, 20000); + if (ret) + return ret; + + /* Wait for MAC to stop receiving the data */ + ret = readl_poll_timeout(ioaddr + GMAC_DEBUG, value, + !(value & GMAC_DEBUG_RPESTS), + 1000, 20000); + if (ret) + return ret; + + /* Make sure Rx FIFOs is empty */ + ret = readl_poll_timeout(ioaddr + GMAC_DEBUG, value, + !(value & GMAC_DEBUG_RXFSTS_MASK), + 1000, 20000); + if (ret) + return ret; + + /* Disable DMA receiver after making sure no data in Rx FIFO */ + for (ch = 0; ch < maxch; ch++) + writel(0, ioaddr + DMA_CHAN_CONTROL(ch)); + + return 0; +} + +/* Clean the basic MAC registers up */ +static void dwmac1000_core_clean(struct mac_device_info *hw) +{ + void __iomem *ioaddr = hw->pcsr; + int i; + + /* Restore default CSRs state */ + writel(0, ioaddr + GMAC_FRAME_FILTER); + writel(0, ioaddr + GMAC_HASH_HIGH); + writel(0, ioaddr + GMAC_HASH_LOW); + for (i = 0; i < 8; i++) + writel(0, ioaddr + GMAC_EXTHASH_BASE + 4 * i); + writel(0, ioaddr + GMAC_FLOW_CTRL); + writel(0, ioaddr + GMAC_VLAN_TAG); + writel(0x80000000, ioaddr + GMAC_PMT); + writel(0, ioaddr + LPI_CTRL_STATUS); + writel(0x03e80000, ioaddr + LPI_TIMER_CTRL); + for (i = 0; i < 128; i++) { + writel(0x0000ffff, ioaddr + GMAC_ADDR_HIGH(i)); + writel(0xffffffff, ioaddr + GMAC_ADDR_LOW(i)); + } + writel(0, ioaddr + GMAC_PCS_BASE); + writel(0, ioaddr + GMAC_RGSMIIIS); + writel(0, ioaddr + GMAC_WDT); + writel(0x1, ioaddr + GMAC_MMC_CTRL); + writel(0, ioaddr + GMAC_VLAN_INCL); + writel(0, ioaddr + GMAC_VLAN_HASH_TABLE); + + /* IEEE 1588-2002/2008 Timestamps */ + writel(0, ioaddr + 0xb00); + writel(0, ioaddr + 0xb04); + writel(0, ioaddr + 0xb10); + writel(0, ioaddr + 0xb14); + writel(0, ioaddr + 0xb18); + + /* L3/L4 filters (not supported by the driver yet) */ + writel(0, ioaddr + 0x400); + writel(0, ioaddr + 0x430); + writel(0, ioaddr + 0x404); + writel(0, ioaddr + 0x434); + for (i = 0; i < 4; i++) + writel(0, ioaddr + 0x410 + 4 * i); + + /* Clear the pending statuses */ + readl(ioaddr + GMAC_INT_STATUS); + readl(ioaddr + GMAC_PMT); + readl(ioaddr + LPI_CTRL_STATUS); + readl(ioaddr + GMAC_RGSMIIIS); + readl(ioaddr + GMAC_MMC_RX_INTR); + readl(ioaddr + GMAC_MMC_TX_INTR); +} + static void dwmac1000_rx_fcs_enable(struct mac_device_info *hw, bool enable) { void __iomem *ioaddr = hw->pcsr; @@ -680,9 +801,44 @@ static void dwmac1000_set_mac_loopback(void __iomem *ioaddr, bool enable) writel(value, ioaddr + GMAC_CONTROL); } +static void dwmac1000_gpio_set_ctrl(struct stmmac_priv *priv, u32 gpie, + u32 gpit, u32 gpo) +{ + u32 value; + + value = FIELD_PREP(GMAC_GPIO_GPO, gpo) | + FIELD_PREP(GMAC_GPIO_GPIE, gpie) | + FIELD_PREP(GMAC_GPIO_GPIT, gpit); + + writel(value, priv->ioaddr + GMAC_GPIO); +} + +static void dwmac1000_gpio_get_ctrl(struct stmmac_priv *priv, u32 *gpie, + u32 *gpit, u32 *gpo) +{ + u32 value; + + value = readl(priv->ioaddr + GMAC_GPIO); + + *gpie = FIELD_GET(GMAC_GPIO_GPIE, value); + *gpit = FIELD_GET(GMAC_GPIO_GPIT, value); + *gpo = FIELD_GET(GMAC_GPIO_GPO, value); +} + +static int dwmac1000_gpio_get_gpis(struct stmmac_priv *priv) +{ + u32 value; + + value = readl(priv->ioaddr + GMAC_GPIO); + + return FIELD_GET(GMAC_GPIO_GPIS, value); +} + const struct stmmac_ops dwmac1000_ops = { .pcs_init = dwmac1000_pcs_init, .core_init = dwmac1000_core_init, + .core_stop = dwmac1000_core_stop, + .core_clean = dwmac1000_core_clean, .irq_modify = dwmac1000_irq_modify, .set_mac = stmmac_set_mac, .rx_fcs = dwmac1000_rx_fcs_enable, @@ -712,6 +868,12 @@ const struct stmmac_vlan_ops dwmac1000_vlan_ops = { .set_hw_vlan_mode = dwmac1000_vlan_set_hw_mode, }; +const struct stmmac_gpio_ops dwmac1000_gpio_ops = { + .set_ctrl = dwmac1000_gpio_set_ctrl, + .get_ctrl = dwmac1000_gpio_get_ctrl, + .get_gpis = dwmac1000_gpio_get_gpis, +}; + int dwmac1000_setup(struct stmmac_priv *priv) { struct mac_device_info *mac = priv->hw; diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c index 8a6a6f79bde61..ddceb33dcd40b 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_dma.c @@ -102,6 +102,29 @@ static void dwmac1000_dma_init_tx(struct stmmac_priv *priv, writel(lower_32_bits(dma_tx_phy), ioaddr + DMA_CHAN_TX_BASE_ADDR(chan)); } +static void dwmac1000_dma_clean(struct stmmac_priv *priv, void __iomem *ioaddr) +{ + const unsigned int maxch = 8; + int ch; + + for (ch = 0; ch < maxch; ch++) { + /* Restore default CSRs state */ + writel(0x00020100, ioaddr + DMA_CHAN_BUS_MODE(ch)); + writel(0, ioaddr + DMA_CHAN_RCV_BASE_ADDR(ch)); + writel(0, ioaddr + DMA_CHAN_TX_BASE_ADDR(ch)); + writel(0, ioaddr + DMA_CHAN_CONTROL(ch)); + writel(0, ioaddr + DMA_CHAN_INTR_ENA(ch)); + writel(0, ioaddr + DMA_CHAN_RX_WATCHDOG(ch)); + + /* Clear the pending statuses */ + readl(ioaddr + DMA_CHAN_MISSED_FRAME_CTR(ch)); + writel(0x0007FFFF, ioaddr + DMA_CHAN_STATUS(ch)); + } + + /* AXI Bus Mode CSR is shared for all channel */ + writel(0x00110001, ioaddr + DMA_AXI_BUS_MODE); +} + static u32 dwmac1000_configure_fc(u32 csr6, int rxfifosz) { csr6 &= ~DMA_CONTROL_RFA_MASK; @@ -286,6 +309,7 @@ const struct stmmac_dma_ops dwmac1000_dma_ops = { .init_chan = dwmac1000_dma_init_channel, .init_rx_chan = dwmac1000_dma_init_rx, .init_tx_chan = dwmac1000_dma_init_tx, + .clean = dwmac1000_dma_clean, .axi = dwmac1000_dma_axi, .dump_regs = dwmac1000_dump_dma_regs, .dma_rx_mode = dwmac1000_dma_operation_mode_rx, diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.c b/drivers/net/ethernet/stmicro/stmmac/hwif.c index ddf1f201969bc..6ed6be48323cb 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.c +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.c @@ -161,6 +161,7 @@ static const struct stmmac_hwif_entry { .ptp = &dwmac1000_ptp_clock_ops, .tc = NULL, .mmc = &dwmac_mmc_ops, + .gpio = &dwmac1000_gpio_ops, .setup = dwmac1000_setup, .quirks = stmmac_dwmac1_quirks, }, { diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h index c8b64b6d47ea0..c19782bf5e20c 100644 --- a/drivers/net/ethernet/stmicro/stmmac/hwif.h +++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h @@ -708,6 +708,7 @@ extern const struct stmmac_dma_ops dwmac100_dma_ops; extern const struct stmmac_ops dwmac1000_ops; extern const struct stmmac_dma_ops dwmac1000_dma_ops; extern const struct stmmac_vlan_ops dwmac1000_vlan_ops; +extern const struct stmmac_gpio_ops dwmac1000_gpio_ops; extern const struct stmmac_ops dwmac4_ops; extern const struct stmmac_dma_ops dwmac4_dma_ops; extern const struct stmmac_ops dwmac410_ops; -- cgit v1.2.3 From fc5caa43f52f9c3036ee823c961ee56b6899771a Mon Sep 17 00:00:00 2001 From: "Russell King (Oracle)" Date: Wed, 18 Mar 2026 16:06:16 +0000 Subject: net: stmmac: add struct stmmac_pcs_info We need to describe one more register (offset and field bitmask) to the PCS code. Move the existing PCS offset and interrupt enable bits to a new struct and pass that in to stmmac_integrated_pcs_init(). Signed-off-by: Russell King (Oracle) --- drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c | 9 ++++++--- drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c | 8 ++++++-- drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.c | 8 ++++---- drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.h | 9 +++++++-- 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c index 31fbaa93aa2eb..64f5963b37f18 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c @@ -25,14 +25,17 @@ #include "dwmac1000.h" #include "dwmac_dma.h" +static const struct stmmac_pcs_info dwmac1000_pcs_info = { + .pcs_offset = GMAC_PCS_BASE, + .int_mask = GMAC_INT_DISABLE_PCSLINK | GMAC_INT_DISABLE_PCSAN, +}; + static int dwmac1000_pcs_init(struct stmmac_priv *priv) { if (!priv->dma_cap.pcs) return 0; - return stmmac_integrated_pcs_init(priv, GMAC_PCS_BASE, - GMAC_INT_DISABLE_PCSLINK | - GMAC_INT_DISABLE_PCSAN); + return stmmac_integrated_pcs_init(priv, &dwmac1000_pcs_info); } static void dwmac1000_core_init(struct mac_device_info *hw, diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c index 5ca647fe1c453..c108912ecb6e3 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c @@ -22,13 +22,17 @@ #include "dwmac4.h" #include "dwmac5.h" +static const struct stmmac_pcs_info dwmac4_pcs_info = { + .pcs_offset = GMAC_PCS_BASE, + .int_mask = GMAC_INT_PCS_LINK | GMAC_INT_PCS_ANE, +}; + static int dwmac4_pcs_init(struct stmmac_priv *priv) { if (!priv->dma_cap.pcs) return 0; - return stmmac_integrated_pcs_init(priv, GMAC_PCS_BASE, - GMAC_INT_PCS_LINK | GMAC_INT_PCS_ANE); + return stmmac_integrated_pcs_init(priv, &dwmac4_pcs_info); } static void dwmac4_core_init(struct mac_device_info *hw, diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.c index 88fa359ea7163..2695e0b9ed03c 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.c @@ -90,8 +90,8 @@ int stmmac_integrated_pcs_get_phy_intf_sel(struct phylink_pcs *pcs, return -EINVAL; } -int stmmac_integrated_pcs_init(struct stmmac_priv *priv, unsigned int offset, - u32 int_mask) +int stmmac_integrated_pcs_init(struct stmmac_priv *priv, + const struct stmmac_pcs_info *pcs_info) { struct stmmac_pcs *spcs; @@ -100,8 +100,8 @@ int stmmac_integrated_pcs_init(struct stmmac_priv *priv, unsigned int offset, return -ENOMEM; spcs->priv = priv; - spcs->base = priv->ioaddr + offset; - spcs->int_mask = int_mask; + spcs->base = priv->ioaddr + pcs_info->pcs_offset; + spcs->int_mask = pcs_info->int_mask; spcs->pcs.ops = &dwmac_integrated_pcs_ops; __set_bit(PHY_INTERFACE_MODE_SGMII, spcs->pcs.supported_interfaces); diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.h b/drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.h index 23bbd4f10bf84..f1ee473d8e3e4 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.h +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.h @@ -27,6 +27,11 @@ struct stmmac_priv; +struct stmmac_pcs_info { + unsigned int pcs_offset; + u32 int_mask; +}; + struct stmmac_pcs { struct stmmac_priv *priv; void __iomem *base; @@ -44,8 +49,8 @@ void stmmac_integrated_pcs_irq(struct stmmac_priv *priv, u32 status, struct stmmac_extra_stats *x); int stmmac_integrated_pcs_get_phy_intf_sel(struct phylink_pcs *pcs, phy_interface_t interface); -int stmmac_integrated_pcs_init(struct stmmac_priv *priv, unsigned int offset, - u32 int_mask); +int stmmac_integrated_pcs_init(struct stmmac_priv *priv, + const struct stmmac_pcs_info *pcs_info); /** * dwmac_ctrl_ane - To program the AN Control Register. -- cgit v1.2.3 From 8f0ed82ac028f4396094b0a4fa330c9b458f4a73 Mon Sep 17 00:00:00 2001 From: "Russell King (Oracle)" Date: Wed, 18 Mar 2026 16:06:21 +0000 Subject: net: stmmac: add support for reading inband SGMII status Report the link, speed and duplex for SGMII links, read from the SGMII, RGMII and SMII status and control register. Signed-off-by: Russell King (Oracle) --- drivers/net/ethernet/stmicro/stmmac/dwmac1000.h | 12 +----- .../net/ethernet/stmicro/stmmac/dwmac1000_core.c | 2 + drivers/net/ethernet/stmicro/stmmac/dwmac4.h | 10 +---- drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c | 2 + drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.c | 49 +++++++++++++++++++++- drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.h | 4 ++ 6 files changed, 58 insertions(+), 21 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000.h b/drivers/net/ethernet/stmicro/stmmac/dwmac1000.h index 1de5d07b64ad2..2688895b4e7e5 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac1000.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000.h @@ -91,18 +91,8 @@ enum power_event { #define GMAC_VLAN_HASH_TABLE 0x00000588 /* SGMII/RGMII status register */ -#define GMAC_RGSMIIIS_LNKMODE BIT(0) -#define GMAC_RGSMIIIS_SPEED GENMASK(2, 1) -#define GMAC_RGSMIIIS_LNKSTS BIT(3) -#define GMAC_RGSMIIIS_JABTO BIT(4) -#define GMAC_RGSMIIIS_FALSECARDET BIT(5) +#define GMAC_RSGMIIIS_MASK GENMASK(15, 0) #define GMAC_RGSMIIIS_SMIDRXS BIT(16) -/* LNKMOD */ -#define GMAC_RGSMIIIS_LNKMOD_MASK 0x1 -/* LNKSPEED */ -#define GMAC_RGSMIIIS_SPEED_125 0x2 -#define GMAC_RGSMIIIS_SPEED_25 0x1 -#define GMAC_RGSMIIIS_SPEED_2_5 0x0 /* GMAC Configuration defines */ #define GMAC_CONTROL_2K 0x08000000 /* IEEE 802.3as 2K packets */ diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c index 64f5963b37f18..fed0e45920905 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c @@ -27,6 +27,8 @@ static const struct stmmac_pcs_info dwmac1000_pcs_info = { .pcs_offset = GMAC_PCS_BASE, + .rgsmii_offset = GMAC_RGSMIIIS, + .rgsmii_status_mask = GMAC_RSGMIIIS_MASK, .int_mask = GMAC_INT_DISABLE_PCSLINK | GMAC_INT_DISABLE_PCSAN, }; diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4.h b/drivers/net/ethernet/stmicro/stmmac/dwmac4.h index a59fab217db21..0fed45ba26cb3 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4.h @@ -488,15 +488,7 @@ static inline u32 mtl_low_credx_base_addr(const struct dwmac4_addrs *addrs, #define GMAC_PHYIF_CTRLSTATUS_TC BIT(0) #define GMAC_PHYIF_CTRLSTATUS_LUD BIT(1) #define GMAC_PHYIF_CTRLSTATUS_SMIDRXS BIT(4) -#define GMAC_PHYIF_CTRLSTATUS_LNKMOD BIT(16) -#define GMAC_PHYIF_CTRLSTATUS_SPEED GENMASK(18, 17) -#define GMAC_PHYIF_CTRLSTATUS_LNKSTS BIT(19) -#define GMAC_PHYIF_CTRLSTATUS_JABTO BIT(20) -#define GMAC_PHYIF_CTRLSTATUS_FALSECARDET BIT(21) -/* LNKSPEED */ -#define GMAC_PHYIF_CTRLSTATUS_SPEED_125 0x2 -#define GMAC_PHYIF_CTRLSTATUS_SPEED_25 0x1 -#define GMAC_PHYIF_CTRLSTATUS_SPEED_2_5 0x0 +#define GMAC_PHYIF_CTRLSTATUS_RSGMII_MASK GENMASK(31, 16) extern const struct stmmac_dma_ops dwmac4_dma_ops; extern const struct stmmac_dma_ops dwmac410_dma_ops; diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c index c108912ecb6e3..330445306e86f 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c @@ -24,6 +24,8 @@ static const struct stmmac_pcs_info dwmac4_pcs_info = { .pcs_offset = GMAC_PCS_BASE, + .rgsmii_offset = GMAC_PHYIF_CONTROL_STATUS, + .rgsmii_status_mask = GMAC_PHYIF_CTRLSTATUS_RSGMII_MASK, .int_mask = GMAC_INT_PCS_LINK | GMAC_INT_PCS_ANE, }; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.c index 2695e0b9ed03c..df72f7c5a6a7b 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.c @@ -16,6 +16,16 @@ #define GMAC_ANE_LPA 0x0c /* ANE link partener ability */ #define GMAC_TBI 0x14 /* TBI extend status */ +/* + * RGSMII status bitfield definitions. + */ +#define GMAC_RGSMII_LNKMOD BIT(0) +#define GMAC_RGSMII_SPEED_MASK GENMASK(2, 1) +#define GMAC_RGSMII_SPEED_125 2 +#define GMAC_RGSMII_SPEED_25 1 +#define GMAC_RGSMII_SPEED_2_5 0 +#define GMAC_RGSMII_LNKSTS BIT(3) + static int dwmac_integrated_pcs_enable(struct phylink_pcs *pcs) { struct stmmac_pcs *spcs = phylink_pcs_to_stmmac_pcs(pcs); @@ -36,7 +46,42 @@ static void dwmac_integrated_pcs_get_state(struct phylink_pcs *pcs, unsigned int neg_mode, struct phylink_link_state *state) { - state->link = false; + struct stmmac_pcs *spcs = phylink_pcs_to_stmmac_pcs(pcs); + u32 status, rgsmii; + + status = readl(spcs->base + GMAC_AN_STATUS); + + if (phy_interface_mode_is_8023z(state->interface)) { + state->link = false; + } else { + rgsmii = field_get(spcs->rgsmii_status_mask, + readl(spcs->rgsmii)); + + state->link = status & BMSR_LSTATUS && + rgsmii & GMAC_RGSMII_LNKSTS; + + if (state->link && neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) { + state->duplex = rgsmii & GMAC_RGSMII_LNKMOD ? + DUPLEX_FULL : DUPLEX_HALF; + switch (FIELD_GET(GMAC_RGSMII_SPEED_MASK, rgsmii)) { + case GMAC_RGSMII_SPEED_2_5: + state->speed = SPEED_10; + break; + + case GMAC_RGSMII_SPEED_25: + state->speed = SPEED_100; + break; + + case GMAC_RGSMII_SPEED_125: + state->speed = SPEED_1000; + break; + + default: + state->link = false; + break; + } + } + } } static int dwmac_integrated_pcs_config(struct phylink_pcs *pcs, @@ -101,6 +146,8 @@ int stmmac_integrated_pcs_init(struct stmmac_priv *priv, spcs->priv = priv; spcs->base = priv->ioaddr + pcs_info->pcs_offset; + spcs->rgsmii = priv->ioaddr + pcs_info->rgsmii_offset; + spcs->rgsmii_status_mask = pcs_info->rgsmii_status_mask; spcs->int_mask = pcs_info->int_mask; spcs->pcs.ops = &dwmac_integrated_pcs_ops; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.h b/drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.h index f1ee473d8e3e4..09e609f111b1b 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.h +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.h @@ -29,12 +29,16 @@ struct stmmac_priv; struct stmmac_pcs_info { unsigned int pcs_offset; + unsigned int rgsmii_offset; + u32 rgsmii_status_mask; u32 int_mask; }; struct stmmac_pcs { struct stmmac_priv *priv; void __iomem *base; + void __iomem *rgsmii; + u32 rgsmii_status_mask; u32 int_mask; struct phylink_pcs pcs; }; -- cgit v1.2.3 From e480f0e6acb66a987cd77e72615f9bdd000e56b4 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 7 Aug 2026 17:44:03 +0300 Subject: net: stmmac: Add PHY link status change IRQ support DW GMAC and DW QoS Ether controllers support getting PHY link status reported via the RGMII, SGMII or SMII interfaces. The status reading has already been added in the commit 083757866735 ("net: stmmac: add support for reading inband SGMII status"). The interface link change support has been implemented long before that but just recently has been converted to properly working with phylink. What has been missing is the SRGMII IRQ enabling and handling in case if in-band getting the status is supported. Let's do that in the framework of so well prepared PCS module. Note the stmmac_integrated_pcs_irq() method doesn't pass the link state to the PCS core intentionally, so to be compatible with the case when PCS-based AN isn't support. It's relevant for the RGMII in-band PHY-link status signaling which is going to be added in one of the next commits. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c | 5 +++-- drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c | 4 ++-- drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.c | 17 ++++++++++++++++- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c index fed0e45920905..3714b98cd8c71 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c @@ -29,7 +29,8 @@ static const struct stmmac_pcs_info dwmac1000_pcs_info = { .pcs_offset = GMAC_PCS_BASE, .rgsmii_offset = GMAC_RGSMIIIS, .rgsmii_status_mask = GMAC_RSGMIIIS_MASK, - .int_mask = GMAC_INT_DISABLE_PCSLINK | GMAC_INT_DISABLE_PCSAN, + .int_mask = GMAC_INT_DISABLE_RGMII | GMAC_INT_DISABLE_PCSLINK | + GMAC_INT_DISABLE_PCSAN, }; static int dwmac1000_pcs_init(struct stmmac_priv *priv) @@ -656,7 +657,7 @@ static int dwmac1000_irq_status(struct stmmac_priv *priv, x->irq_rx_path_exit_lpi_mode_n++; } - if (intr_status & (PCS_ANE_IRQ | PCS_LINK_IRQ)) + if (intr_status & (PCS_ANE_IRQ | PCS_LINK_IRQ | PCS_RGSMIIIS_IRQ)) stmmac_integrated_pcs_irq(priv, intr_status, x); return ret; diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c index 330445306e86f..369bc2535e92d 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c @@ -26,7 +26,7 @@ static const struct stmmac_pcs_info dwmac4_pcs_info = { .pcs_offset = GMAC_PCS_BASE, .rgsmii_offset = GMAC_PHYIF_CONTROL_STATUS, .rgsmii_status_mask = GMAC_PHYIF_CTRLSTATUS_RSGMII_MASK, - .int_mask = GMAC_INT_PCS_LINK | GMAC_INT_PCS_ANE, + .int_mask = GMAC_INT_RGSMIIS | GMAC_INT_PCS_LINK | GMAC_INT_PCS_ANE, }; static int dwmac4_pcs_init(struct stmmac_priv *priv) @@ -721,7 +721,7 @@ static int dwmac4_irq_status(struct stmmac_priv *priv, x->irq_rx_path_exit_lpi_mode_n++; } - if (intr_status & (PCS_ANE_IRQ | PCS_LINK_IRQ)) + if (intr_status & (PCS_ANE_IRQ | PCS_LINK_IRQ | PCS_RGSMIIIS_IRQ)) stmmac_integrated_pcs_irq(priv, intr_status, x); return ret; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.c index df72f7c5a6a7b..23b56a7ae221c 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.c @@ -109,6 +109,7 @@ void stmmac_integrated_pcs_irq(struct stmmac_priv *priv, u32 status, { struct stmmac_pcs *spcs = priv->integrated_pcs; u32 val = readl(spcs->base + GMAC_AN_STATUS); + bool link_changed = false; if (status & PCS_ANE_IRQ) { x->irq_pcs_ane_n++; @@ -122,8 +123,22 @@ void stmmac_integrated_pcs_irq(struct stmmac_priv *priv, u32 status, dev_info(priv->device, "PCS Link %s\n", val & BMSR_LSTATUS ? "Up" : "Down"); - phylink_pcs_change(&spcs->pcs, val & BMSR_LSTATUS); + link_changed = true; } + + if (status & PCS_RGSMIIIS_IRQ) { + x->irq_rgmii_n++; + + val = field_get(spcs->rgsmii_status_mask, readl(spcs->rgsmii)); + dev_info(priv->device, "PHY Link %s\n", + val & GMAC_RGSMII_LNKSTS ? "Up" : "Down"); + + link_changed = true; + } + + /* Let phylink re-read the state again for any supported mode */ + if (link_changed) + phylink_pcs_change(&spcs->pcs, false); } int stmmac_integrated_pcs_get_phy_intf_sel(struct phylink_pcs *pcs, -- cgit v1.2.3 From cf39f0286dee2cae124b8d34c6b5336954cffa22 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sat, 8 Aug 2026 02:05:36 +0300 Subject: net: stmmac: Add Inband/PCS support for RGMII The PCS module currently supports in-band getting the PHY link status for the SGMII interface only. But the DW GMAC and DW QoS Ether controllers are also capable to use in-band signals of RGMII. Let's convert the STMMAC PCS module to supporting it. Basically it means to permit the integrated PCS initialization in case if the RGMII interface is enabled. That's done by checking the ActPhyIF capability detected from the GMAC. Note the link status is now determined irrespective to the PCS negotiation mode. It's fine since in case or SGMII the AN mode is always enabled so the PHY link status will be always reported in-band-ly. In case of RGMII there is no MAC-PHY negotiation and the PHY link status will be delivered for as long as the PHY supports reporting it (MLO_AN_INBAND mode is on). Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/common.h | 1 + .../net/ethernet/stmicro/stmmac/dwmac1000_core.c | 3 --- drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c | 3 --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 13 +++++++++---- drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.c | 21 +++++++++++++++++---- 5 files changed, 27 insertions(+), 14 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/common.h b/drivers/net/ethernet/stmicro/stmmac/common.h index ce383f012220b..489c064881333 100644 --- a/drivers/net/ethernet/stmicro/stmmac/common.h +++ b/drivers/net/ethernet/stmicro/stmmac/common.h @@ -277,6 +277,7 @@ struct stmmac_safety_stats { #define FLOW_AUTO (FLOW_TX | FLOW_RX) /* PCS defines */ +#define STMMAC_PCS_RGMII (1 << 0) #define STMMAC_PCS_SGMII (1 << 1) #define SF_DMA_MODE 1 /* DMA STORE-AND-FORWARD Operation Mode */ diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c index 3714b98cd8c71..323f4c9588461 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac1000_core.c @@ -35,9 +35,6 @@ static const struct stmmac_pcs_info dwmac1000_pcs_info = { static int dwmac1000_pcs_init(struct stmmac_priv *priv) { - if (!priv->dma_cap.pcs) - return 0; - return stmmac_integrated_pcs_init(priv, &dwmac1000_pcs_info); } diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c index 369bc2535e92d..9e1a116a17ee1 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_core.c @@ -31,9 +31,6 @@ static const struct stmmac_pcs_info dwmac4_pcs_info = { static int dwmac4_pcs_init(struct stmmac_priv *priv) { - if (!priv->dma_cap.pcs) - return 0; - return stmmac_integrated_pcs_init(priv, &dwmac4_pcs_info); } diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index fd8bffe9e7d1b..44a40ff66cede 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -1029,11 +1029,13 @@ static struct phylink_pcs *stmmac_mac_select_pcs(struct phylink_config *config, return pcs; } - /* The PCS control register is only relevant for SGMII, TBI and RTBI - * modes. We no longer support TBI or RTBI, so only configure this - * register when operating in SGMII mode with the integrated PCS. + /* The PCS AN CSRs are only relevant for SGMII, TBI and RTBI modes. + * The in-band link status is available for RGMII and SGMII interfaces. + * We no longer support TBI or RTBI, so use the feature when operating + * in RGMII or SGMII mode with the integrated PCS. */ - if (priv->hw->pcs & STMMAC_PCS_SGMII && priv->integrated_pcs) + if (priv->hw->pcs & (STMMAC_PCS_SGMII | STMMAC_PCS_RGMII) && + priv->integrated_pcs) return &priv->integrated_pcs->pcs; return NULL; @@ -1306,6 +1308,9 @@ static void stmmac_check_pcs_mode(struct stmmac_priv *priv) priv->hw->reverse_sgmii_enable = false; break; } + } else if (phy_interface_mode_is_rgmii(interface)) { + netdev_dbg(priv->dev, "PCS RGMII support enabled\n"); + priv->hw->pcs = STMMAC_PCS_RGMII; } } diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.c index 23b56a7ae221c..8fd6905a6831d 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_pcs.c @@ -57,10 +57,11 @@ static void dwmac_integrated_pcs_get_state(struct phylink_pcs *pcs, rgsmii = field_get(spcs->rgsmii_status_mask, readl(spcs->rgsmii)); - state->link = status & BMSR_LSTATUS && - rgsmii & GMAC_RGSMII_LNKSTS; + state->link = !!(rgsmii & GMAC_RGSMII_LNKSTS); + if (state->interface == PHY_INTERFACE_MODE_SGMII) + state->link = state->link && status & BMSR_LSTATUS; - if (state->link && neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) { + if (state->link) { state->duplex = rgsmii & GMAC_RGSMII_LNKMOD ? DUPLEX_FULL : DUPLEX_HALF; switch (FIELD_GET(GMAC_RGSMII_SPEED_MASK, rgsmii)) { @@ -92,6 +93,10 @@ static int dwmac_integrated_pcs_config(struct phylink_pcs *pcs, { struct stmmac_pcs *spcs = phylink_pcs_to_stmmac_pcs(pcs); + /* PCS AN capability isn't applicable for RGMII interface */ + if (phy_interface_mode_is_rgmii(interface)) + return 0; + dwmac_ctrl_ane(spcs->base, 0, 1, spcs->priv->hw->reverse_sgmii_enable); return 0; @@ -146,6 +151,8 @@ int stmmac_integrated_pcs_get_phy_intf_sel(struct phylink_pcs *pcs, { if (interface == PHY_INTERFACE_MODE_SGMII) return PHY_INTF_SEL_SGMII; + else if (phy_interface_mode_is_rgmii(interface)) + return PHY_INTF_SEL_RGMII; return -EINVAL; } @@ -155,6 +162,9 @@ int stmmac_integrated_pcs_init(struct stmmac_priv *priv, { struct stmmac_pcs *spcs; + if (!priv->dma_cap.pcs && priv->dma_cap.actphyif != PHY_INTF_SEL_RGMII) + return 0; + spcs = devm_kzalloc(priv->device, sizeof(*spcs), GFP_KERNEL); if (!spcs) return -ENOMEM; @@ -166,7 +176,10 @@ int stmmac_integrated_pcs_init(struct stmmac_priv *priv, spcs->int_mask = pcs_info->int_mask; spcs->pcs.ops = &dwmac_integrated_pcs_ops; - __set_bit(PHY_INTERFACE_MODE_SGMII, spcs->pcs.supported_interfaces); + if (priv->dma_cap.pcs) + __set_bit(PHY_INTERFACE_MODE_SGMII, spcs->pcs.supported_interfaces); + if (priv->dma_cap.actphyif == PHY_INTF_SEL_RGMII) + phy_interface_set_rgmii(spcs->pcs.supported_interfaces); priv->integrated_pcs = spcs; -- cgit v1.2.3 From 5004cf52edcd5d2fd70c9186b980ea1d93deda34 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 15 Sep 2025 17:32:48 +0300 Subject: net: pcs: xpcs: Drop incorrect PM-active state By default CSR clock is disabled upon request. It makes the PM-runtime state matching the default PM-state of the device - suspended. Thus let's drop the PM-runtime setting up to be active as incorrect. Otherwise the clock-subsystem will be unhappily printing a big fat warning about n-preparing an already unprepared clock. Fixes: f6bb3e9d98c2 ("net: pcs: xpcs: Add Synopsys DW xPCS platform device driver") Signed-off-by: Serge Semin --- drivers/net/pcs/pcs-xpcs-plat.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/net/pcs/pcs-xpcs-plat.c b/drivers/net/pcs/pcs-xpcs-plat.c index b8c48f9effbf7..e48b9586026c1 100644 --- a/drivers/net/pcs/pcs-xpcs-plat.c +++ b/drivers/net/pcs/pcs-xpcs-plat.c @@ -285,7 +285,6 @@ static int xpcs_plat_init_clk(struct dw_xpcs_plat *pxpcs) return dev_err_probe(dev, PTR_ERR(pxpcs->cclk), "Failed to get CSR clock\n"); - pm_runtime_set_active(dev); ret = devm_pm_runtime_enable(dev); if (ret) { dev_err(dev, "Failed to enable runtime-PM\n"); -- cgit v1.2.3 From 83ea1da5507595cf2452ac2772b78069565d5b7e Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 20 Nov 2023 23:31:12 +0300 Subject: net: pcs: xpcs: Stop overriding pause in xpcs_resolve_pma() Besides of MLO_PAUSE_RX and MLO_PAUSE_TX flags the phylink_link_state->pause field can have other flags set (i.e. MLO_PAUSE_AN) by the phylink core. Moreover based on the phylink_pcs_ops.pcs_get_state() kernel doc the later callback is intended to update the MLO_PAUSE_RX and MLO_PAUSE_TX flags state only. So drop overriding the entire phylink_link_state->pause field from the xpcs_resolve_pma() method as pointless and potentially harmful. Link: https://lore.kernel.org/netdev/ZTuvwnGZKEueGDwa@shell.armlinux.org.uk Fixes: fcb26bd2b6ca ("net: phy: Add Synopsys DesignWare XPCS MDIO module") Signed-off-by: Serge Semin --- drivers/net/pcs/pcs-xpcs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c index e69fa2f0a0e8d..86706a18c21e9 100644 --- a/drivers/net/pcs/pcs-xpcs.c +++ b/drivers/net/pcs/pcs-xpcs.c @@ -618,7 +618,7 @@ static int xpcs_resolve_pma(struct dw_xpcs *xpcs, } } - state->pause = MLO_PAUSE_TX | MLO_PAUSE_RX; + state->pause |= MLO_PAUSE_TX | MLO_PAUSE_RX; state->duplex = DUPLEX_FULL; switch (state->interface) { -- cgit v1.2.3 From 7b4b0427822ebcfc5f727d292a959cbaae1432f5 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 20 Nov 2023 23:46:21 +0300 Subject: net: pcs: xpcs: Stop overriding pause in xpcs_get_state_c37_sgmii() The phylink_link_state->pause field can have some flags being pre-set (i.e. MLO_PAUSE_AN) by the phylink core before it's passed to the phylink_pcs_ops.pcs_get_state() callback. Moreover based on the phylink_pcs_ops.pcs_get_state() kernel doc it is intended to update the MLO_PAUSE_RX and MLO_PAUSE_TX flags state only. So drop zeroing out the phylink_link_state->pause field from the xpcs_get_state_c37_sgmii() method as pointless and potentially harmful. Link: https://lore.kernel.org/netdev/ZTuvwnGZKEueGDwa@shell.armlinux.org.uk Fixes: b97b5331b8ab ("net: pcs: add C37 SGMII AN support for intel mGbE controller") Signed-off-by: Serge Semin --- drivers/net/pcs/pcs-xpcs.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c index 86706a18c21e9..3ccee6b5c56e4 100644 --- a/drivers/net/pcs/pcs-xpcs.c +++ b/drivers/net/pcs/pcs-xpcs.c @@ -1060,7 +1060,6 @@ static int xpcs_get_state_c37_sgmii(struct dw_xpcs *xpcs, state->link = false; state->speed = SPEED_UNKNOWN; state->duplex = DUPLEX_UNKNOWN; - state->pause = 0; /* For C37 SGMII mode, we check DW_VR_MII_AN_INTR_STS for link * status, speed and duplex. -- cgit v1.2.3 From 7aa1c891409af10d038479565be6d5a86f2836e7 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sun, 11 Jun 2023 14:16:26 +0300 Subject: net: pcs: xpcs: Enable Autoneg for 10Gbase-KR interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Based on the IEEE Std 802.3 Clause 73 specification 10GBASE-KR interface must support auto-negotiation for any KR and legacy R links (see Table 72–1 in the spec). Thus the link speed is supposed to be determined based on the link-partner modes retrieved on the negotiation stage. Let's fix that by adding the Autoneg flag to the list of the 10GBASE-KR features so the xpcs_get_state_c73() procedure would initialize the phylink state descriptor based on the link-partner abilities. Note since the max link speed is now determined based on the modes retrieved from the remote device there is no need in manual speed setting up in the xpcs_resolve_pma() method. So the respective case clause can be freely dropped from there. Fixes: fcb26bd2b6ca ("net: phy: Add Synopsys DesignWare XPCS MDIO module") Signed-off-by: Serge Semin --- drivers/net/pcs/pcs-xpcs.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c index 3ccee6b5c56e4..cf8c918ce6878 100644 --- a/drivers/net/pcs/pcs-xpcs.c +++ b/drivers/net/pcs/pcs-xpcs.c @@ -33,6 +33,7 @@ static const int xpcs_usxgmii_features[] = { static const int xpcs_10gkr_features[] = { ETHTOOL_LINK_MODE_Pause_BIT, ETHTOOL_LINK_MODE_Asym_Pause_BIT, + ETHTOOL_LINK_MODE_Autoneg_BIT, ETHTOOL_LINK_MODE_10000baseKR_Full_BIT, __ETHTOOL_LINK_MODE_MASK_NBITS, }; @@ -622,9 +623,6 @@ static int xpcs_resolve_pma(struct dw_xpcs *xpcs, state->duplex = DUPLEX_FULL; switch (state->interface) { - case PHY_INTERFACE_MODE_10GKR: - state->speed = SPEED_10000; - break; case PHY_INTERFACE_MODE_XLGMII: state->speed = xpcs_get_max_xlgmii_speed(xpcs, state); break; -- cgit v1.2.3 From 01257fa022a94066cf30e7177a3ed3542c5944c5 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 5 Jul 2023 15:40:04 +0300 Subject: net: phylink: Allow in-band AN for XGMII XGMII is an interface between MAC and PCS. So it's a subject of the PCS device implementation whether it supports passing link information in-band between PHY and MAC PCS. Anyway add XGMII to the list of protocols that support in-band AN mode and let the PCS driver to decide the supported link modes. Signed-off-by: Serge Semin --- drivers/net/phy/phylink.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index aba1b35c7cd7e..c5119b54a4f8b 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -818,6 +818,7 @@ static int phylink_parse_mode(struct phylink *pl, case PHY_INTERFACE_MODE_2500BASEX: case PHY_INTERFACE_MODE_5GBASER: case PHY_INTERFACE_MODE_25GBASER: + case PHY_INTERFACE_MODE_XGMII: case PHY_INTERFACE_MODE_USXGMII: case PHY_INTERFACE_MODE_10G_QXGMII: case PHY_INTERFACE_MODE_10GKR: -- cgit v1.2.3 From 8f1ac2301e0eb597ceaae89f96b4b8bfe560a1c6 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sun, 11 Jun 2023 14:51:26 +0300 Subject: net: pcs: xpcs: Add XGMII interface support Natively DW XPCS (v3.xx) supports two upstream interfaces: XGMII and GMII/MII. Former one is responsible for the 10G link speed. It's a 32-bits interface normally connected to MAC and which is supposed to be used for the PCS-R and 10G PCS-X data paths. Despite of the USXGMII it doesn't support speed change. So just the only thing what needs to be done is to add the interface and features for the XGMII interface and rely on the auto-negotiation procedure to establish the link. Note DW xGMII PHY-mode support must be added to the STMMAC driver in order to have DW XGMAC/XPCS chain working correctly. TODO add some configs to the xpcs_config_xgmii() method. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 13 ++++++++++++- drivers/net/pcs/pcs-xpcs.c | 22 ++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 44a40ff66cede..2fb13d0823ca7 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -1089,7 +1089,15 @@ static void stmmac_mac_link_up(struct phylink_config *config, old_ctrl = readl(priv->ioaddr + MAC_CTRL_REG); ctrl = old_ctrl & ~priv->hw->link.speed_mask; - if (interface == PHY_INTERFACE_MODE_USXGMII) { + if (interface == PHY_INTERFACE_MODE_XGMII) { + switch (speed) { + case SPEED_10000: + ctrl |= priv->hw->link.xgmii.speed10000; + break; + default: + return; + } + } else if (interface == PHY_INTERFACE_MODE_USXGMII) { switch (speed) { case SPEED_10000: ctrl |= priv->hw->link.xgmii.speed10000; @@ -1100,6 +1108,9 @@ static void stmmac_mac_link_up(struct phylink_config *config, case SPEED_2500: ctrl |= priv->hw->link.xgmii.speed2500; break; + case SPEED_1000: + ctrl |= priv->hw->link.speed1000; + break; default: return; } diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c index cf8c918ce6878..43b4fee4a5fd3 100644 --- a/drivers/net/pcs/pcs-xpcs.c +++ b/drivers/net/pcs/pcs-xpcs.c @@ -19,6 +19,15 @@ #define phylink_pcs_to_xpcs(pl_pcs) \ container_of((pl_pcs), struct dw_xpcs, pcs) +static const int xpcs_xgmii_features[] = { + ETHTOOL_LINK_MODE_Pause_BIT, + ETHTOOL_LINK_MODE_Asym_Pause_BIT, + ETHTOOL_LINK_MODE_Autoneg_BIT, + ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT, + ETHTOOL_LINK_MODE_10000baseKR_Full_BIT, + __ETHTOOL_LINK_MODE_MASK_NBITS, +}; + static const int xpcs_usxgmii_features[] = { ETHTOOL_LINK_MODE_Pause_BIT, ETHTOOL_LINK_MODE_Asym_Pause_BIT, @@ -356,6 +365,11 @@ static int xpcs_read_fault_c73(struct dw_xpcs *xpcs, return 0; } +static void xpcs_link_up_xgmii(struct dw_xpcs *xpcs, int speed) +{ + /* TODO add some XGMII-related configs ...*/ +} + static void xpcs_link_up_usxgmii(struct dw_xpcs *xpcs, int speed) { int ret, speed_sel; @@ -1254,6 +1268,10 @@ static void xpcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode, struct dw_xpcs *xpcs = phylink_pcs_to_xpcs(pcs); switch (interface) { + case PHY_INTERFACE_MODE_XGMII: + xpcs_link_up_xgmii(xpcs, speed); + break; + case PHY_INTERFACE_MODE_USXGMII: xpcs_link_up_usxgmii(xpcs, speed); break; @@ -1401,6 +1419,10 @@ static int xpcs_read_ids(struct dw_xpcs *xpcs) static const struct dw_xpcs_compat synopsys_xpcs_compat[] = { { + .interface = PHY_INTERFACE_MODE_XGMII, + .supported = xpcs_xgmii_features, + .an_mode = DW_AN_C73, + }, { .interface = PHY_INTERFACE_MODE_USXGMII, .supported = xpcs_usxgmii_features, .an_mode = DW_AN_C73, -- cgit v1.2.3 From 86dbffcd35b16e5182e88eb90e44b6fee1bf2b45 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Tue, 4 Jul 2023 12:39:29 +0300 Subject: net: pcs: xpcs: Drop compat arg from soft-reset method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It's very much inconvenient to have the soft-reset method requiring the xpcs_compat structure instance passed. The later one is found based on the PHY-interface type which isn't always available. Such design makes an ordinary reset-method context depended and unnecessary limits its usage area. Indeed based on [1,2] all Soft-RST flags exported by the PMA/PMD, PCS, AN or MII MMDs are _shared_. It means it resets all the DWX_xpcs internal blocks including CSRs, but except the Management Interface (MDIO, MCI, APB). Thus it doesn't really matter which MMDs soft-reset flag is set, the result will be the same. So the AN-mode-depended code can be freely dropped from the soft-reset method. But depending on the DW XPCS device capabilities (basically it depends on the IP-core synthesize parameters) it can lack some of the MMDs. In order to solve that difficulty the Vendor-Specific 1 MMD can be utilized. It is also called as Control MMD and exports some generic device info about the device including a list of the available MMDs: PMA/PMD, XS/PCS, AN or MII. This MMD persists on all the DW XPCS device [3]. Thus it can be freely utilize to cross-platformly determine actual MMD to perform the soft-reset. [1] DesignWare® Cores Ethernet PCS, Version 3.11b, June 2015, p.111. [2] DesignWare® Cores Ethernet PCS, Version 3.11b, June 2015, p.268. [3] DesignWare® Cores Ethernet PCS, Version 3.11b, June 2015, p.269. Signed-off-by: Serge Semin --- drivers/net/pcs/pcs-xpcs.c | 38 +++++++++++++++----------------------- drivers/net/pcs/pcs-xpcs.h | 8 ++++++++ 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c index 43b4fee4a5fd3..3b30c8bad1d49 100644 --- a/drivers/net/pcs/pcs-xpcs.c +++ b/drivers/net/pcs/pcs-xpcs.c @@ -285,24 +285,18 @@ static int xpcs_poll_reset(struct dw_xpcs *xpcs, int dev) return ret; } -static int xpcs_soft_reset(struct dw_xpcs *xpcs, - const struct dw_xpcs_compat *compat) +static int xpcs_soft_reset(struct dw_xpcs *xpcs) { int ret, dev; - switch (compat->an_mode) { - case DW_AN_C73: - case DW_10GBASER: - dev = MDIO_MMD_PCS; - break; - case DW_AN_C37_SGMII: - case DW_2500BASEX: - case DW_AN_C37_1000BASEX: + if (xpcs->mmd_ctrl & DW_SR_CTRL_MII_MMD_EN) dev = MDIO_MMD_VEND2; - break; - default: + else if (xpcs->mmd_ctrl & DW_SR_CTRL_PCS_XS_MMD_EN) + dev = MDIO_MMD_PCS; + else if (xpcs->mmd_ctrl & DW_SR_CTRL_PMA_MMD_EN) + dev = MDIO_MMD_PMAPMD; + else return -EINVAL; - } ret = xpcs_write(xpcs, dev, MII_BMCR, BMCR_RESET); if (ret < 0) @@ -733,7 +727,6 @@ static int xpcs_switch_interface_mode(struct dw_xpcs *xpcs, static void xpcs_pre_config(struct phylink_pcs *pcs, phy_interface_t interface) { struct dw_xpcs *xpcs = phylink_pcs_to_xpcs(pcs); - const struct dw_xpcs_compat *compat; int ret; ret = xpcs_switch_interface_mode(xpcs, interface); @@ -744,14 +737,7 @@ static void xpcs_pre_config(struct phylink_pcs *pcs, phy_interface_t interface) if (!xpcs->need_reset) return; - compat = xpcs_find_compat(xpcs, interface); - if (!compat) { - dev_err(&xpcs->mdiodev->dev, "unsupported interface %s\n", - phy_modes(interface)); - return; - } - - ret = xpcs_soft_reset(xpcs, compat); + ret = xpcs_soft_reset(xpcs); if (ret) dev_err(&xpcs->mdiodev->dev, "soft reset failed: %pe\n", ERR_PTR(ret)); @@ -1015,7 +1001,7 @@ static int xpcs_get_state_c73(struct dw_xpcs *xpcs, /* ... and then we check the faults. */ ret = xpcs_read_fault_c73(xpcs, state, pcs_stat1); if (ret) { - ret = xpcs_soft_reset(xpcs, compat); + ret = xpcs_soft_reset(xpcs); if (ret) return ret; @@ -1627,6 +1613,12 @@ static struct dw_xpcs *xpcs_create(struct mdio_device *mdiodev) if (ret) goto out_clear_clks; + ret = xpcs_read(xpcs, MDIO_MMD_VEND1, DW_SR_CTRL_MMD_CTRL); + if (ret < 0) + goto out_clear_clks; + + xpcs->mmd_ctrl = ret; + xpcs_get_interfaces(xpcs, xpcs->pcs.supported_interfaces); if (xpcs->info.pma == WX_TXGBE_XPCS_PMA_10G_ID || diff --git a/drivers/net/pcs/pcs-xpcs.h b/drivers/net/pcs/pcs-xpcs.h index 929fa238445ed..7805881e391dd 100644 --- a/drivers/net/pcs/pcs-xpcs.h +++ b/drivers/net/pcs/pcs-xpcs.h @@ -52,6 +52,13 @@ #define DW_C73_2500KX BIT(0) #define DW_C73_5000KR BIT(1) +/* VR_CTRL_MMD */ +#define DW_SR_CTRL_MMD_CTRL 0x0009 +#define DW_SR_CTRL_AN_MMD_EN BIT(0) +#define DW_SR_CTRL_PCS_XS_MMD_EN BIT(1) +#define DW_SR_CTRL_MII_MMD_EN BIT(2) +#define DW_SR_CTRL_PMA_MMD_EN BIT(3) + /* Clause 37 Defines */ /* VR MII MMD registers offsets */ #define DW_VR_MII_DIG_CTRL1 0x8000 @@ -110,6 +117,7 @@ struct dw_xpcs { const struct dw_xpcs_desc *desc; struct mdio_device *mdiodev; struct clk_bulk_data clks[DW_XPCS_NUM_CLKS]; + u16 mmd_ctrl; struct phylink_pcs pcs; phy_interface_t interface; bool need_reset; -- cgit v1.2.3 From c5a659e37630f5588f9b38cdc42660dccc6a53aa Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 14 Jun 2023 00:30:28 +0300 Subject: net: pcs: xpcs: Add vendor-specific reset support Various DW XPCS setups require vendor-specific reset procedure implementation, i.e. PCS type (KR, KX4, KX, etc) selection. It implies the reset of all DWC_xpcs internal blocks except the Management interface and CSR blocks (the standard reset clears the CSR block too). Since a multi-type XPCS support is about to be added let's add the vendor-specific reset as a preparation before that. Note 1. Since vendor-specific reset doesn't imply the CSR blocks reset we can't just write the flag to the corresponding register because thus we may override/clear some valuable CSR fields. Instead the MDIO-device modify method is utilized. Note 2. xpcs_poll_reset() method is converted to accepting CSR and flag to poll. It is done intentionally even though currently it's used to poll BIT(15) in the specified register. It may and will be used to poll some other fields in the DW XPCS space like reference clock stabilization, etc. Thus the renaming. Signed-off-by: Serge Semin --- drivers/net/pcs/pcs-xpcs.c | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c index 3b30c8bad1d49..2a07e893425b4 100644 --- a/drivers/net/pcs/pcs-xpcs.c +++ b/drivers/net/pcs/pcs-xpcs.c @@ -272,22 +272,22 @@ static int xpcs_modify_vpcs(struct dw_xpcs *xpcs, int reg, u16 mask, u16 val) return xpcs_modify_vendor(xpcs, MDIO_MMD_PCS, reg, mask, val); } -static int xpcs_poll_reset(struct dw_xpcs *xpcs, int dev) +static int xpcs_poll_val(struct dw_xpcs *xpcs, int dev, int reg, u16 mask, u16 goal) { int ret, val; ret = read_poll_timeout(xpcs_read, val, - val < 0 || !(val & BMCR_RESET), - 50000, 600000, true, xpcs, dev, MII_BMCR); + val < 0 || (val & mask) == goal, + 50000, 600000, true, xpcs, dev, reg); if (val < 0) ret = val; return ret; } -static int xpcs_soft_reset(struct dw_xpcs *xpcs) +static int __xpcs_soft_reset(struct dw_xpcs *xpcs, bool vendor) { - int ret, dev; + int ret, dev, reg; if (xpcs->mmd_ctrl & DW_SR_CTRL_MII_MMD_EN) dev = MDIO_MMD_VEND2; @@ -298,11 +298,25 @@ static int xpcs_soft_reset(struct dw_xpcs *xpcs) else return -EINVAL; - ret = xpcs_write(xpcs, dev, MII_BMCR, BMCR_RESET); + reg = MII_BMCR; + if (vendor) + reg |= DW_VENDOR; + + ret = xpcs_modify(xpcs, dev, reg, BMCR_RESET, BMCR_RESET); if (ret < 0) return ret; - return xpcs_poll_reset(xpcs, dev); + return xpcs_poll_val(xpcs, dev, reg, BMCR_RESET, 0); +} + +static int xpcs_soft_reset(struct dw_xpcs *xpcs) +{ + return __xpcs_soft_reset(xpcs, false); +} + +static int xpcs_vendor_reset(struct dw_xpcs *xpcs) +{ + return __xpcs_soft_reset(xpcs, true); } #define xpcs_warn(__xpcs, __state, __args...) \ -- cgit v1.2.3 From a4de026e4156d2a3c9e90db717dd0537fdd48e45 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Tue, 4 Jul 2023 14:25:20 +0300 Subject: net: pcs: xpcs: Globalize CSR accessors In order have the XPCS CSRs read, write, modify, poll and device soft-reset methods accessible from separate objects let's globally define them. They will be utilized from the PMA-config methods which in its turned will be later defined separately in order to simplify the main driver object file. Signed-off-by: Serge Semin --- drivers/net/pcs/pcs-xpcs.c | 14 ++++++-------- drivers/net/pcs/pcs-xpcs.h | 7 +++++++ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c index 2a07e893425b4..b035ba1ac48c2 100644 --- a/drivers/net/pcs/pcs-xpcs.c +++ b/drivers/net/pcs/pcs-xpcs.c @@ -234,19 +234,17 @@ int xpcs_modify(struct dw_xpcs *xpcs, int dev, u32 reg, u16 mask, u16 set) return mdiodev_c45_modify(xpcs->mdiodev, dev, reg, mask, set); } -static int xpcs_modify_changed(struct dw_xpcs *xpcs, int dev, u32 reg, - u16 mask, u16 set) +int xpcs_modify_changed(struct dw_xpcs *xpcs, int dev, u32 reg, u16 mask, u16 set) { return mdiodev_c45_modify_changed(xpcs->mdiodev, dev, reg, mask, set); } -static int xpcs_read_vendor(struct dw_xpcs *xpcs, int dev, u32 reg) +int xpcs_read_vendor(struct dw_xpcs *xpcs, int dev, u32 reg) { return xpcs_read(xpcs, dev, DW_VENDOR | reg); } -static int xpcs_write_vendor(struct dw_xpcs *xpcs, int dev, int reg, - u16 val) +int xpcs_write_vendor(struct dw_xpcs *xpcs, int dev, int reg, u16 val) { return xpcs_write(xpcs, dev, DW_VENDOR | reg, val); } @@ -272,7 +270,7 @@ static int xpcs_modify_vpcs(struct dw_xpcs *xpcs, int reg, u16 mask, u16 val) return xpcs_modify_vendor(xpcs, MDIO_MMD_PCS, reg, mask, val); } -static int xpcs_poll_val(struct dw_xpcs *xpcs, int dev, int reg, u16 mask, u16 goal) +int xpcs_poll_val(struct dw_xpcs *xpcs, int dev, int reg, u16 mask, u16 goal) { int ret, val; @@ -309,12 +307,12 @@ static int __xpcs_soft_reset(struct dw_xpcs *xpcs, bool vendor) return xpcs_poll_val(xpcs, dev, reg, BMCR_RESET, 0); } -static int xpcs_soft_reset(struct dw_xpcs *xpcs) +int xpcs_soft_reset(struct dw_xpcs *xpcs) { return __xpcs_soft_reset(xpcs, false); } -static int xpcs_vendor_reset(struct dw_xpcs *xpcs) +int xpcs_vendor_reset(struct dw_xpcs *xpcs) { return __xpcs_soft_reset(xpcs, true); } diff --git a/drivers/net/pcs/pcs-xpcs.h b/drivers/net/pcs/pcs-xpcs.h index 7805881e391dd..f80de96fcf8c6 100644 --- a/drivers/net/pcs/pcs-xpcs.h +++ b/drivers/net/pcs/pcs-xpcs.h @@ -127,8 +127,15 @@ struct dw_xpcs { int xpcs_read(struct dw_xpcs *xpcs, int dev, u32 reg); int xpcs_write(struct dw_xpcs *xpcs, int dev, u32 reg, u16 val); int xpcs_modify(struct dw_xpcs *xpcs, int dev, u32 reg, u16 mask, u16 set); +int xpcs_modify_changed(struct dw_xpcs *xpcs, int dev, u32 reg, u16 mask, u16 set); +int xpcs_read_vendor(struct dw_xpcs *xpcs, int dev, u32 reg); +int xpcs_write_vendor(struct dw_xpcs *xpcs, int dev, int reg, u16 val); int xpcs_read_vpcs(struct dw_xpcs *xpcs, int reg); int xpcs_write_vpcs(struct dw_xpcs *xpcs, int reg, u16 val); +int xpcs_poll_val(struct dw_xpcs *xpcs, int dev, int reg, u16 mask, u16 goal); +int xpcs_soft_reset(struct dw_xpcs *xpcs); +int xpcs_vendor_reset(struct dw_xpcs *xpcs); + int nxp_sja1105_sgmii_pma_config(struct dw_xpcs *xpcs); int nxp_sja1110_sgmii_pma_config(struct dw_xpcs *xpcs); int nxp_sja1110_2500basex_pma_config(struct dw_xpcs *xpcs); -- cgit v1.2.3 From 30cc4db3e9d3e081b604f9d1d66cfde128dfa6ca Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 5 Jul 2023 14:00:02 +0300 Subject: net: pcs: xpcs: Add 10GBASE-R interface support DW XPCS can be synthesized with 10GBASE-R link support. In that case xGMAC is still connected to the XPCS device by means of normally multi-lane interface meanwhile an external interface is defined as 10GBASE-R. From network subsystem point of view it's as if DW XPCS is fully integrated into the MAC with no need in setting any intermediate interface up. So based on the IEEE 802.3 standard 10GBASE-R doesn't intent any auto-negotiations. Instead it works with the 10Gbps speed and in the Full-duplex mode. Thus a new AN-less mode is defined DW_10GBASER in the driver. It implies PCS and PMA setups required for the device to work in the denoted mode. The respective actions are executed in the xpcs_do_config() method. At the same time xpcs_link_up() makes sure that the PCS/PMA<->PHY link actually works and it has compatible speed and duplex (though having the pcs_link_up() method returning the operation status would look better or at least having pcs_get_state() working for the non-inband setups). Note 1. 10GBASE-R PMA config implies reference clock switch in case if the respective flag doesn't correspond to the specified clock sources. Note 2. XPCS 10GBASE-R interface features list contains all speed-compatible link modes in order to have the pcs_validate() permitting these modes for the externally attached PHYs. Note 3. Soft-reset and poll-mode are activated on the XPCS-descriptor create procedure since the interface initialization is now comprehensive enough to configure device from scratch. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 3 +- drivers/net/pcs/Makefile | 2 +- drivers/net/pcs/pcs-xpcs-pma.c | 89 +++++++++++++++++++++++ drivers/net/pcs/pcs-xpcs.c | 79 ++++++++++++++++++++ drivers/net/pcs/pcs-xpcs.h | 22 ++++++ 5 files changed, 193 insertions(+), 2 deletions(-) create mode 100644 drivers/net/pcs/pcs-xpcs-pma.c diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 2fb13d0823ca7..b0826b833e4a7 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -1089,7 +1089,8 @@ static void stmmac_mac_link_up(struct phylink_config *config, old_ctrl = readl(priv->ioaddr + MAC_CTRL_REG); ctrl = old_ctrl & ~priv->hw->link.speed_mask; - if (interface == PHY_INTERFACE_MODE_XGMII) { + if (interface == PHY_INTERFACE_MODE_XGMII || + interface == PHY_INTERFACE_MODE_10GBASER) { switch (speed) { case SPEED_10000: ctrl |= priv->hw->link.xgmii.speed10000; diff --git a/drivers/net/pcs/Makefile b/drivers/net/pcs/Makefile index 4f7920618b900..95b91cebd8518 100644 --- a/drivers/net/pcs/Makefile +++ b/drivers/net/pcs/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 # Makefile for Linux PCS drivers -pcs_xpcs-$(CONFIG_PCS_XPCS) := pcs-xpcs.o pcs-xpcs-plat.o \ +pcs_xpcs-$(CONFIG_PCS_XPCS) := pcs-xpcs.o pcs-xpcs-plat.o pcs-xpcs-pma.o \ pcs-xpcs-nxp.o pcs-xpcs-wx.o obj-$(CONFIG_PCS_XPCS) += pcs_xpcs.o diff --git a/drivers/net/pcs/pcs-xpcs-pma.c b/drivers/net/pcs/pcs-xpcs-pma.c new file mode 100644 index 0000000000000..128ac789cd251 --- /dev/null +++ b/drivers/net/pcs/pcs-xpcs-pma.c @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Synopsys DesignWare XPCS PMA driver + * + * Copyright (C) 2024 Serge Semin + */ + +#include +#include +#include +#include +#include + +#include "pcs-xpcs.h" + +/* DesignWare Gen5 10G PHY can be clocked by an external clock source. + * It will be enabled instead of the internal one in case if it's specified. + */ +static int xpcs_gen5_10g_ref_clock_select(struct dw_xpcs *xpcs, bool reset) +{ + int ret; + + ret = xpcs_read_vendor(xpcs, MDIO_MMD_PMAPMD, + DW_VR_XS_PMA_GEN5_10G_MPLL_CTRL); + if (ret < 0) + return ret; + + if (xpcs->clks[DW_XPCS_PAD_CLK].clk) + ret &= ~DW_VR_XS_PMA_REF_CLK_SEL_CORE; + else if (xpcs->clks[DW_XPCS_CORE_CLK].clk) + ret |= DW_VR_XS_PMA_REF_CLK_SEL_CORE; + else if (reset) + goto out_vendor_reset; + else + return 0; + + ret = xpcs_write_vendor(xpcs, MDIO_MMD_PMAPMD, + DW_VR_XS_PMA_GEN5_10G_MPLL_CTRL, ret); + if (ret < 0) + return ret; + + /* Vendor reset must be immediately performed. Note it will workout + * only if Tx/Rx are stable (Power_Good state). + */ +out_vendor_reset: + return xpcs_vendor_reset(xpcs); +} + +static int xpcs_10gbaser_gen5_10g_pma_config(struct dw_xpcs *xpcs) +{ + int ret; + + ret = xpcs_read_vendor(xpcs, MDIO_MMD_PMAPMD, + DW_VR_XS_PMA_GEN5_10G_GEN_CTRL); + if (ret < 0) + return ret; + + /* Select KR-mode of the PHY lanes */ + ret &= ~DW_VR_XS_PMA_LANE_MODE; + ret |= FIELD_PREP(DW_VR_XS_PMA_LANE_MODE, DW_VR_XS_PMA_LANE_MODE_KR); + + /* Activate 1 lane per link */ + ret &= ~DW_VR_XS_PMA_LINK_WIDTH; + ret |= FIELD_PREP(DW_VR_XS_PMA_LINK_WIDTH, DW_VR_XS_PMA_LINK_WIDTH_1); + + /* Disable unused 1-3 lanes */ + ret &= ~DW_VR_XS_PMA_LANE_PWR_OFF; + ret |= FIELD_PREP(DW_VR_XS_PMA_LANE_PWR_OFF, 0xe); + + ret = xpcs_write_vendor(xpcs, MDIO_MMD_PMAPMD, + DW_VR_XS_PMA_GEN5_10G_GEN_CTRL, ret); + if (ret < 0) + return ret; + + /* Select PCS/PMA refclk source: Pad or Core. Vendor-reset the device + * to make sure the updates are perceived by the core + */ + return xpcs_gen5_10g_ref_clock_select(xpcs, true); +} + +int xpcs_10gbaser_pma_config(struct dw_xpcs *xpcs) +{ + switch (xpcs->info.pma) { + case DW_XPCS_PMA_GEN5_10G_ID: + return xpcs_10gbaser_gen5_10g_pma_config(xpcs); + default: + return 0; + } +} diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c index b035ba1ac48c2..e746caae64abf 100644 --- a/drivers/net/pcs/pcs-xpcs.c +++ b/drivers/net/pcs/pcs-xpcs.c @@ -124,10 +124,16 @@ static const int xpcs_100gbasep_features[] = { static const int xpcs_10gbaser_features[] = { ETHTOOL_LINK_MODE_Pause_BIT, ETHTOOL_LINK_MODE_Asym_Pause_BIT, + ETHTOOL_LINK_MODE_10000baseKR_Full_BIT, + ETHTOOL_LINK_MODE_10000baseCR_Full_BIT, ETHTOOL_LINK_MODE_10000baseSR_Full_BIT, ETHTOOL_LINK_MODE_10000baseLR_Full_BIT, ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT, ETHTOOL_LINK_MODE_10000baseER_Full_BIT, + /* Speed-compatible modes for the link setup in the external PHYs */ + ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT, + /* ETHTOOL_LINK_MODE_10000baseCX4_Full_BIT, */ + /* ETHTOOL_LINK_MODE_10000baseLX4_Full_BIT, */ __ETHTOOL_LINK_MODE_MASK_NBITS, }; @@ -902,6 +908,41 @@ static int xpcs_config_aneg_c37_1000basex(struct dw_xpcs *xpcs, return changed; } +static int xpcs_config_10gbaser(struct dw_xpcs *xpcs) +{ + int ret; + + /* Disable Clause 73 AN in anyway */ + ret = xpcs_modify(xpcs, MDIO_MMD_AN, MDIO_CTRL1, + MDIO_AN_CTRL1_ENABLE, 0); + if (ret < 0) + return ret; + + /* Disable RXAUI if it's enabled by default */ + ret = xpcs_read_vpcs(xpcs, DW_VR_XS_PCS_XAUI_MODE_CTRL); + if (ret < 0) + return ret; + + ret &= ~DW_VR_XS_PCS_RXAUI_MODE; + ret = xpcs_write_vpcs(xpcs, DW_VR_XS_PCS_XAUI_MODE_CTRL, ret); + if (ret < 0) + return ret; + + /* Set PCS to 10G speed and 10GBASE-R PCS */ + ret = xpcs_modify(xpcs, MDIO_MMD_PCS, MDIO_CTRL1, + MDIO_CTRL1_SPEEDSEL, MDIO_CTRL1_SPEED10G); + if (ret < 0) + return ret; + + ret = xpcs_modify(xpcs, MDIO_MMD_PCS, MDIO_CTRL2, + MDIO_PCS_CTRL2_TYPE, MDIO_PCS_CTRL2_10GBR); + if (ret < 0) + return ret; + + /* Make sure the specified PCS type is perceived by the core */ + return xpcs_vendor_reset(xpcs); +} + static int xpcs_config_2500basex(struct dw_xpcs *xpcs) { int ret; @@ -941,6 +982,9 @@ static int xpcs_do_config(struct dw_xpcs *xpcs, phy_interface_t interface, switch (compat->an_mode) { case DW_10GBASER: + ret = xpcs_config_10gbaser(xpcs); + if (ret) + return ret; break; case DW_AN_C73: if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) { @@ -1229,6 +1273,36 @@ static void xpcs_get_state(struct phylink_pcs *pcs, unsigned int neg_mode, } } +static void xpcs_link_up_check(struct dw_xpcs *xpcs, int speed, int duplex) +{ + int ret; + + ret = xpcs_poll_val(xpcs, MDIO_MMD_PCS, MDIO_STAT1, + MDIO_STAT1_LSTATUS, MDIO_STAT1_LSTATUS); + if (ret < 0) { + dev_err(&xpcs->mdiodev->dev, "link is malfunction\n"); + return; + } + + if (speed != SPEED_10000) + dev_warn(&xpcs->mdiodev->dev, "Incompatible speed\n"); + + if (duplex != DUPLEX_FULL) + dev_warn(&xpcs->mdiodev->dev, "Incompatible duplex\n"); + + ret = xpcs_read(xpcs, MDIO_MMD_PCS, MDIO_STAT2); + if (ret < 0) { + dev_err(&xpcs->mdiodev->dev, "Failed to read PCS status\n"); + return; + } + + if (ret & MDIO_STAT2_RXFAULT) + dev_dbg(&xpcs->mdiodev->dev, "Receiver fault detected\n"); + + if (ret & MDIO_STAT2_TXFAULT) + dev_dbg(&xpcs->mdiodev->dev, "Transmitter fault detected\n"); +} + static void xpcs_link_up_sgmii_1000basex(struct dw_xpcs *xpcs, unsigned int neg_mode, phy_interface_t interface, @@ -1274,6 +1348,10 @@ static void xpcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode, xpcs_link_up_usxgmii(xpcs, speed); break; + case PHY_INTERFACE_MODE_10GBASER: + xpcs_link_up_check(xpcs, speed, duplex); + break; + case PHY_INTERFACE_MODE_SGMII: case PHY_INTERFACE_MODE_1000BASEX: xpcs_link_up_sgmii_1000basex(xpcs, neg_mode, interface, speed, @@ -1452,6 +1530,7 @@ static const struct dw_xpcs_compat synopsys_xpcs_compat[] = { .interface = PHY_INTERFACE_MODE_10GBASER, .supported = xpcs_10gbaser_features, .an_mode = DW_10GBASER, + .pma_config = xpcs_10gbaser_pma_config, }, { .interface = PHY_INTERFACE_MODE_SGMII, .supported = xpcs_sgmii_features, diff --git a/drivers/net/pcs/pcs-xpcs.h b/drivers/net/pcs/pcs-xpcs.h index f80de96fcf8c6..40741e158e163 100644 --- a/drivers/net/pcs/pcs-xpcs.h +++ b/drivers/net/pcs/pcs-xpcs.h @@ -12,6 +12,22 @@ /* Vendor regs access */ #define DW_VENDOR BIT(15) +/* VR_XS_PMA */ +#define DW_VR_XS_PMA_GEN5_10G_MPLL_CTRL 0x007a +#define DW_VR_XS_PMA_REF_CLK_SEL_CORE BIT(13) +#define DW_VR_XS_PMA_GEN5_10G_GEN_CTRL 0x009c +#define DW_VR_XS_PMA_LANE_MODE GENMASK(3, 0) +#define DW_VR_XS_PMA_LANE_MODE_KX 0x3 +#define DW_VR_XS_PMA_LANE_MODE_KX4 0x4 +#define DW_VR_XS_PMA_LANE_MODE_KR 0x5 +#define DW_VR_XS_PMA_LANE_MODE_SGMII 0x6 +#define DW_VR_XS_PMA_LANE_MODE_RXAUI 0x8 +#define DW_VR_XS_PMA_LINK_WIDTH GENMASK(10, 8) +#define DW_VR_XS_PMA_LINK_WIDTH_1 0x0 +#define DW_VR_XS_PMA_LINK_WIDTH_2 0x1 +#define DW_VR_XS_PMA_LINK_WIDTH_4 0x2 +#define DW_VR_XS_PMA_LANE_PWR_OFF GENMASK(15, 12) + /* VR_XS_PCS */ #define DW_USXGMII_RST BIT(10) #define DW_USXGMII_EN BIT(9) @@ -19,7 +35,12 @@ #define DW_VR_RST BIT(15) #define DW_EN_VSMMD1 BIT(13) #define DW_CL37_BP BIT(12) +#define DW_VR_XS_PCS_XAUI_MODE_CTRL 0x0004 +#define DW_VR_XS_PCS_RXAUI_MODE BIT(0) +#define DW_VR_XS_PCS_MRVL_RXAUI BIT(1) #define DW_VR_XS_PCS_DIG_STS 0x0010 +#define DW_PSEQ_STATE GENMASK(4, 2) +#define DW_PSEQ_TXRX_STABLE 0x100 #define DW_RXFIFO_ERR GENMASK(6, 5) #define DW_PSEQ_ST GENMASK(4, 2) #define DW_PSEQ_ST_GOOD FIELD_PREP(GENMASK(4, 2), 0x4) @@ -136,6 +157,7 @@ int xpcs_poll_val(struct dw_xpcs *xpcs, int dev, int reg, u16 mask, u16 goal); int xpcs_soft_reset(struct dw_xpcs *xpcs); int xpcs_vendor_reset(struct dw_xpcs *xpcs); +int xpcs_10gbaser_pma_config(struct dw_xpcs *xpcs); int nxp_sja1105_sgmii_pma_config(struct dw_xpcs *xpcs); int nxp_sja1110_sgmii_pma_config(struct dw_xpcs *xpcs); int nxp_sja1110_2500basex_pma_config(struct dw_xpcs *xpcs); -- cgit v1.2.3 From a33ac36e1d9341cec1b66d6e07b6c78676bc0467 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 5 Jul 2023 16:59:11 +0300 Subject: dt-bindings: net: Add the 10gbase-x connection type Add 10gbase-x to the list of accepted PHY connection types between MAC and a physical PHY. It's physically compatible with xaui but defines PCS and PMA interface (Clause 48) towards the 10GBASE-CX4 (see Clause 54), 10GBASE-KX4 (see Clause 71), and 10GBASE-LX4 (see Clause 53). Network subsystem will be updated in the next commit. Signed-off-by: Serge Semin --- Documentation/devicetree/bindings/net/ethernet-controller.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/net/ethernet-controller.yaml b/Documentation/devicetree/bindings/net/ethernet-controller.yaml index 1bafd687dcb18..06ab21669eed9 100644 --- a/Documentation/devicetree/bindings/net/ethernet-controller.yaml +++ b/Documentation/devicetree/bindings/net/ethernet-controller.yaml @@ -69,6 +69,7 @@ properties: - trgmii - 1000base-x - 2500base-x + - 10gbase-x - 5gbase-r - rxaui - xaui -- cgit v1.2.3 From 2d2e715278dffa7d4304ec780276761cdda90fea Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 5 Jul 2023 23:36:48 +0300 Subject: net: phy: add 10GBASE-X PHY connection types The interface is defined by IEEE 802.3 Clause 48 as a PCS and PMA sublayer. It is normally utilized to connect XGMII with the 10GBASE-CX4 (Clause 54), 10GBASE-KX4 (Clause 71), and 10GBASE-LX4 (Clause 53) PHYs. It's an independent four Tx and Rx lanes interface with 8b/10b coding of the full-duplex stream with constant 10Gb/s speed. 10GBASE-X PCS/PMA is utlized by the XGXS/XAUI specifications (Clause 47). Signed-off-by: Serge Semin --- drivers/net/phy/phy-core.c | 1 + drivers/net/phy/phy_caps.c | 1 + drivers/net/phy/phylink.c | 3 +++ include/linux/phy.h | 4 ++++ 4 files changed, 9 insertions(+) diff --git a/drivers/net/phy/phy-core.c b/drivers/net/phy/phy-core.c index d7a4a977fc8ad..534abb0ab10a1 100644 --- a/drivers/net/phy/phy-core.c +++ b/drivers/net/phy/phy-core.c @@ -183,6 +183,7 @@ int phy_interface_num_ports(phy_interface_t interface) case PHY_INTERFACE_MODE_SMII: case PHY_INTERFACE_MODE_1000BASEX: case PHY_INTERFACE_MODE_2500BASEX: + case PHY_INTERFACE_MODE_10GBASEX: case PHY_INTERFACE_MODE_5GBASER: case PHY_INTERFACE_MODE_10GBASER: case PHY_INTERFACE_MODE_25GBASER: diff --git a/drivers/net/phy/phy_caps.c b/drivers/net/phy/phy_caps.c index 942d43191561e..ce79da3726308 100644 --- a/drivers/net/phy/phy_caps.c +++ b/drivers/net/phy/phy_caps.c @@ -352,6 +352,7 @@ unsigned long phy_caps_from_interface(phy_interface_t interface) break; case PHY_INTERFACE_MODE_XGMII: + case PHY_INTERFACE_MODE_10GBASEX: case PHY_INTERFACE_MODE_RXAUI: case PHY_INTERFACE_MODE_XAUI: case PHY_INTERFACE_MODE_10GBASER: diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index c5119b54a4f8b..e6cfdab4c5dc2 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -272,6 +272,7 @@ static int phylink_interface_max_speed(phy_interface_t interface) return SPEED_5000; case PHY_INTERFACE_MODE_XGMII: + case PHY_INTERFACE_MODE_10GBASEX: case PHY_INTERFACE_MODE_RXAUI: case PHY_INTERFACE_MODE_XAUI: case PHY_INTERFACE_MODE_10GBASER: @@ -821,6 +822,7 @@ static int phylink_parse_mode(struct phylink *pl, case PHY_INTERFACE_MODE_XGMII: case PHY_INTERFACE_MODE_USXGMII: case PHY_INTERFACE_MODE_10G_QXGMII: + case PHY_INTERFACE_MODE_10GBASEX: case PHY_INTERFACE_MODE_10GKR: case PHY_INTERFACE_MODE_10GBASER: case PHY_INTERFACE_MODE_XLGMII: @@ -4352,6 +4354,7 @@ void phylink_mii_c45_pcs_get_state(struct mdio_device *pcs, return; switch (state->interface) { + case PHY_INTERFACE_MODE_10GBASEX: case PHY_INTERFACE_MODE_10GBASER: state->speed = SPEED_10000; state->duplex = DUPLEX_FULL; diff --git a/include/linux/phy.h b/include/linux/phy.h index 6f9979a26892d..ac225cdfe33a9 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -93,6 +93,7 @@ extern const int phy_basic_ports_array[3]; * @PHY_INTERFACE_MODE_100BASEX: 100 BaseX * @PHY_INTERFACE_MODE_1000BASEX: 1000 BaseX * @PHY_INTERFACE_MODE_2500BASEX: 2500 BaseX + * @PHY_INTERFACE_MODE_10GBASEX: 10G BaseX * @PHY_INTERFACE_MODE_5GBASER: 5G BaseR * @PHY_INTERFACE_MODE_RXAUI: Reduced XAUI * @PHY_INTERFACE_MODE_XAUI: 10 Gigabit Attachment Unit Interface @@ -136,6 +137,7 @@ typedef enum { PHY_INTERFACE_MODE_100BASEX, PHY_INTERFACE_MODE_1000BASEX, PHY_INTERFACE_MODE_2500BASEX, + PHY_INTERFACE_MODE_10GBASEX, PHY_INTERFACE_MODE_5GBASER, PHY_INTERFACE_MODE_RXAUI, PHY_INTERFACE_MODE_XAUI, @@ -258,6 +260,8 @@ static inline const char *phy_modes(phy_interface_t interface) return "1000base-kx"; case PHY_INTERFACE_MODE_2500BASEX: return "2500base-x"; + case PHY_INTERFACE_MODE_10GBASEX: + return "10gbase-x"; case PHY_INTERFACE_MODE_5GBASER: return "5gbase-r"; case PHY_INTERFACE_MODE_RXAUI: -- cgit v1.2.3 From e404c5744d212d0a27cf2413297fbcd99f9edd4b Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 6 Jul 2023 14:12:32 +0300 Subject: net: pcs: xpcs: Add 10GBASE-X interface support DW XPCS can be synthesized with 10GBASE-X/XAUI link support. In that case xGMAC is still connected to the XPCS device by means of normally multi-lane high-speed interface (XGMII, USXGMII, etc) meanwhile an external interface is defined as 10GBASE-X. From the network subsystem point of view it's as if DW XPCS is fully integrated into the MAC with no need in setting any intermediate interface up. Similarly to the 10GBASE-R, in accordance with the IEEE 802.3 standard 10GBASE-X doesn't intent any auto-negotiations but it's defined as also being utilized by XGXS (Clause 47). Thus a new AN-less mode is defined DW_10GBASEX in the driver. It implies PCS and PMA setups required for the device to work in the denoted mode. The respective actions are executed in the xpcs_do_config() method. At the same time xpcs_link_up() makes sure that the PCS/PMA<->PHY link actually works and it has compatible speed and duplex: 10Gbps speed and full-duplex mode. Note 1. Synopsys 10G Gen5 PMA requires the reference clock switch in case if the respective flag doesn't correspond to the specified clock sources. Note 2. XPCS 10GBASE-X interface features list contains all speed-compatible link modes in order to have the pcs_validate() permitting these modes for the externally attached PHYs. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 4 +- drivers/net/pcs/pcs-xpcs-pma.c | 42 ++++++++++++++ drivers/net/pcs/pcs-xpcs.c | 70 +++++++++++++++++++++++ drivers/net/pcs/pcs-xpcs.h | 1 + include/linux/pcs/pcs-xpcs.h | 1 + 5 files changed, 117 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index b0826b833e4a7..459d40b95ec83 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -1090,7 +1090,9 @@ static void stmmac_mac_link_up(struct phylink_config *config, ctrl = old_ctrl & ~priv->hw->link.speed_mask; if (interface == PHY_INTERFACE_MODE_XGMII || - interface == PHY_INTERFACE_MODE_10GBASER) { + interface == PHY_INTERFACE_MODE_10GBASER || + interface == PHY_INTERFACE_MODE_10GBASEX || + interface == PHY_INTERFACE_MODE_XAUI) { switch (speed) { case SPEED_10000: ctrl |= priv->hw->link.xgmii.speed10000; diff --git a/drivers/net/pcs/pcs-xpcs-pma.c b/drivers/net/pcs/pcs-xpcs-pma.c index 128ac789cd251..8d9f2e1e85265 100644 --- a/drivers/net/pcs/pcs-xpcs-pma.c +++ b/drivers/net/pcs/pcs-xpcs-pma.c @@ -87,3 +87,45 @@ int xpcs_10gbaser_pma_config(struct dw_xpcs *xpcs) return 0; } } + +static int xpcs_10gbasex_gen5_10g_pma_config(struct dw_xpcs *xpcs) +{ + int ret; + + ret = xpcs_read_vendor(xpcs, MDIO_MMD_PMAPMD, + DW_VR_XS_PMA_GEN5_10G_GEN_CTRL); + if (ret < 0) + return ret; + + /* Select KX4-mode of the PHY lanes */ + ret &= ~DW_VR_XS_PMA_LANE_MODE; + ret |= FIELD_PREP(DW_VR_XS_PMA_LANE_MODE, DW_VR_XS_PMA_LANE_MODE_KX4); + + /* Activate 4 lane per link */ + ret &= ~DW_VR_XS_PMA_LINK_WIDTH; + ret |= FIELD_PREP(DW_VR_XS_PMA_LINK_WIDTH, DW_VR_XS_PMA_LINK_WIDTH_4); + + /* Enable all 4 lanes since it's X4 */ + ret &= ~DW_VR_XS_PMA_LANE_PWR_OFF; + ret |= FIELD_PREP(DW_VR_XS_PMA_LANE_PWR_OFF, 0x0); + + ret = xpcs_write_vendor(xpcs, MDIO_MMD_PMAPMD, + DW_VR_XS_PMA_GEN5_10G_GEN_CTRL, ret); + if (ret < 0) + return ret; + + /* Select PCS/PMA refclk source: Pad or Core. Vendor-reset the device + * to make sure the updates are perceived by the core + */ + return xpcs_gen5_10g_ref_clock_select(xpcs, true); +} + +int xpcs_10gbasex_pma_config(struct dw_xpcs *xpcs) +{ + switch (xpcs->info.pma) { + case DW_XPCS_PMA_GEN5_10G_ID: + return xpcs_10gbasex_gen5_10g_pma_config(xpcs); + default: + return 0; + } +} diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c index e746caae64abf..fcf43f2e82c57 100644 --- a/drivers/net/pcs/pcs-xpcs.c +++ b/drivers/net/pcs/pcs-xpcs.c @@ -137,6 +137,22 @@ static const int xpcs_10gbaser_features[] = { __ETHTOOL_LINK_MODE_MASK_NBITS, }; +static const int xpcs_10gbasex_features[] = { + ETHTOOL_LINK_MODE_Pause_BIT, + ETHTOOL_LINK_MODE_Asym_Pause_BIT, + ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT, + /* ETHTOOL_LINK_MODE_10000baseCX4_Full_BIT, */ + /* ETHTOOL_LINK_MODE_10000baseLX4_Full_BIT, */ + /* Speed-compatible modes for the link setup in the external PHYs */ + ETHTOOL_LINK_MODE_10000baseKR_Full_BIT, + ETHTOOL_LINK_MODE_10000baseCR_Full_BIT, + ETHTOOL_LINK_MODE_10000baseSR_Full_BIT, + ETHTOOL_LINK_MODE_10000baseLR_Full_BIT, + ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT, + ETHTOOL_LINK_MODE_10000baseER_Full_BIT, + __ETHTOOL_LINK_MODE_MASK_NBITS, +}; + static const int xpcs_sgmii_features[] = { ETHTOOL_LINK_MODE_Pause_BIT, ETHTOOL_LINK_MODE_Asym_Pause_BIT, @@ -710,6 +726,7 @@ static unsigned int xpcs_inband_caps(struct phylink_pcs *pcs, return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE; case DW_10GBASER: + case DW_10GBASEX: case DW_2500BASEX: return LINK_INBAND_DISABLE; @@ -943,6 +960,41 @@ static int xpcs_config_10gbaser(struct dw_xpcs *xpcs) return xpcs_vendor_reset(xpcs); } +static int xpcs_config_10gbasex(struct dw_xpcs *xpcs) +{ + int ret; + + /* Disable Clause 73 AN in anyway */ + ret = xpcs_modify(xpcs, MDIO_MMD_AN, MDIO_CTRL1, + MDIO_AN_CTRL1_ENABLE, 0); + if (ret < 0) + return ret; + + /* Disable RXAUI if it's enabled by default */ + ret = xpcs_read_vpcs(xpcs, DW_VR_XS_PCS_XAUI_MODE_CTRL); + if (ret < 0) + return ret; + + ret &= ~DW_VR_XS_PCS_RXAUI_MODE; + ret = xpcs_write_vpcs(xpcs, DW_VR_XS_PCS_XAUI_MODE_CTRL, ret); + if (ret < 0) + return ret; + + /* Set PCS to 10G speed and 10GBASE-X PCS */ + ret = xpcs_modify(xpcs, MDIO_MMD_PCS, MDIO_CTRL1, + MDIO_CTRL1_SPEEDSEL, MDIO_CTRL1_SPEED10G); + if (ret < 0) + return ret; + + ret = xpcs_modify(xpcs, MDIO_MMD_PCS, MDIO_CTRL2, + MDIO_PCS_CTRL2_TYPE, MDIO_PCS_CTRL2_10GBX); + if (ret < 0) + return ret; + + /* Make sure the specified PCS type is perceived by the core */ + return xpcs_vendor_reset(xpcs); +} + static int xpcs_config_2500basex(struct dw_xpcs *xpcs) { int ret; @@ -1004,6 +1056,11 @@ static int xpcs_do_config(struct dw_xpcs *xpcs, phy_interface_t interface, if (ret) return ret; break; + case DW_10GBASEX: + ret = xpcs_config_10gbasex(xpcs); + if (ret) + return ret; + break; case DW_2500BASEX: ret = xpcs_config_2500basex(xpcs); if (ret) @@ -1242,6 +1299,7 @@ static void xpcs_get_state(struct phylink_pcs *pcs, unsigned int neg_mode, switch (compat->an_mode) { case DW_10GBASER: + case DW_10GBASEX: phylink_mii_c45_pcs_get_state(xpcs->mdiodev, state); break; case DW_AN_C73: @@ -1349,6 +1407,8 @@ static void xpcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode, break; case PHY_INTERFACE_MODE_10GBASER: + case PHY_INTERFACE_MODE_10GBASEX: + case PHY_INTERFACE_MODE_XAUI: xpcs_link_up_check(xpcs, speed, duplex); break; @@ -1531,6 +1591,16 @@ static const struct dw_xpcs_compat synopsys_xpcs_compat[] = { .supported = xpcs_10gbaser_features, .an_mode = DW_10GBASER, .pma_config = xpcs_10gbaser_pma_config, + }, { + .interface = PHY_INTERFACE_MODE_10GBASEX, + .supported = xpcs_10gbasex_features, + .an_mode = DW_10GBASEX, + .pma_config = xpcs_10gbasex_pma_config, + }, { + .interface = PHY_INTERFACE_MODE_XAUI, + .supported = xpcs_10gbasex_features, + .an_mode = DW_10GBASEX, + .pma_config = xpcs_10gbasex_pma_config, }, { .interface = PHY_INTERFACE_MODE_SGMII, .supported = xpcs_sgmii_features, diff --git a/drivers/net/pcs/pcs-xpcs.h b/drivers/net/pcs/pcs-xpcs.h index 40741e158e163..0b3c972c9ee5d 100644 --- a/drivers/net/pcs/pcs-xpcs.h +++ b/drivers/net/pcs/pcs-xpcs.h @@ -158,6 +158,7 @@ int xpcs_soft_reset(struct dw_xpcs *xpcs); int xpcs_vendor_reset(struct dw_xpcs *xpcs); int xpcs_10gbaser_pma_config(struct dw_xpcs *xpcs); +int xpcs_10gbasex_pma_config(struct dw_xpcs *xpcs); int nxp_sja1105_sgmii_pma_config(struct dw_xpcs *xpcs); int nxp_sja1110_sgmii_pma_config(struct dw_xpcs *xpcs); int nxp_sja1110_2500basex_pma_config(struct dw_xpcs *xpcs); diff --git a/include/linux/pcs/pcs-xpcs.h b/include/linux/pcs/pcs-xpcs.h index 36073f7b6bb40..67d3d92bc4e0b 100644 --- a/include/linux/pcs/pcs-xpcs.h +++ b/include/linux/pcs/pcs-xpcs.h @@ -20,6 +20,7 @@ #define DW_2500BASEX 3 #define DW_AN_C37_1000BASEX 4 #define DW_10GBASER 5 +#define DW_10GBASEX 6 enum dw_xpcs_pcs_id { DW_XPCS_ID_NATIVE = 0, -- cgit v1.2.3 From bf4d26d001ab0a4c0e1b89db50cc441fb1f69468 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 4 Dec 2023 23:43:50 +0300 Subject: MAINTAINERS: net: pcs: xpcs: Add PMA layer files The drivers/net/pcs/pcs-xpcs-pma.c file has just been added to the DW XPCS driver. It contains the DW XPCS PMA layer initialization functions called from the main part of the driver. Add it to the list of the files maintained in the framework of the generic DW XPCS driver parts. Signed-off-by: Serge Semin --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index d1cc0e12fe1f0..58f37e8e41b7a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -25629,6 +25629,7 @@ F: drivers/net/ethernet/synopsys/ SYNOPSYS DESIGNWARE ETHERNET XPCS DRIVER L: netdev@vger.kernel.org S: Orphan +F: drivers/net/pcs/pcs-xpcs-pma.c F: drivers/net/pcs/pcs-xpcs.c F: drivers/net/pcs/pcs-xpcs.h F: include/linux/pcs/pcs-xpcs.h -- cgit v1.2.3 From 7659472b78e4b53072b4121a92c5f450d868a676 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 6 Jul 2023 14:56:29 +0300 Subject: dt-bindings: net: pcs: Add Baikal-T1 xPCS bindings Baikal-T1 SoC is synthesized with the DW XPCS v3.11b IP-core attached to DW xGMAC v2.11a over the XGMII interface. Externally it's equipped with the Synopsys Enterprise 10G Gen5 PMA working with the KR/KX4 links. XPCS can deliver events by means of a single IRQ line. In addition it can be clocked either from the internal clock source ("core" mode) or from the external pad ("pad" mode). Note using internal clock source may cause link instability. It's preferable to have an externally generated clock signal. Signed-off-by: Serge Semin --- Documentation/devicetree/bindings/net/pcs/snps,dw-xpcs.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/net/pcs/snps,dw-xpcs.yaml b/Documentation/devicetree/bindings/net/pcs/snps,dw-xpcs.yaml index e77eec9ac9ee0..f83b2037bf17c 100644 --- a/Documentation/devicetree/bindings/net/pcs/snps,dw-xpcs.yaml +++ b/Documentation/devicetree/bindings/net/pcs/snps,dw-xpcs.yaml @@ -41,6 +41,8 @@ properties: const: snps,dw-xpcs-gen5-10g - description: Synopsys DesignWare XPCS with Consumer Gen5 12G PMA const: snps,dw-xpcs-gen5-12g + - description: Baikal-T1 XPCS (DW XPCS with Consumer Gen5 10G PMA) + const: baikal,bt1-xpcs reg: items: -- cgit v1.2.3 From 41e73dfe10eb1384faac0daddd877ca8145343df Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 6 Jul 2023 14:39:34 +0300 Subject: net: pcs: xpcs: Add Baikal-T1 XPCS support Baikal-T1 SoC is synthesized with the DW XPCS v3.11b IP-core attached to DW xGMAC v2.11a over the XGMII interface. DW XPCS is configured to be of the Back-plane multi-mode with KR/KX4 speeds support (no 1G speed). Thus the PCS sublayer supports Clause 73 and 72 auto-negotiations performed over the 10GBASE-KR and 10GBASE-KX4 link modes. So in order to have the denoted features enabled for the Baikal-T1 XGMAC/XPCS chain the DW XPCS driver is updated with the Baikal-T1 XPCS-specific compat-list. It will be selected in case if the platform detects the "baikal,bt1-xpcs" compatible device or a device with the custom BT1_XGMAC_XPCS_ID id. Note the XPCS device is synthesized with the Synopsys 10G Gen5 PMA. Thus the corresponding PMA-id is specified in order activate the PMA-specific config methods. Signed-off-by: Serge Semin --- drivers/net/pcs/pcs-xpcs-plat.c | 2 ++ drivers/net/pcs/pcs-xpcs.c | 28 ++++++++++++++++++++++++++++ include/linux/pcs/pcs-xpcs.h | 1 + 3 files changed, 31 insertions(+) diff --git a/drivers/net/pcs/pcs-xpcs-plat.c b/drivers/net/pcs/pcs-xpcs-plat.c index e48b9586026c1..7ee57de7ec57e 100644 --- a/drivers/net/pcs/pcs-xpcs-plat.c +++ b/drivers/net/pcs/pcs-xpcs-plat.c @@ -427,6 +427,7 @@ DW_XPCS_INFO_DECLARE(xpcs_pma_gen4_3g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN4_3G_ID DW_XPCS_INFO_DECLARE(xpcs_pma_gen4_6g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN4_6G_ID); DW_XPCS_INFO_DECLARE(xpcs_pma_gen5_10g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN5_10G_ID); DW_XPCS_INFO_DECLARE(xpcs_pma_gen5_12g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN5_12G_ID); +DW_XPCS_INFO_DECLARE(xpcs_bt1, BT1_XGMAC_XPCS_ID, DW_XPCS_PMA_GEN5_10G_ID); static const struct of_device_id xpcs_of_ids[] = { { .compatible = "snps,dw-xpcs", .data = &xpcs_generic }, @@ -437,6 +438,7 @@ static const struct of_device_id xpcs_of_ids[] = { { .compatible = "snps,dw-xpcs-gen4-6g", .data = &xpcs_pma_gen4_6g }, { .compatible = "snps,dw-xpcs-gen5-10g", .data = &xpcs_pma_gen5_10g }, { .compatible = "snps,dw-xpcs-gen5-12g", .data = &xpcs_pma_gen5_12g }, + { .compatible = "baikal,bt1-xpcs", .data = &xpcs_bt1 }, { /* sentinel */ }, }; MODULE_DEVICE_TABLE(of, xpcs_of_ids); diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c index fcf43f2e82c57..cceae97673997 100644 --- a/drivers/net/pcs/pcs-xpcs.c +++ b/drivers/net/pcs/pcs-xpcs.c @@ -1642,6 +1642,30 @@ static const struct dw_xpcs_compat nxp_sja1110_xpcs_compat[] = { } }; +static const struct dw_xpcs_compat bt1_xpcs_compat[] = { + { + .interface = PHY_INTERFACE_MODE_XGMII, + .supported = xpcs_xgmii_features, + .an_mode = DW_AN_C73, + }, { + .interface = PHY_INTERFACE_MODE_10GBASER, + .supported = xpcs_10gbaser_features, + .an_mode = DW_10GBASER, + .pma_config = xpcs_10gbaser_pma_config, + }, { + .interface = PHY_INTERFACE_MODE_10GBASEX, + .supported = xpcs_10gbasex_features, + .an_mode = DW_10GBASEX, + .pma_config = xpcs_10gbasex_pma_config, + }, { + .interface = PHY_INTERFACE_MODE_XAUI, + .supported = xpcs_10gbasex_features, + .an_mode = DW_10GBASEX, + .pma_config = xpcs_10gbasex_pma_config, + }, { + } +}; + static const struct dw_xpcs_desc xpcs_desc_list[] = { { .id = DW_XPCS_ID, @@ -1655,6 +1679,10 @@ static const struct dw_xpcs_desc xpcs_desc_list[] = { .id = NXP_SJA1110_XPCS_ID, .mask = DW_XPCS_ID_MASK, .compat = nxp_sja1110_xpcs_compat, + }, { + .id = BT1_XGMAC_XPCS_ID, + .mask = DW_XPCS_ID_MASK, + .compat = bt1_xpcs_compat, }, }; diff --git a/include/linux/pcs/pcs-xpcs.h b/include/linux/pcs/pcs-xpcs.h index 67d3d92bc4e0b..e9f29dc33a463 100644 --- a/include/linux/pcs/pcs-xpcs.h +++ b/include/linux/pcs/pcs-xpcs.h @@ -24,6 +24,7 @@ enum dw_xpcs_pcs_id { DW_XPCS_ID_NATIVE = 0, + BT1_XGMAC_XPCS_ID, NXP_SJA1105_XPCS_ID = 0x00000010, NXP_SJA1110_XPCS_ID = 0x00000020, DW_XPCS_ID = 0x7996ced0, -- cgit v1.2.3 From 159980975b441fee4f56cdfd14763d7dc36ea8b3 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 28 Jul 2023 23:54:34 +0300 Subject: dt-bindings: net: Add DT-binding for Marvell 88x2222 PHY DT-bindings wise the Marvell 88X2222 transceiver is a normal 10G PHY with up to four host-side ports (support 10GBASE-R, XAUI/RXAUI, 1000BASE-X protocols) and two line-side ports (support 10GBASE-R and 1000BASE-X interfaces). Besides it is equipped with 12 GPIOs multiplexed with the special functions like LEDs, TWSI SDA/SCL, etc. They can be used to communicate with the SFP+ ports soldered to the device line-side interfaces. Signed-off-by: Serge Semin --- .../devicetree/bindings/net/marvell,88x2222.yaml | 89 ++++++++++++++++++++++ include/dt-bindings/net/mv-phy-88x2222.h | 25 ++++++ 2 files changed, 114 insertions(+) create mode 100644 Documentation/devicetree/bindings/net/marvell,88x2222.yaml create mode 100644 include/dt-bindings/net/mv-phy-88x2222.h diff --git a/Documentation/devicetree/bindings/net/marvell,88x2222.yaml b/Documentation/devicetree/bindings/net/marvell,88x2222.yaml new file mode 100644 index 0000000000000..e34fd4bb39223 --- /dev/null +++ b/Documentation/devicetree/bindings/net/marvell,88x2222.yaml @@ -0,0 +1,89 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/net/marvell,88x2222.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Marvell 88x2222 PHY + +maintainers: + - Andrew Lunn + - Heiner Kallweit + - Russell King + +description: | + The Marvell 88X2222 transceiver is a fully integrated single chip solution + providing end-to-end data transmission over fiber-optic networks as well as + Twinax® copper links. It is a 2-port device that performs all physical layer + functions associated with 10GBASE-R, and 1000BASE-X. + + The line-side interface supports 2 ports of 10GBASE-R and 1000BASE-X. The + line side also supports Clause 73 AP Auto-Negotiation. The host-side + interface supports 4 ports of 10GBASE-R, RXAUI, 1000BASE-X, and 2 ports of + XAUI. Any port from the host side can be attached to any port on the line + side as long as the speeds match. + +allOf: + - $ref: ethernet-phy.yaml# + +properties: + compatible: + enum: + - ethernet-phy-id0141.0f10 + - ethernet-phy-id0141.31b0 + + gpio-controller: true + + '#gpio-cells': + const: 2 + + ngpios: + default: 2 + minimum: 1 + maximum: 12 + + gpio-line-names: + minItems: 1 + maxItems: 12 + + gpio-reserved-ranges: + minItems: 1 + maxItems: 12 + + interrupt-controller: true + + '#interrupt-cells': + const: 2 + +unevaluatedProperties: false + +examples: + - | + #include + #include + + mdio { + #address-cells = <1>; + #size-cells = <0>; + + ethernet-phy@c { + /* Only needed to make DT lint tools working. Do not copy/paste + * it into the real DTS files. + */ + compatible = "ethernet-phy-id0141.0f10"; + reg = <0x0c>; + + interrupt-parent = <&pic>; + interrupts = <27 IRQ_TYPE_LEVEL_LOW>; + interrupt-controller; + #interrupt-cells = <2>; + + gpio-controller; + #gpio-cells = <2>; + gpio-reserved-ranges = , + , + , + ; + + }; + }; diff --git a/include/dt-bindings/net/mv-phy-88x2222.h b/include/dt-bindings/net/mv-phy-88x2222.h new file mode 100644 index 0000000000000..fe21253bc669a --- /dev/null +++ b/include/dt-bindings/net/mv-phy-88x2222.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause */ +/* + * Marvell 88x2222 PHY constants + * + * Copyright (C) 2023 BAIKAL ELECTRONICS, JSC + */ + +#ifndef _DT_BINDINGS_MV_88X2222_H +#define _DT_BINDINGS_MV_88X2222_H + +/* Marvell 88x2222 GPIOs mapping */ +#define MV_88X2222_MOD_ABS 0 +#define MV_88X2222_TX_FAULT 1 +#define MV_88X2222_RX_LOS 2 +#define MV_88X2222_GPIO 3 +#define MV_88X2222_LED0 4 +#define MV_88X2222_LED1 5 +#define MV_88X2222_MPC 6 +#define MV_88X2222_TOD 7 +#define MV_88X2222_TX_DISABLE 8 +#define MV_88X2222_UNDEF 9 +#define MV_88X2222_SDA 10 +#define MV_88X2222_SCL 11 + +#endif /* _DT_BINDINGS_MV_88X2222_H */ -- cgit v1.2.3 From 4d774b697acde32b8b45e6bb849b95ffc72b4f41 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 15 Aug 2022 16:03:37 +0300 Subject: net: phy: marvell-88x2222: Add 88X2222R PHY ID There is a Marvell 88X2222 PHY revision with R suffix, which is mainly compatible with the original PHY but has different ID. Let's add it to the known Marvell PHY IDs table defined in the marvell_phy.h file. Signed-off-by: Serge Semin --- drivers/net/phy/marvell-88x2222.c | 16 ++++++++++++++++ include/linux/marvell_phy.h | 1 + 2 files changed, 17 insertions(+) diff --git a/drivers/net/phy/marvell-88x2222.c b/drivers/net/phy/marvell-88x2222.c index ba1bbb6c63d64..48ed20728314c 100644 --- a/drivers/net/phy/marvell-88x2222.c +++ b/drivers/net/phy/marvell-88x2222.c @@ -591,11 +591,27 @@ static struct phy_driver mv2222_drivers[] = { .read_status = mv2222_read_status, .attach_mii_port = mv2222_attach_mii_port, }, + { + .phy_id = MARVELL_PHY_ID_88X2222R, + .phy_id_mask = MARVELL_PHY_ID_MASK, + .name = "Marvell 88X2222R", + .get_features = mv2222_get_features, + .soft_reset = mv2222_soft_reset, + .config_init = mv2222_config_init, + .config_aneg = mv2222_config_aneg, + .aneg_done = mv2222_aneg_done, + .probe = mv2222_probe, + .suspend = mv2222_suspend, + .resume = mv2222_resume, + .read_status = mv2222_read_status, + .attach_mii_port = mv2222_attach_mii_port, + }, }; module_phy_driver(mv2222_drivers); static const struct mdio_device_id __maybe_unused mv2222_tbl[] = { { MARVELL_PHY_ID_88X2222, MARVELL_PHY_ID_MASK }, + { MARVELL_PHY_ID_88X2222R, MARVELL_PHY_ID_MASK }, { } }; MODULE_DEVICE_TABLE(mdio, mv2222_tbl); diff --git a/include/linux/marvell_phy.h b/include/linux/marvell_phy.h index b1fbe4118414a..a904fb2cc5be3 100644 --- a/include/linux/marvell_phy.h +++ b/include/linux/marvell_phy.h @@ -26,6 +26,7 @@ #define MARVELL_PHY_ID_88X3310 0x002b09a0 #define MARVELL_PHY_ID_88E2110 0x002b09b0 #define MARVELL_PHY_ID_88X2222 0x01410f10 +#define MARVELL_PHY_ID_88X2222R 0x014131b0 #define MARVELL_PHY_ID_88Q2110 0x002b0980 #define MARVELL_PHY_ID_88Q2220 0x002b0b20 -- cgit v1.2.3 From 8a521d160437a8b93a13c067b844aa406afa4cbe Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 7 Jul 2023 16:32:46 +0300 Subject: net: phy: marvell-88x2222: Convert to using bitfield helpers bits.h and bitfield.h provides useful helpers with the bit fields operations. Let's use them in driver instead of hard-coded bitwise shift operation. The update concerns the PCS Host/Line mode select procedure only. Note while at it let's convert the mv2222_config_line() method to collecting the fields in the local variable and then calling the phy_write_mmd() method from a single place of the function. Thus it will look a bit more coherent and the generated code smaller. Signed-off-by: Serge Semin --- drivers/net/phy/marvell-88x2222.c | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/drivers/net/phy/marvell-88x2222.c b/drivers/net/phy/marvell-88x2222.c index 48ed20728314c..c29fb6a52a5dd 100644 --- a/drivers/net/phy/marvell-88x2222.c +++ b/drivers/net/phy/marvell-88x2222.c @@ -7,6 +7,8 @@ * 1000Base-X or 10GBase-R on the line side. * SGMII over 1000Base-X. */ +#include +#include #include #include #include @@ -18,10 +20,12 @@ /* Port PCS Configuration */ #define MV_PCS_CONFIG 0xF002 +#define MV_PCS_HOST_PCS_SELECT GENMASK(6, 0) #define MV_PCS_HOST_XAUI 0x73 -#define MV_PCS_LINE_10GBR (0x71 << 8) -#define MV_PCS_LINE_1GBX_AN (0x7B << 8) -#define MV_PCS_LINE_SGMII_AN (0x7F << 8) +#define MV_PCS_LINE_PCS_SELECT GENMASK(14, 8) +#define MV_PCS_LINE_10GBR 0x71 +#define MV_PCS_LINE_1GBX_AN 0x7B +#define MV_PCS_LINE_SGMII_AN 0x7F /* Port Reset and Power Down */ #define MV_PORT_RST 0xF003 @@ -193,20 +197,25 @@ static bool mv2222_is_sgmii_capable(struct phy_device *phydev) static int mv2222_config_line(struct phy_device *phydev) { struct mv2222_data *priv = phydev->priv; + u16 val; + + val = FIELD_PREP(MV_PCS_HOST_PCS_SELECT, MV_PCS_HOST_XAUI); switch (priv->line_interface) { case PHY_INTERFACE_MODE_10GBASER: - return phy_write_mmd(phydev, MDIO_MMD_VEND2, MV_PCS_CONFIG, - MV_PCS_HOST_XAUI | MV_PCS_LINE_10GBR); + val |= FIELD_PREP(MV_PCS_LINE_PCS_SELECT, MV_PCS_LINE_10GBR); + break; case PHY_INTERFACE_MODE_1000BASEX: - return phy_write_mmd(phydev, MDIO_MMD_VEND2, MV_PCS_CONFIG, - MV_PCS_HOST_XAUI | MV_PCS_LINE_1GBX_AN); + val |= FIELD_PREP(MV_PCS_LINE_PCS_SELECT, MV_PCS_LINE_1GBX_AN); + break; case PHY_INTERFACE_MODE_SGMII: - return phy_write_mmd(phydev, MDIO_MMD_VEND2, MV_PCS_CONFIG, - MV_PCS_HOST_XAUI | MV_PCS_LINE_SGMII_AN); + val |= FIELD_PREP(MV_PCS_LINE_PCS_SELECT, MV_PCS_LINE_SGMII_AN); + break; default: return -EINVAL; } + + return phy_write_mmd(phydev, MDIO_MMD_VEND2, MV_PCS_CONFIG, val); } /* Switch between 1G (1000Base-X/SGMII) and 10G (10GBase-R) modes */ -- cgit v1.2.3 From d1b6bd4ebc20a022b26d7e88c47449f3de31385d Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 7 Jul 2023 18:37:22 +0300 Subject: net: phy: marvell-88x2222: Add 10GBase-R host-side mode support Aside with XAUI it's possible enable the 10GBase-R link between MAC/PCS and Marvell 88x2222 Host-side (XFI). It doesn't require much driver modification. The 10GBse-R host-side PCS must be selected in the Port PCS configuration register. It can be done in the config_init() phy_device callback. Due to that the locally defined mv2222_config_line() method must be modified in order not to override the activated interface mode. Note the read_status() callback is altered to take the host-side link state into account. Signed-off-by: Serge Semin --- drivers/net/phy/marvell-88x2222.c | 73 +++++++++++++++++++++++++++++++++------ 1 file changed, 63 insertions(+), 10 deletions(-) diff --git a/drivers/net/phy/marvell-88x2222.c b/drivers/net/phy/marvell-88x2222.c index c29fb6a52a5dd..95cad5327325e 100644 --- a/drivers/net/phy/marvell-88x2222.c +++ b/drivers/net/phy/marvell-88x2222.c @@ -3,7 +3,7 @@ * Marvell 88x2222 dual-port multi-speed ethernet transceiver. * * Supports: - * XAUI on the host side. + * XAUI or 10GBase-R on the host side. * 1000Base-X or 10GBase-R on the line side. * SGMII over 1000Base-X. */ @@ -21,6 +21,7 @@ /* Port PCS Configuration */ #define MV_PCS_CONFIG 0xF002 #define MV_PCS_HOST_PCS_SELECT GENMASK(6, 0) +#define MV_PCS_HOST_10GBR 0x71 #define MV_PCS_HOST_XAUI 0x73 #define MV_PCS_LINE_PCS_SELECT GENMASK(14, 8) #define MV_PCS_LINE_10GBR 0x71 @@ -53,6 +54,12 @@ #define MV_1GBX_PHY_STAT_SPEED100 BIT(14) #define MV_1GBX_PHY_STAT_SPEED1000 BIT(15) +/* Host-side 10GBase-R PCS Status 1 Register */ +#define MV_HOST_10GBR_PCS_STAT1 (0x0 + MDIO_STAT1) + +/* Host-side XAUI PCS Status 1 Register */ +#define MV_HOST_XAUI_PCS_STAT1 (0x1000 + MDIO_STAT1) + #define AUTONEG_TIMEOUT 3 struct mv2222_data { @@ -194,28 +201,46 @@ static bool mv2222_is_sgmii_capable(struct phy_device *phydev) priv->supported)); } +static int mv2222_config_host(struct phy_device *phydev) +{ + u16 val; + + switch (phydev->interface) { + case PHY_INTERFACE_MODE_XAUI: + val = FIELD_PREP(MV_PCS_HOST_PCS_SELECT, MV_PCS_HOST_XAUI); + break; + case PHY_INTERFACE_MODE_10GBASER: + val = FIELD_PREP(MV_PCS_HOST_PCS_SELECT, MV_PCS_HOST_10GBR); + break; + default: + return -EINVAL; + } + + return phy_modify_mmd(phydev, MDIO_MMD_VEND2, MV_PCS_CONFIG, + MV_PCS_HOST_PCS_SELECT, val); +} + static int mv2222_config_line(struct phy_device *phydev) { struct mv2222_data *priv = phydev->priv; u16 val; - val = FIELD_PREP(MV_PCS_HOST_PCS_SELECT, MV_PCS_HOST_XAUI); - switch (priv->line_interface) { case PHY_INTERFACE_MODE_10GBASER: - val |= FIELD_PREP(MV_PCS_LINE_PCS_SELECT, MV_PCS_LINE_10GBR); + val = FIELD_PREP(MV_PCS_LINE_PCS_SELECT, MV_PCS_LINE_10GBR); break; case PHY_INTERFACE_MODE_1000BASEX: - val |= FIELD_PREP(MV_PCS_LINE_PCS_SELECT, MV_PCS_LINE_1GBX_AN); + val = FIELD_PREP(MV_PCS_LINE_PCS_SELECT, MV_PCS_LINE_1GBX_AN); break; case PHY_INTERFACE_MODE_SGMII: - val |= FIELD_PREP(MV_PCS_LINE_PCS_SELECT, MV_PCS_LINE_SGMII_AN); + val = FIELD_PREP(MV_PCS_LINE_PCS_SELECT, MV_PCS_LINE_SGMII_AN); break; default: return -EINVAL; } - return phy_write_mmd(phydev, MDIO_MMD_VEND2, MV_PCS_CONFIG, val); + return phy_modify_mmd(phydev, MDIO_MMD_VEND2, MV_PCS_CONFIG, + MV_PCS_LINE_PCS_SELECT, val); } /* Switch between 1G (1000Base-X/SGMII) and 10G (10GBase-R) modes */ @@ -417,6 +442,28 @@ static int mv2222_read_status_1g(struct phy_device *phydev) return link; } +static bool mv2222_iface_is_operational(struct phy_device *phydev) +{ + int reg, val; + + switch (phydev->interface) { + case PHY_INTERFACE_MODE_XAUI: + reg = MV_HOST_XAUI_PCS_STAT1; + break; + case PHY_INTERFACE_MODE_10GBASER: + reg = MV_HOST_10GBR_PCS_STAT1; + break; + default: + return false; + } + + val = phy_read_mmd(phydev, MDIO_MMD_PHYXS, reg); + if (val < 0 || !(val & MDIO_STAT1_LSTATUS)) + return false; + + return true; +} + static bool mv2222_link_is_operational(struct phy_device *phydev) { struct mv2222_data *priv = phydev->priv; @@ -441,6 +488,9 @@ static int mv2222_read_status(struct phy_device *phydev) phydev->speed = SPEED_UNKNOWN; phydev->duplex = DUPLEX_UNKNOWN; + if (!mv2222_iface_is_operational(phydev)) + return 0; + if (!mv2222_link_is_operational(phydev)) return 0; @@ -476,10 +526,13 @@ static int mv2222_get_features(struct phy_device *phydev) static int mv2222_config_init(struct phy_device *phydev) { - if (phydev->interface != PHY_INTERFACE_MODE_XAUI) - return -EINVAL; + int ret; - return 0; + ret = mv2222_config_host(phydev); + if (ret) + return ret; + + return mv2222_soft_reset(phydev); } static int mv2222_configure_serdes(struct phy_port *port, bool enable, -- cgit v1.2.3 From e3f915625aab28d9a7370f127d1733161b40b4dd Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 12 Apr 2024 14:26:59 +0300 Subject: net: phy: marvell-88x2222: Fill in possible_interfaces Marvell 88x2222 provides 10GBase-X4/XAUI, 10GBase-R, 1000Base-X and SGMII host interfaces. Fill in the phy_device::possible_interfaces field with the currently supported by the driver modes: XAUI and 10GBase-R. Signed-off-by: Serge Semin --- drivers/net/phy/marvell-88x2222.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/phy/marvell-88x2222.c b/drivers/net/phy/marvell-88x2222.c index 95cad5327325e..561a001f8697e 100644 --- a/drivers/net/phy/marvell-88x2222.c +++ b/drivers/net/phy/marvell-88x2222.c @@ -627,6 +627,10 @@ static int mv2222_probe(struct phy_device *phydev) linkmode_copy(phydev->supported, supported); + /* In perspective the list can be extended with 1000BASE-X and SGMII */ + __set_bit(PHY_INTERFACE_MODE_XAUI, phydev->possible_interfaces); + __set_bit(PHY_INTERFACE_MODE_10GBASER, phydev->possible_interfaces); + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; -- cgit v1.2.3 From df9d70712eb6b483c8d658b114addafccdd90192 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Tue, 15 Aug 2023 13:17:22 +0300 Subject: net: phy: marvell-88x2222: Split up soft-reset Depending on the setups nature it isn't always required to fully soft-reset the PHY-device. For instance the config_init() procedure implies the interface-type setups. Thus the host-side need to be only performed. Meanwhile the external link settings like Auto-negotiation configs or link protocol (type, speed, duplex, etc) need to be followed by the line-reset only. Thus the setup procedures can be sped up a bit by splitting the resets up into the host- or line-side resets performed after the setups which require one or another reset only. Note the soft_reset() PHY-driver callback still implies a full soft-reset. Signed-off-by: Serge Semin --- drivers/net/phy/marvell-88x2222.c | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/drivers/net/phy/marvell-88x2222.c b/drivers/net/phy/marvell-88x2222.c index 561a001f8697e..441626312142d 100644 --- a/drivers/net/phy/marvell-88x2222.c +++ b/drivers/net/phy/marvell-88x2222.c @@ -82,20 +82,34 @@ static int mv2222_tx_disable(struct phy_device *phydev) MDIO_PMD_TXDIS_GLOBAL); } -static int mv2222_soft_reset(struct phy_device *phydev) +static int __mv2222_soft_reset(struct phy_device *phydev, u16 mask) { int val, ret; - ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, MV_PORT_RST, - MV_PORT_RST_SW); + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, MV_PORT_RST, mask); if (ret < 0) return ret; return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND2, MV_PORT_RST, - val, !(val & MV_PORT_RST_SW), + val, !(val & mask), 5000, 1000000, true); } +static inline int mv2222_host_reset(struct phy_device *phydev) +{ + return __mv2222_soft_reset(phydev, MV_HOST_RST_SW); +} + +static inline int mv2222_line_reset(struct phy_device *phydev) +{ + return __mv2222_soft_reset(phydev, MV_LINE_RST_SW); +} + +static inline int mv2222_soft_reset(struct phy_device *phydev) +{ + return __mv2222_soft_reset(phydev, MV_PORT_RST_SW); +} + static int mv2222_disable_aneg(struct phy_device *phydev) { int ret = phy_clear_bits_mmd(phydev, MDIO_MMD_PCS, MV_1GBX_CTRL, @@ -103,7 +117,7 @@ static int mv2222_disable_aneg(struct phy_device *phydev) if (ret < 0) return ret; - return mv2222_soft_reset(phydev); + return mv2222_line_reset(phydev); } static int mv2222_enable_aneg(struct phy_device *phydev) @@ -113,7 +127,7 @@ static int mv2222_enable_aneg(struct phy_device *phydev) if (ret < 0) return ret; - return mv2222_soft_reset(phydev); + return mv2222_line_reset(phydev); } static int mv2222_set_sgmii_speed(struct phy_device *phydev) @@ -532,7 +546,7 @@ static int mv2222_config_init(struct phy_device *phydev) if (ret) return ret; - return mv2222_soft_reset(phydev); + return mv2222_host_reset(phydev); } static int mv2222_configure_serdes(struct phy_port *port, bool enable, -- cgit v1.2.3 From cc67886881f55520147cbdf37b8cd122fbbfb084 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 14 Aug 2023 23:31:16 +0300 Subject: net: phy: marvell-88x2222: Read latched flags twice Prepare the driver to adding the interrupts support by making sure that the actual status is handled during the network link state machine work. Currently it concerns the link status flags in various PHY MMD registers. They are latched-low so the respective CSRs need to be read twice to get the actual flag state at the moment of the event handling. Note there are other latched flags can be discovered in the Marvell 88x2222 registers. For instance AN Page Received status or 1GBase-X Remote fault status. Since the semantic of the respective events handling may differ from the link up/down handling procedure, the mv2222_read_mmd_latched() function is defined to accept an additional argument forcing the registers double read if it's required by the caller. Signed-off-by: Serge Semin --- drivers/net/phy/marvell-88x2222.c | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/drivers/net/phy/marvell-88x2222.c b/drivers/net/phy/marvell-88x2222.c index 441626312142d..9d21aa3e3d673 100644 --- a/drivers/net/phy/marvell-88x2222.c +++ b/drivers/net/phy/marvell-88x2222.c @@ -347,12 +347,32 @@ static int mv2222_config_aneg(struct phy_device *phydev) return mv2222_enable_aneg(phydev); } +/* The link state and some other fields in the status registers are latched + * low/high so that the momentary events could be detected. Do not double-read + * the status in polling mode to detect such a short flag changes except when + * it's forced to be required (i.e. when the link was already down). + */ +static int mv2222_read_mmd_latched(struct phy_device *phydev, int devad, u32 reg, + bool force) +{ + int ret; + + if (!phy_polling_mode(phydev) || force) { + ret = phy_read_mmd(phydev, devad, reg); + if (ret < 0) + return ret; + } + + return phy_read_mmd(phydev, devad, reg); +} + static int mv2222_aneg_done(struct phy_device *phydev) { int ret; if (mv2222_is_10g_capable(phydev)) { - ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_STAT1); + ret = mv2222_read_mmd_latched(phydev, MDIO_MMD_PCS, MDIO_STAT1, + !phydev->link); if (ret < 0) return ret; @@ -373,7 +393,8 @@ static int mv2222_read_status_10g(struct phy_device *phydev) static int timeout; int val, link = 0; - val = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_STAT1); + val = mv2222_read_mmd_latched(phydev, MDIO_MMD_PCS, MDIO_STAT1, + !phydev->link); if (val < 0) return val; @@ -409,7 +430,8 @@ static int mv2222_read_status_1g(struct phy_device *phydev) static int timeout; int val, link = 0; - val = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_1GBX_STAT); + val = mv2222_read_mmd_latched(phydev, MDIO_MMD_PCS, MV_1GBX_STAT, + !phydev->link); if (val < 0) return val; @@ -471,7 +493,8 @@ static bool mv2222_iface_is_operational(struct phy_device *phydev) return false; } - val = phy_read_mmd(phydev, MDIO_MMD_PHYXS, reg); + val = mv2222_read_mmd_latched(phydev, MDIO_MMD_PHYXS, reg, + !phydev->link); if (val < 0 || !(val & MDIO_STAT1_LSTATUS)) return false; -- cgit v1.2.3 From 9991185ee796cae5271bf04a94fa062dbc7acf09 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Tue, 18 Jul 2023 18:07:48 +0300 Subject: net: phy: marvell-88x2222: Add host- and line-side interrupts support It's possible to have the controller reporting the device events via an IRQ line. It's polarity is active-low by default, but also can be configured by means of the Chip-level Global Interrupt Control register (out of this change scope). There are three types of events reported by the interrupt: line-side PCS link state, host-side PCS link state and GPIOs. This update concerns the first two events as they are of the particular PHY subsystem interest. As it's intended by the Network PHY subsystem two callbacks are implemented: config_intr() and handle_interrupt(). The former one is responsible for activating/de-activating the host-side (XAUI and 10GBase-R) and line-side (1000Base-X/SGMII and 10GBase-R) link state interrupts. The host-side IRQs are configured depending on the selected interface mode meanwhile all relevant line-side IRQs are just enabled since there is no info regarding the line-side link type at the moment of the configuration procedure and might get to be available only after the auto-negation is completed or when a SFP module is inserted. The later callback is responsible for the IRQs handling. If there is a MDIO-based communication is detected to be malfunction PHY will get to be halted by means of the phy_error() method. Otherwise the Port-level Interrupt Status value will be utilized to check whether the device has any pending interrupt. Any link state change or errors detection will cause the PHY state machine been triggered. Info regarding the detected errors will be also sent to the system log as a debug-message for now since the errors may happen due to normal link up/down procedures. Signed-off-by: Serge Semin --- drivers/net/phy/marvell-88x2222.c | 358 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 358 insertions(+) diff --git a/drivers/net/phy/marvell-88x2222.c b/drivers/net/phy/marvell-88x2222.c index 9d21aa3e3d673..cb8025fb43cbe 100644 --- a/drivers/net/phy/marvell-88x2222.c +++ b/drivers/net/phy/marvell-88x2222.c @@ -9,6 +9,7 @@ */ #include #include +#include #include #include #include @@ -34,6 +35,12 @@ #define MV_HOST_RST_SW BIT(7) #define MV_PORT_RST_SW (MV_LINE_RST_SW | MV_HOST_RST_SW) +/* Port Interrupt Status */ +#define MV_PORT_INT_STAT 0xF040 +#define MV_PORT_INT_PCS_LINE BIT(0) +#define MV_PORT_INT_PCS_HOST BIT(2) +#define MV_PORT_INT_GPIO BIT(3) + /* PMD Receive Signal Detect */ #define MV_RX_SIGNAL_DETECT 0x000A #define MV_RX_SIGNAL_DETECT_GLOBAL BIT(0) @@ -47,6 +54,41 @@ /* 1000Base-X Auto-Negotiation Advertisement Register */ #define MV_1GBX_ADVERTISE (0x2000 + MII_ADVERTISE) +/* 10GBase-R Interrupt Enable Register */ +#define MV_10GBR_INT_EN 0x8000 +#define MV_10GBR_INT_BLOCK_LOCK BIT(0) +#define MV_10GBR_INT_HIGH_BER BIT(1) +#define MV_10GBR_INT_LINK_STAT BIT(2) +#define MV_10GBR_INT_LOCAL_RXFAULT BIT(10) +#define MV_10GBR_INT_LOCAL_TXFAULT BIT(11) +#define MV_10GBR_INT_MASK (MV_10GBR_INT_LINK_STAT | \ + MV_10GBR_INT_LOCAL_RXFAULT | \ + MV_10GBR_INT_LOCAL_TXFAULT) + +/* 10GBase-R Interrupt Status Register */ +#define MV_10GBR_INT_STAT 0x8001 + +/* 1000Base-X Interrupt Enable Register */ +#define MV_1GBX_INT_EN 0xA001 +#define MV_1GBX_INT_FALSE_CARRIER BIT(7) +#define MV_1GBX_INT_SYMBOL_ERROR BIT(8) +#define MV_1GBX_INT_LINK_UP BIT(9) +#define MV_1GBX_INT_LINK_DOWN BIT(10) +#define MV_1GBX_INT_AN_COMPLETED BIT(11) +#define MV_1GBX_INT_PAGE_RECEIVED BIT(12) +#define MV_1GBX_INT_DUPLEX_CHANGED BIT(13) +#define MV_1GBX_INT_SPEED_CHANGED BIT(14) +#define MV_1GBX_INT_MASK (MV_1GBX_INT_FALSE_CARRIER | \ + MV_1GBX_INT_SYMBOL_ERROR | \ + MV_1GBX_INT_LINK_UP | \ + MV_1GBX_INT_LINK_DOWN | \ + MV_1GBX_INT_AN_COMPLETED | \ + MV_1GBX_INT_DUPLEX_CHANGED | \ + MV_1GBX_INT_SPEED_CHANGED) + +/* 1000Base-X Interrupt Status Register */ +#define MV_1GBX_INT_STAT 0xA002 + /* 1000Base-X PHY Specific Status Register */ #define MV_1GBX_PHY_STAT 0xA003 #define MV_1GBX_PHY_STAT_AN_RESOLVED BIT(11) @@ -60,6 +102,48 @@ /* Host-side XAUI PCS Status 1 Register */ #define MV_HOST_XAUI_PCS_STAT1 (0x1000 + MDIO_STAT1) +/* Host-side 10GBase-R Interrupt Enable Register */ +#define MV_HOST_10GBR_INT_EN 0x8000 +#define MV_HOST_10GBR_INT_BLOCK_LOCK BIT(0) +#define MV_HOST_10GBR_INT_HIGH_BER BIT(1) +#define MV_HOST_10GBR_INT_LINK_STAT BIT(2) +#define MV_HOST_10GBR_INT_LOCAL_RXFAULT BIT(10) +#define MV_HOST_10GBR_INT_LOCAL_TXFAULT BIT(11) +#define MV_HOST_10GBR_INT_MASK (MV_HOST_10GBR_INT_LINK_STAT | \ + MV_HOST_10GBR_INT_LOCAL_RXFAULT | \ + MV_HOST_10GBR_INT_LOCAL_TXFAULT) + +/* Host-side 10GBase-R Interrupt Status Register */ +#define MV_HOST_10GBR_INT_STAT 0x8001 + +/* Host-side XAUI Interrupt Enable 1 Register */ +#define MV_HOST_XAUI_INT_EN1 0x9001 +#define MV_HOST_XAUI_INT_LINK_UP BIT(2) +#define MV_HOST_XAUI_INT_LINK_DOWN BIT(3) +#define MV_HOST_XAUI_INT_MASK1 (MV_HOST_XAUI_INT_LINK_UP | \ + MV_HOST_XAUI_INT_LINK_DOWN) + +/* Host-side XAUI Interrupt Enable 2 Register */ +#define MV_HOST_XAUI_INT_EN2 0x9002 +#define MV_HOST_XAUI_INT_LANE0_SYNC BIT(0) +#define MV_HOST_XAUI_INT_LANE1_SYNC BIT(1) +#define MV_HOST_XAUI_INT_LANE2_SYNC BIT(2) +#define MV_HOST_XAUI_INT_LANE3_SYNC BIT(3) +#define MV_HOST_XAUI_INT_LANE0_ENERGY BIT(4) +#define MV_HOST_XAUI_INT_LANE1_ENERGY BIT(5) +#define MV_HOST_XAUI_INT_LANE2_ENERGY BIT(6) +#define MV_HOST_XAUI_INT_LANE3_ENERGY BIT(7) +#define MV_HOST_XAUI_INT_TXFAULT BIT(8) +#define MV_HOST_XAUI_INT_RXFAULT BIT(9) +#define MV_HOST_XAUI_INT_MASK2 (MV_HOST_XAUI_INT_TXFAULT | \ + MV_HOST_XAUI_INT_RXFAULT) + +/* Host-side XAUI Interrupt Status 1 Register */ +#define MV_HOST_XAUI_INT_STAT1 0x9003 + +/* Host-side XAUI Interrupt Status 2 Register */ +#define MV_HOST_XAUI_INT_STAT2 0x9004 + #define AUTONEG_TIMEOUT 3 struct mv2222_data { @@ -544,6 +628,276 @@ static int mv2222_read_status(struct phy_device *phydev) return 0; } +static int mv2222_config_intr_host_10gbr(struct phy_device *phydev) +{ + int ret; + + if (phydev->interface == PHY_INTERFACE_MODE_10GBASER && + phydev->interrupts == PHY_INTERRUPT_ENABLED) { + ret = phy_read_mmd(phydev, MDIO_MMD_PHYXS, MV_HOST_10GBR_INT_STAT); + if (ret < 0) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_PHYXS, MV_HOST_10GBR_INT_EN, + MV_HOST_10GBR_INT_MASK); + } else { + ret = phy_write_mmd(phydev, MDIO_MMD_PHYXS, MV_HOST_10GBR_INT_EN, 0); + if (ret < 0) + return ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_PHYXS, MV_HOST_10GBR_INT_STAT); + } + + return ret; +} + +static int mv2222_config_intr_host_xaui(struct phy_device *phydev) +{ + int ret; + + if (phydev->interface == PHY_INTERFACE_MODE_XAUI && + phydev->interrupts == PHY_INTERRUPT_ENABLED) { + ret = phy_read_mmd(phydev, MDIO_MMD_PHYXS, MV_HOST_XAUI_INT_STAT1); + if (ret < 0) + return ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_PHYXS, MV_HOST_XAUI_INT_STAT2); + if (ret < 0) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_PHYXS, MV_HOST_XAUI_INT_EN1, + MV_HOST_XAUI_INT_MASK1); + if (ret < 0) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_PHYXS, MV_HOST_XAUI_INT_EN2, + MV_HOST_XAUI_INT_MASK2); + } else { + ret = phy_write_mmd(phydev, MDIO_MMD_PHYXS, MV_HOST_XAUI_INT_EN2, 0); + if (ret < 0) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_PHYXS, MV_HOST_XAUI_INT_EN1, 0); + if (ret < 0) + return ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_PHYXS, MV_HOST_XAUI_INT_STAT2); + if (ret < 0) + return ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_PHYXS, MV_HOST_XAUI_INT_STAT1); + } + + return ret; +} + +static int mv2222_config_intr_10g(struct phy_device *phydev) +{ + int ret; + + if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { + ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_10GBR_INT_STAT); + if (ret < 0) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_PCS, MV_10GBR_INT_EN, + MV_10GBR_INT_MASK); + } else { + ret = phy_write_mmd(phydev, MDIO_MMD_PCS, MV_10GBR_INT_EN, 0); + if (ret < 0) + return ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_10GBR_INT_STAT); + } + + return ret; +} + +static int mv2222_config_intr_1g(struct phy_device *phydev) +{ + int ret; + + if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { + ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_1GBX_INT_STAT); + if (ret < 0) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_PCS, MV_1GBX_INT_EN, + MV_1GBX_INT_MASK); + } else { + ret = phy_write_mmd(phydev, MDIO_MMD_PCS, MV_1GBX_INT_EN, 0); + if (ret < 0) + return ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_1GBX_INT_STAT); + } + + return ret; +} + +static int mv2222_config_intr(struct phy_device *phydev) +{ + int ret; + + ret = mv2222_config_intr_10g(phydev); + if (ret < 0) + return ret; + + ret = mv2222_config_intr_1g(phydev); + if (ret < 0) + return ret; + + ret = mv2222_config_intr_host_10gbr(phydev); + if (ret < 0) + return ret; + + ret = mv2222_config_intr_host_xaui(phydev); + if (ret < 0) + return ret; + + return 0; +} + +static int mv2222_handle_interrupt_host_10gbr(struct phy_device *phydev) +{ + int val; + + val = phy_read_mmd(phydev, MDIO_MMD_PHYXS, MV_HOST_10GBR_INT_STAT); + if (val < 0) + return val; + + if (val & MV_HOST_10GBR_INT_LINK_STAT) + phydev_dbg(phydev, "Host link status changed\n"); + + if (val & MV_HOST_10GBR_INT_LOCAL_RXFAULT) + phydev_dbg(phydev, "Host Rx fault detected\n"); + + if (val & MV_HOST_10GBR_INT_LOCAL_TXFAULT) + phydev_dbg(phydev, "Host Tx fault detected\n"); + + return 0; +} + +static int mv2222_handle_interrupt_host_xaui(struct phy_device *phydev) +{ + int val1, val2; + + val1 = phy_read_mmd(phydev, MDIO_MMD_PHYXS, MV_HOST_XAUI_INT_STAT1); + if (val1 < 0) + return val1; + + val2 = phy_read_mmd(phydev, MDIO_MMD_PHYXS, MV_HOST_XAUI_INT_STAT2); + if (val2 < 0) + return val2; + + if (val1 & MV_HOST_XAUI_INT_LINK_UP) + phydev_dbg(phydev, "Host link is up\n"); + + if (val1 & MV_HOST_XAUI_INT_LINK_DOWN) + phydev_dbg(phydev, "Host link is down\n"); + + if (val2 & MV_HOST_XAUI_INT_TXFAULT) + phydev_dbg(phydev, "Host Tx fault detected\n"); + + if (val2 & MV_HOST_XAUI_INT_RXFAULT) + phydev_dbg(phydev, "Host Rx fault detected\n"); + + return 0; +} + +static int mv2222_handle_interrupt_10g(struct phy_device *phydev) +{ + int val; + + val = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_10GBR_INT_STAT); + if (val < 0) + return val; + + if (val & MV_10GBR_INT_LINK_STAT) + phydev_dbg(phydev, "Line link status changed\n"); + + if (val & MV_10GBR_INT_LOCAL_RXFAULT) + phydev_dbg(phydev, "Line Rx fault detected\n"); + + if (val & MV_10GBR_INT_LOCAL_TXFAULT) + phydev_dbg(phydev, "Line Tx fault detected\n"); + + return 0; +} + +static int mv2222_handle_interrupt_1g(struct phy_device *phydev) +{ + int val; + + val = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_1GBX_INT_STAT); + if (val < 0) + return val; + + if (val & MV_1GBX_INT_FALSE_CARRIER) + phydev_dbg(phydev, "Line false carrier detected\n"); + + if (val & MV_1GBX_INT_SYMBOL_ERROR) + phydev_dbg(phydev, "Line symbol error detected\n"); + + if (val & MV_1GBX_INT_LINK_UP) + phydev_dbg(phydev, "Line link is up\n"); + + if (val & MV_1GBX_INT_LINK_DOWN) + phydev_dbg(phydev, "Line link is down\n"); + + if (val & MV_1GBX_INT_AN_COMPLETED) + phydev_dbg(phydev, "Line AN is completed\n"); + + if (val & MV_1GBX_INT_DUPLEX_CHANGED) + phydev_dbg(phydev, "Line link duplex changed\n"); + + if (val & MV_1GBX_INT_SPEED_CHANGED) + phydev_dbg(phydev, "Line link speed changed\n"); + + return 0; +} + +static irqreturn_t mv2222_handle_interrupt(struct phy_device *phydev) +{ + int val, ret; + + val = phy_read_mmd(phydev, MDIO_MMD_VEND2, MV_PORT_INT_STAT); + if (val < 0) + goto err_set_state; + + if (!(val & (MV_PORT_INT_PCS_HOST | MV_PORT_INT_PCS_LINE))) + return IRQ_NONE; + + if (val & MV_PORT_INT_PCS_HOST) { + ret = mv2222_handle_interrupt_host_10gbr(phydev); + if (ret < 0) + goto err_set_state; + + ret = mv2222_handle_interrupt_host_xaui(phydev); + if (ret < 0) + goto err_set_state; + } + + if (val & MV_PORT_INT_PCS_LINE) { + ret = mv2222_handle_interrupt_10g(phydev); + if (ret < 0) + goto err_set_state; + + ret = mv2222_handle_interrupt_1g(phydev); + if (ret < 0) + goto err_set_state; + } + + phy_trigger_machine(phydev); + + return IRQ_HANDLED; + +err_set_state: + phy_error(phydev); + + return IRQ_NONE; +} + static int mv2222_resume(struct phy_device *phydev) { return mv2222_tx_enable(phydev); @@ -687,6 +1041,8 @@ static struct phy_driver mv2222_drivers[] = { .soft_reset = mv2222_soft_reset, .config_init = mv2222_config_init, .config_aneg = mv2222_config_aneg, + .config_intr = mv2222_config_intr, + .handle_interrupt = mv2222_handle_interrupt, .aneg_done = mv2222_aneg_done, .probe = mv2222_probe, .suspend = mv2222_suspend, @@ -702,6 +1058,8 @@ static struct phy_driver mv2222_drivers[] = { .soft_reset = mv2222_soft_reset, .config_init = mv2222_config_init, .config_aneg = mv2222_config_aneg, + .config_intr = mv2222_config_intr, + .handle_interrupt = mv2222_handle_interrupt, .aneg_done = mv2222_aneg_done, .probe = mv2222_probe, .suspend = mv2222_suspend, -- cgit v1.2.3 From 01260cda90e449d341173748237ee956ea35ca37 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 23 Oct 2023 20:02:44 +0300 Subject: net: phy: marvell-88x2222: Add faulty side debug printout At least on debug-stage it gets to be handy to know what side causes the link being unavailable: host or line side. Add debug printouts activated depending on the respective conditional statement being true in the Marvell 88x2222 status read procedure. Signed-off-by: Serge Semin --- drivers/net/phy/marvell-88x2222.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/net/phy/marvell-88x2222.c b/drivers/net/phy/marvell-88x2222.c index cb8025fb43cbe..2f6950ce12125 100644 --- a/drivers/net/phy/marvell-88x2222.c +++ b/drivers/net/phy/marvell-88x2222.c @@ -609,11 +609,15 @@ static int mv2222_read_status(struct phy_device *phydev) phydev->speed = SPEED_UNKNOWN; phydev->duplex = DUPLEX_UNKNOWN; - if (!mv2222_iface_is_operational(phydev)) + if (!mv2222_iface_is_operational(phydev)) { + phydev_dbg(phydev, "Host interface isn't operational\n"); return 0; + } - if (!mv2222_link_is_operational(phydev)) + if (!mv2222_link_is_operational(phydev)) { + phydev_dbg(phydev, "Line side isn't operational\n"); return 0; + } if (priv->line_interface == PHY_INTERFACE_MODE_10GBASER) link = mv2222_read_status_10g(phydev); -- cgit v1.2.3 From 1235ead764fbc95ae89c5c2f95783c654e3d2603 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 26 Jul 2023 16:54:13 +0300 Subject: net: phy: marvell-88x2222: Add GPIOs/SFP+ support Marvell 88x2222 PHY is equipped with 11 general purpose pins, which can be configured to work either as a special-function pins (LEDs, TWSI SDA/SCL, SFP+, etc) or as GPIOs. In the later case they can be registered in the kernel GPIO subsystem and used then either for some platform-specific sake or as the SFP+ port GPIOs seeing there is no MMD-based SFP+ implementation currently supported by the kernel. All eleven GPIOs can support input and output (open-drain) modes determined by the TRISTATE control register. In case of the input mode the state change is reported by means of the PHY IRQ line. Each GPIO interrupt can be individually enabled/disabled by the IRQ Enable register. There are all five interrupt types supported: high/low level triggered, rising/falling/both edges triggered. Note GPIO IRQs are handled in a separate ISR instead of doing that in the phy_driver.handle_interrupt() function. The later handler is utilized only if the PHY is attached to a network device. It's unacceptable for the general-purpose IO pins since they can be utilized anytime the device is successfully probed. For a similar reason the GPIOs aren't supported if the device-specific hard-reset line is specified. It's toggled during the PHY re-attachment which will clear the entire device state out and will break the GPIOs driver functionality for sure. Note 1. In case of some of the GPIOs are marked as invalid the respective pins special-function is activated/preserved. The pins can be defined as reserved for instance by means of the "gpio-reserved-ranges" property. It will be useful for the platforms which rely on the special-functions, like LEDs, activated. Note 2. The private data caches are utilized in order to implement the access to some of the GPIO CSRs. It's done for several reasons: in order to implement the IRQ-chip support for a device living on a slow asynchronous bus; in order speed up the CSRs access; in order to overcome a race condition around the GPIO Data register RMW-access (see the commit body for details). Signed-off-by: Serge Semin --- drivers/net/phy/Kconfig | 7 + drivers/net/phy/marvell-88x2222.c | 658 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 665 insertions(+) diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index 7b73332a13d95..d1f578955b7f3 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -277,6 +277,13 @@ config MARVELL_88X2222_PHY Support for the Marvell 88X2222 Dual-port Multi-speed Ethernet Transceiver. +config MARVELL_88X2222_GPIO + bool "Marvell 88X2222 SFP+ GPIOs support" + depends on MARVELL_88X2222_PHY + depends on GPIOLIB && GPIOLIB_IRQCHIP + help + Support for the Marvell 88X2222 PHY SFP+/GPIO signals. + config MAXLINEAR_GPHY tristate "Maxlinear Ethernet PHYs" select POLYNOMIAL if HWMON diff --git a/drivers/net/phy/marvell-88x2222.c b/drivers/net/phy/marvell-88x2222.c index 2f6950ce12125..46807891a253c 100644 --- a/drivers/net/phy/marvell-88x2222.c +++ b/drivers/net/phy/marvell-88x2222.c @@ -10,8 +10,11 @@ #include #include #include +#include #include +#include #include +#include #include #include #include @@ -19,6 +22,8 @@ #include #include +#include + /* Port PCS Configuration */ #define MV_PCS_CONFIG 0xF002 #define MV_PCS_HOST_PCS_SELECT GENMASK(6, 0) @@ -35,6 +40,68 @@ #define MV_HOST_RST_SW BIT(7) #define MV_PORT_RST_SW (MV_LINE_RST_SW | MV_HOST_RST_SW) +/* GPIO Interrupt Enable */ +#define MV_GPIO_INT_EN 0xF010 + +/* GPIO Interrupt Status */ +#define MV_GPIO_INT_STAT 0xF011 + +/* GPIO Input/Output data */ +#define MV_GPIO_DATA 0xF012 + +/* GPIO Tristate control */ +#define MV_GPIO_TRISTATE_CTRL 0xF013 + +/* GPIO Interrupt type 1 (GPIOs 0 - 3) */ +#define MV_GPIO_INT_TYPE1 0xF014 +#define MV_GPIO_INT_TYPE_PIN0 GENMASK(2, 0) +#define MV_GPIO_INT_NO_IRQ 0 +#define MV_GPIO_INT_LEVEL_LOW 2 +#define MV_GPIO_INT_LEVEL_HIGH 3 +#define MV_GPIO_INT_EDGE_FALLING 4 +#define MV_GPIO_INT_EDGE_RISING 5 +#define MV_GPIO_INT_EDGE_BOTH 7 +#define MV_GPIO_INT_TYPE_PIN1 GENMASK(6, 4) +#define MV_GPIO_FUNC_TX_FLT_PIN1 BIT(7) +#define MV_GPIO_INT_TYPE_PIN2 GENMASK(10, 8) +#define MV_GPIO_FUNC_RX_LOS_PIN2 BIT(11) +#define MV_GPIO_INT_TYPE_PIN3 GENMASK(14, 12) + +/* GPIO Interrupt type 2 (GPIOs 4 - 7) */ +#define MV_GPIO_INT_TYPE2 0xF015 +#define MV_GPIO_INT_TYPE_PIN4 GENMASK(2, 0) +#define MV_GPIO_FUNC_LED0_PIN4 BIT(3) +#define MV_GPIO_INT_TYPE_PIN5 GENMASK(6, 4) +#define MV_GPIO_FUNC_LED1_PIN5 BIT(7) +#define MV_GPIO_INT_TYPE_PIN6 GENMASK(10, 8) +#define MV_GPIO_FUNC_MPC_PIN6 BIT(11) +#define MV_GPIO_INT_TYPE_PIN7 GENMASK(14, 12) +#define MV_GPIO_FUNC_TOD_PIN7 BIT(15) + +/* GPIO Interrupt type 3 (GPIOs 8, 10, 11) */ +#define MV_GPIO_INT_TYPE3 0xF016 +#define MV_GPIO_INT_TYPE_PIN8 GENMASK(2, 0) +#define MV_GPIO_FUNC_TX_DIS_PIN8 GENMASK(4, 3) +#define MV_GPIO_FUNC_TX_DIS_LED 0 +#define MV_GPIO_FUNC_TX_DIS_PIN 1 +#define MV_GPIO_FUNC_TX_DIS_REG 2 +#define MV_GPIO_INT_TYPE_PIN10 GENMASK(10, 8) +#define MV_GPIO_FUNC_SDA_PIN10 BIT(11) +#define MV_GPIO_INT_TYPE_PIN11 GENMASK(14, 12) +#define MV_GPIO_FUNC_SCL_PIN11 BIT(15) + +/* GPIO Interrupt type 1-3 helpers */ +#define MV_GPIO_INT_TYPE_REG(_pin) \ + ((_pin) / 4) +#define MV_GPIO_INT_TYPE_PREP(_pin, _val) \ + ((_val) << ((_pin) % 4) * 4) +#define MV_GPIO_INT_TYPE_MASK(_pin) \ + MV_GPIO_INT_TYPE_PREP(_pin, GENMASK(2, 0)) +#define MV_GPIO_FUNC_PREP(_pin, _val) \ + ((_val) << (3 + ((_pin) % 4) * 4)) +#define MV_GPIO_FUNC_MASK(_pin) \ + MV_GPIO_FUNC_PREP(_pin, (_pin) != 8 ? BIT(0) : GENMASK(1, 0)) + /* Port Interrupt Status */ #define MV_PORT_INT_STAT 0xF040 #define MV_PORT_INT_PCS_LINE BIT(0) @@ -144,6 +211,11 @@ /* Host-side XAUI Interrupt Status 2 Register */ #define MV_HOST_XAUI_INT_STAT2 0x9004 +/* GPIO controller settings (#9 always invalid, #0 and #3 always available) */ +#define MV_GPIO_NUM 12 +#define MV_GPIO_VAL_MASK 0xDFF +#define MV_GPIO_INT_UPD_FLAG BIT(31) + #define AUTONEG_TIMEOUT 3 struct mv2222_data { @@ -152,6 +224,20 @@ struct mv2222_data { bool sfp_link; }; +#ifdef CONFIG_MARVELL_88X2222_GPIO +struct mv2222_gpio { + struct gpio_chip gc; + struct phy_device *phydev; + unsigned int irq; + u32 cache_val_mask; + u32 cache_data; + u32 cache_tri_ctrl; + u32 cache_int_en; + u32 cache_int_type[MV_GPIO_INT_TYPE_REG(MV_GPIO_NUM)]; + struct mutex cache_lock; +}; +#endif + /* SFI PMA transmit enable */ static int mv2222_tx_enable(struct phy_device *phydev) { @@ -930,6 +1016,573 @@ static int mv2222_config_init(struct phy_device *phydev) return mv2222_host_reset(phydev); } +#ifdef CONFIG_MARVELL_88X2222_GPIO +/* + * Marvell 88x2222 GPIO control registers access is implemented by means of the + * private data caches. It has several reasons. First of all the IRQ chip + * implementation requires the IRQ-related settings applied in a non-atomic + * context for the slow asynchronously accessed bus. Thus caching the IRQ-masks + * and types is neccessary in order to flush the respective CSRs data in the + * may-sleep context. Second the implementation decreases a bus traffic which + * improves the accesses performance espcially in case of the bit-banged MDIO + * bus. Finally it prevents a race condition in the output value setup on the + * output GPIO-mode settings. The GPIO data register has a multiplexed input + * output data semantics. Any read from the CSRs returns either the input GPIO + * state or the output GPIO value. Meanwhile any write to the data register + * updates the output GPIO value. So the race condition may happen if the + * output value is updated before the output GPIO-mode is activated and if + * there is a parallel read-modify-write is performed to the GPIO data + * register. In that case the input GPIO state will be read and written back as + * the output GPIO value. Using the private data cache prevents that. + */ + +static inline bool mv2222_gpio_offset_is_valid(unsigned int ofs) +{ + return ofs < MV_GPIO_NUM && ofs != 9; +} + +static int mv2222_gpio_read_tri_ctrl(struct mv2222_gpio *gpio) +{ + int val; + + mutex_lock(&gpio->cache_lock); + + val = phy_read_mmd(gpio->phydev, MDIO_MMD_VEND2, MV_GPIO_TRISTATE_CTRL); + if (val < 0) + goto err_mutex_unlock; + + gpio->cache_tri_ctrl = val; + +err_mutex_unlock: + mutex_unlock(&gpio->cache_lock); + + return val; +} + +static int mv2222_gpio_wthru_tri_ctrl(struct mv2222_gpio *gpio, u16 mask, u16 val) +{ + int ret; + + mutex_lock(&gpio->cache_lock); + + gpio->cache_tri_ctrl &= ~mask; + gpio->cache_tri_ctrl |= val; + + ret = phy_write_mmd(gpio->phydev, MDIO_MMD_VEND2, + MV_GPIO_TRISTATE_CTRL, gpio->cache_tri_ctrl); + + mutex_unlock(&gpio->cache_lock); + + return ret; +} + +static int mv2222_gpio_read_data(struct mv2222_gpio *gpio) +{ + int val; + + mutex_lock(&gpio->cache_lock); + + val = phy_read_mmd(gpio->phydev, MDIO_MMD_VEND2, MV_GPIO_DATA); + if (val < 0) + goto err_mutex_unlock; + + gpio->cache_data = val; + +err_mutex_unlock: + mutex_unlock(&gpio->cache_lock); + + return val; +} + +static int mv2222_gpio_wthru_data(struct mv2222_gpio *gpio, u16 mask, u16 val) +{ + int ret; + + mutex_lock(&gpio->cache_lock); + + gpio->cache_data &= ~mask; + gpio->cache_data |= val; + + ret = phy_write_mmd(gpio->phydev, MDIO_MMD_VEND2, + MV_GPIO_DATA, gpio->cache_data); + + mutex_unlock(&gpio->cache_lock); + + return ret; +} + +static int mv2222_gpio_cache_int_en(struct mv2222_gpio *gpio, + unsigned int ofs, bool int_en) +{ + if (!mv2222_gpio_offset_is_valid(ofs)) + return -EINVAL; + + if (int_en) + gpio->cache_int_en |= BIT(ofs); + else + gpio->cache_int_en &= ~BIT(ofs); + + gpio->cache_int_en |= MV_GPIO_INT_UPD_FLAG; + + return 0; +} + +static int mv2222_gpio_cache_func(struct mv2222_gpio *gpio, + unsigned int ofs, bool gpio_en) +{ + u16 val; + int reg; + + if (!mv2222_gpio_offset_is_valid(ofs)) + return -EINVAL; + + /* Pins #0 and #3 always work as GPIOs */ + if (ofs == 0 || ofs == 3) + return !gpio_en ? -EINVAL : 0; + + if (ofs == 8 && !gpio_en) + val = MV_GPIO_FUNC_TX_DIS_REG; + else + val = !!gpio_en; + + reg = MV_GPIO_INT_TYPE_REG(ofs); + gpio->cache_int_type[reg] &= ~MV_GPIO_FUNC_MASK(ofs); + gpio->cache_int_type[reg] |= MV_GPIO_FUNC_PREP(ofs, val); + + gpio->cache_int_type[reg] |= MV_GPIO_INT_UPD_FLAG; + + return 0; +} + +static int mv2222_gpio_cache_int_type(struct mv2222_gpio *gpio, + unsigned int ofs, unsigned int type) +{ + u16 val; + int reg; + + if (!mv2222_gpio_offset_is_valid(ofs)) + return -EINVAL; + + switch (type) { + case IRQ_TYPE_NONE: + val = MV_GPIO_INT_NO_IRQ; + break; + case IRQ_TYPE_EDGE_RISING: + val = MV_GPIO_INT_EDGE_RISING; + break; + case IRQ_TYPE_EDGE_FALLING: + val = MV_GPIO_INT_EDGE_FALLING; + break; + case IRQ_TYPE_EDGE_BOTH: + val = MV_GPIO_INT_EDGE_BOTH; + break; + case IRQ_TYPE_LEVEL_HIGH: + val = MV_GPIO_INT_LEVEL_HIGH; + break; + case IRQ_TYPE_LEVEL_LOW: + val = MV_GPIO_INT_LEVEL_LOW; + break; + default: + return -EINVAL; + } + + reg = MV_GPIO_INT_TYPE_REG(ofs); + gpio->cache_int_type[reg] &= ~MV_GPIO_INT_TYPE_MASK(ofs); + gpio->cache_int_type[reg] |= MV_GPIO_INT_TYPE_PREP(ofs, val); + + gpio->cache_int_type[reg] |= MV_GPIO_INT_UPD_FLAG; + + return 0; +} + +static int mv2222_gpio_cache_int_flush(struct mv2222_gpio *gpio) +{ + int i, ret; + + if (gpio->cache_int_en & MV_GPIO_INT_UPD_FLAG) { + ret = phy_write_mmd(gpio->phydev, MDIO_MMD_VEND2, + MV_GPIO_INT_EN, gpio->cache_int_en); + if (ret < 0) + return ret; + + gpio->cache_int_en &= ~MV_GPIO_INT_UPD_FLAG; + } + + for (i = 0; i < ARRAY_SIZE(gpio->cache_int_type); ++i) { + if (!(gpio->cache_int_type[i] & MV_GPIO_INT_UPD_FLAG)) + continue; + + ret = phy_write_mmd(gpio->phydev, MDIO_MMD_VEND2, + MV_GPIO_INT_TYPE1 + i, gpio->cache_int_type[i]); + if (ret < 0) + return ret; + + gpio->cache_int_type[i] &= ~MV_GPIO_INT_UPD_FLAG; + } + + return 0; +} + +static int mv2222_gpio_init(struct mv2222_gpio *gpio) +{ + int i, ret; + + /* Setup GPIO function and default IRQs state for all valid GPIOs */ + for (i = 0; i < MV_GPIO_NUM; ++i) { + mv2222_gpio_cache_int_en(gpio, i, false); + + if (gpio->cache_val_mask & BIT(i)) + mv2222_gpio_cache_func(gpio, i, true); + else + mv2222_gpio_cache_func(gpio, i, false); + + mv2222_gpio_cache_int_type(gpio, i, IRQ_TYPE_NONE); + } + + ret = mv2222_gpio_cache_int_flush(gpio); + if (ret < 0) + return ret; + + ret = mv2222_gpio_read_tri_ctrl(gpio); + if (ret < 0) + return ret; + + ret = mv2222_gpio_read_data(gpio); + if (ret < 0) + return ret; + + return 0; +} + +static int mv2222_gpio_get_direction(struct gpio_chip *gc, unsigned int ofs) +{ + struct mv2222_gpio *gpio = gpiochip_get_data(gc); + int ret; + + mutex_lock(&gpio->cache_lock); + ret = !(gpio->cache_tri_ctrl & BIT(ofs)); + mutex_unlock(&gpio->cache_lock); + + return ret; +} + +static int mv2222_gpio_direction_input(struct gpio_chip *gc, unsigned int ofs) +{ + struct mv2222_gpio *gpio = gpiochip_get_data(gc); + u16 mask = BIT(ofs); + + return mv2222_gpio_wthru_tri_ctrl(gpio, mask, 0); +} + +static int mv2222_gpio_direction_output(struct gpio_chip *gc, + unsigned int ofs, int val) +{ + struct mv2222_gpio *gpio = gpiochip_get_data(gc); + u16 mask = BIT(ofs); + int ret; + + ret = mv2222_gpio_wthru_data(gpio, mask, val ? mask : 0); + if (ret < 0) + return ret; + + return mv2222_gpio_wthru_tri_ctrl(gpio, mask, mask); +} + +static int mv2222_gpio_get(struct gpio_chip *gc, unsigned int ofs) +{ + struct mv2222_gpio *gpio = gpiochip_get_data(gc); + u16 mask = BIT(ofs); + int val; + + val = mv2222_gpio_read_data(gpio); + if (val < 0) + return val; + + return !!(val & mask); +} + +static int mv2222_gpio_get_multiple(struct gpio_chip *gc, + unsigned long *mask, unsigned long *bits) +{ + struct mv2222_gpio *gpio = gpiochip_get_data(gc); + int val; + + val = mv2222_gpio_read_data(gpio); + if (val < 0) + return val; + + *bits &= ~*mask; + *bits |= val & *mask; + + return 0; +} + +static int mv2222_gpio_set(struct gpio_chip *gc, unsigned ofs, int val) +{ + struct mv2222_gpio *gpio = gpiochip_get_data(gc); + u16 mask = BIT(ofs); + int ret; + + ret = mv2222_gpio_wthru_data(gpio, mask, val ? mask : 0); + if (ret < 0) + phydev_err(gpio->phydev, "Failed to set GPIO %d\n", ofs); + + return ret; +} + +static int mv2222_gpio_set_multiple(struct gpio_chip *gc, + unsigned long *mask, unsigned long *bits) +{ + struct mv2222_gpio *gpio = gpiochip_get_data(gc); + int ret; + + ret = mv2222_gpio_wthru_data(gpio, *mask, *bits); + if (ret < 0) + phydev_err(gpio->phydev, "Failed to set GPIOs 0x%04lx\n", *bits); + + return ret; +} + +static int mv2222_gpio_set_config(struct gpio_chip *gc, unsigned int ofs, + unsigned long cfg) +{ + enum pin_config_param mode = pinconf_to_config_param(cfg); + + /* All output pins operate as open drain */ + if (mode != PIN_CONFIG_DRIVE_OPEN_DRAIN) + return -ENOTSUPP; + + return 0; +} + +static int mv2222_gpio_init_valid_mask(struct gpio_chip *gc, + unsigned long *valid_mask, + unsigned int ngpios) +{ + struct mv2222_gpio *gpio = gpiochip_get_data(gc); + int ret; + + if (ngpios > MV_GPIO_NUM) + return -EINVAL; + + *valid_mask &= MV_GPIO_VAL_MASK; + + gpio->cache_val_mask = *valid_mask; + + ret = mv2222_gpio_init(gpio); + if (ret < 0) + return ret; + + return 0; +} + +static void mv2222_gpio_irq_mask(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct mv2222_gpio *gpio = gpiochip_get_data(gc); + irq_hw_number_t hwirq = irqd_to_hwirq(d); + + mv2222_gpio_cache_int_en(gpio, hwirq, false); + gpiochip_disable_irq(gc, hwirq); +} + +static void mv2222_gpio_irq_unmask(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct mv2222_gpio *gpio = gpiochip_get_data(gc); + irq_hw_number_t hwirq = irqd_to_hwirq(d); + + gpiochip_enable_irq(gc, hwirq); + mv2222_gpio_cache_int_en(gpio, hwirq, true); +} + +static int mv2222_gpio_irq_set_type(struct irq_data *d, unsigned int type) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct mv2222_gpio *gpio = gpiochip_get_data(gc); + irq_hw_number_t hwirq = irqd_to_hwirq(d); + + return mv2222_gpio_cache_int_type(gpio, hwirq, type); +} + +static int mv2222_gpio_irq_set_wake(struct irq_data *d, unsigned int on) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct mv2222_gpio *gpio = gpiochip_get_data(gc); + + return irq_set_irq_wake(gpio->irq, on); +} + +static void mv2222_gpio_irq_bus_lock(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct mv2222_gpio *gpio = gpiochip_get_data(gc); + + mutex_lock(&gpio->cache_lock); +} + +static void mv2222_gpio_irq_bus_sync_unlock(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct mv2222_gpio *gpio = gpiochip_get_data(gc); + int ret; + + ret = mv2222_gpio_cache_int_flush(gpio); + if (ret < 0) + phydev_err(gpio->phydev, "Failed to flush GPIO IRQs state\n"); + + mutex_unlock(&gpio->cache_lock); +} + +static void mv2222_gpio_irq_print_chip(struct irq_data *d, struct seq_file *p) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + + seq_printf(p, dev_name(gc->parent)); +} + +static const struct irq_chip mv2222_gpio_irq_chip = { + .name = "mv88x2222", + .irq_mask = mv2222_gpio_irq_mask, + .irq_unmask = mv2222_gpio_irq_unmask, + .irq_set_wake = mv2222_gpio_irq_set_wake, + .irq_set_type = mv2222_gpio_irq_set_type, + .irq_bus_lock = mv2222_gpio_irq_bus_lock, + .irq_bus_sync_unlock = mv2222_gpio_irq_bus_sync_unlock, + .irq_print_chip = mv2222_gpio_irq_print_chip, + .flags = IRQCHIP_IMMUTABLE, + GPIOCHIP_IRQ_RESOURCE_HELPERS, +}; + +static irqreturn_t mv2222_gpio_handle_interrupt(int irq, void *devid) +{ + struct mv2222_gpio *gpio = devid; + unsigned long pending; + int i, val; + + val = phy_read_mmd(gpio->phydev, MDIO_MMD_VEND2, MV_GPIO_INT_STAT); + if (val < 0) + return IRQ_NONE; + + /* The interrupt status register exports the raw IRQ status */ + mutex_lock(&gpio->cache_lock); + pending = val & gpio->cache_int_en; + mutex_unlock(&gpio->cache_lock); + + for_each_set_bit(i, &pending, gpio->gc.ngpio) + handle_nested_irq(irq_find_mapping(gpio->gc.irq.domain, i)); + + return IRQ_RETVAL(pending); +} + +static const char * const mv2222_gpio_names[MV_GPIO_NUM] = { + [MV_88X2222_MOD_ABS] = "MOD_ABS", + [MV_88X2222_TX_FAULT] = "TX_FAULT", + [MV_88X2222_RX_LOS] = "RX_LOS", + [MV_88X2222_GPIO] = "GPIO", + [MV_88X2222_LED0] = "LED0", + [MV_88X2222_LED1] = "LED1", + [MV_88X2222_MPC] = "MPC", + [MV_88X2222_TOD] = "TOD", + [MV_88X2222_TX_DISABLE] = "TX_DISABLE", + [MV_88X2222_UNDEF] = NULL, + [MV_88X2222_SDA] = "SDA", + [MV_88X2222_SCL] = "SCL", +}; + +static int mv2222_gpio_probe(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + struct mv2222_gpio *gpio; + struct gpio_chip *gc; + int ret; + + /* + * No GPIO-chip registration if the PHY-device isn't marked as a + * GPIO-controller. This is another level of protection for the + * backward compatibility in case if the platforms rely on the + * default/pre-initialized pins functions. + */ + if (!device_property_present(dev, "gpio-controller")) + return 0; + + /* + * Marvell 88x2222 GPIO CSRs are tolerant to the soft-resets. They are + * marked as "Retain" in the "SW Rst" calumn of the registers + * description table defined in the HW-databook. On the contrary the + * hard-reset will reset all the GPIO CSRs to their default states which + * in its turn will break the driver GPIO functionality for sure. + */ + if (phydev->mdio.reset_gpio || phydev->mdio.reset_ctrl) { + phydev_warn(phydev, "Hard-reset detected, GPIOs unsupported\n"); + return 0; + } + + gpio = devm_kzalloc(dev, sizeof(*gpio), GFP_KERNEL); + if (!gpio) + return -ENOMEM; + + gpio->phydev = phydev; + + mutex_init(&gpio->cache_lock); + + gc = &gpio->gc; + gc->label = "mv88x2222"; + gc->parent = dev; + gc->owner = THIS_MODULE; + gc->get_direction = mv2222_gpio_get_direction; + gc->direction_input = mv2222_gpio_direction_input; + gc->direction_output = mv2222_gpio_direction_output; + gc->get = mv2222_gpio_get; + gc->get_multiple = mv2222_gpio_get_multiple; + gc->set = mv2222_gpio_set; + gc->set_multiple = mv2222_gpio_set_multiple; + gc->set_config = mv2222_gpio_set_config; + gc->init_valid_mask = mv2222_gpio_init_valid_mask; + gc->base = -1; + gc->ngpio = MV_GPIO_NUM; + gc->names = mv2222_gpio_names; + gc->can_sleep = true; + + if (phy_interrupt_is_valid(phydev)) { + struct gpio_irq_chip *girq = &gc->irq; + + gpio->irq = phydev->irq; + + girq->handler = handle_bad_irq; + girq->default_type = IRQ_TYPE_NONE; + girq->parent_handler = NULL; + girq->num_parents = 0; + girq->parents = NULL; + girq->threaded = true; + + gpio_irq_chip_set_chip(girq, &mv2222_gpio_irq_chip); + + ret = devm_request_threaded_irq(dev, gpio->irq, NULL, + mv2222_gpio_handle_interrupt, + IRQF_ONESHOT | IRQF_SHARED, + phydev_name(phydev), gpio); + if (ret) { + phydev_err(phydev, "Failed to request GPIO IRQ\n"); + return ret; + } + } else { + gpio->irq = IRQ_NOTCONNECTED; + } + + ret = devm_gpiochip_add_data(dev, gc, gpio); + if (ret) + phydev_err(phydev, "Failed to register GPIO chip\n"); + + return ret; +} +#else +static int mv2222_gpio_probe(struct phy_device *phydev) +{ + return 0; +} +#endif /* !CONFIG_MARVELL_88X2222_GPIO */ + static int mv2222_configure_serdes(struct phy_port *port, bool enable, phy_interface_t interface) { @@ -998,6 +1651,7 @@ static int mv2222_probe(struct phy_device *phydev) { struct device *dev = &phydev->mdio.dev; struct mv2222_data *priv = NULL; + int ret; __ETHTOOL_DECLARE_LINK_MODE_MASK(supported) = { 0, }; @@ -1033,6 +1687,10 @@ static int mv2222_probe(struct phy_device *phydev) priv->line_interface = PHY_INTERFACE_MODE_NA; phydev->priv = priv; + ret = mv2222_gpio_probe(phydev); + if (ret) + return ret; + return 0; } -- cgit v1.2.3 From 3b1f813848445bd66116f8b3af2c0c94e70788f5 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 28 Jul 2023 12:57:58 +0300 Subject: net: phy: marvell-88x2222: Add I2C/SFP+ support Marvell 88x2222 PHY is equipped with a basic TWSI/SMBus controller which SDA/SCL pins are multiplexed with GPIO pins 10 and 11. The main purpose of the interface is to access the externally attached modules EEPROM (for that sake there is even an internal cache which if enabled can be filled with the EEPROM data) but in general it can be used to access any I2C peripheral device as long as it supports the SMBus transfers the I2C-controller provides. Alas the SMBus controller functionality is very limited. It's just SMBus byte data read and write transfers, which should be enough for the EEPROM IO operations but won't provide a full support of the SFP+ equipments like MDIO-I2C-based PHY or thermal sensors. Anyway if a respective platform is already designed to rely on the Marvell 88x2222 I2C-interface it will be handy to have the interface registered in kernel so the SFP-port driver would at least access the module EEPROM. The provided driver first probes whether the I2C-interface functionality is available and supported by the current platform setup. That is it makes sure that there is no GPIO-function activated on the SDA/SCL pins and there is no device hard-reset capability. Then it pre-initializes the I2C-interface: disables the EEPROM caching and Read-after-write functionality. After that the I2C-adapter is ready to be registered and utilized then by the client drivers (mainly by the SFP-port driver) for the basic SMBus Byte Data Read/Write operations. Signed-off-by: Serge Semin --- drivers/net/phy/Kconfig | 7 + drivers/net/phy/marvell-88x2222.c | 291 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 298 insertions(+) diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index d1f578955b7f3..a8272a7c869f2 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -284,6 +284,13 @@ config MARVELL_88X2222_GPIO help Support for the Marvell 88X2222 PHY SFP+/GPIO signals. +config MARVELL_88X2222_I2C + bool "Marvell 88X2222 SFP+ I2C support" + depends on MARVELL_88X2222_PHY + depends on I2C + help + Support for the Marvell 88X2222 PHY SFP+/I2C signals. + config MAXLINEAR_GPHY tristate "Maxlinear Ethernet PHYs" select POLYNOMIAL if HWMON diff --git a/drivers/net/phy/marvell-88x2222.c b/drivers/net/phy/marvell-88x2222.c index 46807891a253c..f8fd5c71f4915 100644 --- a/drivers/net/phy/marvell-88x2222.c +++ b/drivers/net/phy/marvell-88x2222.c @@ -9,6 +9,7 @@ */ #include #include +#include #include #include #include @@ -121,6 +122,76 @@ /* 1000Base-X Auto-Negotiation Advertisement Register */ #define MV_1GBX_ADVERTISE (0x2000 + MII_ADVERTISE) +/* Two Wire Interface Caching Control/Status Register */ +#define MV_TWSI_CACHE_CTRL 0x8000 +#define MV_TWSI_CACHE_A0_CTRL GENMASK(1, 0) +#define MV_TWSI_CACHE_NO_AUTO 0 +#define MV_TWSI_CACHE_AT_PLUGIN 1 +#define MV_TWSI_CACHE_AT_PLUGIN_POLL 2 +#define MV_TWSI_CACHE_MANUAL 3 +#define MV_TWSI_CACHE_A0_CMD_STAT GENMASK(3, 2) +#define MV_TWSI_CACHE_NO_UPDATE 0 +#define MV_TWSI_CACHE_UPDATED_ONCE 1 +#define MV_TWSI_CACHE_IS_LOADING 2 +#define MV_TWSI_CACHE_FAILED 3 +#define MV_TWSI_CACHE_A0_VALID BIT(9) +#define MV_TWSI_RESET BIT(10) +#define MV_TWSI_CACHE_A2_CTRL GENMASK(12, 11) +#define MV_TWSI_CACHE_A2_CMD_STAT GENMASK(14, 13) +#define MV_TWSI_CACHE_A2_VALID BIT(15) + +/* Two Wire Interface Memory Address Register */ +#define MV_TWSI_MEM_ADDR 0x8001 +#define MV_TWSI_BYTE_ADDR GENMASK(7, 0) +#define MV_TWSI_BYTE_READ BIT(8) +#define MV_TWSI_SLV_ADDR GENMASK(15, 9) + +/* Two Wire Interface Memory Read Data and Status Register */ +#define MV_TWSI_MEM_READ_STAT 0x8002 +#define MV_TWSI_MEM_READ_DATA GENMASK(7, 0) +#define MV_TWSI_STAT GENMASK(10, 8) +#define MV_TWSI_READY 0 +#define MV_TWSI_CMD_DONE 1 +#define MV_TWSI_CMD_IN_PROG 2 +#define MV_TWSI_CMD_WDONE_RFAIL 3 +#define MV_TWSI_CMD_FAIL 5 +#define MV_TWSI_BUSY 7 +#define MV_TWSI_CACHE_ECC_UNCOR_ERR BIT(11) +#define MV_TWSI_CACHE_ECC_COR_ERR BIT(12) + +/* Two Wire Interface Memory Write Data and Control Register */ +#define MV_TWSI_MEM_WRITE_CTRL 0x8003 +#define MV_TWSI_MEM_WRITE_DATA GENMASK(7, 0) +#define MV_TWSI_MEM_AUTO_RBACK BIT(9) +#define MV_TWSI_MEM_WRITE_TIME GENMASK(15, 12) + +/* Two Wire Interface Caching Delay Register */ +#define MV_TWSI_CACHE_DELAY 0x8004 +#define MV_TWSI_CACHE_A2_ADDR_MSB BIT(0) +#define MV_TWSI_CACHE_A2_SLV_ADDR GENMASK(7, 1) +#define MV_TWSI_CACHE_RELOAD_FREQ GENMASK(10, 9) +#define MV_TWSI_CACHE_RELOAD_250MS 0 +#define MV_TWSI_CACHE_RELOAD_500MS 1 +#define MV_TWSI_CACHE_RELOAD_1S 2 +#define MV_TWSI_CACHE_RELOAD_2S 3 +#define MV_TWSI_CACHE_ECC_UNCOR_INT_EN BIT(11) +#define MV_TWSI_CACHE_ECC_COR_INT_EN BIT(12) +#define MV_TWSI_CACHE_AUTO_DELAY GENMASK(15, 13) +#define MV_TWSI_CACHE_NO_DELAY 0 +#define MV_TWSI_CACHE_DELAY_250MS 1 +#define MV_TWSI_CACHE_DELAY_500MS 2 +#define MV_TWSI_CACHE_DELAY_1S 3 +#define MV_TWSI_CACHE_DELAY_2S 4 +#define MV_TWSI_CACHE_DELAY_4S 5 +#define MV_TWSI_CACHE_DELAY_8S 6 +#define MV_TWSI_CACHE_AUTO_DIS 7 + +/* Two Wire Interface EEPROM Cache Page A0 Registers */ +#define MV_TWSI_CACHE_EEPROM_A0 0x8007 + +/* Two Wire Interface EEPROM Cache Page A2 Registers */ +#define MV_TWSI_CACHE_EEPROM_A2 0x8087 + /* 10GBase-R Interrupt Enable Register */ #define MV_10GBR_INT_EN 0x8000 #define MV_10GBR_INT_BLOCK_LOCK BIT(0) @@ -216,6 +287,9 @@ #define MV_GPIO_VAL_MASK 0xDFF #define MV_GPIO_INT_UPD_FLAG BIT(31) +/* I2C controller settings */ +#define MV_I2C_POLL_NUM 3 + #define AUTONEG_TIMEOUT 3 struct mv2222_data { @@ -238,6 +312,13 @@ struct mv2222_gpio { }; #endif +#ifdef CONFIG_MARVELL_88X2222_I2C +struct mv2222_i2c { + struct i2c_adapter ia; + struct phy_device *phydev; +}; +#endif + /* SFI PMA transmit enable */ static int mv2222_tx_enable(struct phy_device *phydev) { @@ -1583,6 +1664,212 @@ static int mv2222_gpio_probe(struct phy_device *phydev) } #endif /* !CONFIG_MARVELL_88X2222_GPIO */ +#ifdef CONFIG_MARVELL_88X2222_I2C +static int mv2222_i2c_init(struct mv2222_i2c *i2c) +{ + struct i2c_timings t; + u16 val; + int ret; + + /* The I2C bus is available if the SDA/SCL pins function isn't GPIO */ + ret = phy_read_mmd(i2c->phydev, MDIO_MMD_VEND2, MV_GPIO_INT_TYPE3); + if (ret < 0) + return ret; + + if (ret & MV_GPIO_FUNC_SDA_PIN10 || ret & MV_GPIO_FUNC_SCL_PIN11) + return -EBUSY; + + /* Make sure the only supported bus speed is specified */ + i2c_parse_fw_timings(&i2c->phydev->mdio.dev, &t, true); + if (t.bus_freq_hz != I2C_MAX_STANDARD_MODE_FREQ) + return -EINVAL; + + /* Disable the EEPROM caching. It will interfere the I2C-adapter work */ + val = FIELD_PREP(MV_TWSI_CACHE_A0_CTRL, MV_TWSI_CACHE_NO_AUTO) | + FIELD_PREP(MV_TWSI_CACHE_A2_CTRL, MV_TWSI_CACHE_NO_AUTO); + + ret = phy_write_mmd(i2c->phydev, MDIO_MMD_PMAPMD, MV_TWSI_CACHE_CTRL, val); + if (ret < 0) + return ret; + + /* Disable the Read-back-after-write functionality */ + ret = phy_write_mmd(i2c->phydev, MDIO_MMD_PMAPMD, MV_TWSI_MEM_WRITE_CTRL, 0); + if (ret < 0) + return ret; + + /* Once again disable the auto-caching */ + val = FIELD_PREP(MV_TWSI_CACHE_AUTO_DELAY, MV_TWSI_CACHE_AUTO_DIS); + + ret = phy_write_mmd(i2c->phydev, MDIO_MMD_PMAPMD, MV_TWSI_CACHE_DELAY, val); + if (ret < 0) + return ret; + + return 0; +} + +static inline unsigned long mv2222_i2c_get_udelay(char read_write) +{ + const unsigned long p = USEC_PER_SEC / I2C_MAX_STANDARD_MODE_FREQ; + unsigned long ud; + + /* S, addr, Ack, cmd, Ack, data, Ack/Nack and P bits together */ + ud = 29 * p; + + /* Additional Sr, addr and Ack bits in case of the read op */ + if (read_write == I2C_SMBUS_READ) + ud += 10 * p; + + return ud; +} + +static inline bool mv2222_i2c_ready(int val) +{ + if (FIELD_GET(MV_TWSI_STAT, val) == MV_TWSI_READY) + return true; + + return false; +} + +static inline bool mv2222_i2c_cmd_done(int val) +{ + if (FIELD_GET(MV_TWSI_STAT, val) != MV_TWSI_CMD_IN_PROG) + return true; + + return false; +} + +static int mv2222_i2c_xfer(struct i2c_adapter *ia, + u16 addr, unsigned short flags, char read_write, + u8 cmd, int size, union i2c_smbus_data *data) +{ + struct mv2222_i2c *i2c = ia->algo_data; + unsigned long ud; + int val, ret; + + if (flags & I2C_CLIENT_TEN || size != I2C_SMBUS_BYTE_DATA) + return -EOPNOTSUPP; + + /* Make sure the interface is ready to execute the command */ + ud = mv2222_i2c_get_udelay(I2C_SMBUS_READ); + + ret = phy_read_mmd_poll_timeout(i2c->phydev, MDIO_MMD_PMAPMD, + MV_TWSI_MEM_READ_STAT, val, + mv2222_i2c_ready(val), + ud, MV_I2C_POLL_NUM * ud, false); + if (ret < 0) + return ret; + + /* Collect the command parameters */ + if (read_write == I2C_SMBUS_WRITE) { + ret = phy_write_mmd(i2c->phydev, MDIO_MMD_PMAPMD, + MV_TWSI_MEM_WRITE_CTRL, data->byte); + if (ret < 0) + return ret; + + val = 0; + } else { + val = MV_TWSI_BYTE_READ; + } + + val |= FIELD_PREP(MV_TWSI_BYTE_ADDR, cmd) | + FIELD_PREP(MV_TWSI_SLV_ADDR, addr); + + ret = phy_write_mmd(i2c->phydev, MDIO_MMD_PMAPMD, MV_TWSI_MEM_ADDR, val); + if (ret < 0) + return ret; + + /* Issue the command and wait until the CMD in-progress status is set */ + ud = mv2222_i2c_get_udelay(read_write); + + ret = phy_read_mmd_poll_timeout(i2c->phydev, MDIO_MMD_PMAPMD, + MV_TWSI_MEM_READ_STAT, val, + mv2222_i2c_cmd_done(val), + ud, MV_I2C_POLL_NUM * ud, true); + if (ret < 0) + return ret; + + /* The interface may respond with the READY status on the early stage + * of the SFP-module attachement. In that case ask to try again. + */ + switch (FIELD_GET(MV_TWSI_STAT, val)) { + case MV_TWSI_CMD_FAIL: + return -EIO; + case MV_TWSI_BUSY: + return -ENXIO; + case MV_TWSI_READY: + return -EAGAIN; + } + + if (read_write == I2C_SMBUS_READ) + data->byte = FIELD_GET(MV_TWSI_MEM_READ_DATA, val); + + return 0; +} + +static u32 mv2222_i2c_func(struct i2c_adapter *ia) +{ + return I2C_FUNC_SMBUS_BYTE_DATA; +} + +static struct i2c_algorithm mv2222_i2c_algo = { + .smbus_xfer = mv2222_i2c_xfer, + .functionality = mv2222_i2c_func, +}; + +static int mv2222_i2c_probe(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + struct mv2222_i2c *i2c; + struct i2c_adapter *ia; + int ret; + + /* + * Marvell 88x2222 I2C CSRs are tolerant to the soft-resets. Alas that + * can be said about the hard-resets. It will halt any communications + * with possibly no error reported. So it isn't safe to use the + * interface if a sudden hard-reset might be performed. + */ + if (phydev->mdio.reset_gpio || phydev->mdio.reset_ctrl) { + phydev_warn(phydev, "Hard-reset detected, I2C unsupported\n"); + return 0; + } + + i2c = devm_kzalloc(dev, sizeof(*i2c), GFP_KERNEL); + if (!i2c) + return -ENOMEM; + + i2c->phydev = phydev; + + /* Make sure the I2C-interface is ready before to proceed */ + ret = mv2222_i2c_init(i2c); + if (ret == -EBUSY) { + phydev_warn(phydev, "I2C interface unavailable\n"); + return 0; + } else if (ret) { + phydev_err(phydev, "Failed to init I2C-interface\n"); + return ret; + } + + ia = &i2c->ia; + strscpy(ia->name, "mv88x2222", sizeof(ia->name)); + ia->dev.parent = dev; + ia->owner = THIS_MODULE; + ia->algo = &mv2222_i2c_algo; + ia->algo_data = i2c; + + ret = devm_i2c_add_adapter(dev, ia); + if (ret) + phydev_err(phydev, "Failed to register I2C adapter\n"); + + return ret; +} +#else +static int mv2222_i2c_probe(struct phy_device *phydev) +{ + return 0; +} +#endif /* !CONFIG_MARVELL_88X2222_I2C */ + static int mv2222_configure_serdes(struct phy_port *port, bool enable, phy_interface_t interface) { @@ -1691,6 +1978,10 @@ static int mv2222_probe(struct phy_device *phydev) if (ret) return ret; + ret = mv2222_i2c_probe(phydev); + if (ret) + return ret; + return 0; } -- cgit v1.2.3 From 451f0f6a6e2e121cce69f24b702942ea4f4c245f Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sat, 30 Aug 2025 05:55:43 +0300 Subject: net: stmmac: Add ARP offload feature support DW QoS Ether and DW XGMAC/XLGMAC can be optionally equipped with a IPv4 ARP-offload engine. When the feature is enabled, the controller generates the ARP reply packets for broadcast ARP request packets. The ARP packet for IPv4 is L2 layer packet with Length/Type of 0x0806. It's not that comprehensive supporting a single IPv4 configured and a single ARP-request handling at a time. So if an ARP-request with an unknown IP-address received it will be passed to the application as just an ordinary IPv4 packet with no ARP-type recognized. If a recognized ARP-request received while another ARP-request being handled it will be passed to the application with the RDES2.RPNG flag set. In the other case if the ARP-request has been recognized and handled the respective frame will be delivered to the application anyway but the RDES4.LT=ARP-req-type. Taking all the above into account let's introduce the IPv4 ARP-offload engine support to the STMMAC driver. It will support only a first primary IPv4 address configured with falling back to another primary IPv4 address if the previous one has been removed. Note the implementation provides the offloaded ARP-requests counter which can be used to checkout whether the engine actually works. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/common.h | 14 +- drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c | 5 + drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.h | 1 + drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h | 1 + .../net/ethernet/stmicro/stmmac/dwxgmac2_descs.c | 5 + drivers/net/ethernet/stmicro/stmmac/stmmac.h | 4 + .../net/ethernet/stmicro/stmmac/stmmac_ethtool.c | 1 + drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 169 ++++++++++++++++++++- 8 files changed, 193 insertions(+), 7 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/common.h b/drivers/net/ethernet/stmicro/stmmac/common.h index 489c064881333..42c0130fd1f35 100644 --- a/drivers/net/ethernet/stmicro/stmmac/common.h +++ b/drivers/net/ethernet/stmicro/stmmac/common.h @@ -189,6 +189,7 @@ struct stmmac_extra_stats { unsigned long ptp_frame_type; unsigned long ptp_ver; unsigned long timestamp_dropped; + unsigned long arp_pkt_ofld; unsigned long av_pkt_rcvd; unsigned long av_tagged_pkt_rcvd; unsigned long vlan_tag_priority_val; @@ -361,12 +362,13 @@ enum rx_frame_status { discard_frame = 0x1, csum_none = 0x2, llc_snap = 0x4, - dma_own = 0x8, - rx_not_ls = 0x10, - len_err = 0x20, /* Under/Oversized frames */ - csum_err = 0x40, /* Control sums check failure */ - proto_err = 0x80, /* L1 protocol failures */ - cutoff_err = 0x100, /* Frame' cut off due to HW failures */ + arp_done = 0x8, + dma_own = 0x10, + rx_not_ls = 0x20, + len_err = 0x40, /* Under/Oversized frames */ + csum_err = 0x80, /* Control sums check failure */ + proto_err = 0x100, /* L1 protocol failures */ + cutoff_err = 0x200, /* Frame' cut off due to HW failures */ rx_err = len_err | csum_err | proto_err | cutoff_err, }; diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c index 4287c381e5304..59c46d1b16a7d 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.c @@ -119,6 +119,11 @@ static int dwmac4_wrback_get_rx_status(struct stmmac_extra_stats *x, case RDES3_PACKET_LENGTH: ret |= llc_snap; break; + case RDES3_PACKET_ARP: + x->arp_pkt_ofld++; + if (likely(!(rdes2 & RDES2_RPNG))) + ret |= arp_done; + break; case RDES3_PACKET_VLAN: case RDES3_PACKET_DVLAN: x->rx_vlan++; diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.h b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.h index e3562fb1ea61c..4f9e2601d8572 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac4_descs.h @@ -108,6 +108,7 @@ #define RDES2_L4_FILTER_MATCH BIT(28) #define RDES2_L3_L4_FILT_NB_MATCH_MASK GENMASK(27, 26) #define RDES2_L3_L4_FILT_NB_MATCH_SHIFT 26 +#define RDES2_RPNG BIT(10) #define RDES2_HL GENMASK(9, 0) /* RDES3 (write back format) */ diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h index 549574454a7d9..24ed95954b651 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h @@ -501,6 +501,7 @@ #define XGMAC_RDES2_DAF BIT(17) #define XGMAC_RDES2_SAF BIT(16) #define XGMAC_RDES2_VF BIT(15) +#define XGMAC_RDES2_RPNG BIT(14) #define XGMAC_RDES2_HL GENMASK(9, 0) #define XGMAC_RDES3_OWN BIT(31) #define XGMAC_RDES3_CTXT BIT(30) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c index c61b143443004..d39462402dc97 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c @@ -45,6 +45,11 @@ static int dwxgmac2_get_rx_status(struct stmmac_extra_stats *x, case XGMAC_L2T_LENGTH_PACKET: ret |= llc_snap; break; + case XGMAC_L2T_ARP_REQUEST: + x->arp_pkt_ofld++; + if (likely(!(rdes2 & XGMAC_RDES2_RPNG))) + ret |= arp_done; + break; case XGMAC_L2T_AV_CONTROL: x->av_pkt_rcvd++; break; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac.h b/drivers/net/ethernet/stmicro/stmmac/stmmac.h index 9ebfd26d44b85..d1aabf3321691 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h @@ -420,6 +420,10 @@ struct stmmac_priv { /* Receive Side Scaling */ struct stmmac_rss rss; + /* ARP offload */ + __be32 arp_addr; + struct notifier_block arp_cb; + /* General Purpose IO */ struct stmmac_gpio gpio; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c index 058b83584e30e..6e30a7fbf49dd 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c @@ -122,6 +122,7 @@ static const struct stmmac_stats stmmac_gstrings_stats[] = { STMMAC_STAT(ptp_frame_type), STMMAC_STAT(ptp_ver), STMMAC_STAT(timestamp_dropped), + STMMAC_STAT(arp_pkt_ofld), STMMAC_STAT(av_pkt_rcvd), STMMAC_STAT(av_tagged_pkt_rcvd), STMMAC_STAT(vlan_tag_priority_val), diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 459d40b95ec83..0e77a4c93b1a9 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -3865,6 +3866,10 @@ static int stmmac_hw_setup(struct net_device *dev) /* Initialize Rx Checksum Offload Engine */ stmmac_rx_ipc(priv, priv->hw, dev->features & NETIF_F_RXCSUM); + /* Initialize ARP offload */ + stmmac_set_arp_offload(priv, priv->hw, priv->arp_addr != -1, + ntohl(priv->arp_addr)); + /* Enable the MAC Rx/Tx */ stmmac_mac_set(priv, priv->ioaddr, true); @@ -5200,7 +5205,7 @@ static int stmmac_get_rx_status(struct stmmac_priv *priv, struct dma_desc *p) int ret; ret = stmmac_rx_status(priv, &priv->xstats, p); - if (likely(!(ret & rx_err) || rxall)) + if (likely(!(ret & (rx_err | arp_done)) || rxall)) return ret; return ret | discard_frame; @@ -8506,6 +8511,160 @@ static const struct xdp_metadata_ops stmmac_xdp_metadata_ops = { .xmo_rx_timestamp = stmmac_xdp_rx_timestamp, }; +#ifdef CONFIG_INET +/** + * stmmac_update_arp_addr - add/remove IPv4 address to ARP-offload + * @priv: private structure + * @add: add/remove flag + * @ifa: interface address + * Description: initialize ARP-offload with new IPv4 address if it hasn't + * been initialized yet, replace being removed primary IPv4 address with + * a one from the inet-device list, disable ARP-offload engine if the last + * primary IPv4 address has been removed. + * Return: 0 on success and negative error on a failure + */ +static int stmmac_update_arp_addr(struct stmmac_priv *priv, bool add, + const struct in_ifaddr *ifa) +{ + const struct in_ifaddr *ifai; + struct in_device *in_dev; + __be32 new_addr = -1; + int ret; + + if (add) { + if (priv->arp_addr != -1) + return 0; + + /* New primary address with no ARP-offload initialized yet */ + priv->arp_addr = ifa->ifa_address; + goto set_addr; + } else if (priv->arp_addr == -1 || priv->arp_addr != ifa->ifa_address) { + return 0; + } + + in_dev = __in_dev_get_rtnl(priv->dev); + if (!in_dev) { + priv->arp_addr = -1; + goto set_addr; + } + + /* Replace being removed primary address or disable ARP-offload */ + in_dev_for_each_ifa_rtnl(ifai, in_dev) { + if (ifai->ifa_flags & IFA_F_SECONDARY) + continue; + + if (ifai->ifa_address == priv->arp_addr) { + goto keep_addr; + } else if (new_addr == -1) { + add = true; + new_addr = ifai->ifa_address; + } + } + + priv->arp_addr = new_addr; + +set_addr: + ret = stmmac_set_arp_offload(priv, priv->hw, add, ntohl(priv->arp_addr)); + if (ret) + return ret; + +keep_addr: + return 0; +} + +/** + * stmmac_set_arp_inetaddr_cb - setup ARP-offload on IP-address change event + * @nb: notifier block handler + * @event: event caused the notifier call + * @ptr: even-specific data (in_ifaddr) + * Description: make sure the event belongs to the STMMAC device, the subjected + * IPv4 address is a primary address and set/clear/replace one. + * Return: notify event status + */ +static int stmmac_set_arp_inetaddr_cb(struct notifier_block *nb, unsigned long event, + void *ptr) +{ + const struct in_ifaddr *ifa = ptr; + struct stmmac_priv *priv; + struct net_device *ndev; + int ret = 0; + + ndev = ifa->ifa_dev->dev; + + if (is_vlan_dev(ndev)) + ndev = vlan_dev_real_dev(ndev); + + if (ndev->netdev_ops != &stmmac_netdev_ops) + return NOTIFY_DONE; + + priv = netdev_priv(ndev); + if (!priv) + return NOTIFY_DONE; + + if (ifa->ifa_flags & IFA_F_SECONDARY) + return NOTIFY_DONE; + + switch (event) { + case NETDEV_UP: + ret = stmmac_update_arp_addr(priv, true, ifa); + break; + case NETDEV_DOWN: + ret = stmmac_update_arp_addr(priv, false, ifa); + break; + default: + return NOTIFY_DONE; + } + + return notifier_from_errno(ret); +} + +/** + * stmmac_arp_setup - initialize ARP infrastructure + * @priv: private structure + * Description: initialize and register inet-address change callback + * for the ARP offload feature. + * Return: 0 on success and negative error on a failure + */ +static int stmmac_arp_setup(struct stmmac_priv *priv) +{ + int ret; + + if (!priv->dma_cap.arpoffsel) + return 0; + + priv->arp_addr = -1; + priv->arp_cb.notifier_call = stmmac_set_arp_inetaddr_cb; + ret = register_inetaddr_notifier(&priv->arp_cb); + if (ret) + return ret; + + return 0; +} + +/** + * stmmac_arp_clean - cleanup ARP infrastructure + * @priv: private structure + * Description: cleanup inet-address change callback responsible for + * the ARP offload IP-address setup. + */ +static void stmmac_arp_clean(struct stmmac_priv *priv) +{ + if (!priv->dma_cap.arpoffsel) + return; + + unregister_inetaddr_notifier(&priv->arp_cb); +} +#else +static int stmmac_arp_setup(struct stmmac_priv *priv) +{ + priv->arp_addr = -1; + + return 0; +} + +static void stmmac_arp_clean(struct stmmac_priv *priv) {} +#endif + static int stmmac_dl_ts_coarse_set(struct devlink *dl, u32 id, struct devlink_param_gset_ctx *ctx, struct netlink_ext_ack *extack) @@ -8872,6 +9031,10 @@ static int __stmmac_dvr_probe(struct device *device, if (!pm_runtime_enabled(device)) pm_runtime_enable(device); + ret = stmmac_arp_setup(priv); + if (ret) + goto error_arp_setup; + ret = stmmac_gpio_setup(priv); if (ret) goto error_gpio_setup; @@ -8930,6 +9093,8 @@ error_pcs_setup: error_mdio_register: stmmac_gpio_clean(priv); error_gpio_setup: + stmmac_arp_clean(priv); +error_arp_setup: pm_runtime_put_noidle(device); pm_runtime_disable(device); stmmac_napi_del(ndev); @@ -8991,6 +9156,8 @@ void stmmac_dvr_remove(struct device *dev) unregister_netdev(ndev); + stmmac_arp_clean(priv); + #ifdef CONFIG_DEBUG_FS stmmac_exit_fs(ndev); #endif -- cgit v1.2.3 From 90b9a356d33393439f259f98443a85a19a09c3b5 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 27 Oct 2021 21:27:32 +0300 Subject: net: stmmac: Permit having the multicast-filter table of zero size At least for DW GMAC and DW x(L)GMAC it is possible to have the imperfect filtering disabled during the IP-core synthesize. In that case the functionality of the DA hash-based filter will be just unavailable. Let's cover that case by permitting the "snps,multicast-filter-bins" property to have a zero value. That will effectively disable the multicast filtering in the core-ops->set_filter() methods. Note this modification won't affect for the DW MAC100 HW code since by design it assumes that the hash-based filtering is always available with the table of 64-bins size. Without the HW-manual I can't say whether it's right or wrong so we have to leave it as is for now. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c index b02a557c67e5d..e912383156e7c 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c @@ -40,6 +40,7 @@ static int dwmac1000_validate_mcast_bins(struct device *dev, int mcast_bins) int x = mcast_bins; switch (x) { + case 0: case HASH_TABLE_SIZE: case 128: case 256: -- cgit v1.2.3 From 3b15e0cfa6741fbafe083707138efbcb55fca592 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sat, 7 Aug 2021 13:03:57 +0300 Subject: net: stmmac: Parse "max-frame-size" DT-prop for any IP-core Indeed there is nothing DW MAC v3.x-specific in that property, while MTU can be platform-specific for any DW MAC IP-core. Move the "max-frame-size" DT-property read operation to the root path of the stmmac_probe_config_dt() method. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c index e912383156e7c..27a1561b22ae6 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c @@ -508,6 +508,15 @@ stmmac_probe_config_dt(struct platform_device *pdev, u8 *mac) if (of_property_read_u32(np, "snps,data-width", &plat->data_width)) plat->data_width = 16; + /* Note that the max-frame-size parameter as defined in the + * ePAPR v1.1 spec is defined as max-frame-size, it's + * actually used as the IEEE definition of MAC Client + * data, or MTU. The ePAPR specification is confusing as + * the definition is max-frame-size, but usage examples + * are clearly MTUs + */ + of_property_read_u32(np, "max-frame-size", &plat->maxmtu); + plat->force_sf_dma_mode = of_property_read_bool(np, "snps,force_sf_dma_mode"); @@ -527,14 +536,6 @@ stmmac_probe_config_dt(struct platform_device *pdev, u8 *mac) of_device_is_compatible(np, "snps,dwmac-3.70a") || of_device_is_compatible(np, "snps,dwmac-3.72a") || of_device_is_compatible(np, "snps,dwmac")) { - /* Note that the max-frame-size parameter as defined in the - * ePAPR v1.1 spec is defined as max-frame-size, it's - * actually used as the IEEE definition of MAC Client - * data, or MTU. The ePAPR specification is confusing as - * the definition is max-frame-size, but usage examples - * are clearly MTUs - */ - of_property_read_u32(np, "max-frame-size", &plat->maxmtu); of_property_read_u32(np, "snps,multicast-filter-bins", &plat->multicast_filter_bins); of_property_read_u32(np, "snps,perfect-filter-entries", -- cgit v1.2.3 From c12160e62787a9914b9d18b3cd213f13899b763d Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Mon, 23 Nov 2020 23:37:59 +0300 Subject: net: stmmac: Use pclk to set MDC clock frequency In accordance with [1] the MDC clock frequency is supposed to be selected with respect to the CSR clock frequency. CSR clock can be either tied to the DW MAC system clock (GMAC main clock) or supplied via a dedicated clk_csr_i signal. Current MDC clock selection procedure handles the former case while having no support of the later one. That's wrong for the devices which have separate system and CSR clocks. Let's fix it by first trying to get the synchro-signal rate from the "pclk" clock, if it hasn't been specified then fall-back to the "stmmaceth" clock. [1] DesignWare Cores Ethernet MAC Universal Databook, Revision 3.73a, October 2013, p. 424. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c index a7c2496b39f28..6451435cc634b 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c @@ -491,7 +491,12 @@ static u32 stmmac_clk_csr_set(struct stmmac_priv *priv) unsigned long clk_rate; u32 value = ~0; - clk_rate = clk_get_rate(priv->plat->stmmac_clk); + /* If APB clock has been specified then it is supposed to be used + * to select the CSR mode. Otherwise the application clock is the + * source of the periodic signal for the CSR interface. + */ + clk_rate = clk_get_rate(priv->plat->pclk) ?: + clk_get_rate(priv->plat->stmmac_clk); /* Platform provided default clk_csr would be assumed valid * for all other cases except for the below mentioned ones. -- cgit v1.2.3 From 3853314791ecc44b97f254c6eadfdbb9e8dc08e8 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sat, 29 Jul 2023 23:51:58 +0300 Subject: net: stmmac: Add per-DMA-channels platform IRQs support The main part of the functionality was added in the framework of the commit b42446b9b37b ("stmmac: intel: add support for multi-vector msi and msi-x"). But for some reason it was done for the PCI-based netword device only meanwhile the per-DMA-channel IRQs can be available on the normal platforms too. Let's fix the stmmac_get_platform_resources() method so one would request the DMA Tx/Rx platform IRQs. The DW *MAC DT-bindings requires them to have the names "(rx|tx)-queue-[0-9]". The IRQs request and per-channel ISR setup will be performed later in the stmmac_main.c driver. Note even though the driver and DT-bindings call them per-MTL-queue IRQs in fact these are the DMA per-channel IRQs. The driver just maps DMA channel and MTL queues one-on-one so the IRQs turn out to be raised as if for the respective queues too. Alas nothing can be done at this stage to fix the naming since the DT-bindings has already being defined. Changing the names would make the DT-schema even more messy than it already is. Signed-off-by: Serge Semin --- .../net/ethernet/stmicro/stmmac/stmmac_platform.c | 24 ++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c index 27a1561b22ae6..bc193b4d517b4 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c @@ -730,6 +730,9 @@ EXPORT_SYMBOL_GPL(stmmac_pltfr_find_clk); int stmmac_get_platform_resources(struct platform_device *pdev, struct stmmac_resources *stmmac_res) { + char irq_name[IFNAMSIZ]; + int irq, i; + memset(stmmac_res, 0, sizeof(*stmmac_res)); /* Get IRQ information early to have an ability to ask for deferred @@ -763,6 +766,27 @@ int stmmac_get_platform_resources(struct platform_device *pdev, dev_info(&pdev->dev, "IRQ sfty not found\n"); } + /* Request optional MTL per-queue IRQs. Note in fact these are the + * DMA per-channel IRQs, the driver just maps them one-on-one. + */ + for (i = 0; i < MTL_MAX_RX_QUEUES; i++) { + snprintf(irq_name, IFNAMSIZ, "rx-queue-%d", i); + irq = platform_get_irq_byname_optional(pdev, irq_name); + if (irq < 0) + break; + + stmmac_res->rx_irq[i] = irq; + } + + for (i = 0; i < MTL_MAX_TX_QUEUES; i++) { + snprintf(irq_name, IFNAMSIZ, "tx-queue-%d", i); + irq = platform_get_irq_byname_optional(pdev, irq_name); + if (irq < 0) + break; + + stmmac_res->tx_irq[i] = irq; + } + stmmac_res->addr = devm_platform_ioremap_resource(pdev, 0); return PTR_ERR_OR_ZERO(stmmac_res->addr); -- cgit v1.2.3 From ff59a6c08753a287f80f7f9b56cf6092f409ce3b Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 13 Feb 2025 19:09:45 +0300 Subject: net: stmmac: Add platform-specific multi-channel TSO flag It's possible to have more than a single DMA-channel supporting the TCP-segmentation offloading. The driver currently doesn't support anything to detected and utilize that. In the meantime the feature may greatly improve the TCP/IP traffic transfer performance. Let's fix that by introducing a platform-specific flag STMMAC_FLAG_TSO_FULL which would indicate such feature availability and permit selecting any Tx-channel to send TCP/UDP/IP traffic with the TSO engine. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 4 +++- include/linux/stmmac.h | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 0e77a4c93b1a9..103c8b4be9d13 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -6831,9 +6831,11 @@ static int stmmac_setup_tc(struct net_device *ndev, enum tc_setup_type type, static u16 stmmac_select_queue(struct net_device *dev, struct sk_buff *skb, struct net_device *sb_dev) { + struct stmmac_priv *priv = netdev_priv(dev); int gso = skb_shinfo(skb)->gso_type; - if (gso & (SKB_GSO_TCPV4 | SKB_GSO_TCPV6 | SKB_GSO_UDP_L4)) { + if (gso & (SKB_GSO_TCPV4 | SKB_GSO_TCPV6 | SKB_GSO_UDP_L4) && + !(priv->plat->flags & STMMAC_FLAG_TSO_FULL)) { /* * There is no way to determine the number of TSO/USO * capable Queues. Let's use always the Queue 0 diff --git a/include/linux/stmmac.h b/include/linux/stmmac.h index 4e53b946d63e9..d1bb1209fe655 100644 --- a/include/linux/stmmac.h +++ b/include/linux/stmmac.h @@ -189,6 +189,7 @@ enum dwmac_core_type { #define STMMAC_FLAG_EN_TX_LPI_CLK_PHY_CAP BIT(12) #define STMMAC_FLAG_HWTSTAMP_CORRECT_LATENCY BIT(13) #define STMMAC_FLAG_KEEP_PREAMBLE_BEFORE_SFD BIT(14) +#define STMMAC_FLAG_TSO_FULL BIT(15) struct mac_device_info; -- cgit v1.2.3 From 4db915253f713a182b8cd01a332be313e352c37b Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 12 Aug 2026 19:43:53 +0300 Subject: net: stmmac: xgmac: Fix PHY-interface select naming MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In accordance with [1] the phy_intf_sel_i[1:0] signal is encoded as follows: 00: non-RGMII interface (XGMII/GMII/MII) 01: RGMII interface 10: RMII Interface 11: Reserved So convert convert PHY_INTF_GMII to being PHY_INTF_XGMII with multi-variant MII-based name and add PHY_INTF_RMII interface name to comply with the official DW XGMAC databook. [1] DesignWare® Cores XGMAC - 10G Ethernet MAC, Revision 3.20a, October 2022, p. 424. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/common.h | 5 +++-- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/common.h b/drivers/net/ethernet/stmicro/stmmac/common.h index 42c0130fd1f35..a549dd82eafa5 100644 --- a/drivers/net/ethernet/stmicro/stmmac/common.h +++ b/drivers/net/ethernet/stmicro/stmmac/common.h @@ -324,9 +324,10 @@ struct stmmac_safety_stats { #define PHY_INTF_SEL_SMII 6 #define PHY_INTF_SEL_REVMII 7 -/* XGMAC uses a different encoding - from the AgileX5 documentation */ -#define PHY_INTF_GMII 0 +/* XGMAC uses a different encoding */ +#define PHY_INTF_XGMII 0 #define PHY_INTF_RGMII 1 +#define PHY_INTF_RMII 2 /* MSI defines */ #define STMMAC_MSI_VEC_MAX 32 diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 103c8b4be9d13..a4cbdac59c4b1 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -147,8 +147,9 @@ static const char *stmmac_dwmac_actphyif[8] = { }; static const char *stmmac_dwxgmac_phyif[4] = { - [PHY_INTF_GMII] = "GMII", + [PHY_INTF_XGMII] = "(XG)MII", [PHY_INTF_RGMII] = "RGMII", + [PHY_INTF_RMII] = "RMII", }; static irqreturn_t stmmac_interrupt(int irq, void *dev_id); -- cgit v1.2.3 From 01e194d68f64c7ecb72e46a32f35c9b726d996ee Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 18 Aug 2023 00:57:45 +0800 Subject: net: stmmac: xgmac: Use interrupt mode INTM=1 for multi-MSI The INTM=0 interrupt mode causes both respective per-channel sbd_perch_[tx|rx]_intr_o and common sbd_intr_o signals being triggered on the RI/TI status bits change. The later event is not only redundant in this case but less efficient since both IRQs handlers will be called due to a single event thus wasting the CPU resources. Let's enable the INTM=1 mode which implies triggering the per-channel signals only on the normal RI/TI IRQs. Note a similar fix has been incorporated to the DW GMAC4 sub-module in the commit 6ccf12ae111e ("net: stmmac: use interrupt mode INTM=1 for multi-MSI"). Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h | 2 ++ drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h index 24ed95954b651..c40c59ed2626a 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2.h @@ -355,6 +355,8 @@ /* DMA Registers */ #define XGMAC_DMA_MODE 0x00003000 +#define XGMAC_INTM GENMASK(13, 12) +#define XGMAC_INTM_MODE1 0x1 #define XGMAC_SWR BIT(0) #define XGMAC_DMA_SYSBUS_MODE 0x00003004 #define XGMAC_WR_OSR_LMT GENMASK(29, 24) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c index a05f448bfd7a4..dabca4e4b8e0d 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_dma.c @@ -32,6 +32,12 @@ static void dwxgmac2_dma_init(void __iomem *ioaddr, value |= XGMAC_EAME; writel(value, ioaddr + XGMAC_DMA_SYSBUS_MODE); + + if (dma_cfg->multi_msi_en) { + value = readl(ioaddr + XGMAC_DMA_MODE); + FIELD_MODIFY(XGMAC_INTM, &value, XGMAC_INTM_MODE1); + writel(value, ioaddr + XGMAC_DMA_MODE); + } } static void dwxgmac2_dma_init_chan(struct stmmac_priv *priv, -- cgit v1.2.3 From 91706ab69f5ee4ca615bb0dc9bf577ac482cc477 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sat, 29 Jul 2023 03:04:04 +0300 Subject: net: stmmac: xgmac: Add display_ring() desc operation It might get to be handy to print out the DMA descriptors content during the initial DMA interface initialization. Thus let's add the display_ring() callback then in the similar way as it's done for the rest of the DW {XG}MAC controllers. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c index d39462402dc97..8a4b9a09310d4 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwxgmac2_descs.c @@ -505,6 +505,25 @@ static void dwxgmac2_set_tbs(struct dma_edesc *p, u32 sec, u32 nsec) p->des7 = 0; } +static void dwxgmac2_display_ring(void *head, unsigned int size, bool rx, + dma_addr_t dma_rx_phy, unsigned int desc_size) +{ + struct dma_desc *p = (struct dma_desc *)head; + dma_addr_t dma_addr; + int i; + + pr_info("%s descriptor ring:\n", rx ? "RX" : "TX"); + + for (i = 0; i < size; i++) { + dma_addr = dma_rx_phy + i * sizeof(*p); + pr_info("%03d [%pad]: 0x%x 0x%x 0x%x 0x%x\n", + i, &dma_addr, + le32_to_cpu(p->des0), le32_to_cpu(p->des1), + le32_to_cpu(p->des2), le32_to_cpu(p->des3)); + p++; + } +} + const struct stmmac_desc_ops dwxgmac210_desc_ops = { .tx_status = dwxgmac2_get_tx_status, .rx_status = dwxgmac2_get_rx_status, @@ -539,4 +558,5 @@ const struct stmmac_desc_ops dwxgmac210_desc_ops = { .set_vlan_tag = dwxgmac2_set_vlan_tag, .set_vlan = dwxgmac2_set_vlan, .set_tbs = dwxgmac2_set_tbs, + .display_ring = dwxgmac2_display_ring, }; -- cgit v1.2.3 From 44b04d99a1b04bd573039a7709ed5f54ab949ce5 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 11 Sep 2025 16:53:13 +0300 Subject: dt-bindings: net: Add Baikal-T1 GMAC bindings Baikal-T1 SoC is equipped with two DW GMAC v3.73a-based 1GBE Ethernet interfaces synthesized with: RGMII PHY interface, AXI-DMA and APB3 CSR, 16KB Tx/Rx FIFOs and PBL up to half of that, PTP, PMT, TCP/IP CoE, up to 4 outstanding AXI read/write requests, maximum AXI burst length of 16 beats, up to eight MAC address slots, one GPI and one GPO ports. Let's add the respective DT-bindings so the device tree sources could be correctly verified. The STMMAC/Baikal GMAC platform driver will be added next. Signed-off-by: Serge Semin Reviewed-by: Rob Herring --- .../devicetree/bindings/net/baikal,bt1-gmac.yaml | 169 +++++++++++++++++++++ .../devicetree/bindings/net/snps,dwmac.yaml | 1 + 2 files changed, 170 insertions(+) create mode 100644 Documentation/devicetree/bindings/net/baikal,bt1-gmac.yaml diff --git a/Documentation/devicetree/bindings/net/baikal,bt1-gmac.yaml b/Documentation/devicetree/bindings/net/baikal,bt1-gmac.yaml new file mode 100644 index 0000000000000..7e29400964f08 --- /dev/null +++ b/Documentation/devicetree/bindings/net/baikal,bt1-gmac.yaml @@ -0,0 +1,169 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright (C) 2020 BAIKAL ELECTRONICS, JSC +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/net/baikal,bt1-gmac.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Baikal-T1 DW GMAC Network Interface + +maintainers: + - Serge Semin + +description: + Baikal-T1 is equipped with two DW GMAC v3.73a network interfaces. Each of + them doesn't have any on-SoC PHY attached, but instead exports RGMII + interface to connect any compatible physical layer transceiver. + +select: + properties: + compatible: + contains: + const: baikal,bt1-gmac + + required: + - compatible + +allOf: + - $ref: "snps,dwmac.yaml#" + +properties: + compatible: + items: + - const: baikal,bt1-gmac + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + interrupt-names: + const: macirq + + clocks: + minItems: 4 + maxItems: 4 + + clock-names: + minItems: 4 + maxItems: 4 + contains: + enum: + - stmmaceth + - pclk + - tx + - ptp_ref + + resets: + maxItems: 1 + + reset-names: + const: stmmaceth + + ngpios: + description: + Baikal-T1 GMAC have been created with one GPI and one GPO ports + enabled. So there are total two GPIOs available. + const: 2 + + phy-mode: + description: + Bailal-T1 GMACs always add 2ns delay of TXC with respect to TXD. + So either PCB or PHY shall add the RXC delay. + enum: [rgmii-id, rxgmii-txid] + + tx-internal-delay-ps: + description: + DW MAC Tx clocks generator has been designed to always add 2ns delay + of TXC with respect to TXD. + const: 2000 + + rx-fifo-depth: + const: 16384 + + tx-fifo-depth: + const: 16384 + + snps,perfect-filter-entries: + const: 8 + + snps,multicast-filter-bins: + const: 0 + + snps,data-width: + const: 16 + + stmmac-axi-config: + type: object + + properties: + snps,wr_osr_lmt: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Maximum write outstanding requests is limited with 4 + maximum: 3 + + snps,rd_osr_lmt: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Maximum read outstanding requests is limited with 4 + maximum: 3 + + snps,blen: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: AXI-bus burst length width is limited with just 4 bits + items: + enum: [16, 8, 4, 0] + +required: + - compatible + - reg + - interrupts + - interrupt-names + - clocks + - clock-names + - resets + - reset-names + - phy-mode + +unevaluatedProperties: false + +examples: + - | + ethernet@1f05e000 { + compatible = "baikal,bt1-gmac"; + reg = <0x1f05e000 0x2000>; + #address-cells = <1>; + #size-cells = <2>; + + interrupts = <72>; + interrupt-names = "macirq"; + + clocks = <&ccu_sys 1>, <&ccu_axi 3>, <&ccu_sys 2>, <&ccu_sys 3>; + clock-names = "pclk", "stmmaceth", "tx", "ptp_ref"; + + resets = <&ccu_axi 3>; + reset-names = "stmmaceth"; + + ngpios = <2>; + + gpio-controller; + #gpio-cells = <2>; + + phy-mode = "rgmii-id"; + tx-internal-delay-ps = <2000>; + + rx-fifo-depth = <16384>; + tx-fifo-depth = <16384>; + + snps,pbl = <32>; + snps,data-width = <16>; + + snps,axi-config = <&gmac_axi_config>; + + gmac_axi_config: stmmac-axi-config { + snps,wr_osr_lmt = <0x3>; + snps,rd_osr_lmt = <0x3>; + snps,blen = <0 0 0 0 16 8 4>; + }; + }; +... diff --git a/Documentation/devicetree/bindings/net/snps,dwmac.yaml b/Documentation/devicetree/bindings/net/snps,dwmac.yaml index 80e0a83de9ca3..c37f9ce2011b7 100644 --- a/Documentation/devicetree/bindings/net/snps,dwmac.yaml +++ b/Documentation/devicetree/bindings/net/snps,dwmac.yaml @@ -62,6 +62,7 @@ properties: - amlogic,meson8m2-dwmac - amlogic,meson-gxbb-dwmac - amlogic,meson-axg-dwmac + - baikal,bt1-gmac - ingenic,jz4775-mac - ingenic,x1000-mac - ingenic,x1600-mac -- cgit v1.2.3 From a20299db9d8b967f2bca4fc2bcfa0f173d331ac4 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Thu, 11 Sep 2025 17:31:48 +0300 Subject: dt-bindings: net: Add Baikal-T1 XGMAC bindings Baikal-T1 SoC is equipped with one DW XGMAC v2.11a-based 10GBE interface synthesized with: XGMII-bus connected to DW XPCS v3.11b, AXI3-DMA and APB3 CSR, 32KB Tx/Rx MTL FIFOs with up two 4 MTL Rx Queues and 2 MTL Tx Queue using two DMA-interfaces for Tx and Rx, PTP, PMT, TCP/IP CoE, TSO, up to 8 outstanding AXI read/write requests, maximum AXI burst length of 16 beats (due to AXI3 constraints), up to eight MAC address slots and 64-bits hash -based MAC-address filter. Let's add the respective DT-bindings so the device tree sources could be correctly verified. The STMMAC/Baikal XGMAC platform driver will be added next. Signed-off-by: Serge Semin --- .../devicetree/bindings/net/baikal,bt1-xgmac.yaml | 212 +++++++++++++++++++++ .../devicetree/bindings/net/snps,dwmac.yaml | 2 + 2 files changed, 214 insertions(+) create mode 100644 Documentation/devicetree/bindings/net/baikal,bt1-xgmac.yaml diff --git a/Documentation/devicetree/bindings/net/baikal,bt1-xgmac.yaml b/Documentation/devicetree/bindings/net/baikal,bt1-xgmac.yaml new file mode 100644 index 0000000000000..948a2aaaeb6e1 --- /dev/null +++ b/Documentation/devicetree/bindings/net/baikal,bt1-xgmac.yaml @@ -0,0 +1,212 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright (C) 2020 BAIKAL ELECTRONICS, JSC +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/net/baikal,bt1-xgmac.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Baikal-T1 DW XGMAC Network Interface + +maintainers: + - Serge Semin + +description: + Baikal-T1 is equipped with one DW XGMAC v2.11a network interfaces. It has + an DW XPCS v3.11b attached supporting the 10GBase-KR/KX4 external interfaces. + +select: + properties: + compatible: + contains: + const: baikal,bt1-xgmac + + required: + - compatible + +allOf: + - $ref: "snps,dwmac.yaml#" + +properties: + compatible: + items: + - const: baikal,bt1-xgmac + + reg: + maxItems: 1 + + interrupts: + minItems: 5 + maxItems: 5 + + interrupt-names: + minItems: 5 + maxItems: 5 + items: + enum: + - macirq + - tx-queue-0 + - tx-queue-1 + - rx-queue-0 + - rx-queue-1 + + clocks: + minItems: 4 + maxItems: 4 + + clock-names: + minItems: 4 + maxItems: 4 + contains: + enum: + - stmmaceth + - pclk + - tx + - ptp_ref + + resets: + maxItems: 1 + + reset-names: + const: stmmaceth + + phy-mode: + description: + Bailal-T1 XGMAC supports 10Gbase-KR/KX4 interfaces via the always + attached XPCS block. The XGMAC and XPCS are connected with the XGMII + interface. So in case of the external interface auto-negotiation + implied by the platform use XGMII phy-mode. + enum: [xgmii, xaui, 10gbase-r, 10gbase-kr] + + rx-fifo-depth: + const: 32768 + + tx-fifo-depth: + const: 32768 + + snps,perfect-filter-entries: + const: 8 + + snps,multicast-filter-bins: + const: 64 + + snps,tso: true + + snps,data-width: + const: 16 + + stmmac-axi-config: + type: object + + properties: + snps,wr_osr_lmt: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Maximum write outstanding requests is limited with 8 + maximum: 7 + + snps,rd_osr_lmt: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Maximum read outstanding requests is limited with 8 + maximum: 7 + + snps,blen: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: AXI-bus burst length width is limited with just 4 bits + items: + enum: [16, 8, 4, 0] + + rx-queues-config: + type: object + + properties: + snps,rx-queues-to-use: + $ref: /schemas/types.yaml#/definitions/uint32 + description: At most four MTL Rx Queue supported with two DMA-channels + minimum: 1 + maximum: 4 + + tx-queues-config: + type: object + + properties: + snps,tx-queues-to-use: + $ref: /schemas/types.yaml#/definitions/uint32 + description: At most two MTL Tx Queue supported + minimum: 1 + maximum: 2 + +required: + - compatible + - reg + - interrupts + - interrupt-names + - clocks + - clock-names + - resets + - reset-names + - phy-mode + +unevaluatedProperties: false + +examples: + - | + ethernet@1f054000 { + compatible = "baikal,bt1-xgmac"; + reg = <0x1f054000 0x4000>; + + interrupts = <74>, <75>, <76>, <77>, <78>; + interrupt-names = "macirq", "tx-queue-0", "tx-queue-1", + "rx-queue-0", "rx-queue-1"; + + clocks = <&ccu_sys 4>, <&ccu_axi 4>, <&ccu_sys 5>, <&ccu_sys 6>; + clock-names = "pclk", "stmmaceth", "tx", "ptp_ref"; + + resets = <&ccu_axi 4>; + reset-names = "stmmaceth"; + + phy-mode = "xgmii"; + + rx-fifo-depth = <32768>; + tx-fifo-depth = <32768>; + + snps,pbl = <32>; + snps,data-width = <16>; + + snps,axi-config = <&gmac0_axi_config>; + snps,mtl-rx-config = <&xgmac_rxq_config>; + snps,mtl-tx-config = <&xgmac_txq_config>; + + xgmac_axi_config: stmmac-axi-config { + snps,wr_osr_lmt = <0x7>; + snps,rd_osr_lmt = <0x7>; + snps,blen = <0 0 0 0 16 8 4>; + }; + + xgmac_rxq_config: rx-queues-config { + snps,rx-queues-to-use = <2>; + snps,rx-sched-sp; + + queue0 { + snps,map-to-dma-channel = <0>; + }; + + queue1 { + snps,map-to-dma-channel = <1>; + }; + }; + + xgmac_txq_config: tx-queues-config { + snps,tx-queues-to-use = <2>; + snps,tx-sched-sp; + + queue0 { + snps,dcb-algorithm; + snps,priority = <0x1>; + }; + + queue1 { + snps,dcb-algorithm; + snps,priority = <0x2>; + }; + }; + }; +... diff --git a/Documentation/devicetree/bindings/net/snps,dwmac.yaml b/Documentation/devicetree/bindings/net/snps,dwmac.yaml index c37f9ce2011b7..b8186b9aa971f 100644 --- a/Documentation/devicetree/bindings/net/snps,dwmac.yaml +++ b/Documentation/devicetree/bindings/net/snps,dwmac.yaml @@ -63,6 +63,7 @@ properties: - amlogic,meson-gxbb-dwmac - amlogic,meson-axg-dwmac - baikal,bt1-gmac + - baikal,bt1-xgmac - ingenic,jz4775-mac - ingenic,x1000-mac - ingenic,x1600-mac @@ -663,6 +664,7 @@ allOf: - allwinner,sun8i-r40-gmac - allwinner,sun8i-v3s-emac - allwinner,sun50i-a64-emac + - baikal,bt1-xgmac - loongson,ls2k-dwmac - loongson,ls7a-dwmac - ingenic,jz4775-mac -- cgit v1.2.3 From 8a50c618aa50996e4f7f588d9372f379435af1d9 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Sun, 14 Aug 2022 00:45:54 +0300 Subject: net: stmmac: Add Baikal-T1 GMAC/XGMAC support Baikal-T1 is equipped with two DW GMAC v3.73a controllers synthesized with RGMII interface, MDIO bus, 16KB MTL Rx/Tx FIFO, 8 MAC address perfect filter slots, HW-based SA/VLAN/CRC insertion, 1xGPO and 1xGPI, EEE logic, IEEE 1588 Timestamping with Advanced capabilities, PMT capability with MagicPacket and Remote Wake-up support, Full Tx/Rx CSUM, IPv4 ARP offload, no hash-table and no L3/L4 filter logic. In addition to that there is DW XGMAC v2.11a and DW XPCS 3.11b synthesized with 10GBase-KR and 10GBase-KX4 physical interface, no MDIO bus, 32KB MTL Rx/Tx FIFO, 8 MAC address perfect filter slots, 64 slot in the Hash-based DA filter, VLAN Hash-based filter, 2x L3/L4 filters, 2x TCs, 2x Tx MTL Queues, 4x Rx MTL Queues, DCB feature, TSO and SPH for TCP/IP packets, Full Tx/Rx CSUM Offload, IPv4 ARP offload, 64-entries RSS table, IEEE 1588 Timestamping with Advanced capabilities, no PMT. Most of that is more-or-less supported by the STMMAC driver. The only thing left to do is to add the Baikal-T1 platform-specific glue driver for it. The LLDD is mainly responsible for the standard STMMAC driver pre-initialization and the platform device probing. There are some peculiar Baikal-T1 GMAC-specific actions performed aside of that though. First of all we need to enable/disable the RGMII Tx clock and tune its rate up based on the RGMII interface mode. Secondly due to the having the most of the Baikal-T1 platforms using the embedded GPO for the attached PHYs reset we have to make sure the DW GMAC core isn't reset after the device is probed otherwise any PHY initializations performed afterwards will be lost. In order to do that the custom DW GMAC software reset callback is introduced. Finally for the same reason we've added a custom MDIO bus reset method so the GPO would be set to a mainly valid value before the platform Ethernet PHYs are actually probed. Note the custom MDIO/COre reset methods is a temporary solution until we get to clean the STMMAC driver up and introduce a fully generic GPIO interface for the driver core. Signed-off-by: Serge Semin --- drivers/net/ethernet/stmicro/stmmac/Kconfig | 9 + drivers/net/ethernet/stmicro/stmmac/Makefile | 1 + drivers/net/ethernet/stmicro/stmmac/dwmac-bt1.c | 227 ++++++++++++++++++++++++ 3 files changed, 237 insertions(+) create mode 100644 drivers/net/ethernet/stmicro/stmmac/dwmac-bt1.c diff --git a/drivers/net/ethernet/stmicro/stmmac/Kconfig b/drivers/net/ethernet/stmicro/stmmac/Kconfig index 71c1a21ba9690..9d8c9347893f8 100644 --- a/drivers/net/ethernet/stmicro/stmmac/Kconfig +++ b/drivers/net/ethernet/stmicro/stmmac/Kconfig @@ -70,6 +70,15 @@ config DWMAC_ANARION This selects the Anarion SoC glue layer support for the stmmac driver. +config DWMAC_BT1 + tristate "Baikal-T1 GMAC/xGMAC support" + depends on OF && (MIPS_BAIKAL_T1 || COMPILE_TEST) + help + Support for Baikal-T1 (x)GMAC Ethernet controller. + + This selects the Baikal-T1 platform specific glue layer of the + STMMAC driver. + config DWMAC_EIC7700 tristate "Support for Eswin eic7700 ethernet driver" depends on OF && HAS_DMA && ARCH_ESWIN || COMPILE_TEST diff --git a/drivers/net/ethernet/stmicro/stmmac/Makefile b/drivers/net/ethernet/stmicro/stmmac/Makefile index cd3c62b4a9646..a63522ee0b385 100644 --- a/drivers/net/ethernet/stmicro/stmmac/Makefile +++ b/drivers/net/ethernet/stmicro/stmmac/Makefile @@ -14,6 +14,7 @@ stmmac-$(CONFIG_STMMAC_SELFTESTS) += stmmac_selftests.o # Ordering matters. Generic driver must be last. obj-$(CONFIG_STMMAC_PLATFORM) += stmmac-platform.o obj-$(CONFIG_DWMAC_ANARION) += dwmac-anarion.o +obj-$(CONFIG_DWMAC_BT1) += dwmac-bt1.o obj-$(CONFIG_DWMAC_EIC7700) += dwmac-eic7700.o obj-$(CONFIG_DWMAC_INGENIC) += dwmac-ingenic.o obj-$(CONFIG_DWMAC_IPQ806X) += dwmac-ipq806x.o diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-bt1.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-bt1.c new file mode 100644 index 0000000000000..d14b233d13c2c --- /dev/null +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-bt1.c @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Baikal-T1 GMAC driver + * + * Copyright (C) 2022 BAIKAL ELECTRONICS, JSC + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "stmmac.h" +#include "stmmac_platform.h" + +struct bt1_xgmac { + struct device *dev; +}; + +typedef int (*bt1_xgmac_plat_init)(struct bt1_xgmac *, struct plat_stmmacenet_data *); + +static void bt1_gmac_get_interfaces(struct stmmac_priv *priv, void *bsp_priv, + unsigned long *interfaces) +{ + phy_interface_set_rgmii(interfaces); +} + +static int bt1_gmac_set_clk_tx_rate(void *bsp_priv, struct clk *clk_tx_i, + phy_interface_t interface, int speed) +{ + long rate; + int ret; + + if (!phy_interface_mode_is_rgmii(interface)) + return -EINVAL; + + rate = rgmii_clock(speed); + if (rate < 0) + return -EINVAL; + + /* Tx clock must be gated to successfully update the rate */ + clk_disable_unprepare(clk_tx_i); + + /* Clock is running with double rate (5 MHz, 50 MHz and 250 MHz) */ + ret = clk_set_rate(clk_tx_i, 2 * rate); + if (ret) + goto err_en_clk; + + return clk_prepare_enable(clk_tx_i); + +err_en_clk: + clk_prepare_enable(clk_tx_i); + + return ret; +} + +static int bt1_gmac_plat_data_init(struct bt1_xgmac *btxg, + struct plat_stmmacenet_data *plat) +{ + phy_interface_t interface; + int phyif; + + plat->core_type = DWMAC_CORE_GMAC; + plat->host_dma_width = 32; + plat->tx_fifo_size = SZ_16K; + plat->rx_fifo_size = SZ_16K; + plat->enh_desc = 1; + plat->tx_coe = 1; + plat->rx_coe = 1; + plat->pmt = 1; + plat->unicast_filter_entries = 8; + plat->multicast_filter_bins = 0; + plat->set_clk_tx_rate = bt1_gmac_set_clk_tx_rate; + plat->get_interfaces = bt1_gmac_get_interfaces; + plat->mdio_bus_data->needs_reset = true; + plat->ngpios = 2; + + phyif = stmmac_get_phy_intf_sel(plat->phy_interface); + if (phyif != PHY_INTF_SEL_RGMII) { + dev_err(btxg->dev, "Unsupported PHY interface specified\n"); + return -EINVAL; + } + + interface = phy_fix_phy_mode_for_mac_delays(plat->phy_interface, true, false); + if (interface == PHY_INTERFACE_MODE_NA) { + dev_err(btxg->dev, "Unsupported RGMII mode specified\n"); + return -EINVAL; + } + + plat->phy_interface = interface; + + return 0; +} + +static void bt1_xgmac_get_interfaces(struct stmmac_priv *priv, void *bsp_priv, + unsigned long *interfaces) +{ + __set_bit(PHY_INTERFACE_MODE_XGMII, interfaces); + __set_bit(PHY_INTERFACE_MODE_10GBASER, interfaces); + __set_bit(PHY_INTERFACE_MODE_10GBASEX, interfaces); + __set_bit(PHY_INTERFACE_MODE_XAUI, interfaces); +} + +static struct phylink_pcs *bt1_xgmac_select_pcs(struct stmmac_priv *priv, + phy_interface_t interface) +{ + return xpcs_to_phylink_pcs(priv->hw->xpcs); +} + +static int bt1_xgmac_plat_data_init(struct bt1_xgmac *btxg, + struct plat_stmmacenet_data *plat) +{ + /* Note Disabling SPH improves the network performance for about + * 300Mbit/s. Might be due to the DMA-syncing for two buffers. + * Also SPH consumes twice the normal memory especially if there + * is a single page-per-buffer allocated. + */ + plat->core_type = DWMAC_CORE_XGMAC; + plat->host_dma_width = 40; + plat->tx_fifo_size = SZ_32K; + plat->rx_fifo_size = SZ_32K; + plat->tx_coe = 1; + plat->rx_coe = 1; + plat->rss_en = 1; /* & cap.rssen */ + plat->unicast_filter_entries = 8; + plat->multicast_filter_bins = 64; + plat->select_pcs = bt1_xgmac_select_pcs; + plat->get_interfaces = bt1_xgmac_get_interfaces; + plat->flags |= STMMAC_FLAG_TSO_EN | /* & cap.tsoen */ + STMMAC_FLAG_TSO_FULL | /* all channels TSO-capable */ + STMMAC_FLAG_SPH_DISABLE | /* & cap.sph */ + STMMAC_FLAG_MULTI_MSI_EN; + + /* Attached XPCS supports 10GBase-KR/KX4 modes */ + switch (plat->phy_interface) { + case PHY_INTERFACE_MODE_XGMII: + case PHY_INTERFACE_MODE_10GBASER: + case PHY_INTERFACE_MODE_10GBASEX: + case PHY_INTERFACE_MODE_XAUI: + break; + default: + dev_err(btxg->dev, "Unsupported PHY interface specified\n"); + return -EINVAL; + } + + return 0; +} + +static int bt1_xgmac_data_init(struct platform_device *pdev, + struct plat_stmmacenet_data *plat) +{ + bt1_xgmac_plat_init plat_init; + struct bt1_xgmac *btxg; + int ret; + + btxg = devm_kzalloc(&pdev->dev, sizeof(*btxg), GFP_KERNEL); + if (!btxg) + return -ENOMEM; + + btxg->dev = &pdev->dev; + + plat->bsp_priv = btxg; + plat->clk_tx_i = devm_clk_get_enabled(&pdev->dev, "tx"); + if (IS_ERR(plat->clk_tx_i)) + return dev_err_probe(&pdev->dev, PTR_ERR(plat->clk_tx_i), + "Failed to get tx clock\n"); + + plat_init = device_get_match_data(&pdev->dev); + if (plat_init) { + ret = plat_init(btxg, plat); + if (ret) + return ret; + } + + return 0; +} + +static int bt1_xgmac_probe(struct platform_device *pdev) +{ + struct stmmac_resources stmmac_res; + struct plat_stmmacenet_data *plat; + int ret; + + ret = stmmac_get_platform_resources(pdev, &stmmac_res); + if (ret) + return ret; + + plat = devm_stmmac_probe_config_dt(pdev, stmmac_res.mac); + if (IS_ERR(plat)) + return dev_err_probe(&pdev->dev, PTR_ERR(plat), "DT config failed\n"); + + ret = bt1_xgmac_data_init(pdev, plat); + if (ret) + return ret; + + ret = devm_stmmac_pltfr_probe(pdev, plat, &stmmac_res); + if (ret) + return ret; + + return 0; +} + +static const struct of_device_id bt1_xgmac_match[] = { + { .compatible = "baikal,bt1-gmac", .data = (void *)bt1_gmac_plat_data_init }, + { .compatible = "baikal,bt1-xgmac", .data = (void *)bt1_xgmac_plat_data_init }, + { } +}; +MODULE_DEVICE_TABLE(of, bt1_xgmac_match); + +static struct platform_driver bt1_xgmac_driver = { + .probe = bt1_xgmac_probe, + .driver = { + .name = "bt1-xgmac", + .pm = &stmmac_pltfr_pm_ops, + .of_match_table = of_match_ptr(bt1_xgmac_match), + }, +}; +module_platform_driver(bt1_xgmac_driver); + +MODULE_AUTHOR("Serge Semin "); +MODULE_DESCRIPTION("Baikal-T1 GMAC/XGMAC glue driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3