diff --git a/target/linux/realtek/dts/rtl838x.dtsi b/target/linux/realtek/dts/rtl838x.dtsi index 13ba6450b2d6f0..bdb022a86eda23 100644 --- a/target/linux/realtek/dts/rtl838x.dtsi +++ b/target/linux/realtek/dts/rtl838x.dtsi @@ -44,6 +44,15 @@ phy-mode = #m ; \ }; +#define SWITCH_PORT_SDS(n, s, m, sds1, sds2) \ + port@##n { \ + reg = <##n>; \ + label = SWITCH_PORT_LABEL(s) ; \ + phy-handle = <&phy##n>; \ + phy-mode = #m ; \ + phys = <&serdes0 sds1 sds2>; \ + }; + #define SWITCH_SFP_PORT(n, s, m) \ port##n: port@##n { \ reg = <##n>; \ @@ -276,6 +285,15 @@ }; }; + serdes0: serdes@1b00e780 { + compatible = "realtek,rtl8380-serdes", "realtek,otto-serdes"; + reg = <0x1b00e780 0x1200>; + port-count = <6>; + page-count = <4>; + controlled-ports = <0x0000>; + #phy-cells = <2>; + }; + sram0: sram@9f000000 { compatible = "mmio-sram"; reg = <0x9f000000 0x10000>; diff --git a/target/linux/realtek/dts/rtl839x.dtsi b/target/linux/realtek/dts/rtl839x.dtsi index 3f87f5622e23df..949157e3688142 100644 --- a/target/linux/realtek/dts/rtl839x.dtsi +++ b/target/linux/realtek/dts/rtl839x.dtsi @@ -44,6 +44,15 @@ phy-mode = #m ; \ }; +#define SWITCH_PORT_SDS(n, s, m, sds1, sds2) \ + port@##n { \ + reg = <##n>; \ + label = SWITCH_PORT_LABEL(s) ; \ + phy-handle = <&phy##n>; \ + phy-mode = #m ; \ + phys = <&serdes0 sds1 sds2>; \ + }; + #define SWITCH_SFP_PORT(n, s, m) \ port@##n { \ reg = <##n>; \ @@ -292,9 +301,9 @@ }; }; - ethernet0: ethernet@1b00a300 { + ethernet0: ethernet@1b00780c { compatible = "realtek,rtl838x-eth"; - reg = <0x1b00a300 0x100>; + reg = <0x1b00780c 0x74>; interrupt-parent = <&intc>; interrupts = <24 3>; @@ -307,6 +316,15 @@ }; }; + serdes0: serdes@1b00a000 { + compatible = "realtek,rtl8390-serdes", "realtek,otto-serdes"; + reg = <0x1b00a000 0x1c00>; + port-count = <14>; + page-count = <12>; + controlled-ports = <0x0000>; + #phy-cells = <2>; + }; + sram0: sram@9f000000 { compatible = "mmio-sram"; reg = <0x9f000000 0x18000>; diff --git a/target/linux/realtek/dts/rtl930x.dtsi b/target/linux/realtek/dts/rtl930x.dtsi index 9b0a5781a2630b..7e7cb7bcffea7d 100644 --- a/target/linux/realtek/dts/rtl930x.dtsi +++ b/target/linux/realtek/dts/rtl930x.dtsi @@ -173,6 +173,16 @@ }; }; + serdes0: serdes@1b0003b0 { + compatible = "realtek,rtl9300-serdes", "realtek,otto-serdes"; + reg = <0x1b0003b0 0x8>; + port-count = <12>; + page-count = <64>; + controlled-ports = <0x0000>; + #phy-cells = <2>; + }; + + switch0: switch@1b000000 { compatible = "realtek,rtl83xx-switch"; status = "okay"; diff --git a/target/linux/realtek/dts/rtl931x.dtsi b/target/linux/realtek/dts/rtl931x.dtsi index 61599e89b54e92..2418922fc54d63 100644 --- a/target/linux/realtek/dts/rtl931x.dtsi +++ b/target/linux/realtek/dts/rtl931x.dtsi @@ -195,6 +195,15 @@ }; }; + serdes0: serdes@1b005638 { + compatible = "realtek,rtl9310-serdes", "realtek,otto-serdes"; + reg = <0x1b005638 0x8>; + port-count = <14>; + page-count = <192>; + controlled-ports = <0x0000>; + #phy-cells = <2>; + }; + switch0: switch@1b000000 { compatible = "realtek,rtl83xx-switch"; status = "okay"; diff --git a/target/linux/realtek/files-6.6/Documentation/devicetree/bindings/phy/realtek,otto-serdes.yaml b/target/linux/realtek/files-6.6/Documentation/devicetree/bindings/phy/realtek,otto-serdes.yaml new file mode 100644 index 00000000000000..c7d9233e5c1331 --- /dev/null +++ b/target/linux/realtek/files-6.6/Documentation/devicetree/bindings/phy/realtek,otto-serdes.yaml @@ -0,0 +1,184 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/phy/realtek,otto-serdes.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Realtek Otto SerDes controller + +maintainers: + - Markus Stockhausen + +description: | + The MIPS based Realtek Switch SoCs of the Realtek RTL838x, RTL839x, + RTL930x and RTL931x series have multiple SerDes built in. They are + linked to different single/quad/octa PHYs like the RTL8218B, RTL8218D + or RTL8214FC and are the integral part of the up-to-52-port switch + architecture. + Although the The SerDes controllers have common basics they behave + differently on the SoC families and rely on heavy register magic. + To keep the driver clean it can load patch sequences from devictree + and executes them during the controller actions like phy_init(). + +properties: + $nodename: + pattern: "^serdes@[0-9a-f]+$" + + compatible: + items: + - enum: + - realtek,rtl8380-serdes + - realtek,rtl8390-serdes + - realtek,rtl9300-serdes + - realtek,rtl9310-serdes + + reg: + items: + - description: serdes registers + + port-count: + description: | + The number of SerDes ports of the SoC. The RTL838x series has 6 + ports, the RTL839x has 14 ports, the RTL930x has 12 ports and the + RTL931x has 14 ports. + + page-count: + description: | + The number of register pages the SerDes have. The RTL838x series + has 4 pages, the RTL839x has 12 pages, the RTL930x has 64 pages and + the RTL931x has 128 pages. Each page consists of 32 registers with + 16 bit each. + + controlled-ports: + description: | + A bit mask defining the ports that are actively controlled by the + driver. In case a bit is not set the driver will only process read + operations on the SerDes. If not set the driver will run all ports + in read only mode. + + "#phy-cells": + const: 2 + description: | + The first number defines the SerDes to use. The second number a + linked SerDes. E.g. if a octa 1G PHY is attached to two QSGMII + SerDes. + + cmd-setup: + description: | + A field of 16 bit values that contain a patch/command sequence to run + on the SerDes registers during driver setup. + + cmd-init: + description: | + A field of 16 bit values that contain a patch/command sequence to run + on the SerDes registers when a controller calls phy_init(). + + cmd-power-on: + description: | + A field of 16 bit values that contain a patch/command sequence to run + on the SerDes registers when a controller calls phy_power_on(). + + cmd-pre-set-mode: + description: | + A field of 16 bit values that contain a patch/command sequence to run + on the SerDes registers when a controller calls phy_set_mode() and before + the driver actually sets the mode. + + cmd-post-set-mode: + description: | + A field of 16 bit values that contain a patch/command sequence to run + on the SerDes registers when a controller calls phy_set_mode() and after + the driver has set the mode. + + cmd-pre-reset: + description: | + A field of 16 bit values that contain a patch/command sequence to run + on the SerDes registers when a controller calls phy_reset() and before + the driver actually resets the SerDes. + + cmd-post-reset: + description: | + A field of 16 bit values that contain a patch/command sequence to run + on the SerDes registers when a controller calls phy_reset() and after + the driver has reset the SerDes. + + cmd-pre-power-off: + description: | + A field of 16 bit values that contain a patch/command sequence to run + on the SerDes registers when a controller calls phy_power_off() and + befor the driver actually powers off the SerDes. + + cmd-post-power-off: + description: | + A field of 16 bit values that contain a patch/command sequence to run + on the SerDes registers when a controller calls phy_power_off() and + after the driver has powered off the SerDes. + +reguired: + - compatible + - reg + - port-count + - page-count + - "#phy-cells" + +additionalProperties: + false + +examples: + - | + serdes: serdes@1b00e780 { + compatible = "realtek,rtl8380-serdes", "realtek,otto-serdes"; + reg = <0x1b00e780 0x1200>; + port-count = <6>; + page-count = <4>; + controlled-ports = <0x003f>; + #phy-cells = <2>; + }; + - | + serdes: serdes@1b00a000 { + compatible = "realtek,rtl8390-serdes", "realtek,otto-serdes"; + reg = <0x1b00a000 0x1c00>; + port-count = <14>; + page-count = <12>; + controlled-ports = <0x3fff>; + #phy-cells = <2>; + }; + - | + serdes: serdes@1b0003b0 { + compatible = "realtek,rtl9300-serdes", "realtek,otto-serdes"; + reg = <0x1b0003b0 0x8>; + port-count = <12>; + page-count = <64>; + controlled-ports = <0x0fff>; + #phy-cells = <2>; + }; + - | + serdes: serdes@1b005638 { + compatible = "realtek,rtl9310-serdes", "realtek,otto-serdes"; + reg = <0x1b005638 0x8>; + port-count = <14>; + page-count = <192>; + controlled-ports = <0x3fff>; + #phy-cells = <2>; + }; + - | + #define _MASK_ 1 + #define _WAIT_ 2 + serdes: serdes@1b00a000 { + compatible = "realtek,rtl8390-serdes", "realtek,otto-serdes"; + reg = <0x1b00a000 0x1c00>; + port-count = <14>; + page-count = <12>; + controlled-ports = <0x3fff>; + #phy-cells = <2>; + cmd-setup = /bits/ 16 < + /* + * set clock edge bit 14 during driver setup for ports 10-11 + * on page 0, register 7. Wait 128 ms. Afterwards set whole + * register 0 on page 10 of ports 8, 9, 12, 13 to 0x5800. + */ + _MASK_ 0x0c00 0x00 0x07 0x4000 0x4000 + _WAIT_ 0x0c00 0x00 0x00 0x0080 0x0000 + _MASK_ 0x3300 0x0a 0x00 0x5800 0xffff + >; + }; \ No newline at end of file diff --git a/target/linux/realtek/files-6.6/drivers/phy/realtek/phy-rtl-otto-serdes.c b/target/linux/realtek/files-6.6/drivers/phy/realtek/phy-rtl-otto-serdes.c new file mode 100644 index 00000000000000..2d9817fbf417d6 --- /dev/null +++ b/target/linux/realtek/files-6.6/drivers/phy/realtek/phy-rtl-otto-serdes.c @@ -0,0 +1,1020 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Realtek RTL838x, RTL839x, RTL930x & RTL931x SerDes PHY driver + * Copyright (c) 2024 Markus Stockhausen + */ + +#include "phy-rtl-otto-serdes.h" + +/* + * The Otto platform has a lot of undocumented features and registers that configure + * the SerDes behaviour. Trying to include that here would clutter the driver. To + * provide maximum flexibility the driver can run register modification sequences + * during operation, E.g. when calling phy_reset() or phy_power_on(). These sequences + * need to bes stored in the device tree. More documentation over there. + */ + +static const char *rtsds_events[RTSDS_EVENT_MAX] = { + [RTSDS_EVENT_SETUP] = "cmd-setup", + [RTSDS_EVENT_INIT] = "cmd-init", + [RTSDS_EVENT_POWER_ON] = "cmd-power-on", + [RTSDS_EVENT_PRE_SET_MODE] = "cmd-pre-set-mode", + [RTSDS_EVENT_POST_SET_MODE] = "cmd-post-set-mode", + [RTSDS_EVENT_PRE_RESET] = "cmd-pre-reset", + [RTSDS_EVENT_POST_RESET] = "cmd-post-reset", + [RTSDS_EVENT_PRE_POWER_OFF] = "cmd-pre-power-off", + [RTSDS_EVENT_POST_POWER_OFF] = "cmd-post-power-off", +}; + +void rtsds_load_events(struct rtsds_ctrl *ctrl) +{ + int i, elems, sz = sizeof(struct rtsds_seq); + + for (i = 0; i < RTSDS_EVENT_MAX; i++) { + elems = of_property_count_u16_elems(ctrl->dev->of_node ,rtsds_events[i]); + if (elems <= 0) + continue; + + if ((elems * sizeof(u16)) % sz) { + dev_err(ctrl->dev, "ignore sequence %s (incomplete data)\n", rtsds_events[i]); + continue; + } + + /* alloc one more element to provide stop marker in case it is missing in dt */ + ctrl->sequence[i] = devm_kzalloc(ctrl->dev, elems * sizeof(u16) + sz, GFP_KERNEL); + if (!ctrl->sequence[i]) { + dev_err(ctrl->dev, "ignore sequence %s (allocation failed)\n", rtsds_events[i]); + continue; + } + + if (of_property_read_u16_array(ctrl->dev->of_node, rtsds_events[i], + (u16 *)ctrl->sequence[i], elems)) { + dev_err(ctrl->dev, "ignore sequence %s (DT load failed)\n", rtsds_events[i]); + kfree(ctrl->sequence[i]); + ctrl->sequence[i] = NULL; + continue; + } + } +} + +void rtsds_run_event(struct rtsds_ctrl *ctrl, int sds, int evt) +{ + struct rtsds_seq *seq = ctrl->sequence[evt]; + int delay = 0; + + if (!seq) + return; + + while (seq->action != RTSDS_SEQ_STOP) { + if ((seq->action == RTSDS_SEQ_WAIT) && (seq->ports & BIT(sds))) + delay = seq->val; + + if (delay) + usleep_range(delay << 10, (delay << 10) + 1000); + + if ((seq->action == RTSDS_SEQ_MASK) && (seq->ports & BIT(sds))) + ctrl->conf->mask(ctrl, sds, seq->page, + seq->reg, seq->val, seq->mask); + + seq++; + } +} + +static int rtsds_hwmode_to_phymode(struct rtsds_ctrl *ctrl, int hwmode) +{ + int phymode = PHY_INTERFACE_MODE_MAX; + + for (int m = PHY_INTERFACE_MODE_MAX - 1; m >= 0; m--) + if (ctrl->conf->mode_map[m] == hwmode) + phymode = m; + + return phymode; +} + +static void rtsds_83xx_soft_reset(struct rtsds_ctrl *ctrl, int sdslo, int sdshi, int usec) +{ + int sds; + + for (sds = sdslo; sds <= sdshi; sds++) + ctrl->conf->mask(ctrl, sds, 0x00, 0x03, 0x7146, 0xffff); + usleep_range(usec, usec + 1000); + for (sds = sdslo; sds <= sdshi; sds++) + ctrl->conf->mask(ctrl, sds, 0x00, 0x03, 0x7106, 0xffff); +} + +/* + * The RTL838x has 6 SerDes. The 16 bit registers start at 0xbb00e780 and are mapped + * directly into 32 bit memory addresses. High 16 bits are always empty. + */ + +static int rtsds_838x_read(struct rtsds_ctrl *ctrl, int sds, unsigned int page, unsigned int reg) +{ + unsigned int offs; + + if (page == 0 || page == 3) + offs = (sds << 9) + (page << 7) + (reg << 2); + else + offs = 0xb80 + (sds << 8) + (page << 7) + (reg << 2); + + return ioread32(ctrl->base + offs); +} + +static int rtsds_838x_mask(struct rtsds_ctrl *ctrl, int sds, unsigned int page, unsigned reg, unsigned val, unsigned mask) +{ + unsigned int offs; + + if (page == 0 || page == 3) + offs = (sds << 9) + (page << 7) + (reg << 2); + else + offs = 0xb80 + (sds << 8) + (page << 7) + (reg << 2); + + iomask32(mask, val, ctrl->base + offs); + return 0; +} + +static int rtsds_838x_reset(struct rtsds_ctrl *ctrl, int sds) +{ + /* RX reset */ + rtsds_838x_mask(ctrl, sds, 0x01, 0x09, 0x0200, 0x0200); + rtsds_838x_mask(ctrl, sds, 0x01, 0x09, 0x0000, 0x0200); + + /* CMU reset */ + rtsds_838x_mask(ctrl, sds, 0x01, 0x00, 0x4040, 0xffff); + rtsds_838x_mask(ctrl, sds, 0x01, 0x00, 0x4740, 0xffff); + rtsds_838x_mask(ctrl, sds, 0x01, 0x00, 0x47c0, 0xffff); + rtsds_838x_mask(ctrl, sds, 0x01, 0x00, 0x4000, 0xffff); + + rtsds_83xx_soft_reset(ctrl, sds, sds, 1000); + + /* RX/TX reset */ + rtsds_838x_mask(ctrl, sds, 0x00, 0x00, 0x0400, 0xffff); + rtsds_838x_mask(ctrl, sds, 0x00, 0x00, 0x0403, 0xffff); + + return 0; +} + +static void rtsds_838x_set_mode(struct rtsds_ctrl *ctrl, int sds, int mode) +{ + unsigned int shift = 25 - sds * 5; + + iomask32(0x1f << shift, (mode & 0x1f) << shift, RTSDS_838X_SDS_MODE_SEL); +} + +static int rtsds_838x_get_mode(struct rtsds_ctrl *ctrl, int sds) +{ + unsigned int shift = 25 - sds * 5; + + return (ioread32(RTSDS_838X_SDS_MODE_SEL) >> shift) & 0x1f; +} + +/* + * The RLT839x has 14 SerDes starting at 0xbb00a000. 0-7, 10, 11 are 5GBit, 8, 9, 12, 13 + * are 10GBit. Two adjacent SerDes are tightly coupled and share a 1024 bytes register area. + * Per 32 bit address two registers are stored. The first register is stored in the lower + * 2 bytes ("on the right" due to big endian) and the second register in the upper 2 bytes. + * We know the following register areas: + * + * - XSG0 (4 pages @ offset 0x000): for even SerDes + * - XSG1 (4 pages @ offset 0x100): for odd SerDes + * - TGRX (4 pages @ offset 0x200): for even 10G SerDes + * - ANA_RG (2 pages @ offset 0x300): for even 5G SerDes + * - ANA_RG (2 pages @ offset 0x380): for odd 5G SerDes + * - ANA_TG (2 pages @ offset 0x300): for even 10G SerDes + * - ANA_TG (2 pages @ offset 0x380): for odd 10G SerDes + * + * The most consistent mapping we can achieve that aligns to the RTL93xx devices is: + * + * even 5G SerDes odd 5G SerDes even 10G SerDes odd 10G SerDes + * Page 0: XSG0/0 XSG1/0 XSG0/0 XSG1/0 + * Page 1: XSG0/1 XSG1/1 XSG0/1 XSG1/1 + * Page 2: XSG0/2 XSG1/2 XSG0/2 XSG1/2 + * Page 3: XSG0/3 XSG1/3 XSG0/3 XSG1/3 + * Page 4: TGRX/0 + * Page 5: TGRX/1 + * Page 6: TGRX/2 + * Page 7: TGRX/3 + * Page 8: ANA_RG ANA_RG + * Page 9: ANA_RG_EXT ANA_RG_EXT + * Page 10: ANA_TG ANA_TG + * Page 11: ANA_TG_EXT ANA_TG_EXT + */ + +static int rtsds_839x_read(struct rtsds_ctrl *ctrl, int sds, unsigned int page, unsigned int reg) +{ + unsigned int offs = ((sds & 0xfe) << 9) + ((reg & 0xfe) << 1); + unsigned int shift = (reg << 4) & 0x10; + + if (page < 4) { + offs += ((sds & 1) << 8) + (page << 6); + } else if (page < 8) { + if (sds != 8 && sds != 12) + return 0; + offs += 0x100 + (page << 6); + } else if (page < 10) { + if (sds == 8 || sds == 9 || sds == 12 || sds == 13) + return 0; + offs += 0x100 + ((sds & 1) << 7) + (page << 6); + } else { + if (sds != 8 && sds != 9 && sds != 12 && sds != 13) + return 0; + offs += 0x100 + ((sds & 1) << 7) + ((page - 2) << 6); + } + + /* read twice for link status latch */ + if (page == 2 && reg == 1) + ioread32(ctrl->base + offs); + + return (ioread32(ctrl->base + offs) >> shift) & 0xffff; +} + +static int rtsds_839x_mask(struct rtsds_ctrl *ctrl, int sds, unsigned int page, + unsigned int reg, unsigned int val, unsigned int mask) +{ + unsigned int oldval, offs = ((sds & 0xfe) << 9) + ((reg & 0xfe) << 1); + + if (page < 4) { + offs += ((sds & 1) << 8) + (page << 6); + } else if (page < 8) { + if (sds != 8 && sds != 12) + return 0; + offs += 0x100 + (page << 6); + } else if (page < 10) { + if (sds == 8 || sds == 9 || sds == 12 || sds == 13) + return 0; + offs += 0x100 + ((sds & 1) << 7) + (page << 6); + } else { + if (sds != 8 && sds != 9 && sds != 12 && sds != 13) + return 0; + offs += 0x100 + ((sds & 1) << 7) + ((page - 2) << 6); + } + + /* read twice for link status latch */ + if (page == 2 && reg == 1) + ioread32(ctrl->base + offs); + + oldval = ioread32(ctrl->base + offs); + val = reg & 1 ? (oldval & ~(mask << 16)) | (val << 16) : (oldval & ~mask) | val; + iowrite32(val, ctrl->base + offs); + + return 0; +} + +static void rtsds_839x_set_mode(struct rtsds_ctrl *ctrl, int sds, int mode) +{ + int shift = (sds & 7) << 2, offs = (sds >> 1) & ~3; + + iomask32(0xf << shift, (mode & 0xf) << shift, + RTSDS_839X_MAC_SERDES_IF_CTRL + offs); +} + +static int rtsds_839x_get_mode(struct rtsds_ctrl *ctrl, int sds) +{ + int shift = (sds & 7) << 2, offs = (sds >> 1) & ~3; + + return (ioread32(RTSDS_839X_MAC_SERDES_IF_CTRL + offs) >> shift) & 0xf; +} + +static int rtsds_839x_reset(struct rtsds_ctrl *ctrl, int sds) +{ + int lo = sds & ~1, hi = sds | 1; + + if (sds < 0 || sds > ctrl->max_port) + return -EINVAL; + + /* + * A reset basically consists of two steps. First a clock (CMU) reset and a + * digital soft reset afterwards. Some of the CMU registers are shared on + * adjacent SerDes so as of now we can only perform a reset on a pair. + */ + + if (lo < 8 || lo == 10) { + rtsds_839x_mask(ctrl, hi, 0x09, 0x01, 0x0050, 0xffff); + rtsds_839x_mask(ctrl, hi, 0x09, 0x01, 0x00f0, 0xffff); + rtsds_839x_mask(ctrl, hi, 0x09, 0x01, 0x0000, 0xffff); + rtsds_839x_mask(ctrl, lo, 0x08, 0x14, 0x0000, 0x0001); + rtsds_839x_mask(ctrl, lo, 0x08, 0x14, 0x0200, 0x0200); + usleep_range(100000, 101000); + rtsds_839x_mask(ctrl, lo, 0x08, 0x14, 0x0000, 0x0200); + } else { + rtsds_839x_mask(ctrl, lo, 0x0a, 0x10, 0x0000, 0x0008); + rtsds_839x_mask(ctrl, lo, 0x0b, 0x00, 0x8000, 0x8000); + usleep_range(100000, 101000); + rtsds_839x_mask(ctrl, lo, 0x0b, 0x00, 0x0000, 0x8000); + } + + rtsds_83xx_soft_reset(ctrl, lo, hi, 100000); + + return 0; +} + +/* + * The RTL930x family has 12 SerdDes. They are accessed through two IO registers + * at 0xbb0003b0 which simulate commands to an internal MDIO bus. From the current + * observation there are 3 types of SerDes: + * + * - SerDes 0,1 are of unknown type + * - SerDes 2-9 are USXGMII capabable with 10M, 100M, 1G, 2.5G, 5G, or 10G data + * rates over a 10.3125 Gb/s link + * - SerDes 10-11 are of unknown type + */ + +static int rtsds_930x_read(struct rtsds_ctrl *ctrl, int sds, unsigned int page, unsigned int reg) +{ + int cnt = 100, cmd = (sds << 2) | (page << 7) | (reg << 13) | 1; + + iowrite32(cmd, ctrl->base); + + while (--cnt && (ioread32(ctrl->base) & 1)) + usleep_range(50, 60); + + return cnt ? ioread32(ctrl->base + 4) & 0xffff : -EIO; +} + +static int rtsds_930x_mask(struct rtsds_ctrl *ctrl, int sds, unsigned int page, + unsigned int reg, unsigned int val, unsigned int mask) +{ + int oldval, cnt = 100, cmd = (sds << 2) | (page << 7) | (reg << 13) | 3; + + if (mask != 0xffff) { + oldval = rtsds_930x_read(ctrl, sds, page, reg); + if (oldval < 0) + return -EIO; + oldval &= ~mask; + val |= oldval; + } + + iowrite32(val, ctrl->base + 4); + iowrite32(cmd, ctrl->base); + + while (--cnt && (ioread32(ctrl->base) & 1)) + usleep_range(50, 60); + + return cnt ? 0 : - EIO; +} + +static void rtsds_930x_set_mode(struct rtsds_ctrl *ctrl, int sds, int mode) +{ + int shift; + + if (sds < 4) { + shift = sds * 6; + iomask32(0x1f << shift, (mode & 0x1f) << shift, + RTSDS_930X_SDS_MODE_SEL_0); + } else if (sds < 8) { + shift = (sds & 0x3) * 6; + iomask32(0x1f << shift, (mode & 0x1f) << shift, + RTSDS_930X_SDS_MODE_SEL_1); + } else if (sds < 10) { + shift = (sds & 0x1) * 6; + iomask32(0x1f << shift, (mode & 0x1f) << shift, + RTSDS_930X_SDS_MODE_SEL_2); + } else { + shift = (sds & 0x1) * 6; + iomask32(0x1f << shift, (mode & 0x1f) << shift, + RTSDS_930X_SDS_MODE_SEL_3); + } +} + +static int rtsds_930x_get_mode(struct rtsds_ctrl *ctrl, int sds) +{ + int shift; + + if (sds < 4) { + shift = sds * 6; + return ioread32(RTSDS_930X_SDS_MODE_SEL_0) >> shift & 0x1f; + } else if (sds < 8) { + shift = (sds & 0x3) * 6; + return ioread32(RTSDS_930X_SDS_MODE_SEL_1) >> shift & 0x1f; + } else if (sds < 10) { + shift = (sds & 0x1) * 6; + return ioread32(RTSDS_930X_SDS_MODE_SEL_2) >> shift & 0x1f; + } else { + shift = (sds & 0x1) * 6; + return ioread32(RTSDS_930X_SDS_MODE_SEL_3) >> shift & 0x1f; + } +} + +static int rtsds_930x_reset(struct rtsds_ctrl *ctrl, int sds) +{ + int modecur = rtsds_930x_get_mode(ctrl, sds); + int modeoff = ctrl->conf->mode_map[PHY_INTERFACE_MODE_NA]; + + /* It is enough to power off SerDes and set to old mode again */ + if (modecur != modeoff) { + rtsds_930x_set_mode(ctrl, sds, modeoff); + rtsds_930x_set_mode(ctrl, sds, modecur); + } + + return 0; +} + +/* + * The RTL931x family has 14 "frontend" SerDes that are magically cascaded. Reset, + * interrupt and other operations work on this combined view while their registers + * are evenly distributed over a total of 32 background SerDes. A "simple" SerDes + * with numbers 0, 1, 2, 4, 6, 8, 10, 12 seem to be all-in-one analog/XGMII enabled + * and each has a single background SerDes with one page/register set. The "combined" + * 3, 5, 7, 9, 11 & 13 SerDes consist of a total of 3 background SerDes (one analog + * and two XGMII) each with an own page/register set. To get this aligned the driver + * triples the pages of a simple frontend SerDes and mixes the pages of a combined + * frontend SerDes as follows: + * + * frontend page simple frontend SerDes combined frontend SerDes + * page 0-63 (analog): SerDes page 0-63 analog SerDes page 0-63 + * page 64-127 (XGMII1): SerDes page 0-63 XGMII1 SerDes page 0-63 + * page 128-191 (XGMII2): SerDes page 0-63 XGMII2 SerDes page 0-63 + */ + +static int rtsds_931x_read(struct rtsds_ctrl *ctrl, int sds, unsigned int page, unsigned int reg) +{ + int map[] = {0, 1, 2, 3, 6, 7, 10, 11, 14, 15, 18, 19, 22, 23}; + int cmd, cnt = 100, backsds = map[sds]; + + /* combined SerDes */ + if ((sds & 1) && (sds != 1)) + backsds += (page >> 6); + + cmd = (backsds << 2) | ((page & 0x3f) << 7) | (reg << 13) | 1; + + iowrite32(cmd, ctrl->base); + while (--cnt && (ioread32(ctrl->base) & 1)) + usleep_range(50, 60); + + return cnt ? ioread32(ctrl->base + 4) & 0xffff : -EIO; +} + +static int rtsds_931x_mask(struct rtsds_ctrl *ctrl, int sds, unsigned int page, + unsigned int reg, unsigned int val, unsigned int mask) +{ + int map[] = {0, 1, 2, 3, 6, 7, 10, 11, 14, 15, 18, 19, 22, 23}; + int cmd, oldval, cnt = 100, backsds = map[sds]; + + /* combined SerDes */ + if ((sds & 1) && (sds != 1)) + backsds += (page >> 6); + + cmd = (backsds << 2) | ((page & 0x3f) << 7) | (reg << 13) | 3; + + if (mask != 0xffff) { + oldval = rtsds_931x_read(ctrl, sds, page, reg); + if (oldval < 0) + return -EIO; + oldval &= ~mask; + val |= oldval; + } + + iowrite32(val, ctrl->base + 4); + iowrite32(cmd, ctrl->base); + while (--cnt && (ioread32(ctrl->base) & 1)) + usleep_range(50, 60); + + return cnt ? 0 : - EIO; +} + +static void rtsds_931x_set_mode(struct rtsds_ctrl *ctrl, int sds, int mode) +{ + int shift = (sds & 3) << 3, offs = sds & ~3; + + mode |= 0x80; /* force SerDes setup */ + iomask32(0x1f << shift, (mode & 0x1f) << shift, + RTSDS_931X_SERDES_MODE_CTRL + offs); +} + +static int rtsds_931x_get_mode(struct rtsds_ctrl *ctrl, int sds) +{ + int shift = (sds & 3) << 3, offs = sds & ~3; + + return (ioread32(RTSDS_931X_SERDES_MODE_CTRL + offs) >> shift) & 0x1f; +} + +static int rtsds_931x_reset(struct rtsds_ctrl *ctrl, int sds) +{ + int pwr = ioread32(RTSDS_931X_PS_SERDES_OFF_MODE_CTRL); + int modecur = rtsds_931x_get_mode(ctrl, sds); + int modeoff = ctrl->conf->mode_map[PHY_INTERFACE_MODE_NA]; + + /* reset with mode off/on cycle while being powered off */ + if (modecur != modeoff) { + iowrite32(pwr | (1 << sds), RTSDS_931X_PS_SERDES_OFF_MODE_CTRL); + rtsds_931x_set_mode(ctrl, sds, modeoff); + rtsds_931x_set_mode(ctrl, sds, modecur); + iowrite32(pwr, RTSDS_931X_PS_SERDES_OFF_MODE_CTRL); + } + + return 0; +} + +int rtsds_read(struct phy *phy, unsigned int page, unsigned int reg) +{ + struct rtsds_macro *macro = phy_get_drvdata(phy); + struct rtsds_ctrl *ctrl = macro->ctrl; + int sds = macro->port; + + if (sds < 0 || sds > ctrl->max_port || page > ctrl->max_page || reg >= 32) + return -EINVAL; + + return ctrl->conf->read(ctrl, sds, page, reg); +} + +int rtsds_mask(struct phy *phy, unsigned int page, unsigned int reg, unsigned int val, unsigned int mask) +{ + struct rtsds_macro *macro = phy_get_drvdata(phy); + struct rtsds_ctrl *ctrl = macro->ctrl; + int sds = macro->port; + + if (!(ctrl->port_mask & BIT(sds))) + return -EACCES; + + if (sds < 0 || sds > ctrl->max_port || page > ctrl->max_page || + reg >= 32 || val > 0xffff || mask > 0xffff) + return -EINVAL; + + return ctrl->conf->mask(ctrl, sds, page, reg, val, mask); +} + +int rtsds_write(struct phy *phy, unsigned int page, unsigned int reg, unsigned int val) +{ + return rtsds_mask(phy, page, reg, val, 0xffff); +} + +int rtsds_phy_init(struct phy *phy) +{ + struct rtsds_macro *macro = phy_get_drvdata(phy); + struct rtsds_ctrl *ctrl = macro->ctrl; + int sds = macro->port; + + if (!(ctrl->port_mask & BIT(sds))) + return 0; + + mutex_lock(&ctrl->lock); + rtsds_run_event(ctrl, sds, RTSDS_EVENT_INIT); + mutex_unlock(&ctrl->lock); + return 0; +} + +int rtsds_phy_power_on(struct phy *phy) +{ + struct rtsds_macro *macro = phy_get_drvdata(phy); + struct rtsds_ctrl *ctrl = macro->ctrl; + int sds = macro->port; + + if (!(ctrl->port_mask & BIT(sds))) + return 0; + + mutex_lock(&ctrl->lock); + /* simply run sequence as we have no real power on command */ + rtsds_run_event(ctrl, sds, RTSDS_EVENT_POWER_ON); + mutex_unlock(&ctrl->lock); + + return 0; +} + +int rtsds_phy_power_off(struct phy *phy) +{ + struct rtsds_macro *macro = phy_get_drvdata(phy); + struct rtsds_ctrl *ctrl = macro->ctrl; + int sds = macro->port; + + if (!(ctrl->port_mask & BIT(sds))) + return 0; + + mutex_lock(&ctrl->lock); + rtsds_run_event(ctrl, sds, RTSDS_EVENT_PRE_POWER_OFF); + ctrl->conf->set_mode(ctrl, sds, ctrl->conf->mode_map[PHY_INTERFACE_MODE_NA]); + rtsds_run_event(ctrl, sds, RTSDS_EVENT_POST_POWER_OFF); + mutex_unlock(&ctrl->lock); + + return 0; +} + +int rtsds_phy_set_mode_int(struct rtsds_ctrl *ctrl, int sds, int phymode, int hwmode) +{ + mutex_lock(&ctrl->lock); + rtsds_run_event(ctrl, sds, RTSDS_EVENT_PRE_SET_MODE); + ctrl->port[sds].mode = phymode; + ctrl->conf->set_mode(ctrl, sds, hwmode); + rtsds_run_event(ctrl, sds, RTSDS_EVENT_POST_SET_MODE); + mutex_unlock(&ctrl->lock); + + return 0; +} + +int rtsds_phy_set_mode(struct phy *phy, enum phy_mode mode, int submode) +{ + struct rtsds_macro *macro = phy_get_drvdata(phy); + struct rtsds_ctrl *ctrl = macro->ctrl; + int sds = macro->port; + + if (!(ctrl->port_mask & BIT(sds))) + return 0; + + if (mode != PHY_MODE_ETHERNET) + return -EINVAL; + + return rtsds_phy_set_mode_int(ctrl, sds, submode, ctrl->conf->mode_map[submode]); +} + +int rtsds_phy_reset_int(struct rtsds_ctrl *ctrl, int sds) +{ + int ret; + + mutex_lock(&ctrl->lock); + rtsds_run_event(ctrl, sds, RTSDS_EVENT_PRE_RESET); + ret = ctrl->conf->reset(ctrl, sds); + rtsds_run_event(ctrl, sds, RTSDS_EVENT_POST_RESET); + mutex_unlock(&ctrl->lock); + + return ret; +} + +int rtsds_phy_reset(struct phy *phy) +{ + struct rtsds_macro *macro = phy_get_drvdata(phy); + struct rtsds_ctrl *ctrl = macro->ctrl; + int sds = macro->port; + + if (!(ctrl->port_mask & BIT(sds))) + return 0; + + return rtsds_phy_reset_int(ctrl, sds); +} + +static const struct phy_ops rtsds_phy_ops = { + .init = rtsds_phy_init, + .power_on = rtsds_phy_power_on, + .power_off = rtsds_phy_power_off, + .reset = rtsds_phy_reset, + .set_mode = rtsds_phy_set_mode, + .owner = THIS_MODULE, +}; + +/* + * The SerDes offer a lot of magic that sill needs to be uncovered. To help further + * development provide some basic debugging about registers, modes and polarity. The + * mode can be changed on the fly and executes the normal setter including events. + */ + +#ifdef CONFIG_DEBUG_FS +static const char *rtsds_page_name[RTSDS_PAGE_NAME_MAX] = { + [0] = "SDS", [1] = "SDS_EXT", + [2] = "FIB", [3] = "FIB_EXT", + [4] = "DTE", [5] = "DTE_EXT", + [6] = "TGX", [7] = "TGX_EXT", + [8] = "ANA_RG", [9] = "ANA_RG_EXT", + [10] = "ANA_TG", [11] = "ANA_TG_EXT", + [31] = "ANA_WDIG", + [32] = "ANA_MISC", [33] = "ANA_COM", + [34] = "ANA_SP", [35] = "ANA_SP_EXT", + [36] = "ANA_1G", [37] = "ANA_1G_EXT", + [38] = "ANA_2G", [39] = "ANA_2G_EXT", + [40] = "ANA_3G", [41] = "ANA_3G_EXT", + [42] = "ANA_5G", [43] = "ANA_5G_EXT", + [44] = "ANA_6G", [45] = "ANA_6G_EXT", + [46] = "ANA_10G", [47] = "ANA_10G_EXT", +}; + +static ssize_t rtsds_dbg_mode_show(struct seq_file *seqf, void *unused) +{ + struct rtsds_macro *macro = dev_get_drvdata(seqf->private); + struct rtsds_ctrl *ctrl = macro->ctrl; + int mode, sds = macro->port; + + mutex_lock(&ctrl->lock); + mode = ctrl->conf->get_mode(ctrl, sds); + mutex_unlock(&ctrl->lock); + + seq_printf(seqf, "hw mode: %d\n", mode); + seq_printf(seqf, "phy mode: "); + + if (ctrl->port[sds].mode == PHY_INTERFACE_MODE_NA) + seq_printf(seqf, "off\n"); + else + seq_printf(seqf, "%s\n", phy_modes(ctrl->port[sds].mode)); + + return 0; +} + +static ssize_t rtsds_dbg_mode_write(struct file *file, const char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct seq_file *seqf = file->private_data; + struct rtsds_macro *macro = dev_get_drvdata(seqf->private); + struct rtsds_ctrl *ctrl = macro->ctrl; + int ret, hwmode, phymode, sds = macro->port; + + ret = kstrtou32_from_user(userbuf, count, 10, &hwmode); + if (ret) + return ret; + + phymode = rtsds_hwmode_to_phymode(ctrl, hwmode); + rtsds_phy_set_mode_int(ctrl, sds, phymode, hwmode); + + return count; +} +DEFINE_SHOW_STORE_ATTRIBUTE(rtsds_dbg_mode); + +int rtsds_dbg_registers_show(struct seq_file *seqf, void *unused) +{ + struct rtsds_macro *macro = dev_get_drvdata(seqf->private); + struct rtsds_ctrl *ctrl = macro->ctrl; + int page, reg, sds = macro->port; + + seq_printf(seqf, "%*s", 12 , ""); + for (int i = 0;i < 32; i++) + seq_printf(seqf, "%*d", 5, i); + + for (page = 0; page <= ctrl->max_page; page++) { + if (page < RTSDS_PAGE_NAME_MAX && rtsds_page_name[page]) + seq_printf(seqf, "\n%*s: ", -11, rtsds_page_name[page]); + else if (page == 64 || page == 128) + seq_printf(seqf, "\nXGMII_%d : ", page >> 6); + else + seq_printf(seqf, "\nPAGE_%03d : ", page); + for (reg = 0; reg < 32; reg++) + seq_printf(seqf, "%04X ", ctrl->conf->read(ctrl, sds, page, reg)); + } + seq_printf(seqf, "\n"); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(rtsds_dbg_registers); + +int rtsds_dbg_polarity_show(struct seq_file *seqf, void *unused) +{ + struct rtsds_macro *macro = dev_get_drvdata(seqf->private); + struct rtsds_ctrl *ctrl = macro->ctrl; + int reg, sds = macro->port; + + reg = ctrl->conf->read(ctrl, sds, RTSDS_PAGE_SDS, 0); + + seq_printf(seqf, "tx polarity: "); + seq_printf(seqf, reg & 0x100 ? "inverse" : "normal"); + seq_printf(seqf, "\nrx polarity: "); + seq_printf(seqf, reg & 0x200 ? "inverse" : "normal"); + seq_printf(seqf, "\n"); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(rtsds_dbg_polarity); + +void rtsds_dbg_init(struct rtsds_ctrl *ctrl, int sds) +{ + debugfs_create_file("mode", 0600, ctrl->port[sds].phy->debugfs, + &ctrl->port[sds].phy->dev, &rtsds_dbg_mode_fops); + + debugfs_create_file("polarity", 0400, ctrl->port[sds].phy->debugfs, + &ctrl->port[sds].phy->dev, &rtsds_dbg_polarity_fops); + + debugfs_create_file("registers", 0400, ctrl->port[sds].phy->debugfs, + &ctrl->port[sds].phy->dev, &rtsds_dbg_registers_fops); +} +#endif /* CONFIG_DEBUG_FS */ + +void rtsds_setup(struct rtsds_ctrl *ctrl) +{ + int hwmode; + + for (int sds = 0; sds <= ctrl->max_port; sds++) { + if (ctrl->port_mask & BIT(sds)) { + /* power off controlled SerDes */ + ctrl->conf->set_mode(ctrl, sds, ctrl->conf->mode_map[ctrl->port[sds].mode]); + rtsds_run_event(ctrl, sds, RTSDS_EVENT_SETUP); + } else { + /* sync status of uncontrolled SerDes */ + hwmode = ctrl->conf->get_mode(ctrl, sds); + ctrl->port[sds].mode = rtsds_hwmode_to_phymode(ctrl, hwmode); + } + } +} + +static struct phy *rtsds_simple_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct rtsds_ctrl *ctrl = dev_get_drvdata(dev); + int sds, sds2; + + /* + * Some Realtek Ethernet transceivers (e.g. RLT8218B) will be attached via a + * bonded 2x QSGMII link to two SerDes. Others (e.g. RTL8218D) allow to make + * use of single XGMII or dual QSGMII links. When a switch port tries to lookup + * the SerDes it is attached to we honour that by an enhanced mapping. We allow + * two possible configuration options. Standalone or linked to another. E.g. + * + * Single: port@24 { phys = <&serdes 4 -1>; }; + * Dual: port@24 { phys = <&serdes 4 5>; }; + * + * As we can only hand over a single phy this function will return the primary + * phy. The secondary phy can be identified later on by the link attribute in + * the controller structure. + */ + + if (args->args_count != 2) + return ERR_PTR(-EINVAL); + + sds = args->args[0]; + if (sds < 0 || sds > ctrl->max_port) + return ERR_PTR(-EINVAL); + + sds2 = args->args[1]; + if (sds2 < -1 || sds2 > ctrl->max_port) + return ERR_PTR(-EINVAL); + + ctrl->port[sds].link = sds2; + if (sds2 >= 0) + ctrl->port[sds2].link = sds; + + return ctrl->port[sds].phy; +} + +static int rtsds_phy_create(struct rtsds_ctrl *ctrl, int sds) +{ + struct rtsds_macro *macro; + + ctrl->port[sds].phy = devm_phy_create(ctrl->dev, NULL, &rtsds_phy_ops); + if (IS_ERR(ctrl->port[sds].phy)) + return PTR_ERR(ctrl->port[sds].phy); + + macro = devm_kzalloc(ctrl->dev, sizeof(*macro), GFP_KERNEL); + if (!macro) + return -ENOMEM; + + macro->port = sds; + macro->ctrl = ctrl; + phy_set_drvdata(ctrl->port[sds].phy, macro); + + ctrl->port[sds].link = -1; + ctrl->port[sds].mode = PHY_INTERFACE_MODE_NA; + +#ifdef CONFIG_DEBUG_FS + rtsds_dbg_init(ctrl, sds); +#endif + return 0; +} + +static int rtsds_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct phy_provider *provider; + struct rtsds_ctrl *ctrl; + int ret; + + if (!np) + return -EINVAL; + + ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL); + if (!ctrl) + return -ENOMEM; + + ctrl->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ctrl->base)) { + dev_err(dev, "failed to map SerDes memory\n"); + return PTR_ERR(ctrl->base); + } + + ctrl->dev = dev; + ctrl->conf = (struct rtsds_conf *)of_device_get_match_data(dev); + + ret = of_property_read_u32(np, "port-count", &ctrl->max_port); + if (ret || ctrl->max_port <= 0 || ctrl->max_port > RTSDS_SERDES_MAX) { + dev_err(dev, "property port-count not found or wrong\n"); + return -EINVAL; + } + ctrl->max_port--; + + ret = of_property_read_u32(np, "page-count", &ctrl->max_page); + if (ret || ctrl->max_page <= 0 || ctrl->max_page > RTSDS_PAGE_MAX) { + dev_err(dev, "property page-count not found or wrong\n"); + return -EINVAL; + } + ctrl->max_page--; + + ret = of_property_read_u32(np, "controlled-ports", &ctrl->port_mask); + if (ret) { + ctrl->port_mask = 0; + dev_warn(dev, "property controlled-ports not found, switched to read-only mode\n"); + } + + for (int sds = 0; sds <= ctrl->max_port; sds++) { + ret = rtsds_phy_create(ctrl, sds); + if (ret) { + dev_err(dev, "failed to create port %d\n", sds); + return ret; + } + } + + mutex_init(&ctrl->lock); + dev_set_drvdata(dev, ctrl); + provider = devm_of_phy_provider_register(dev, rtsds_simple_xlate); + + rtsds_load_events(ctrl); + rtsds_setup(ctrl); + + dev_info(dev, "initialized (%d ports, %d pages, 32 registers, mask 0x%04x)", + ctrl->max_port + 1, ctrl->max_page + 1, ctrl->port_mask); + + return PTR_ERR_OR_ZERO(provider); +} + +static const struct rtsds_conf rtsds_838x_conf = { + .mode_map = { + [PHY_INTERFACE_MODE_NA] = 0, + [PHY_INTERFACE_MODE_1000BASEX] = 4, + [PHY_INTERFACE_MODE_100BASEX] = 5, + [PHY_INTERFACE_MODE_QSGMII] = 6, + }, + .mask = rtsds_838x_mask, + .read = rtsds_838x_read, + .reset = rtsds_838x_reset, + .set_mode = rtsds_838x_set_mode, + .get_mode = rtsds_838x_get_mode, +}; + +static const struct rtsds_conf rtsds_839x_conf = { + .mode_map = { + [PHY_INTERFACE_MODE_NA] = 0, + [PHY_INTERFACE_MODE_10GBASER] = 1, + [PHY_INTERFACE_MODE_1000BASEX] = 7, + [PHY_INTERFACE_MODE_100BASEX] = 8, + [PHY_INTERFACE_MODE_QSGMII] = 6, + }, + .mask = rtsds_839x_mask, + .read = rtsds_839x_read, + .reset = rtsds_839x_reset, + .set_mode = rtsds_839x_set_mode, + .get_mode = rtsds_839x_get_mode, +}; + +static const struct rtsds_conf rtsds_930x_conf = { + .mode_map = { + [PHY_INTERFACE_MODE_NA] = 31, + [PHY_INTERFACE_MODE_QSGMII] = 6, + [PHY_INTERFACE_MODE_10GBASER] = 26, + [PHY_INTERFACE_MODE_XGMII] = 16, + [PHY_INTERFACE_MODE_RXAUI] = 25, + [PHY_INTERFACE_MODE_USXGMII] = 13, + /* + * TODO: USXGMII mode requires a submode to be set (2.5G, 5G or 10G). + * As this is stored in the register SDS_SUBMODE_CTRL_X it cannot be + * compensated by an event command sequence. For more details look at + * _dal_longan_sds_subMode_set() in GPL source drops. + */ + }, + .mask = rtsds_930x_mask, + .read = rtsds_930x_read, + .reset = rtsds_930x_reset, + .set_mode = rtsds_930x_set_mode, + .get_mode = rtsds_930x_get_mode, +}; + +static const struct rtsds_conf rtsds_931x_conf = { + .mode_map = { + [PHY_INTERFACE_MODE_NA] = 31, + [PHY_INTERFACE_MODE_QSGMII] = 6, + [PHY_INTERFACE_MODE_XGMII] = 16, + [PHY_INTERFACE_MODE_USXGMII] = 13, + [PHY_INTERFACE_MODE_SGMII] = 2, + /* + * TODO: Fiber mode setting is not straight forward. The SerDes needs + * to be disabled and the analog backend SerDes has to be configured in + * page 31 and register 9. See _phy_rtl9310_sds_fiber_mode_set() in GPL + * source drops for more details. + */ + }, + .mask = rtsds_931x_mask, + .read = rtsds_931x_read, + .reset = rtsds_931x_reset, + .set_mode = rtsds_931x_set_mode, + .get_mode = rtsds_931x_get_mode, +}; + +static const struct of_device_id rtsds_compatible_ids[] = { + { .compatible = "realtek,rtl8380-serdes", + .data = &rtsds_838x_conf, + }, + { .compatible = "realtek,rtl8390-serdes", + .data = &rtsds_839x_conf, + }, + { .compatible = "realtek,rtl9300-serdes", + .data = &rtsds_930x_conf, + }, + { .compatible = "realtek,rtl9310-serdes", + .data = &rtsds_931x_conf, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, rtsds_compatible_ids); + +static struct platform_driver rtsds_platform_driver = { + .probe = rtsds_probe, + .driver = { + .name = "realtek,otto-serdes", + .of_match_table = of_match_ptr(rtsds_compatible_ids), + }, +}; + +module_platform_driver(rtsds_platform_driver); + +MODULE_AUTHOR("Markus Stockhausen "); +MODULE_DESCRIPTION("SerDes driver for Realtek RTL83xx, RTL93xx switches"); +MODULE_LICENSE("Dual MIT/GPL"); \ No newline at end of file diff --git a/target/linux/realtek/files-6.6/drivers/phy/realtek/phy-rtl-otto-serdes.h b/target/linux/realtek/files-6.6/drivers/phy/realtek/phy-rtl-otto-serdes.h new file mode 100644 index 00000000000000..b0cd1937395257 --- /dev/null +++ b/target/linux/realtek/files-6.6/drivers/phy/realtek/phy-rtl-otto-serdes.h @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Realtek RTL838x, RTL839x, RTL930x & RTL931x SerDes PHY driver + * Copyright (c) 2024 Markus Stockhausen + */ + +#ifndef _PHY_RTL_OTTO_SERDES_H +#define _PHY_RTL_OTTO_SERDES_H + +#include +#include +#include +#include +#include +#include +#include + +#define DEFINE_SHOW_STORE_ATTRIBUTE(__name) \ +static int __name ## _open(struct inode *inode, struct file *file) \ +{ \ + return single_open(file, __name ## _show, inode->i_private); \ +} \ + \ +static const struct file_operations __name ## _fops = { \ + .owner = THIS_MODULE, \ + .open = __name ## _open, \ + .read = seq_read, \ + .write = __name ## _write, \ + .llseek = seq_lseek, \ + .release = single_release, \ +} + +#define RTSDS_SERDES_MAX 14 + +#define RTSDS_PAGE_SDS 0 +#define RTSDS_PAGE_SDS_EXT 1 +#define RTSDS_PAGE_FIB 2 +#define RTSDS_PAGE_FIB_EXT 3 +#define RTSDS_PAGE_MAX 192 +#define RTSDS_PAGE_NAME_MAX 48 + +#define RTSDS_EVENT_SETUP 0 +#define RTSDS_EVENT_INIT 1 +#define RTSDS_EVENT_POWER_ON 2 +#define RTSDS_EVENT_PRE_SET_MODE 3 +#define RTSDS_EVENT_POST_SET_MODE 4 +#define RTSDS_EVENT_PRE_RESET 5 +#define RTSDS_EVENT_POST_RESET 6 +#define RTSDS_EVENT_PRE_POWER_OFF 7 +#define RTSDS_EVENT_POST_POWER_OFF 8 +#define RTSDS_EVENT_MAX 9 + +#define RTSDS_SEQ_STOP 0 +#define RTSDS_SEQ_MASK 1 +#define RTSDS_SEQ_WAIT 2 + +#define RTSDS_SWITCH_ADDR_BASE (0xbb000000) +#define RTSDS_REG(x) ((void __iomem __force *)RTSDS_SWITCH_ADDR_BASE + (x)) +#define iomask32(mask, value, addr) iowrite32((ioread32(addr) & ~(mask)) | (value), addr) + +#define RTSDS_838X_SDS_MODE_SEL RTSDS_REG(0x0028) + +#define RTSDS_839X_MAC_SERDES_IF_CTRL RTSDS_REG(0x0008) + +#define RTSDS_930X_SDS_MODE_SEL_0 RTSDS_REG(0x0194) +#define RTSDS_930X_SDS_MODE_SEL_1 RTSDS_REG(0x02a0) +#define RTSDS_930X_SDS_MODE_SEL_2 RTSDS_REG(0x02a4) +#define RTSDS_930X_SDS_MODE_SEL_3 RTSDS_REG(0x0198) + +#define RTSDS_931X_SERDES_MODE_CTRL RTSDS_REG(0x13cc) +#define RTSDS_931X_PS_SERDES_OFF_MODE_CTRL RTSDS_REG(0x13f4) + +struct __attribute__ ((__packed__)) rtsds_seq { + uint16_t action; + uint16_t ports; + uint16_t page; + uint16_t reg; + uint16_t val; + uint16_t mask; +}; + +struct rtsds_port { + struct phy *phy; + int mode; + int link; +}; + +struct rtsds_ctrl { + struct device *dev; + void __iomem *base; + struct mutex lock; + int max_port; + int max_page; + int port_mask; + struct rtsds_conf *conf; + struct rtsds_port port[RTSDS_SERDES_MAX]; + struct rtsds_seq *sequence[RTSDS_EVENT_MAX]; +}; + +struct rtsds_macro { + struct rtsds_ctrl *ctrl; + int port; +}; + +struct rtsds_conf { + int (*read)(struct rtsds_ctrl *ctrl, int sds, unsigned int page, unsigned int reg); + int (*mask)(struct rtsds_ctrl *ctrl, int sds, unsigned int page, unsigned int reg, unsigned int val, unsigned int mask); + int (*reset)(struct rtsds_ctrl *ctrl, int sds); + void (*set_mode)(struct rtsds_ctrl *ctrl, int sds, int mode); + int (*get_mode)(struct rtsds_ctrl *ctrl, int sds); + int mode_map[PHY_INTERFACE_MODE_MAX]; +}; + +/* + * This SerDes module should be written in such a clear way, that direct calls are + * not needed. The following functions are provided just in case ... + */ + +int rtsds_read(struct phy *phy, unsigned int page, unsigned int reg); +int rtsds_write(struct phy *phy, unsigned int page, unsigned int reg, unsigned int val); +int rtsds_mask(struct phy *phy, unsigned int page, unsigned int reg, unsigned int val, unsigned int mask); + +#endif /* _PHY_RTL_OTTO_SERDES_H */ \ No newline at end of file diff --git a/target/linux/realtek/patches-6.6/322-add-serdes-driver.patch b/target/linux/realtek/patches-6.6/322-add-serdes-driver.patch new file mode 100644 index 00000000000000..3092c65a8b06b9 --- /dev/null +++ b/target/linux/realtek/patches-6.6/322-add-serdes-driver.patch @@ -0,0 +1,57 @@ +From 0414347ab5646232bb7256a6bcaa058c311910b4 Mon Sep 17 00:00:00 2001 +From: Markus Stockhausen +Date: Mon, 23 Sep 2024 02:10:50 -0400 +Subject: [PATCH] realtek: Add SerDes driver for Realtek Otto platform + +The Realtek switch platforms contain several SerDes that need to be +initialized and managed. Provide a driver for that. + +Signed-off-by: Markus Stockhausen +--- + drivers/phy/Kconfig | 1 + + drivers/phy/Makefile | 1 + + drivers/phy/realtek/Kconfig | 11 +++++++++++ + drivers/phy/realtek/Makefile | 2 ++ + 4 files changed, 15 insertions(+) + create mode 100644 drivers/phy/realtek/Kconfig + create mode 100644 drivers/phy/realtek/Makefile + +--- a/drivers/phy/Kconfig ++++ b/drivers/phy/Kconfig +@@ -87,6 +87,7 @@ source "drivers/phy/motorola/Kconfig" + source "drivers/phy/mscc/Kconfig" + source "drivers/phy/qualcomm/Kconfig" + source "drivers/phy/ralink/Kconfig" ++source "drivers/phy/realtek/Kconfig" + source "drivers/phy/renesas/Kconfig" + source "drivers/phy/rockchip/Kconfig" + source "drivers/phy/samsung/Kconfig" +--- a/drivers/phy/Makefile ++++ b/drivers/phy/Makefile +@@ -27,6 +27,7 @@ obj-y += allwinner/ \ + qualcomm/ \ + ralink/ \ + renesas/ \ ++ realtek/ \ + rockchip/ \ + samsung/ \ + socionext/ \ +--- /dev/null ++++ b/drivers/phy/realtek/Kconfig +@@ -0,0 +1,11 @@ ++# SPDX-License-Identifier: GPL-2.0 ++# ++# Phy drivers for Realtek platforms ++# ++ ++config PHY_REALTEK_OTTO_SERDES ++ tristate "SerDes driver for the Realtek Otto platform" ++ depends on OF && RTL83XX ++ select GENERIC_PHY ++ help ++ Enable this for SerDes support in RTL83xx and RTL93xx switches. +--- /dev/null ++++ b/drivers/phy/realtek/Makefile +@@ -0,0 +1,2 @@ ++# SPDX-License-Identifier: GPL-2.0 ++obj-$(CONFIG_PHY_REALTEK_OTTO_SERDES) += phy-rtl-otto-serdes.o diff --git a/target/linux/realtek/rtl838x/config-6.6 b/target/linux/realtek/rtl838x/config-6.6 index ad2c1b43cce8d7..886200b8ec1f1b 100644 --- a/target/linux/realtek/rtl838x/config-6.6 +++ b/target/linux/realtek/rtl838x/config-6.6 @@ -175,6 +175,7 @@ CONFIG_PERF_USE_VMALLOC=y CONFIG_PGTABLE_LEVELS=2 CONFIG_PHYLIB=y CONFIG_PHYLINK=y +CONFIG_PHY_REALTEK_OTTO_SERDES=y CONFIG_PINCTRL=y CONFIG_PM_OPP=y CONFIG_POWER_RESET=y diff --git a/target/linux/realtek/rtl839x/config-6.6 b/target/linux/realtek/rtl839x/config-6.6 index c8d841c01e92e1..8f7522487b9f49 100644 --- a/target/linux/realtek/rtl839x/config-6.6 +++ b/target/linux/realtek/rtl839x/config-6.6 @@ -183,6 +183,7 @@ CONFIG_PERF_USE_VMALLOC=y CONFIG_PGTABLE_LEVELS=2 CONFIG_PHYLIB=y CONFIG_PHYLINK=y +CONFIG_PHY_REALTEK_OTTO_SERDES=y CONFIG_PINCTRL=y CONFIG_PM_OPP=y CONFIG_POWER_RESET=y diff --git a/target/linux/realtek/rtl930x/config-6.6 b/target/linux/realtek/rtl930x/config-6.6 index af5f2ca7a313b4..5c09881856d070 100644 --- a/target/linux/realtek/rtl930x/config-6.6 +++ b/target/linux/realtek/rtl930x/config-6.6 @@ -156,6 +156,7 @@ CONFIG_PERF_USE_VMALLOC=y CONFIG_PGTABLE_LEVELS=2 CONFIG_PHYLIB=y CONFIG_PHYLINK=y +CONFIG_PHY_REALTEK_OTTO_SERDES=y CONFIG_PINCTRL=y CONFIG_POWER_RESET=y CONFIG_POWER_RESET_GPIO_RESTART=y diff --git a/target/linux/realtek/rtl931x/config-6.6 b/target/linux/realtek/rtl931x/config-6.6 index 736f4720296d8c..85e6aa20715bfe 100644 --- a/target/linux/realtek/rtl931x/config-6.6 +++ b/target/linux/realtek/rtl931x/config-6.6 @@ -176,6 +176,7 @@ CONFIG_PERF_USE_VMALLOC=y CONFIG_PGTABLE_LEVELS=2 CONFIG_PHYLIB=y CONFIG_PHYLINK=y +CONFIG_PHY_REALTEK_OTTO_SERDES=y CONFIG_PINCTRL=y CONFIG_POWER_RESET=y CONFIG_POWER_RESET_SYSCON=y