diff --git a/Documentation/devicetree/bindings/net/aeonsemi,as21xxx.yaml b/Documentation/devicetree/bindings/net/aeonsemi,as21xxx.yaml index 69eb29dc4d7b8..a97871d8785f9 100644 --- a/Documentation/devicetree/bindings/net/aeonsemi,as21xxx.yaml +++ b/Documentation/devicetree/bindings/net/aeonsemi,as21xxx.yaml @@ -4,7 +4,7 @@ $id: http://devicetree.org/schemas/net/aeonsemi,as21xxx.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Aeonsemi AS21XXX Ethernet PHY +title: Aeonsemi AS21XXX and AS22XXX Ethernet PHY maintainers: - Christian Marangi @@ -54,6 +54,7 @@ select: - ethernet-phy-id7500.9472 - ethernet-phy-id7500.9482 - ethernet-phy-id7500.9492 + - ethernet-phy-id7500.94a1 required: - compatible @@ -65,6 +66,14 @@ properties: description: specify the name of PHY firmware to load maxItems: 1 + sgmii-usxgmii-switch-quirk: + type: boolean + description: + Enable AS22xxx system-side switching between SGMII at speeds up to + 1 Gbit/s and USXGMII at speeds of 2.5 Gbit/s and above. The attached MAC + node must also contain this property. Without it, the PHY does not change + its system-side interface in response to copper link speed. + required: - compatible - reg @@ -73,7 +82,9 @@ if: properties: compatible: contains: - const: ethernet-phy-id7500.9410 + enum: + - ethernet-phy-id7500.9410 + - ethernet-phy-id7500.94a1 then: required: - firmware-name diff --git a/Documentation/devicetree/bindings/net/toshiba,tc956x-dwmac.yaml b/Documentation/devicetree/bindings/net/toshiba,tc956x-dwmac.yaml index 37dca8c78021e..e371d61086bbc 100644 --- a/Documentation/devicetree/bindings/net/toshiba,tc956x-dwmac.yaml +++ b/Documentation/devicetree/bindings/net/toshiba,tc956x-dwmac.yaml @@ -35,6 +35,21 @@ properties: gpio-controller: true + tc956x,aspm-quirk: + type: boolean + description: + Disable ASPM L1 and its substates on this PCI function's endpoint link. + Set this only on platforms where TC956x L1 entry gates shared clocks and + stalls a neighbouring device. + + sgmii-usxgmii-switch-quirk: + type: boolean + description: + Enable runtime switching between SGMII at speeds up to 1 Gbit/s and + USXGMII at speeds of 2.5 Gbit/s and above. The attached AS22xxx PHY node + must also contain this property. Without it, the interface remains in + the mode selected by phy-mode. + # We can't allOf reference Ethernet-controller.yaml because we end up with # contradictory $nodename rules (`ethernet@` versus `pci@`). Happily only a # small number of the properties are useful on TC956x so we can just reference diff --git a/drivers/misc/tc956x_pci.c b/drivers/misc/tc956x_pci.c index 741a0ae0d3afb..be1756f6ce780 100644 --- a/drivers/misc/tc956x_pci.c +++ b/drivers/misc/tc956x_pci.c @@ -46,6 +46,7 @@ #include #include #include +#include #include #include #include @@ -71,6 +72,11 @@ #define NCID_OFFSET 0x0000 #define NCID_REV_ID_MASK GENMASK(7, 0) +#define NMODESTS_OFFSET 0x0004 +#define NMODESTS_MODE2 BIT(10) /* 0 = USP x4, 1 = USP x2 */ +#define NBUSCTRL_OFFSET 0x1014 +#define NBUSCTRL_FREQ BIT(16) /* 0 = 250 MHz, 1 = 125 MHz */ + /* Reset and clock register offsets. MAC resets and clocks are controlled * by bits in register 0 for MAC0, register 1 for MAC1. Other non-MAC * resets and clocks (whose IDs are defined here) are controlled by bits @@ -141,6 +147,7 @@ enum clock_id { */ #define EMAC_CTL_OFFSET(_mac_id) ((_mac_id) ? 0x1074 : 0x1070) #define MSIGEN_OFFSET(_mac_id) ((_mac_id) ? 0xf100 : 0xf000) +#define TC956X_MSI_VECTORS 32 #define DWMAC_OFFSET(_mac_id) ((_mac_id) ? 0x48000 : 0x40000) /* @@ -149,6 +156,7 @@ enum clock_id { * @sfr: Mapped SFR regions (BAR 4, one per PCI function) * @bridge_config: Regmap used for bridge configuration (BAR 0) * @reset_clock_regmap: Regmap used for resets and clocks + * @instance_id: TC956X instance ID used for auxiliary device IDs * @rev_id: Chip revision ID (for quirks) */ struct tc956x_chip { @@ -156,9 +164,12 @@ struct tc956x_chip { void __iomem *sfr[2]; void __iomem *bridge_config; struct regmap *reset_clock_regmap; + int instance_id; u8 rev_id; }; +static DEFINE_IDA(tc956x_instance_ida); + static const struct regmap_config gpio_regmap_config = { .name = "tc956x-gpio", .reg_bits = 32, @@ -235,26 +246,36 @@ static void adev_remove(void *data) auxiliary_device_uninit(adev); } +static void chip_instance_id_free(void *data) +{ + struct tc956x_chip *chip = data; + + ida_free(&tc956x_instance_ida, chip->instance_id); +} + +/* The of_node reference is always be dropped (success or not) */ static int adev_device_add(struct device *dev, const char *name, u32 id, - void *platform_data) + struct device_node *of_node, void *platform_data) { struct auxiliary_device *adev; int ret; adev = kzalloc_obj(*adev); - if (!adev) + if (!adev) { + of_node_put(of_node); return -ENOMEM; + } adev->id = id; adev->name = name; adev->dev.parent = dev; adev->dev.platform_data = platform_data; adev->dev.release = adev_release; - device_set_of_node_from_dev(&adev->dev, dev); + adev->dev.of_node = of_node; ret = auxiliary_device_init(adev); if (ret) { - of_node_put(adev->dev.of_node); + of_node_put(of_node); kfree(adev); return ret; } @@ -268,51 +289,145 @@ static int adev_device_add(struct device *dev, const char *name, u32 id, return devm_add_action_or_reset(dev, adev_remove, adev); } +static bool dev_node_has_mdio_child(struct device_node *np) +{ + struct device_node *mdio; + + mdio = of_get_child_by_name(np, "mdio"); + if (!mdio) + return false; + + of_node_put(mdio); + + return true; +} + +static bool dev_node_is_gpio(struct device *dev, struct device_node *np) +{ + if (!of_property_present(np, "gpio-controller")) + return false; + + if (!of_property_present(np, "#gpio-cells")) { + dev_err(dev, "gpio node contains no #gpio-cells property\n"); + return false; + } + + return true; +} + +/* Returns a reference to the GPIO's DT node, or a null pointer */ +static struct device_node *dev_node_gpio(struct device *dev) +{ + struct device_node *np; + + /* The GPIO sub-node is not required (platform might not need it) */ + for_each_child_of_node(dev->of_node, np) + if (!strcmp(np->name, "gpio")) + break; + if (np) { + if (dev_node_is_gpio(dev, np)) + return np; + + of_node_put(np); + + return NULL; + } + + /* + * The original TC956x binding placed the GPIO controller properties on + * PCI function 0 itself. Keep accepting that form so existing DTs do not + * need to grow a gpio sub-node. + */ + if (dev_node_is_gpio(dev, dev->of_node)) + return of_node_get(dev->of_node); + + return NULL; +} + +/* Returns a reference to the XGMAC's DT node, or a null pointer */ +static struct device_node *dev_node_xgmac(struct device *dev) +{ + struct device_node *np; + + for_each_child_of_node(dev->of_node, np) + if (!strcmp(np->name, "ethernet")) + return np; + + /* + * The original TC956x binding placed Ethernet controller properties on + * the PCI function node. Treat that node as the XGMAC node when it has + * the old shape. + */ + if (of_property_present(dev->of_node, "phy-mode") || + of_property_present(dev->of_node, "phy-connection-type") || + of_property_present(dev->of_node, "phy-handle") || + dev_node_has_mdio_child(dev->of_node)) + return of_node_get(dev->of_node); + + return NULL; +} + /* The embedded GPIO controller has an auxiliary device driver */ static int chip_gpio_adev_add(struct tc956x_chip *chip) { struct device *dev = chip->dev; + struct device_node *np; struct regmap *regmap; - /* If needed, PCIe function 0 implements the GPIO controller. */ - if (!device_property_present(dev, "gpio-controller")) + np = dev_node_gpio(dev); + if (!np) return 0; regmap = devm_regmap_init_mmio(dev, chip->sfr[0], &gpio_regmap_config); - if (IS_ERR(regmap)) + if (IS_ERR(regmap)) { + of_node_put(np); return PTR_ERR(regmap); + } - return adev_device_add(dev, GPIO_DEVICE_NAME, 0, regmap); + return adev_device_add(dev, GPIO_DEVICE_NAME, chip->instance_id, np, + regmap); } /* The two embedded XGMAC controllers have an auxiliary device driver */ static int function_xgmac_adev_add(struct pci_dev *pdev, struct tc956x_chip *chip, - unsigned int msigen_irq) + unsigned int msigen_irq, + unsigned int msigen_nvec) { u8 mac_id = PCI_FUNC(pdev->devfn); struct device *dev = &pdev->dev; struct tc956x_dwmac_data *data; + struct device_node *np; void __iomem *sfr; int ret; if (mac_id > 1) return -EINVAL; + + np = dev_node_xgmac(dev); + if (!np) + return 0; + sfr = chip->sfr[mac_id]; data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); - if (!data) + if (!data) { + of_node_put(np); return -ENOMEM; + } data->chip = chip; data->msigen = sfr + MSIGEN_OFFSET(mac_id); data->msigen_irq = msigen_irq; + data->msigen_nvec = msigen_nvec; data->emac = sfr + DWMAC_OFFSET(mac_id); data->emac_ctl = sfr + EMAC_CTL_OFFSET(mac_id); + data->sfr = sfr; data->rev_id = chip->rev_id; data->mac_id = mac_id; - ret = adev_device_add(dev, TC956X_XGMAC_DEV_NAME, mac_id, data); + ret = adev_device_add(dev, TC956X_XGMAC_DEV_NAME, + chip->instance_id * 2 + mac_id, np, data); if (ret) return ret; @@ -476,6 +591,15 @@ static struct tc956x_chip *chip_get(struct pci_dev *pdev) if (!chip) return ERR_PTR(-ENOMEM); + ret = ida_alloc(&tc956x_instance_ida, GFP_KERNEL); + if (ret < 0) + return ERR_PTR(ret); + chip->instance_id = ret; + + ret = devm_add_action_or_reset(dev, chip_instance_id_free, chip); + if (ret) + return ERR_PTR(ret); + /* * The function whose device pointer matches the chip's * device pointer manages common resources (like MSIGEN). @@ -552,6 +676,7 @@ tc956x_function_probe(struct pci_dev *pdev, const struct pci_device_id *id) struct device *dev = &pdev->dev; struct tc956x_chip *chip; unsigned int msigen_irq; + unsigned int msigen_nvec; int ret; /* Despite being a PCI device, we require devicetree */ @@ -564,22 +689,35 @@ tc956x_function_probe(struct pci_dev *pdev, const struct pci_device_id *id) pci_set_master(pdev); + if (of_property_present(dev->of_node, "tc956x,aspm-quirk")) { + ret = pci_disable_link_state(pdev, PCIE_LINK_STATE_L1 | + PCIE_LINK_STATE_L1_1 | + PCIE_LINK_STATE_L1_2 | + PCIE_LINK_STATE_L1_1_PCIPM | + PCIE_LINK_STATE_L1_2_PCIPM); + if (ret) + dev_warn(dev, "failed to disable ASPM L1: %d\n", ret); + } + /* Function 1 gets -EPROBE_DEFER until function 0 sets platform data */ chip = chip_get(pdev); if (IS_ERR(chip)) return dev_err_probe(dev, PTR_ERR(chip), "failed to get chip\n"); /* We called pcim_enable_device() so this will be freed automatically */ - ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI); + ret = pci_alloc_irq_vectors(pdev, 1, TC956X_MSI_VECTORS, PCI_IRQ_MSI); if (ret < 1) return dev_err_probe(dev, ret ? : -EIO, "failed to allocate IRQ vectors\n"); + msigen_nvec = ret; ret = pci_irq_vector(pdev, 0); if (ret < 1) return dev_err_probe(dev, ret ? : -EIO, "failed to get IRQ\n"); msigen_irq = ret; + dev_info(dev, "%u MSI vector(s), base IRQ %u\n", msigen_nvec, msigen_irq); + ret = chip_init(chip, pdev); if (ret) return dev_err_probe(dev, ret, "failed to initialize chip\n"); @@ -587,7 +725,7 @@ tc956x_function_probe(struct pci_dev *pdev, const struct pci_device_id *id) /* We're ready; the other function can now probe */ dev->platform_data = chip; - ret = function_xgmac_adev_add(pdev, chip, msigen_irq); + ret = function_xgmac_adev_add(pdev, chip, msigen_irq, msigen_nvec); if (ret) return dev_err_probe(dev, ret, "failed to add xgmap device\n"); diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-tc956x.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-tc956x.c index fbe1fc9e7bddd..c4cff5cada096 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-tc956x.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-tc956x.c @@ -10,13 +10,16 @@ #include #include +#include #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -31,27 +34,53 @@ #define DRIVER_NAME "dwmac-tc956x" #define TC956X_PTP_CLOCK_RATE (250 * HZ_PER_MHZ) +#define TC956X_MAC_CLOCK_RATE (125 * HZ_PER_MHZ) -#define TC956X_RX_FIFO_KB 46 /* Shared by all RX queues */ -#define TC956X_TX_FIFO_KB 46 /* Shared by all TX queues */ +#define TC956X_RX_FIFO_KB 32 /* Shared by all RX queues */ +#define TC956X_TX_FIFO_KB 8 /* Shared by all TX queues */ +#define TC956X_RX_QUEUES 1 +#define TC956X_TX_QUEUES 1 /* Fields and values for the EMACTL registers */ #define EMAC_SP_SEL_MASK GENMASK(3, 0) -#define SP_SEL_SGMII_2500M 4 + +#define SP_SEL_2500BASEX 4 #define SP_SEL_SGMII_1000M 5 #define SP_SEL_SGMII_100M 6 #define SP_SEL_SGMII_10M 7 +#define SP_SEL_USXGMII_10G_10G 8 +#define SP_SEL_USXGMII_5G_10G 9 +#define SP_SEL_USXGMII_2500M_10G 11 +#define SP_SEL_USXGMII_2500M_2500M 13 #define EMAC_PHY_INF_SEL_MASK GENMASK(5, 4) #define PCS_CLK_PHY 1 /* Clock from PHY */ #define EMAC_INV_SGM_SIG_DET BIT(6) /* 1 = polarity inverted */ #define EMAC_LPIHWCLKEN BIT(8) /* 1 = low power mode */ -#define EMAC_INIT_DONE BIT(21) +#define MISC_CTL_OFFSET 0x1800 +#define MISC_SP_ETH0_MASK GENMASK(18, 16) +#define MISC_SP_ETH1_MASK GENMASK(26, 24) +#define MISC_SP_ETH_1G 1 +#define MISC_SP_ETH_100M 3 +#define MISC_SP_ETH_10M 7 + +#define NRSTCTRL_OFFSET(_id) (0x1008 + (_id) * 0x8) + +#define RST_MAC_MACRST BIT(7) + +#define MAC_TX_CONFIG_OFFSET 0x0000 +#define MAC_TX_CONFIG_TE BIT(0) + +#define MAC_RX_CONFIG_OFFSET 0x0004 +#define MAC_RX_CONFIG_RE BIT(0) /* MSIGEN Registers */ #define MSI_OUT_EN_OFFSET 0x0000 +#define MSI_MASK_SET_OFFSET 0x0008 #define MSI_MASK_CLR_OFFSET 0x000c -#define MSI_MASK_VALUE BIT(0) #define MSI_INT_STS_OFFSET 0x0010 +#define MSI_VECT_SET_OFFSET(_src) (0x0020 + ((_src) / 4) * 4) +#define MSI_VECT_SET_SHIFT(_src) (((_src) % 4) * 8) +#define MSI_VECT_SET_MASK GENMASK(4, 0) enum msigen_hwirq { HWIRQ_LPI = 0, @@ -66,6 +95,11 @@ enum msigen_hwirq { }; #define HWIRQ_COUNT 25 +#define TC956X_DMA_CHANS 8 +#define MSI_VEC_MISC 1 +#define MSI_VEC_TX(_ch) (2 + (_ch)) +#define MSI_VEC_RX(_ch) (2 + TC956X_DMA_CHANS + (_ch)) +#define MSI_VEC_COUNT (2 + 2 * TC956X_DMA_CHANS) /* Offset to the XPCS memory block, relative to the EMAC address range */ #define DWMAC_XPCS_OFFSET 0x3a00 @@ -74,6 +108,8 @@ enum msigen_hwirq { #define DWMAC_PMATOP_OFFSET 0x4000 #define PMA_CML_GL_PM_CFG0 0x01b8 +#define PMA_PCS_GL_PC_ST0 0x0168 +#define PMA_PCS_GL_PC_ST0_PLL_VALID BIT(0) /* * Five sets three registers must be configured for PMA. The HWT_REFCLK @@ -96,6 +132,12 @@ enum msigen_hwirq { #define COMM_CFG_WRITE_DATA_MASK GENMASK(7, 0) #define WRITE_DATA_VALUE 0x04 /* Power-on value */ +struct tc956x_msi_vec { + struct tc956x_data *td; + u32 src_mask; /* MSIGEN sources on this vector */ + u8 vec; /* MSI vector number */ +}; + /** * struct tc956x_data - Toshiba-specific platform data * @dev: Device pointer @@ -113,6 +155,14 @@ struct tc956x_data { struct irq_domain *irq_domain; struct tc956x_dwmac_data *auxbus_data; struct plat_stmmacenet_data *plat; + bool mode_switch; + + struct tc956x_msi_vec msi_vec[MSI_VEC_COUNT]; + unsigned int msi_vec_used; + + phy_interface_t interface; + + int boot_speed; /* These three fields are used by the plat_stmmacenet_data structure */ struct stmmac_dma_cfg dma_cfg; @@ -131,11 +181,18 @@ struct tc956x_mac_speed { }; static struct tc956x_mac_speed mac_speed[] = { - { PHY_INTERFACE_MODE_2500BASEX, SPEED_2500, SP_SEL_SGMII_2500M, }, - { PHY_INTERFACE_MODE_SGMII, SPEED_2500, SP_SEL_SGMII_2500M, }, - { PHY_INTERFACE_MODE_SGMII, SPEED_1000, SP_SEL_SGMII_1000M, }, - { PHY_INTERFACE_MODE_SGMII, SPEED_100, SP_SEL_SGMII_100M, }, - { PHY_INTERFACE_MODE_SGMII, SPEED_10, SP_SEL_SGMII_10M, }, + { PHY_INTERFACE_MODE_USXGMII, SPEED_10000, SP_SEL_USXGMII_10G_10G }, + { PHY_INTERFACE_MODE_USXGMII, SPEED_5000, SP_SEL_USXGMII_5G_10G }, + { PHY_INTERFACE_MODE_USXGMII, SPEED_2500, SP_SEL_USXGMII_2500M_10G }, + { PHY_INTERFACE_MODE_USXGMII, SPEED_1000, SP_SEL_USXGMII_10G_10G }, + { PHY_INTERFACE_MODE_USXGMII, SPEED_100, SP_SEL_USXGMII_5G_10G }, + { PHY_INTERFACE_MODE_USXGMII, SPEED_10, SP_SEL_USXGMII_5G_10G }, + { PHY_INTERFACE_MODE_2500BASEX, SPEED_2500, SP_SEL_2500BASEX }, + { PHY_INTERFACE_MODE_1000BASEX, SPEED_1000, SP_SEL_SGMII_1000M }, + { PHY_INTERFACE_MODE_SGMII, SPEED_2500, SP_SEL_2500BASEX }, + { PHY_INTERFACE_MODE_SGMII, SPEED_1000, SP_SEL_SGMII_1000M }, + { PHY_INTERFACE_MODE_SGMII, SPEED_100, SP_SEL_SGMII_100M }, + { PHY_INTERFACE_MODE_SGMII, SPEED_10, SP_SEL_SGMII_10M }, }; /* TC956x uses indirect addressing so this need only describe a 1KiB range */ @@ -149,33 +206,95 @@ static const struct regmap_config xpcs_regmap_config = { static void tc956x_msigen_irq_handler(struct irq_desc *desc) { - struct irq_domain *irq_domain = irq_desc_get_handler_data(desc); + struct tc956x_msi_vec *mv = irq_desc_get_handler_data(desc); struct irq_chip *chip = irq_desc_get_chip(desc); struct irq_chip_generic *gc; unsigned long status; unsigned int hwirq; - gc = irq_get_domain_generic_chip(irq_domain, 0); + gc = irq_get_domain_generic_chip(mv->td->irq_domain, 0); chained_irq_enter(chip, desc); - status = irq_reg_readl(gc, MSI_INT_STS_OFFSET); + status = irq_reg_readl(gc, MSI_INT_STS_OFFSET) & mv->src_mask; for_each_set_bit(hwirq, &status, HWIRQ_COUNT) - generic_handle_domain_irq(irq_domain, hwirq); + generic_handle_domain_irq(mv->td->irq_domain, hwirq); /* * Clear the MSI flag. Most interrupts within TC956X are level-high * type. If any interrupts are still asserted then clearing this flag * will cause the (edge-triggered) MSI to be regenerated. */ - irq_reg_writel(gc, MSI_MASK_VALUE, MSI_MASK_CLR_OFFSET); + irq_reg_writel(gc, BIT(mv->vec), MSI_MASK_CLR_OFFSET); chained_irq_exit(chip, desc); } +static void tc956x_msigen_route(struct irq_chip_generic *gc, + const u8 *src_vec, unsigned int nsrc) +{ + unsigned int src, reg; + u32 val; + + irq_reg_writel(gc, 0, MSI_OUT_EN_OFFSET); + + for (reg = 0; reg < DIV_ROUND_UP(nsrc, 4); reg++) { + val = 0; + for (src = reg * 4; src < min(nsrc, (reg + 1) * 4); src++) + val |= (src_vec[src] & MSI_VECT_SET_MASK) << + MSI_VECT_SET_SHIFT(src); + irq_reg_writel(gc, val, MSI_VECT_SET_OFFSET(reg * 4)); + } +} + +static void tc956x_msigen_plan(struct tc956x_data *td, u8 *src_vec) +{ + unsigned int src, ch; + + if (td->auxbus_data->msigen_nvec < MSI_VEC_COUNT) { + for (src = 0; src < HWIRQ_COUNT; src++) + src_vec[src] = 0; + td->msi_vec[0] = (struct tc956x_msi_vec){ + .td = td, .vec = 0, + .src_mask = GENMASK(HWIRQ_COUNT - 1, 0), + }; + td->msi_vec_used = 1; + return; + } + + for (src = 0; src < HWIRQ_COUNT; src++) + src_vec[src] = MSI_VEC_MISC; + for (ch = 0; ch < TC956X_DMA_CHANS; ch++) { + src_vec[HWIRQ_TX0 + ch] = MSI_VEC_TX(ch); + src_vec[HWIRQ_RX0 + ch] = MSI_VEC_RX(ch); + } + + td->msi_vec[0] = (struct tc956x_msi_vec){ + .td = td, .vec = MSI_VEC_MISC, + .src_mask = GENMASK(HWIRQ_EVENT, HWIRQ_LPI) | + GENMASK(HWIRQ_COUNT - 1, HWIRQ_XPCS), + }; + td->msi_vec_used = 1; + + /* Interleaved so that TX and RX of the same channel index differ */ + for (ch = 0; ch < TC956X_DMA_CHANS; ch++) { + td->msi_vec[td->msi_vec_used++] = (struct tc956x_msi_vec){ + .td = td, .vec = MSI_VEC_TX(ch), + .src_mask = BIT(HWIRQ_TX0 + ch), + }; + td->msi_vec[td->msi_vec_used++] = (struct tc956x_msi_vec){ + .td = td, .vec = MSI_VEC_RX(ch), + .src_mask = BIT(HWIRQ_RX0 + ch), + }; + } +} + static int tc956x_msigen_irq_chip_init(struct irq_chip_generic *gc) { struct tc956x_data *td = gc->domain->host_data; + u8 src_vec[HWIRQ_COUNT]; + u32 vec_used = 0; + unsigned int i; gc->reg_base = td->auxbus_data->msigen; gc->chip_types[0].regs.mask = MSI_OUT_EN_OFFSET; @@ -185,6 +304,15 @@ static int tc956x_msigen_irq_chip_init(struct irq_chip_generic *gc) /* Disable all interrupts */ irq_reg_writel(gc, 0, MSI_OUT_EN_OFFSET); + tc956x_msigen_plan(td, src_vec); + tc956x_msigen_route(gc, src_vec, HWIRQ_COUNT); + + for (i = 0; i < td->msi_vec_used; i++) + vec_used |= BIT(td->msi_vec[i].vec); + + irq_reg_writel(gc, ~vec_used & ~BIT(0), MSI_MASK_SET_OFFSET); + irq_reg_writel(gc, vec_used, MSI_MASK_CLR_OFFSET); + return 0; } @@ -196,10 +324,41 @@ static void tc956x_msigen_irq_chip_exit(struct irq_chip_generic *gc) static int tc956x_msigen_irq_domain_init(struct irq_domain *irq_domain) { struct tc956x_data *td = irq_domain->host_data; + unsigned int i, cpu; + int last = -1; + + if (td->msi_vec_used == 1) { + for_each_cpu(cpu, cpu_online_mask) + last = cpu; + irq_set_affinity_and_hint(td->auxbus_data->msigen_irq, + cpumask_of(last > 0 ? last - 1 : 0)); + irq_set_chained_handler_and_data(td->auxbus_data->msigen_irq, + tc956x_msigen_irq_handler, + &td->msi_vec[0]); + dev_info(td->dev, "%u MSI vector(s), sharing one interrupt\n", + td->auxbus_data->msigen_nvec); + return 0; + } + + cpu = cpumask_first(cpu_online_mask); + for (i = 0; i < td->msi_vec_used; i++) { + unsigned int irq = td->auxbus_data->msigen_irq + + td->msi_vec[i].vec; + + irq_set_chained_handler_and_data(irq, + tc956x_msigen_irq_handler, + &td->msi_vec[i]); + + /* Leave the shared low-rate vector wherever it lands */ + if (td->msi_vec[i].vec == MSI_VEC_MISC) + continue; + + irq_set_affinity_and_hint(irq, cpumask_of(cpu)); + cpu = cpumask_next_wrap(cpu, cpu_online_mask); + } - irq_set_chained_handler_and_data(td->auxbus_data->msigen_irq, - tc956x_msigen_irq_handler, - irq_domain); + dev_info(td->dev, "%u MSI vectors, one per DMA channel\n", + td->auxbus_data->msigen_nvec); return 0; } @@ -207,9 +366,16 @@ static int tc956x_msigen_irq_domain_init(struct irq_domain *irq_domain) static void tc956x_msigen_irq_domain_exit(struct irq_domain *irq_domain) { struct tc956x_data *td = irq_domain->host_data; + unsigned int i; - irq_set_chained_handler_and_data(td->auxbus_data->msigen_irq, - NULL, NULL); + for (i = 0; i < td->msi_vec_used; i++) { + unsigned int irq = td->auxbus_data->msigen_irq; + + if (td->msi_vec_used > 1) + irq += td->msi_vec[i].vec; + + irq_set_chained_handler_and_data(irq, NULL, NULL); + } } /* We have one IRQ chip instance with 25 IRQs in its domain */ @@ -242,18 +408,17 @@ tc956x_msigen_irq_domain_instantiate(struct tc956x_data *td) * tc956x_pma_init() - Initialize PMA * @td: bsp_priv pointer * - * Initialize (or re-initialize) the PMA, configure the clocks and wait for the - * eMAC to be ready. */ -static void tc956x_pma_init(struct tc956x_data *td) +static int tc956x_pma_init(struct tc956x_data *td) { const struct tc956x_chip *chip = td->auxbus_data->chip; - void __iomem *emac_ctl = td->auxbus_data->emac_ctl; u32 id = td->auxbus_data->mac_id; void __iomem *pmatop; + int ret; u32 val; u32 i; + pmatop = td->auxbus_data->emac + DWMAC_PMATOP_OFFSET; /* * When we re-initialize the PMA then the reset will already have * been deasserted. We must make sure the PMA reset is asserted before @@ -261,8 +426,6 @@ static void tc956x_pma_init(struct tc956x_data *td) */ tc956x_reset_assert(chip, id, MAC_RESET_PMA); - pmatop = td->auxbus_data->emac + DWMAC_PMATOP_OFFSET; - /* Power on CML buffer (0 = normal mode, 1 = power down) */ writel(0, pmatop + PMA_CML_GL_PM_CFG0); @@ -285,13 +448,22 @@ static void tc956x_pma_init(struct tc956x_data *td) tc956x_reset_deassert(chip, id, MAC_RESET_PMA); - WARN_ON(readl_poll_timeout(emac_ctl, val, val & EMAC_INIT_DONE, 50, 1000000)); + ret = readl_poll_timeout(pmatop + PMA_PCS_GL_PC_ST0, val, + val & PMA_PCS_GL_PC_ST0_PLL_VALID, + 10, 100000); + if (ret) { + dev_err(td->dev, + "Ethernet PMA PLL did not become valid (PCS_GL_PC_ST0=%08x)\n", + val); + return ret; + } + + return 0; } -static int tc956x_mac_speed_select(struct tc956x_data *td, int speed) +static int tc956x_mac_speed_select(struct tc956x_data *td, + phy_interface_t phy_interface, int speed) { - phy_interface_t phy_interface = td->plat->phy_interface; - struct net_device *netdev; int i; for (i = 0; i < ARRAY_SIZE(mac_speed); i++) { @@ -301,20 +473,23 @@ static int tc956x_mac_speed_select(struct tc956x_data *td, int speed) if (mac_speed[i].phy_interface == phy_interface) return mac_speed[i].sp_sel; } - netdev = dev_get_drvdata(td->dev); - netdev_err(netdev, "%s/%d unsupported\n", - phy_modes(phy_interface), speed); + dev_err(td->dev, "%s/%d unsupported\n", + phy_modes(phy_interface), speed); return -EOPNOTSUPP; } -static int tc956x_mac_configure(struct tc956x_data *td, int speed) +static int tc956x_mac_configure(struct tc956x_data *td, + phy_interface_t phy_interface, int speed) { void __iomem *emac_ctl = td->auxbus_data->emac_ctl; + void __iomem *misc_ctl = td->auxbus_data->sfr + MISC_CTL_OFFSET; + u32 id = td->auxbus_data->mac_id; + u32 rate = 0; int sp_sel; u32 val; - sp_sel = tc956x_mac_speed_select(td, speed); + sp_sel = tc956x_mac_speed_select(td, phy_interface, speed); if (sp_sel < 0) return sp_sel; @@ -325,13 +500,33 @@ static int tc956x_mac_configure(struct tc956x_data *td, int speed) val = u32_replace_bits(val, sp_sel, EMAC_SP_SEL_MASK); writel(val, emac_ctl); + if (phy_interface == PHY_INTERFACE_MODE_USXGMII) { + switch (speed) { + case SPEED_1000: + rate = MISC_SP_ETH_1G; + break; + case SPEED_100: + rate = MISC_SP_ETH_100M; + break; + case SPEED_10: + rate = MISC_SP_ETH_10M; + break; + } + } + + val = readl(misc_ctl); + if (id) + val = u32_replace_bits(val, rate, MISC_SP_ETH1_MASK); + else + val = u32_replace_bits(val, rate, MISC_SP_ETH0_MASK); + writel(val, misc_ctl); + return 0; } static int tc956x_mac_enable(struct tc956x_data *td) { const struct tc956x_chip *chip = td->auxbus_data->chip; - struct plat_stmmacenet_data *plat = td->plat; u32 id = td->auxbus_data->mac_id; int ret; @@ -341,13 +536,21 @@ static int tc956x_mac_enable(struct tc956x_data *td) if (id) tc956x_clock_enable(chip, id, MAC_CLOCK_RMII); - /* Set the speed related registers */ - ret = tc956x_mac_configure(td, plat->max_speed); + tc956x_common_clock_enable(chip, COMMON_CLOCK_PLL); + tc956x_common_clock_enable(chip, COMMON_CLOCK_SGMII); + tc956x_common_clock_enable(chip, COMMON_CLOCK_REFCLK); + tc956x_clock_enable(chip, id, MAC_CLOCK_125M); + tc956x_clock_enable(chip, id, MAC_CLOCK_312_5M); + + ret = tc956x_mac_configure(td, td->interface, td->boot_speed); if (ret) - return ret; + return dev_err_probe(td->dev, ret, + "failed to configure MAC speed\n"); tc956x_reset_deassert(chip, id, MAC_RESET_MAC); - tc956x_pma_init(td); + ret = tc956x_pma_init(td); + if (ret) + return ret; tc956x_reset_deassert(chip, id, MAC_RESET_XPCS); return 0; @@ -357,7 +560,6 @@ static void tc956x_mac_disable(struct tc956x_data *td) { const struct tc956x_chip *chip = td->auxbus_data->chip; u32 id = td->auxbus_data->mac_id; - tc956x_reset_assert(chip, id, MAC_RESET_MAC); tc956x_reset_assert(chip, id, MAC_RESET_PMA); tc956x_reset_assert(chip, id, MAC_RESET_XPCS); @@ -367,6 +569,9 @@ static void tc956x_mac_disable(struct tc956x_data *td) tc956x_clock_disable(chip, id, MAC_CLOCK_TX); if (id) tc956x_clock_disable(chip, id, MAC_CLOCK_RMII); + + tc956x_clock_disable(chip, id, MAC_CLOCK_125M); + tc956x_clock_disable(chip, id, MAC_CLOCK_312_5M); } static void tc956x_mac_init_state(struct tc956x_data *td) @@ -452,6 +657,13 @@ static int tc956x_mac_setup(void *apriv, struct mac_device_info *mac) td = priv->plat->bsp_priv; + /* 10G USXGMII traffic needs the maximum descriptor ring to avoid + * starving the DMA path. Keep this TC956X-specific instead of raising + * stmmac's global defaults for every platform. + */ + priv->dma_conf.dma_rx_size = DMA_MAX_RX_SIZE; + priv->dma_conf.dma_tx_size = DMA_MAX_TX_SIZE; + /* dwxgmac301_dma_ops needs extending to provide DMA address translation */ dma = &td->dma; *dma = dwxgmac301_dma_ops; @@ -474,6 +686,7 @@ static int tc956x_mac_setup(void *apriv, struct mac_device_info *mac) static int tc956x_pcs_init(struct stmmac_priv *priv) { struct xpcs_regmap_config xpcs_regmap_cfg; + struct tc956x_data *td = priv->plat->bsp_priv; void __iomem *emac = priv->ioaddr; struct regmap *xpcs_regmap; void __iomem *xpcs_addr; @@ -492,6 +705,8 @@ static int tc956x_pcs_init(struct stmmac_priv *priv) if (IS_ERR(xpcs)) return PTR_ERR(xpcs); + xpcs_config_qps615(xpcs, td->mode_switch, + td->plat->phy_interface == PHY_INTERFACE_MODE_SGMII); xpcs_config_eee_mult_fact(xpcs, priv->plat->mult_fact_100ns); priv->hw->phylink_pcs = xpcs_to_phylink_pcs(xpcs); @@ -504,12 +719,114 @@ static struct phylink_pcs *tc956x_select_pcs(struct stmmac_priv *priv, return priv->hw->phylink_pcs; } +static int tc956x_mac_finish(struct net_device *ndev, void *bsp_priv, + unsigned int mode, phy_interface_t interface) +{ + struct tc956x_data *td = bsp_priv; + + td->interface = interface; + + return 0; +} + +static int tc956x_mac_prepare(struct net_device *ndev, void *bsp_priv, + unsigned int mode, + phy_interface_t interface) +{ + struct tc956x_data *td = bsp_priv; + const struct tc956x_chip *chip = td->auxbus_data->chip; + u32 id = td->auxbus_data->mac_id; + void __iomem *rst = td->auxbus_data->sfr + NRSTCTRL_OFFSET(id); + u32 rst_before, rst_after; + int speed, ret; + + if (td->interface == interface) + return 0; + if (!td->mode_switch && + td->plat->phy_interface != PHY_INTERFACE_MODE_SGMII) + return -EOPNOTSUPP; + + speed = ndev->phydev ? ndev->phydev->speed : SPEED_UNKNOWN; + if (tc956x_mac_speed_select(td, interface, speed) < 0) { + switch (interface) { + case PHY_INTERFACE_MODE_USXGMII: + speed = SPEED_10000; + break; + case PHY_INTERFACE_MODE_2500BASEX: + speed = SPEED_2500; + break; + case PHY_INTERFACE_MODE_SGMII: + case PHY_INTERFACE_MODE_1000BASEX: + speed = SPEED_1000; + break; + default: + return -EOPNOTSUPP; + } + } + + rst_before = readl(rst); + + tc956x_reset_assert(chip, id, MAC_RESET_XPCS); + + ret = tc956x_mac_configure(td, interface, speed); + if (ret) + return ret; + + ret = tc956x_pma_init(td); + if (ret) + return ret; + td->interface = interface; + tc956x_reset_deassert(chip, id, MAC_RESET_XPCS); + + rst_after = readl(rst); + if ((rst_before | rst_after) & RST_MAC_MACRST) { + netdev_err(ndev, + "eMAC core reset unexpectedly asserted during PMA/XPCS-only switch: NRSTCTRL %08x -> %08x\n", + rst_before, rst_after); + return -EIO; + } + return 0; +} + static void tc956x_fix_mac_speed(void *bsp_priv, int speed, unsigned int mode) { struct tc956x_data *td = bsp_priv; + phy_interface_t interface = td->interface; + int ret; + + ret = tc956x_mac_configure(td, interface, speed); + if (ret) { + dev_err(td->dev, "failed to configure %s at %d Mbps: %d\n", + phy_modes(interface), speed, ret); + return; + } +} + +static void tc956x_post_mac_link_up(struct net_device *ndev, void *bsp_priv, + struct phy_device *phydev, + unsigned int mode, + phy_interface_t interface, int speed) +{ + struct tc956x_data *td = bsp_priv; + void __iomem *mac = td->auxbus_data->emac; + u32 tx, rx; + int ret; + + if (td->mode_switch && interface == PHY_INTERFACE_MODE_USXGMII) { + tx = readl(mac + MAC_TX_CONFIG_OFFSET); + rx = readl(mac + MAC_RX_CONFIG_OFFSET); + if (!(tx & MAC_TX_CONFIG_TE) || !(rx & MAC_RX_CONFIG_RE)) + return; - tc956x_mac_configure(td, speed); - tc956x_pma_init(td); + if (!phydev) + return; + + ret = phy_config_inband(phydev, LINK_INBAND_BYPASS); + if (ret) + netdev_err(ndev, + "post-MAC-enable USXGMII PHY synchronization failed: %d\n", + ret); + } } static int tc956x_dwmac_suspend(struct device *dev, void *bsp_priv) @@ -555,18 +872,85 @@ static int tc956x_dwmac_parse_dt(struct tc956x_data *td) return 0; } +static void tc956x_deassert_phy_resets(struct tc956x_data *td) +{ + struct device_node *child; + struct gpio_desc *reset; + u32 delay_us; + + if (!td->plat->mdio_node) + return; + + for_each_available_child_of_node(td->plat->mdio_node, child) { + reset = fwnode_gpiod_get_index(of_fwnode_handle(child), + "reset", 0, GPIOD_OUT_LOW, + "phy-reset"); + if (IS_ERR(reset)) + continue; + + gpiod_set_value_cansleep(reset, 0); + gpiod_put(reset); + + if (!of_property_read_u32(child, "reset-deassert-us", &delay_us)) + fsleep(delay_us); + } +} + static int tc956x_lookup_max_speed(phy_interface_t phy_interface) { switch (phy_interface) { + case PHY_INTERFACE_MODE_USXGMII: + return SPEED_10000; + case PHY_INTERFACE_MODE_SGMII: case PHY_INTERFACE_MODE_2500BASEX: return SPEED_2500; + case PHY_INTERFACE_MODE_1000BASEX: + return SPEED_1000; + default: return -EOPNOTSUPP; } } +static const phy_interface_t tc956x_serdes_interfaces[] = { + PHY_INTERFACE_MODE_USXGMII, + PHY_INTERFACE_MODE_2500BASEX, + PHY_INTERFACE_MODE_SGMII, + PHY_INTERFACE_MODE_1000BASEX, +}; + +static bool tc956x_is_serdes_interface(phy_interface_t phy_interface) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(tc956x_serdes_interfaces); i++) + if (tc956x_serdes_interfaces[i] == phy_interface) + return true; + + return false; +} + +static int tc956x_max_speed(struct tc956x_data *td, + phy_interface_t phy_interface) +{ + int max, ret, i; + + max = tc956x_lookup_max_speed(phy_interface); + if (max < 0 || !td->mode_switch || + !tc956x_is_serdes_interface(phy_interface)) + return max; + + for (i = 0; i < ARRAY_SIZE(tc956x_serdes_interfaces); i++) { + ret = tc956x_lookup_max_speed(tc956x_serdes_interfaces[i]); + if (ret > max) + max = ret; + } + + return max; +} + /* Called by tc956x_dwmac_probe(); return errors with dev_err_probe() */ static int tc956x_plat_dat_init(struct tc956x_data *td) { @@ -582,6 +966,9 @@ static int tc956x_plat_dat_init(struct tc956x_data *td) if (phy_interface < 0) return -ENODEV; + td->mode_switch = device_property_read_bool(dev, + "sgmii-usxgmii-switch-quirk"); + /* The platform structure is allocated with devm_kzalloc() */ plat = stmmac_plat_dat_alloc(dev); if (!plat) @@ -592,8 +979,11 @@ static int tc956x_plat_dat_init(struct tc956x_data *td) return dev_err_probe(dev, ret, "unsupported phy speed\n"); speed = ret; + td->interface = phy_interface; + td->boot_speed = speed; + plat->core_type = DWMAC_CORE_XGMAC; - plat->bus_id = td->auxbus_data->mac_id; + plat->bus_id = to_auxiliary_dev(dev)->id; plat->phy_interface = phy_interface; plat->mdio_bus_data = &td->mdio_bus_data; /* Parent PCI device is used for DMA */ @@ -607,39 +997,32 @@ static int tc956x_plat_dat_init(struct tc956x_data *td) * represents "divide by 62" and gets the best rate under 2.5 MHz. */ plat->clk_csr = 0; /* MDC clock = clk_csr_i / 62 */ - plat->force_sf_dma_mode = 1; - plat->max_speed = speed; + plat->mdio_bus_data->default_an_inband = + !td->mode_switch && phy_interface != PHY_INTERFACE_MODE_USXGMII; + plat->force_sf_dma_mode = true; + plat->max_speed = tc956x_max_speed(td, phy_interface); plat->unicast_filter_entries = 32; - /* - * TC956x has 8 RX queues but we observe significantly reduced RX - * bandwidth if we don't have at least 8k FIFO space per queue, so - * by default we avoid using all the queues. - */ - plat->rx_queues_to_use = 4; - - /* - * TX956x has 8 TX queues but only #0 to #3 work for general IP traffic. - * For now we will limit the driver to only these queues. - */ - plat->tx_queues_to_use = 4; + plat->rx_queues_to_use = TC956X_RX_QUEUES; + plat->tx_queues_to_use = TC956X_TX_QUEUES; + plat->rss_en = TC956X_RX_QUEUES > 1; /* * Oversized FIFOs result in reduced performance in bandwidth tests. * Limit them to 8KiB per queue, or the total available. */ - plat->tx_fifo_size = - min(TC956X_TX_FIFO_KB, 8 * plat->tx_queues_to_use) * SZ_1K; - plat->rx_fifo_size = - min(TC956X_RX_FIFO_KB, 8 * plat->rx_queues_to_use) * SZ_1K; + plat->tx_fifo_size = TC956X_TX_FIFO_KB * SZ_1K; + plat->rx_fifo_size = TC956X_RX_FIFO_KB * SZ_1K; plat->host_dma_width = 36; plat->rx_sched_algorithm = MTL_RX_ALGORITHM_SP; plat->tx_sched_algorithm = MTL_TX_ALGORITHM_WRR; /* Default RX chan is set to queue index (0..rx_queues_to_use-1) */ - for (i = 0; i < plat->rx_queues_to_use; i++) + for (i = 0; i < plat->rx_queues_to_use; i++) { plat->rx_queues_cfg[i].mode_to_use = MTL_QUEUE_DCB; + plat->rx_queues_cfg[i].chan = i; + } for (i = 0; i < plat->tx_queues_to_use; i++) { plat->tx_queues_cfg[i].weight = 12; @@ -651,6 +1034,9 @@ static int tc956x_plat_dat_init(struct tc956x_data *td) } plat->fix_mac_speed = tc956x_fix_mac_speed; + plat->post_mac_link_up = tc956x_post_mac_link_up; + plat->mac_prepare = tc956x_mac_prepare; + plat->mac_finish = tc956x_mac_finish; plat->suspend = tc956x_dwmac_suspend; plat->resume = tc956x_dwmac_resume; plat->mac_setup = tc956x_mac_setup; @@ -659,10 +1045,11 @@ static int tc956x_plat_dat_init(struct tc956x_data *td) plat->bsp_priv = td; plat->clk_ptp_rate = TC956X_PTP_CLOCK_RATE; + plat->clk_ref_rate = TC956X_MAC_CLOCK_RATE; /* AXI Configuration */ axi = &td->axi; - axi->axi_lpi_en = 1; + axi->axi_lpi_en = 0; axi->axi_wr_osr_lmt = 31; axi->axi_rd_osr_lmt = 31; /* All sizes (2^2..2^8) are supported */ @@ -670,7 +1057,7 @@ static int tc956x_plat_dat_init(struct tc956x_data *td) plat->axi = axi; plat->mac_port_sel_speed = speed; - plat->flags = STMMAC_FLAG_MULTI_MSI_EN | STMMAC_FLAG_TSO_EN; + plat->flags = STMMAC_FLAG_MULTI_MSI_EN; td->plat = plat; @@ -740,6 +1127,10 @@ static int tc956x_dwmac_probe(struct auxiliary_device *adev, /* Put the MAC in a known initial state */ tc956x_mac_init_state(td); + ret = tc956x_mac_enable(td); + if (ret) + return dev_err_probe(dev, ret, "failed initial MAC enable\n"); + tc956x_deassert_phy_resets(td); ret = tc956x_stmmac_resources_init(td, &res); if (ret) @@ -750,10 +1141,6 @@ static int tc956x_dwmac_probe(struct auxiliary_device *adev, if (ret == -EPROBE_DEFER) return dev_err_probe(dev, ret, "failed to get MAC address\n"); - ret = tc956x_mac_enable(td); - if (ret) - return dev_err_probe(dev, ret, "failed to enable MAC\n"); - ret = stmmac_dvr_probe(dev, td->plat, &res); if (ret) return dev_err_probe(dev, ret, "failed stmmac probe\n"); diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index a7bb2f3e02047..30a87d046512d 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -971,6 +971,19 @@ static void stmmac_mac_config(struct phylink_config *config, unsigned int mode, /* Nothing to do, xpcs_config() handles everything */ } +static int stmmac_mac_prepare(struct phylink_config *config, unsigned int mode, + phy_interface_t interface) +{ + struct net_device *ndev = to_net_dev(config->dev); + struct stmmac_priv *priv = netdev_priv(ndev); + + if (priv->plat->mac_prepare) + return priv->plat->mac_prepare(ndev, priv->plat->bsp_priv, mode, + interface); + + return 0; +} + static int stmmac_mac_finish(struct phylink_config *config, unsigned int mode, phy_interface_t interface) { @@ -1024,6 +1037,15 @@ 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; + case SPEED_100: + ctrl |= priv->hw->link.speed100; + break; + case SPEED_10: + ctrl |= priv->hw->link.speed10; + break; default: return; } @@ -1106,6 +1128,10 @@ static void stmmac_mac_link_up(struct phylink_config *config, } stmmac_mac_set(priv, priv->ioaddr, true); + if (priv->plat->post_mac_link_up) + priv->plat->post_mac_link_up(priv->dev, priv->plat->bsp_priv, + phy, mode, interface, speed); + if (priv->dma_cap.eee) stmmac_set_eee_pls(priv, priv->hw, true); @@ -1193,6 +1219,7 @@ static int stmmac_mac_wol_set(struct phylink_config *config, u32 wolopts, static const struct phylink_mac_ops stmmac_phylink_mac_ops = { .mac_get_caps = stmmac_mac_get_caps, .mac_select_pcs = stmmac_mac_select_pcs, + .mac_prepare = stmmac_mac_prepare, .mac_config = stmmac_mac_config, .mac_finish = stmmac_mac_finish, .mac_link_down = stmmac_mac_link_down, @@ -6390,6 +6417,7 @@ 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)) { @@ -6399,7 +6427,8 @@ static u16 stmmac_select_queue(struct net_device *dev, struct sk_buff *skb, * because if TSO/USO is supported then at least this * one will be capable. */ - return 0; + if (priv->dma_cap.tsoen && priv->plat->flags & STMMAC_FLAG_TSO_EN) + return 0; } return netdev_pick_tx(dev, skb, NULL) % dev->real_num_tx_queues; diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c index 1d62d5b31c61c..3f2ce6196c9d5 100644 --- a/drivers/net/pcs/pcs-xpcs.c +++ b/drivers/net/pcs/pcs-xpcs.c @@ -19,6 +19,7 @@ #define phylink_pcs_to_xpcs(pl_pcs) \ container_of((pl_pcs), struct dw_xpcs, pcs) +static int xpcs_usra_reset(struct dw_xpcs *xpcs); static const int xpcs_usxgmii_features[] = { ETHTOOL_LINK_MODE_Pause_BIT, ETHTOOL_LINK_MODE_Asym_Pause_BIT, @@ -27,6 +28,11 @@ static const int xpcs_usxgmii_features[] = { ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT, ETHTOOL_LINK_MODE_10000baseKR_Full_BIT, ETHTOOL_LINK_MODE_2500baseX_Full_BIT, + ETHTOOL_LINK_MODE_100baseT_Full_BIT, + ETHTOOL_LINK_MODE_1000baseT_Full_BIT, + ETHTOOL_LINK_MODE_2500baseT_Full_BIT, + ETHTOOL_LINK_MODE_5000baseT_Full_BIT, + ETHTOOL_LINK_MODE_10000baseT_Full_BIT, __ETHTOOL_LINK_MODE_MASK_NBITS, }; @@ -355,6 +361,33 @@ static int xpcs_read_fault_c73(struct dw_xpcs *xpcs, return 0; } +static int xpcs_usra_reset(struct dw_xpcs *xpcs) +{ + int ret; + int val; + + ret = xpcs_modify_vpcs(xpcs, DW_VR_XS_PCS_DIG_CTRL1, DW_USXGMII_RST, + DW_USXGMII_RST); + if (ret < 0) + return ret; + + ret = read_poll_timeout(xpcs_read_vpcs, val, + val < 0 || !(val & DW_USXGMII_RST), + 100, 20000, false, + xpcs, DW_VR_XS_PCS_DIG_CTRL1); + if (val < 0) + ret = val; + + if (ret) + return ret < 0 ? ret : -ETIMEDOUT; + + val = xpcs_read_vendor(xpcs, MDIO_MMD_PCS, DW_VR_XS_PCS_DIG_STS); + if (val < 0) + return val; + + return 0; +} + static void xpcs_link_up_usxgmii(struct dw_xpcs *xpcs, int speed) { int ret, speed_sel; @@ -387,13 +420,44 @@ static void xpcs_link_up_usxgmii(struct dw_xpcs *xpcs, int speed) if (ret < 0) goto out; - ret = xpcs_modify(xpcs, MDIO_MMD_VEND2, MII_BMCR, DW_USXGMII_SS_MASK, + ret = xpcs_modify(xpcs, MDIO_MMD_VEND2, MII_BMCR, DW_USXGMII_CTRL_MASK, speed_sel | DW_USXGMII_FULL); if (ret < 0) goto out; - ret = xpcs_modify_vpcs(xpcs, MDIO_CTRL1, DW_USXGMII_RST, - DW_USXGMII_RST); + ret = xpcs_modify_vpcs(xpcs, DW_VR_XS_PCS_DIG_CTRL1, + BIT(2), 0); + if (ret < 0) + goto out; + + ret = xpcs_modify(xpcs, MDIO_MMD_VEND2, DW_VR_MII_DIG_CTRL1, + DW_VR_MII_DIG_CTRL1_MAC_AUTO_SW, + 0); + if (ret < 0) + goto out; + + if (xpcs->qps615_oob) { + ret = xpcs_modify(xpcs, MDIO_MMD_VEND2, DW_VR_MII_AN_CTRL, + DW_VR_MII_PCS_MODE_MASK | + DW_VR_MII_TX_CONFIG_MASK | + DW_VR_MII_AN_INTR_EN, + xpcs->pcs.poll ? 0 : DW_VR_MII_AN_INTR_EN); + if (ret < 0) + goto out; + + ret = xpcs_write(xpcs, MDIO_MMD_VEND2, + DW_VR_MII_AN_INTR_STS, 0); + if (ret < 0) + goto out; + + ret = xpcs_modify(xpcs, MDIO_MMD_VEND2, MII_BMCR, + BMCR_ANENABLE | BMCR_ANRESTART, + BMCR_ANENABLE | BMCR_ANRESTART); + if (ret < 0) + goto out; + } + + ret = xpcs_usra_reset(xpcs); if (ret < 0) goto out; @@ -673,6 +737,10 @@ static unsigned int xpcs_inband_caps(struct phylink_pcs *pcs, struct dw_xpcs *xpcs = phylink_pcs_to_xpcs(pcs); const struct dw_xpcs_compat *compat; + if (interface == PHY_INTERFACE_MODE_USXGMII && + xpcs->qps615_oob) + return LINK_INBAND_DISABLE; + compat = xpcs_find_compat(xpcs, interface); if (!compat) return 0; @@ -705,46 +773,122 @@ static void xpcs_get_interfaces(struct dw_xpcs *xpcs, unsigned long *interfaces) static int xpcs_switch_interface_mode(struct dw_xpcs *xpcs, phy_interface_t interface) { - int ret = 0; + int mdio_stat2, ret; - if (xpcs->info.pma == WX_TXGBE_XPCS_PMA_10G_ID) { - ret = txgbe_xpcs_switch_mode(xpcs, interface); - } else if (xpcs->interface != interface) { - if (interface == PHY_INTERFACE_MODE_SGMII) - xpcs->need_reset = true; - xpcs->interface = interface; + /* Wangxun provides a full alternative implementation to handle quirks */ + if (xpcs->info.pma == WX_TXGBE_XPCS_PMA_10G_ID) + return txgbe_xpcs_switch_mode(xpcs, interface); + + mdio_stat2 = xpcs_read(xpcs, MDIO_MMD_PCS, MDIO_STAT2); + if (mdio_stat2 < 0) + return mdio_stat2; + + if (xpcs->qps615 && interface != PHY_INTERFACE_MODE_USXGMII) { + ret = xpcs_modify_vpcs(xpcs, DW_VR_XS_PCS_DIG_CTRL1, + DW_USXGMII_EN | BIT(2), 0); + if (ret < 0) + return ret; + + ret = xpcs_modify_vpcs(xpcs, DW_VR_XS_PCS_KR_CTRL, + DW_USXG_MODE | DW_USXG_2PT5G_GMII, 0); + if (ret < 0) + return ret; } - return ret; + /* + * If this XPCS supports 10Gbase-R then that will be the default + * operating mode. There are several interface modes where this default + * is unhelpful. Change the operating mode for interfaces were we know + * the default is wrong, and restore the default otherwise. + */ + if (mdio_stat2 & MDIO_PCS_STAT2_10GBR) { + switch (interface) { + case PHY_INTERFACE_MODE_SGMII: + case PHY_INTERFACE_MODE_1000BASEX: + case PHY_INTERFACE_MODE_2500BASEX: + if (xpcs->qps615) { + u16 type = interface == PHY_INTERFACE_MODE_2500BASEX && + !xpcs->qps615_sgmii_plus ? + QPS615_PCS_CTRL2_2500BASEX : + MDIO_PCS_CTRL2_10GBX; + + ret = xpcs_modify(xpcs, MDIO_MMD_PCS, MDIO_CTRL2, + QPS615_PCS_CTRL2_TYPE_MASK, + type); + } else { + ret = xpcs_write(xpcs, MDIO_MMD_PCS, MDIO_CTRL2, + MDIO_PCS_CTRL2_TYPE + 1); + } + if (ret < 0) + return ret; + break; + default: + if (xpcs->qps615) + ret = xpcs_modify(xpcs, MDIO_MMD_PCS, MDIO_CTRL2, + QPS615_PCS_CTRL2_TYPE_MASK, + MDIO_PCS_CTRL2_10GBR); + else + ret = xpcs_write(xpcs, MDIO_MMD_PCS, MDIO_CTRL2, + MDIO_PCS_CTRL2_10GBR); + if (ret < 0) + return ret; + break; + } + } + + xpcs->interface = interface; + + return 0; } 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; + bool switch_first; + bool force_reset; int ret; - ret = xpcs_switch_interface_mode(xpcs, interface); - if (ret) - dev_err(&xpcs->mdiodev->dev, "switch interface failed: %pe\n", - ERR_PTR(ret)); - - if (!xpcs->need_reset) - return; + /* + * According to the XPCS datasheet, a soft reset is required to initiate + * Clause 37 auto-negotiation when the XPCS switches interface modes. + */ + force_reset = interface == PHY_INTERFACE_MODE_SGMII; + switch_first = xpcs->qps615 && force_reset; - compat = xpcs_find_compat(xpcs, interface); - if (!compat) { - dev_err(&xpcs->mdiodev->dev, "unsupported interface %s\n", - phy_modes(interface)); - return; + if (switch_first) { + ret = xpcs_switch_interface_mode(xpcs, interface); + if (ret) { + dev_err(&xpcs->mdiodev->dev, + "switch interface failed: %pe\n", ERR_PTR(ret)); + return; + } } - ret = xpcs_soft_reset(xpcs, compat); - if (ret) - dev_err(&xpcs->mdiodev->dev, "soft reset failed: %pe\n", - ERR_PTR(ret)); + if (force_reset || xpcs->need_reset) { + compat = xpcs_find_compat(xpcs, interface); + if (!compat) { + dev_err(&xpcs->mdiodev->dev, "unsupported interface %s\n", + phy_modes(interface)); + return; + } - xpcs->need_reset = false; + ret = xpcs_soft_reset(xpcs, compat); + if (ret) { + dev_err(&xpcs->mdiodev->dev, "soft reset failed: %pe\n", + ERR_PTR(ret)); + return; + } + + xpcs->need_reset = false; + } + + if (!switch_first) { + ret = xpcs_switch_interface_mode(xpcs, interface); + if (ret) + dev_err(&xpcs->mdiodev->dev, + "switch interface failed: %pe\n", ERR_PTR(ret)); + } } static int xpcs_config_operating_mode(struct dw_xpcs *xpcs, int an_mode) @@ -759,19 +903,20 @@ static int xpcs_config_operating_mode(struct dw_xpcs *xpcs, int an_mode) if (mdio_stat2 < 0) return mdio_stat2; - /* - * If this XPCS supports 10Gbase-R then it will be the default - * which prevents 1000base-X and slower from working correctly. - * - * Why are we writing MDIO_PCS_CTRL2_TYPE + 1? We want the modal - * behaviour that comes when we pick a reserved value. XPCS - * allocates extra bits to this field and allocates values from - * 15 down so MDIO_PCS_CTRL2_TYPE + 1 is the value likely to - * be allocated last (and hopefully never). - */ if (mdio_stat2 & MDIO_PCS_STAT2_10GBR) { - ret = xpcs_write(xpcs, MDIO_MMD_PCS, MDIO_CTRL2, - MDIO_PCS_CTRL2_TYPE + 1); + if (xpcs->qps615) { + u16 type = an_mode == DW_2500BASEX && + !xpcs->qps615_sgmii_plus ? + QPS615_PCS_CTRL2_2500BASEX : + MDIO_PCS_CTRL2_10GBX; + + ret = xpcs_modify(xpcs, MDIO_MMD_PCS, MDIO_CTRL2, + QPS615_PCS_CTRL2_TYPE_MASK, + type); + } else { + ret = xpcs_write(xpcs, MDIO_MMD_PCS, MDIO_CTRL2, + MDIO_PCS_CTRL2_TYPE + 1); + } if (ret < 0) return ret; } @@ -784,9 +929,13 @@ static int xpcs_config_operating_mode(struct dw_xpcs *xpcs, int an_mode) static int xpcs_config_aneg_c37_sgmii(struct dw_xpcs *xpcs, unsigned int neg_mode) { + bool use_inband_status; int ret, mdio_ctrl, tx_conf; u16 mask, val; + use_inband_status = neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED || + xpcs->qps615_oob; + /* For AN for C37 SGMII mode, the settings are :- * 1) VR_MII_MMD_CTRL Bit(12) [AN_ENABLE] = 0b (Disable SGMII AN in case it is already enabled) @@ -816,6 +965,10 @@ static int xpcs_config_aneg_c37_sgmii(struct dw_xpcs *xpcs, return ret; } + if (xpcs->qps615_oob) + mdio_ctrl = (mdio_ctrl & ~DW_USXGMII_CTRL_MASK) | + DW_USXGMII_1000 | DW_USXGMII_FULL; + mask = DW_VR_MII_PCS_MODE_MASK | DW_VR_MII_TX_CONFIG_MASK; val = FIELD_PREP(DW_VR_MII_PCS_MODE_MASK, DW_VR_MII_PCS_MODE_C37_SGMII); @@ -838,7 +991,7 @@ static int xpcs_config_aneg_c37_sgmii(struct dw_xpcs *xpcs, val = 0; mask = DW_VR_MII_DIG_CTRL1_2G5_EN | DW_VR_MII_DIG_CTRL1_MAC_AUTO_SW; - if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) + if (use_inband_status && !xpcs->qps615_oob) val = DW_VR_MII_DIG_CTRL1_MAC_AUTO_SW; if (xpcs->info.pma == WX_TXGBE_XPCS_PMA_10G_ID) { @@ -850,9 +1003,15 @@ static int xpcs_config_aneg_c37_sgmii(struct dw_xpcs *xpcs, if (ret < 0) return ret; - if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) + if (use_inband_status) { + ret = xpcs_write(xpcs, MDIO_MMD_VEND2, + DW_VR_MII_AN_INTR_STS, 0); + if (ret < 0) + return ret; + ret = xpcs_write(xpcs, MDIO_MMD_VEND2, MII_BMCR, mdio_ctrl | BMCR_ANENABLE); + } return ret; } @@ -930,6 +1089,16 @@ static int xpcs_config_2500basex(struct dw_xpcs *xpcs) { int ret; + if (xpcs->qps615_sgmii_plus) { + ret = xpcs_modify(xpcs, MDIO_MMD_VEND2, DW_VR_MII_AN_CTRL, + DW_VR_MII_PCS_MODE_MASK | + DW_VR_MII_TX_CONFIG_MASK, + FIELD_PREP(DW_VR_MII_PCS_MODE_MASK, + DW_VR_MII_PCS_MODE_C37_SGMII)); + if (ret < 0) + return ret; + } + ret = xpcs_modify(xpcs, MDIO_MMD_VEND2, DW_VR_MII_DIG_CTRL1, DW_VR_MII_DIG_CTRL1_2G5_EN | DW_VR_MII_DIG_CTRL1_MAC_AUTO_SW, @@ -942,6 +1111,46 @@ static int xpcs_config_2500basex(struct dw_xpcs *xpcs) BMCR_SPEED1000); } +static int xpcs_config_usxgmii(struct dw_xpcs *xpcs) +{ + int ret, val; + + /* Select the 10GBASE-R PCS type used as the base for USXGMII */ + ret = xpcs_modify(xpcs, MDIO_MMD_PCS, MDIO_CTRL2, + xpcs->qps615 ? QPS615_PCS_CTRL2_TYPE_MASK : + MDIO_PCS_CTRL2_TYPE, MDIO_PCS_CTRL2_10GBR); + if (ret < 0) + return ret; + + ret = xpcs_modify_vpcs(xpcs, DW_VR_XS_PCS_DIG_CTRL1, + DW_USXGMII_EN, DW_USXGMII_EN); + if (ret < 0) + return ret; + + ret = xpcs_modify_vpcs(xpcs, DW_VR_XS_PCS_KR_CTRL, + DW_USXG_MODE | DW_USXG_2PT5G_GMII, + 0); + if (ret < 0) + return ret; + + /* Apply the configuration with a vendor soft reset and wait for it + * to self-clear. + */ + ret = xpcs_modify_vpcs(xpcs, DW_VR_XS_PCS_DIG_CTRL1, + DW_VR_RST, DW_VR_RST); + if (ret < 0) + return ret; + + ret = read_poll_timeout(xpcs_read_vpcs, val, + val < 0 || !(val & DW_VR_RST), + 1000, 50000, false, + xpcs, DW_VR_XS_PCS_DIG_CTRL1); + if (val < 0) + return val; + + return ret; +} + static int xpcs_do_config(struct dw_xpcs *xpcs, phy_interface_t interface, const unsigned long *advertising, unsigned int neg_mode) @@ -953,6 +1162,12 @@ static int xpcs_do_config(struct dw_xpcs *xpcs, phy_interface_t interface, if (!compat) return -ENODEV; + if (interface == PHY_INTERFACE_MODE_USXGMII) { + ret = xpcs_config_usxgmii(xpcs); + if (ret < 0) + return ret; + } + ret = xpcs_config_operating_mode(xpcs, compat->an_mode); if (ret < 0) return ret; @@ -1264,12 +1479,13 @@ static void xpcs_link_up_sgmii_1000basex(struct dw_xpcs *xpcs, int speed, int duplex) { u16 an_enable; - int ret; + int an_speed, an_sts, ret; if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) return; - an_enable = (interface == PHY_INTERFACE_MODE_SGMII ? BMCR_ANENABLE : 0); + an_enable = interface == PHY_INTERFACE_MODE_SGMII && xpcs->qps615_oob ? + BMCR_ANENABLE : 0; if (interface == PHY_INTERFACE_MODE_1000BASEX) { if (speed != SPEED_1000) { @@ -1287,9 +1503,55 @@ static void xpcs_link_up_sgmii_1000basex(struct dw_xpcs *xpcs, ret = xpcs_write(xpcs, MDIO_MMD_VEND2, MII_BMCR, mii_bmcr_encode_fixed(speed, duplex) | an_enable); - if (ret) + if (ret) { dev_err(&xpcs->mdiodev->dev, "%s: xpcs_write returned %pe\n", __func__, ERR_PTR(ret)); + return; + } + + if (!xpcs->qps615_oob || interface != PHY_INTERFACE_MODE_SGMII) + return; + + switch (speed) { + case SPEED_10: + an_speed = DW_VR_MII_C37_ANSGM_SP_10; + break; + case SPEED_100: + an_speed = DW_VR_MII_C37_ANSGM_SP_100; + break; + case SPEED_1000: + an_speed = DW_VR_MII_C37_ANSGM_SP_1000; + break; + default: + dev_err(&xpcs->mdiodev->dev, + "QPS615 SGMII cannot resolve unsupported speed %d\n", + speed); + return; + } + + ret = read_poll_timeout(xpcs_read, an_sts, + an_sts < 0 || + ((an_sts & (DW_VR_MII_C37_ANSGM_SP_LNKSTS | + DW_VR_MII_AN_STS_C37_ANSGM_FD | + DW_VR_MII_AN_STS_C37_ANCMPLT_INTR)) == + (DW_VR_MII_C37_ANSGM_SP_LNKSTS | + (duplex == DUPLEX_FULL ? + DW_VR_MII_AN_STS_C37_ANSGM_FD : 0) | + DW_VR_MII_AN_STS_C37_ANCMPLT_INTR) && + FIELD_GET(DW_VR_MII_AN_STS_C37_ANSGM_SP, an_sts) == + an_speed), + 1000, 60000000, false, xpcs, MDIO_MMD_VEND2, + DW_VR_MII_AN_INTR_STS); + if (an_sts < 0) + ret = an_sts; + if (ret) { + dev_err(&xpcs->mdiodev->dev, + "QPS615 SGMII Clause 37 did not resolve speed %d duplex %d: status=%04x, error=%pe\n", + speed, duplex, an_sts < 0 ? 0 : an_sts, + ERR_PTR(ret)); + return; + } + } static void xpcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode, @@ -1378,6 +1640,15 @@ void xpcs_config_eee_mult_fact(struct dw_xpcs *xpcs, u8 mult_fact) } EXPORT_SYMBOL_GPL(xpcs_config_eee_mult_fact); +void xpcs_config_qps615(struct dw_xpcs *xpcs, bool out_of_band, + bool sgmii_plus) +{ + xpcs->qps615 = true; + xpcs->qps615_oob = out_of_band; + xpcs->qps615_sgmii_plus = sgmii_plus; +} +EXPORT_SYMBOL_GPL(xpcs_config_qps615); + static int xpcs_read_ids(struct dw_xpcs *xpcs) { int ret; diff --git a/drivers/net/pcs/pcs-xpcs.h b/drivers/net/pcs/pcs-xpcs.h index 929fa238445ed..0617dbff4f18c 100644 --- a/drivers/net/pcs/pcs-xpcs.h +++ b/drivers/net/pcs/pcs-xpcs.h @@ -16,6 +16,8 @@ #define DW_USXGMII_RST BIT(10) #define DW_USXGMII_EN BIT(9) #define DW_VR_XS_PCS_DIG_CTRL1 0x0000 +#define QPS615_PCS_CTRL2_TYPE_MASK GENMASK(3, 0) +#define QPS615_PCS_CTRL2_2500BASEX 0x000e #define DW_VR_RST BIT(15) #define DW_EN_VSMMD1 BIT(13) #define DW_CL37_BP BIT(12) @@ -24,9 +26,18 @@ #define DW_PSEQ_ST GENMASK(4, 2) #define DW_PSEQ_ST_GOOD FIELD_PREP(GENMASK(4, 2), 0x4) +#define DW_VR_XS_PCS_KR_CTRL 0x0007 + +#define DW_USXG_2PT5G_GMII BIT(13) +#define DW_USXG_MODE GENMASK(12, 10) +#define DW_USXG_MODE_10G FIELD_PREP(DW_USXG_MODE, 0x0) + /* SR_MII */ #define DW_USXGMII_FULL BIT(8) #define DW_USXGMII_SS_MASK (BIT(13) | BIT(6) | BIT(5)) + +#define DW_USXGMII_CTRL_MASK (DW_USXGMII_SS_MASK | \ + DW_USXGMII_FULL | BMCR_ANENABLE) #define DW_USXGMII_10000 (BIT(13) | BIT(6)) #define DW_USXGMII_5000 (BIT(13) | BIT(5)) #define DW_USXGMII_2500 (BIT(5)) @@ -113,6 +124,10 @@ struct dw_xpcs { struct phylink_pcs pcs; phy_interface_t interface; bool need_reset; + + bool qps615; + bool qps615_oob; + bool qps615_sgmii_plus; u8 eee_mult_fact; }; diff --git a/drivers/net/phy/as21xxx.c b/drivers/net/phy/as21xxx.c index d5738117eca68..c7b0eda729ac0 100644 --- a/drivers/net/phy/as21xxx.c +++ b/drivers/net/phy/as21xxx.c @@ -7,9 +7,15 @@ #include #include +#include #include #include #include +#include + +#define VEND1_CHIP_CTRL 0x2 +#define VEND1_CHIP_CTRL_XFI_ACCESS BIT(15) +#define VEND1_CHIP_CTRL_SXGMII_MODE BIT(13) #define VEND1_GLB_REG_CPU_RESET_ADDR_LO_BASEADDR 0x3 #define VEND1_GLB_REG_CPU_RESET_ADDR_HI_BASEADDR 0x4 @@ -21,6 +27,9 @@ BIT(_n)) #define VEND1_FW_START_ADDR 0x100 +#define AS22XXX_AN_STATES1 0x8005 +#define AS22XXX_AN_STATES1_ARB_MASK GENMASK(15, 12) +#define AS22XXX_LINK_GOOD 9 #define VEND1_GLB_REG_MDIO_INDIRECT_ADDRCMD 0x101 #define VEND1_GLB_REG_MDIO_INDIRECT_LOAD 0x102 @@ -64,6 +73,37 @@ #define IPC_CMD_TEMP_MON 0x15 /* Temperature monitoring function */ #define IPC_CMD_SET_LED 0x23 /* Set led */ +#define IPC_OPCODE_DBGCMD 0x16 +#define IPC_OPCODE_POLL 0x17 +#define IPC_OPCODE_WBUF 0x18 +#define IPC_OPCODE_RBUF 0x19 + +#define IPC_DBGCMD_DPC 0x8b + +#define IPC_DBGCMD_SDS 0x96 + +#define IPC_SDS_RST 26 + +#define IPC_DPC_SDS_SET_CFG 0x2 +#define IPC_DPC_SDS_GET_CFG 0x3 +#define IPC_DPC_ETH_STS_HW_UPD_CFG 0x8 +#define IPC_DPC_FC_SET 0xd + +#define AEON_SDS_PCS_SEL_NORMAL 0 +#define AEON_SDS_PCS_SEL_64B66B 1 +#define AEON_SDS_SPD_1G 0 +#define AEON_SDS_SPD_10G 3 + +#define AEON_CU_AN_TOP_SPD_10M 0x02 +#define AEON_CU_AN_TOP_SPD_100M 0x04 +#define AEON_CU_AN_TOP_SPD_1G 0x08 +#define AEON_CU_AN_TOP_SPD_2500M 0x10 +#define AEON_CU_AN_TOP_SPD_5G 0x20 +#define AEON_CU_AN_TOP_SPD_10G 0x40 + +#define AEON_DPC_MAX_POLL 100 +#define AEON_DPC_RBUF_MAX 144 + #define VEND1_IPC_STS 0x5802 #define AEON_IPC_STS_PARITY BIT(15) #define AEON_IPC_STS_SIZE GENMASK(14, 10) @@ -101,6 +141,8 @@ /* CFG DIRECT sub command */ #define IPC_CFG_PARAM_DIRECT_NG_PHYCTRL 0x1 #define IPC_CFG_PARAM_DIRECT_CU_AN 0x2 +#define IPC_CMD_CFG_CU_AN_RESTART 0xa +#define IPC_CMD_CFG_CU_AN_TOP_SPD 0xc #define IPC_CFG_PARAM_DIRECT_SDS_PCS 0x3 #define IPC_CFG_PARAM_DIRECT_AUTO_EEE 0x4 #define IPC_CFG_PARAM_DIRECT_SDS_PMA 0x5 @@ -117,6 +159,30 @@ #define AS21XXX_MDIO_AN_C22 0xffe0 +#define AS22XXX_SDS_MII_ADV_LINK BIT(15) +#define AS22XXX_SDS_MII_ADV_ACK BIT(14) +#define AS22XXX_SDS_MII_ADV_REMOTE_FAULT BIT(13) +#define AS22XXX_SDS_MII_ADV_FULL BIT(12) +#define AS22XXX_SDS_MII_ADV_SPEED GENMASK(11, 10) +#define AS22XXX_SDS_MII_ADV_EEE BIT(9) +#define AS22XXX_SDS_MII_ADV_EEE_CLK_STOP BIT(8) +#define AS22XXX_SDS_MII_ADV_RESERVED_7 BIT(7) +#define AS22XXX_SDS_MII_ADV_SELECTOR BIT(0) +#define AS22XXX_SDS_MII_ADV_CONTROL_MASK (AS22XXX_SDS_MII_ADV_LINK | \ + AS22XXX_SDS_MII_ADV_ACK | \ + AS22XXX_SDS_MII_ADV_REMOTE_FAULT | \ + AS22XXX_SDS_MII_ADV_FULL | \ + AS22XXX_SDS_MII_ADV_SPEED | \ + AS22XXX_SDS_MII_ADV_RESERVED_7 | \ + AS22XXX_SDS_MII_ADV_SELECTOR) + +#define AS22XXX_SDS_PCS_STS20 0x0020 +#define AS22XXX_SDS_PCS_LINK_UP BIT(12) +#define AS22XXX_SDS_PCS_HIGH_BER BIT(1) +#define AS22XXX_SDS_PCS_BLOCK_LOCK BIT(0) +#define AS22XXX_SDS_READY_POLL_US 100000 +#define AS22XXX_SDS_READY_TIMEOUT_US 10000000 + #define PHY_ID_AS21XXX 0x75009410 /* AS21xxx ID Legend * AS21x1xxB1 @@ -139,6 +205,7 @@ #define PHY_ID_AS21510PB1 0x75009472 #define PHY_ID_AS21210JB1 0x75009482 #define PHY_ID_AS21210PB1 0x75009492 +#define PHY_ID_AS22XXX 0x750094a1 #define PHY_VENDOR_AEONSEMI 0x75009400 #define AEON_MAX_LEDS 5 @@ -177,6 +244,12 @@ enum as21xxx_led_event { VEND1_LED_REG_A_EVENT_OFF, }; +enum as22xxx_system_sync_state { + AS22XXX_SYSTEM_SYNC_IDLE, + AS22XXX_SYSTEM_SYNC_NEEDS_PHY_ENABLE, + AS22XXX_SYSTEM_SYNC_WAIT_PHY_ENABLE, +}; + struct as21xxx_led_pattern_info { unsigned int pattern; u16 val; @@ -184,8 +257,16 @@ struct as21xxx_led_pattern_info { struct as21xxx_priv { bool parity_status; + bool mode_switch; /* Protect concurrent IPC access */ struct mutex ipc_lock; + + struct mutex sds_lock; + + phy_interface_t sds_interface; + int sds_speed; + + enum as22xxx_system_sync_state system_sync_state; }; static struct as21xxx_led_pattern_info as21xxx_led_supported_pattern[] = { @@ -452,9 +533,9 @@ static int aeon_ipc_send_cmd(struct phy_device *phydev, * If data not NULL, return number of Bytes received from IPC or * a negative error. */ -static int aeon_ipc_send_msg(struct phy_device *phydev, - u16 opcode, u16 *data, unsigned int data_len, - u16 *ret_data) +static int aeon_ipc_send_msg_locked(struct phy_device *phydev, u16 opcode, + u16 *data, unsigned int data_len, + u16 *ret_data) { struct as21xxx_priv *priv = phydev->priv; unsigned int ret_size; @@ -465,17 +546,18 @@ static int aeon_ipc_send_msg(struct phy_device *phydev, /* IPC have a max of 8 register to transfer data, * make sure we never exceed this. */ - if (data_len > AEON_IPC_DATA_MAX) + if (data_len > AEON_IPC_DATA_MAX || (data_len && !data)) return -EINVAL; - for (i = 0; i < data_len / sizeof(u16); i++) - phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_IPC_DATA(i), - data[i]); - cmd = FIELD_PREP(AEON_IPC_CMD_SIZE, data_len) | FIELD_PREP(AEON_IPC_CMD_OPCODE, opcode); - mutex_lock(&priv->ipc_lock); + for (i = 0; i < DIV_ROUND_UP(data_len, sizeof(u16)); i++) { + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_IPC_DATA(i), + data[i]); + if (ret) + goto out; + } ret = aeon_ipc_send_cmd(phydev, priv, cmd, &ret_sts); if (ret) { @@ -484,7 +566,7 @@ static int aeon_ipc_send_msg(struct phy_device *phydev, goto out; } - if (!data) + if (!ret_data) goto out; if ((ret_sts & AEON_IPC_STS_STATUS) == AEON_IPC_STS_STATUS_ERROR) { @@ -514,11 +596,183 @@ static int aeon_ipc_send_msg(struct phy_device *phydev, ret = ret_size; out: + return ret; +} + +static int aeon_ipc_send_msg(struct phy_device *phydev, u16 opcode, u16 *data, + unsigned int data_len, u16 *ret_data) +{ + struct as21xxx_priv *priv = phydev->priv; + int ret; + + mutex_lock(&priv->ipc_lock); + ret = aeon_ipc_send_msg_locked(phydev, opcode, data, data_len, + ret_data); mutex_unlock(&priv->ipc_lock); return ret; } +static int aeon_ipc_sync_parity(struct phy_device *phydev, + struct as21xxx_priv *priv); +static int aeon_ipc_sync_parity_locked(struct phy_device *phydev, + struct as21xxx_priv *priv); + +static int aeon_cfg_xfer(struct phy_device *phydev, u16 section, u16 sub, + const void *payload, unsigned int payload_len, + void *rbuf, unsigned int rbytes) +{ + u16 wbuf[AEON_IPC_DATA_NUM_REGISTERS] = {}; + u16 data[AEON_IPC_DATA_NUM_REGISTERS] = {}; + struct as21xxx_priv *priv = phydev->priv; + unsigned int i, done, chunk; + u16 cmd, hdr[3], ret_sts = 0; + u8 *out = rbuf; + int ret, iter; + + if (payload_len > sizeof(wbuf) || rbytes > AEON_DPC_RBUF_MAX) + return -EINVAL; + + hdr[0] = (section << 8) | sub; + hdr[1] = payload_len; + hdr[2] = 0; + + mutex_lock(&priv->ipc_lock); + + ret = aeon_ipc_send_msg_locked(phydev, IPC_OPCODE_DBGCMD, hdr, + sizeof(hdr), data); + if (ret < 0) { + phydev_err(phydev, + "firmware command %02x/%02x start failed: %d\n", + section, sub, ret); + goto out; + } + + ret = aeon_ipc_sync_parity_locked(phydev, priv); + if (ret) + goto out; + + if (payload && payload_len) { + memcpy(wbuf, payload, payload_len); + ret = aeon_ipc_send_msg_locked(phydev, IPC_OPCODE_WBUF, wbuf, + payload_len, data); + if (ret < 0) { + phydev_err(phydev, + "firmware command %02x/%02x payload failed: %d\n", + section, sub, ret); + goto out; + } + + ret = aeon_ipc_sync_parity_locked(phydev, priv); + if (ret) + goto out; + } + + for (iter = 0; iter < AEON_DPC_MAX_POLL; iter++) { + cmd = FIELD_PREP(AEON_IPC_CMD_OPCODE, IPC_OPCODE_POLL); + ret = aeon_ipc_send_cmd(phydev, priv, cmd, &ret_sts); + if (ret) { + phydev_err(phydev, "firmware command %02x/%02x poll failed: %d sts %04x\n", + section, sub, ret, ret_sts); + goto out; + } + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_IPC_DATA(0)); + if (ret < 0) + goto out; + if (ret) + break; + } + + if (iter == AEON_DPC_MAX_POLL) { + phydev_err(phydev, "firmware command %02x/%02x timed out\n", + section, sub); + ret = -ETIMEDOUT; + goto out; + } + + ret = 0; + if (!rbuf || !rbytes) + goto out_sync; + + for (done = 0; done < rbytes; done += chunk) { + chunk = min_t(unsigned int, rbytes - done, AEON_IPC_DATA_MAX); + + cmd = FIELD_PREP(AEON_IPC_CMD_OPCODE, IPC_OPCODE_RBUF); + ret = aeon_ipc_send_cmd(phydev, priv, cmd, &ret_sts); + if (ret) { + phydev_err(phydev, "firmware command %02x/%02x result failed: %d sts %04x\n", + section, sub, ret, ret_sts); + goto out; + } + + for (i = 0; i < AEON_IPC_DATA_NUM_REGISTERS; i++) { + ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, + VEND1_IPC_DATA(i)); + if (ret < 0) + goto out; + + data[i] = ret; + } + + memcpy(out + done, data, chunk); + } + + ret = 0; + +out_sync: + ret = aeon_ipc_sync_parity_locked(phydev, priv); + +out: + mutex_unlock(&priv->ipc_lock); + return ret; +} + +static int aeon_dpc_cmd(struct phy_device *phydev, u16 sub, + const void *payload, unsigned int payload_len) +{ + return aeon_cfg_xfer(phydev, IPC_DBGCMD_DPC, sub, payload, payload_len, + NULL, 0); +} + +static int aeon_dpc_query(struct phy_device *phydev, u16 sub, void *rbuf, + unsigned int rbytes) +{ + return aeon_cfg_xfer(phydev, IPC_DBGCMD_DPC, sub, NULL, 0, rbuf, + rbytes); +} + +static int aeon_dpc_fc_apply_mode(struct phy_device *phydev, u8 pcs_sel) +{ + u8 enable = pcs_sel == AEON_SDS_PCS_SEL_NORMAL; + + return aeon_dpc_cmd(phydev, IPC_DPC_FC_SET, &enable, sizeof(enable)); +} + +static int aeon_dpc_sds_get(struct phy_device *phydev, u8 *pcs_sel, + u8 *sds_spd) +{ + u8 cfg[AEON_IPC_DATA_MAX] = {}; + int ret; + + ret = aeon_dpc_query(phydev, IPC_DPC_SDS_GET_CFG, cfg, sizeof(cfg)); + if (ret) + return ret; + + *pcs_sel = cfg[0]; + *sds_spd = cfg[1]; + + return 0; +} + +static int aeon_dpc_sds_set(struct phy_device *phydev, u8 pcs_sel, u8 sds_spd) +{ + u16 payload = pcs_sel | (sds_spd << 8); + + return aeon_dpc_cmd(phydev, IPC_DPC_SDS_SET_CFG, &payload, + sizeof(payload)); +} + static int aeon_ipc_noop(struct phy_device *phydev, struct as21xxx_priv *priv, u16 *ret_sts) { @@ -535,16 +789,16 @@ static int aeon_ipc_noop(struct phy_device *phydev, * to handle the packet only for the second one. This way * we make sure we are sync for every next cmd. */ -static int aeon_ipc_sync_parity(struct phy_device *phydev, - struct as21xxx_priv *priv) +static int aeon_ipc_sync_parity_locked(struct phy_device *phydev, + struct as21xxx_priv *priv) { - u16 ret_sts; + u16 ret_sts = 0; int ret; - mutex_lock(&priv->ipc_lock); - /* Send NOP with no parity */ - aeon_ipc_noop(phydev, priv, NULL); + ret = aeon_ipc_noop(phydev, priv, NULL); + if (ret) + return ret; /* Reset packet parity */ priv->parity_status = false; @@ -552,8 +806,6 @@ static int aeon_ipc_sync_parity(struct phy_device *phydev, /* Send second NOP with no parity */ ret = aeon_ipc_noop(phydev, priv, &ret_sts); - mutex_unlock(&priv->ipc_lock); - /* We expect to return -EINVAL */ if (ret != -EINVAL) return ret; @@ -567,26 +819,43 @@ static int aeon_ipc_sync_parity(struct phy_device *phydev, return 0; } -static int aeon_ipc_get_fw_version(struct phy_device *phydev) +static int aeon_ipc_sync_parity(struct phy_device *phydev, + struct as21xxx_priv *priv) { - u16 ret_data[AEON_IPC_DATA_NUM_REGISTERS], data[1]; - char fw_version[AEON_IPC_DATA_MAX + 1]; int ret; - data[0] = IPC_INFO_VERSION; + mutex_lock(&priv->ipc_lock); + ret = aeon_ipc_sync_parity_locked(phydev, priv); + mutex_unlock(&priv->ipc_lock); + + return ret; +} + +static int aeon_ipc_get_fw_version(struct phy_device *phydev) +{ + u16 ret_data[AEON_IPC_DATA_NUM_REGISTERS], data[] = { IPC_INFO_VERSION }; + int ret; ret = aeon_ipc_send_msg(phydev, IPC_CMD_INFO, data, sizeof(data), ret_data); - if (ret < 0) - return ret; - /* Make sure FW version is NULL terminated */ - memcpy(fw_version, ret_data, ret); - fw_version[ret] = '\0'; + return ret < 0 ? ret : 0; +} + +static int aeon_ipc_phy_enable_mode(struct phy_device *phydev, bool enable) +{ + struct as21xxx_priv *priv = phydev->priv; + u16 data[] = { IPC_SYS_CPU_PHY_ENABLE, enable }; + int ret; - phydev_info(phydev, "Firmware Version: %s\n", fw_version); + mutex_lock(&priv->ipc_lock); + ret = aeon_ipc_sync_parity_locked(phydev, priv); + if (!ret) + ret = aeon_ipc_send_msg_locked(phydev, IPC_CMD_SYS_CPU, data, + sizeof(data), NULL); + mutex_unlock(&priv->ipc_lock); - return 0; + return ret; } static int aeon_dpc_ra_enable(struct phy_device *phydev) @@ -600,6 +869,74 @@ static int aeon_dpc_ra_enable(struct phy_device *phydev) sizeof(data), NULL); } +static int aeon_dpc_eth_sts_hw_update(struct phy_device *phydev, bool enable) +{ + u8 value = enable ? 1 : 0; + + return aeon_dpc_cmd(phydev, IPC_DPC_ETH_STS_HW_UPD_CFG, &value, + sizeof(value)); +} + +static int as22xxx_restart_an(struct phy_device *phydev) +{ + u16 data[3]; + + data[0] = IPC_CFG_PARAM_DIRECT; + data[1] = IPC_CFG_PARAM_DIRECT_CU_AN; + data[2] = IPC_CMD_CFG_CU_AN_RESTART; + + return aeon_ipc_send_msg(phydev, IPC_CMD_CFG_PARAM, data, + sizeof(data), NULL); +} + +static int as22xxx_set_top_speed(struct phy_device *phydev) +{ + u16 data[4] = { + IPC_CFG_PARAM_DIRECT, + IPC_CFG_PARAM_DIRECT_CU_AN, + IPC_CMD_CFG_CU_AN_TOP_SPD, + AEON_CU_AN_TOP_SPD_10M, + }; + unsigned long *advertising = phydev->advertising; + + if (linkmode_test_bit(ETHTOOL_LINK_MODE_10000baseT_Full_BIT, + advertising)) + data[3] = AEON_CU_AN_TOP_SPD_10G; + else if (linkmode_test_bit(ETHTOOL_LINK_MODE_5000baseT_Full_BIT, + advertising)) + data[3] = AEON_CU_AN_TOP_SPD_5G; + else if (linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, + advertising)) + data[3] = AEON_CU_AN_TOP_SPD_2500M; + else if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, + advertising)) + data[3] = AEON_CU_AN_TOP_SPD_1G; + else if (linkmode_test_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, + advertising)) + data[3] = AEON_CU_AN_TOP_SPD_100M; + + return aeon_ipc_send_msg(phydev, IPC_CMD_CFG_PARAM, data, + sizeof(data), NULL); +} + +static int as22xxx_config_aneg(struct phy_device *phydev) +{ + int ret; + + if (phydev->autoneg == AUTONEG_DISABLE) + return genphy_c45_pma_setup_forced(phydev); + + ret = genphy_c45_an_config_aneg(phydev); + if (ret < 0) + return ret; + + ret = as22xxx_set_top_speed(phydev); + if (ret) + return ret; + + return as22xxx_restart_an(phydev); +} + static int as21xxx_probe(struct phy_device *phydev) { struct as21xxx_priv *priv; @@ -615,6 +952,9 @@ static int as21xxx_probe(struct phy_device *phydev) &priv->ipc_lock); if (ret) return ret; + ret = devm_mutex_init(&phydev->mdio.dev, &priv->sds_lock); + if (ret) + return ret; ret = aeon_ipc_sync_parity(phydev, priv); if (ret) @@ -768,87 +1108,1075 @@ static int as21xxx_read_status(struct phy_device *phydev) return 0; } -static int as21xxx_led_brightness_set(struct phy_device *phydev, - u8 index, enum led_brightness value) +static int as22xxx_read_link(struct phy_device *phydev, int *bmcr) { - u16 val = VEND1_LED_REG_A_EVENT_OFF; + int status = 0; + bool link_up; - if (index > AEON_MAX_LEDS) - return -EINVAL; + *bmcr = phy_read_mmd(phydev, MDIO_MMD_AN, + AS21XXX_MDIO_AN_C22 + MII_BMCR); + if (*bmcr < 0) + return *bmcr; - if (value) - val = VEND1_LED_REG_A_EVENT_ON; + if (*bmcr & BMCR_ANRESTART) + goto done; - return phy_modify_mmd(phydev, MDIO_MMD_VEND1, - VEND1_LED_REG(index), - VEND1_LED_REG_A_EVENT, - FIELD_PREP(VEND1_LED_REG_A_EVENT, val)); + status = phy_read_mmd(phydev, MDIO_MMD_AN, AS22XXX_AN_STATES1); + if (status < 0) + return status; + +done: + link_up = FIELD_GET(AS22XXX_AN_STATES1_ARB_MASK, status) == + AS22XXX_LINK_GOOD; + phydev->link = link_up; + phydev->autoneg_complete = link_up; + + if (phydev->autoneg == AUTONEG_ENABLE && !phydev->autoneg_complete) + phydev->link = 0; + + return 0; } -static int as21xxx_led_hw_is_supported(struct phy_device *phydev, u8 index, - unsigned long rules) +static int as22xxx_read_lpa(struct phy_device *phydev) { - int i; + int lpa, ret; - if (index > AEON_MAX_LEDS) - return -EINVAL; + if (phydev->autoneg == AUTONEG_ENABLE && !phydev->autoneg_complete) { + mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising, 0); + mii_lpa_mod_linkmode_lpa_t(phydev->lp_advertising, 0); + return 0; + } - for (i = 0; i < ARRAY_SIZE(as21xxx_led_supported_pattern); i++) - if (rules == as21xxx_led_supported_pattern[i].pattern) - return 0; + ret = as21xxx_read_c22_lpa(phydev); + if (ret) + return ret; - return -EOPNOTSUPP; -} + lpa = phy_read_mmd(phydev, MDIO_MMD_AN, + AS21XXX_MDIO_AN_C22 + MII_LPA); + if (lpa < 0) + return lpa; -static int as21xxx_led_hw_control_get(struct phy_device *phydev, u8 index, - unsigned long *rules) -{ - int i, val; + mii_lpa_mod_linkmode_lpa_t(phydev->lp_advertising, lpa); - if (index > AEON_MAX_LEDS) - return -EINVAL; + lpa = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_STAT); + if (lpa < 0) + return lpa; - val = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_LED_REG(index)); - if (val < 0) - return val; + mii_10gbt_stat_mod_linkmode_lpa_t(phydev->lp_advertising, lpa); - val = FIELD_GET(VEND1_LED_REG_A_EVENT, val); - for (i = 0; i < ARRAY_SIZE(as21xxx_led_supported_pattern); i++) - if (val == as21xxx_led_supported_pattern[i].val) { - *rules = as21xxx_led_supported_pattern[i].pattern; - return 0; - } + return 0; +} - /* Should be impossible */ - return -EINVAL; +static void as22xxx_read_speed(struct phy_device *phydev, int bmcr) +{ + int speed; + + speed = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_SPEED_STATUS); + if (speed < 0) + return; + + speed &= VEND1_SPEED_MASK; + if (speed == VEND1_SPEED_10000) { + phydev->speed = SPEED_10000; + phydev->duplex = DUPLEX_FULL; + } else if (speed == VEND1_SPEED_5000) { + phydev->speed = SPEED_5000; + phydev->duplex = DUPLEX_FULL; + } else if (speed == VEND1_SPEED_2500) { + phydev->speed = SPEED_2500; + phydev->duplex = DUPLEX_FULL; + } else if (speed == VEND1_SPEED_1000) { + phydev->speed = SPEED_1000; + if (bmcr & BMCR_FULLDPLX) + phydev->duplex = DUPLEX_FULL; + else + phydev->duplex = DUPLEX_HALF; + } else if (speed == VEND1_SPEED_100) { + phydev->speed = SPEED_100; + if (bmcr & BMCR_FULLDPLX) + phydev->duplex = DUPLEX_FULL; + else + phydev->duplex = DUPLEX_HALF; + } else { + phydev->speed = SPEED_10; + phydev->duplex = DUPLEX_FULL; + } } -static int as21xxx_led_hw_control_set(struct phy_device *phydev, u8 index, - unsigned long rules) +static phy_interface_t as22xxx_speed_interface(int speed) { - u16 val = 0; - int i; + switch (speed) { + case SPEED_10: + case SPEED_100: + case SPEED_1000: + return PHY_INTERFACE_MODE_SGMII; + + case SPEED_2500: + case SPEED_5000: + case SPEED_10000: + return PHY_INTERFACE_MODE_USXGMII; + + default: + return PHY_INTERFACE_MODE_NA; + } +} - if (index > AEON_MAX_LEDS) - return -EINVAL; +static int as22xxx_interface_sds(phy_interface_t interface, u8 *pcs_sel, + u8 *sds_spd) +{ + switch (interface) { + case PHY_INTERFACE_MODE_SGMII: + *pcs_sel = AEON_SDS_PCS_SEL_NORMAL; + *sds_spd = AEON_SDS_SPD_1G; + return 0; - for (i = 0; i < ARRAY_SIZE(as21xxx_led_supported_pattern); i++) - if (rules == as21xxx_led_supported_pattern[i].pattern) { - val = as21xxx_led_supported_pattern[i].val; - break; - } + case PHY_INTERFACE_MODE_USXGMII: - return phy_modify_mmd(phydev, MDIO_MMD_VEND1, - VEND1_LED_REG(index), - VEND1_LED_REG_A_EVENT, - FIELD_PREP(VEND1_LED_REG_A_EVENT, val)); + *pcs_sel = AEON_SDS_PCS_SEL_64B66B; + *sds_spd = AEON_SDS_SPD_10G; + return 0; + + default: + return -EOPNOTSUPP; + } } -static int as21xxx_led_polarity_set(struct phy_device *phydev, int index, - unsigned long modes) +static int as22xxx_program_system_side(struct phy_device *phydev, + phy_interface_t interface, + u8 pcs_sel, u8 sds_spd); +static int as22xxx_sds_mii_config(struct phy_device *phydev, + phy_interface_t interface, int speed); + +static bool as22xxx_update_interface(struct phy_device *phydev) { - bool led_active_low = false; - u16 mask, val = 0; + struct as21xxx_priv *priv = phydev->priv; + phy_interface_t interface; + u8 pcs_sel, sds_spd; + + if (!priv->mode_switch || !phydev->link) + return true; + + interface = as22xxx_speed_interface(phydev->speed); + if (interface == PHY_INTERFACE_MODE_NA) + return true; + + if (interface == priv->sds_interface) { + if (interface == PHY_INTERFACE_MODE_SGMII && + phydev->speed != priv->sds_speed) { + if (as22xxx_sds_mii_config(phydev, interface, + phydev->speed)) + return false; + if (aeon_dpc_ra_enable(phydev)) + return false; + + priv->sds_speed = phydev->speed; + } + + phydev->interface = interface; + return true; + } + + if (as22xxx_interface_sds(interface, &pcs_sel, &sds_spd)) + return true; + + if (as22xxx_program_system_side(phydev, interface, pcs_sel, sds_spd)) + return false; + + priv->sds_interface = interface; + priv->sds_speed = phydev->speed; + phydev->interface = interface; + + return true; +} + +static int as22xxx_select_system_side(struct phy_device *phydev, + phy_interface_t interface); +static int as22xxx_select_system_side(struct phy_device *phydev, + phy_interface_t interface) +{ + struct as21xxx_priv *priv = phydev->priv; + u16 want = interface == PHY_INTERFACE_MODE_USXGMII ? + VEND1_CHIP_CTRL_SXGMII_MODE : + 0; + int ret, result = 0; + + mutex_lock(&priv->sds_lock); + + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1, VEND1_CHIP_CTRL, + VEND1_CHIP_CTRL_SXGMII_MODE, want); + if (ret < 0) { + result = ret; + goto out; + } + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_CHIP_CTRL); + if (ret < 0) { + result = ret; + goto out; + } + if ((ret & VEND1_CHIP_CTRL_SXGMII_MODE) != want) { + phydev_err(phydev, + "system-side Chip Control personality did not take for %s (chip_ctrl=%04x)\n", + phy_modes(interface), ret); + result = -EIO; + goto out; + } + +out: + mutex_unlock(&priv->sds_lock); + return result; +} + +static int as22xxx_sds_mii_config(struct phy_device *phydev, + phy_interface_t interface, int speed) +{ + struct as21xxx_priv *priv = phydev->priv; + u16 mask = BMCR_ANENABLE | BMCR_ANRESTART | BMCR_SPEED1000 | + BMCR_SPEED100 | BMCR_FULLDPLX; + u16 want, adv_speed = 0, adv_want = 0; + int orig, paged, val, adv, adv_readback, restored; + int ret = 0; + + if (interface == PHY_INTERFACE_MODE_SGMII) { + switch (speed) { + case SPEED_10: + adv_speed = FIELD_PREP(AS22XXX_SDS_MII_ADV_SPEED, 0); + break; + case SPEED_100: + adv_speed = FIELD_PREP(AS22XXX_SDS_MII_ADV_SPEED, 1); + break; + case SPEED_1000: + adv_speed = FIELD_PREP(AS22XXX_SDS_MII_ADV_SPEED, 2); + break; + default: + return -EOPNOTSUPP; + } + want = mii_bmcr_encode_fixed(speed, DUPLEX_FULL); + } else if (interface == PHY_INTERFACE_MODE_USXGMII) { + switch (speed) { + case SPEED_2500: + adv_speed = MDIO_USXGMII_2500; + break; + case SPEED_5000: + adv_speed = MDIO_USXGMII_5000; + break; + case SPEED_10000: + adv_speed = MDIO_USXGMII_10G; + break; + default: + return -EOPNOTSUPP; + } + + want = BMCR_SPEED100 | BMCR_SPEED1000 | BMCR_FULLDPLX; + + adv_want = MDIO_USXGMII_LINK | MDIO_USXGMII_FULL_DUPLEX | + adv_speed | AS22XXX_SDS_MII_ADV_SELECTOR; + } else { + return -EOPNOTSUPP; + } + + mutex_lock(&priv->sds_lock); + + orig = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_CHIP_CTRL); + if (orig < 0) { + ret = orig; + goto out_unlock; + } + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_CHIP_CTRL, + orig | VEND1_CHIP_CTRL_XFI_ACCESS); + if (ret) + goto out_restore; + + paged = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_CHIP_CTRL); + if (paged < 0) { + ret = paged; + goto out_restore; + } + if (!(paged & VEND1_CHIP_CTRL_XFI_ACCESS)) { + ret = -EIO; + goto out_restore; + } + + ret = phy_modify_mmd(phydev, MDIO_MMD_AN, + AS21XXX_MDIO_AN_C22 + MII_BMCR, + BMCR_ANENABLE | BMCR_ANRESTART, 0); + if (ret < 0) + goto out_restore; + + if (interface == PHY_INTERFACE_MODE_SGMII) { + adv = phy_read_mmd(phydev, MDIO_MMD_AN, + AS21XXX_MDIO_AN_C22 + MII_ADVERTISE); + if (adv < 0) { + ret = adv; + goto out_restore; + } + adv_want = (adv & ~AS22XXX_SDS_MII_ADV_CONTROL_MASK) | + AS22XXX_SDS_MII_ADV_LINK | + AS22XXX_SDS_MII_ADV_FULL | + adv_speed | + AS22XXX_SDS_MII_ADV_SELECTOR; + + ret = phy_modify_mmd(phydev, MDIO_MMD_AN, + AS21XXX_MDIO_AN_C22 + MII_ADVERTISE, + AS22XXX_SDS_MII_ADV_CONTROL_MASK, + adv_want); + if (ret < 0) + goto out_restore; + + adv_readback = phy_read_mmd(phydev, MDIO_MMD_AN, + AS21XXX_MDIO_AN_C22 + MII_ADVERTISE); + if (adv_readback < 0) { + ret = adv_readback; + goto out_restore; + } + if ((adv_readback & AS22XXX_SDS_MII_ADV_CONTROL_MASK) != + (adv_want & AS22XXX_SDS_MII_ADV_CONTROL_MASK)) { + phydev_err(phydev, + "SGMII MII advertisement did not take (read %04x, want %04x)\n", + adv_readback, adv_want); + ret = -EIO; + goto out_restore; + } + } else { + ret = phy_write_mmd(phydev, MDIO_MMD_AN, + AS21XXX_MDIO_AN_C22 + MII_ADVERTISE, + adv_want); + if (ret) + goto out_restore; + + adv_readback = phy_read_mmd(phydev, MDIO_MMD_AN, + AS21XXX_MDIO_AN_C22 + MII_ADVERTISE); + if (adv_readback < 0) { + ret = adv_readback; + goto out_restore; + } + if (adv_readback != adv_want) { + phydev_err(phydev, + "USXGMII MII advertisement did not take for %d Mbps (read %04x, want %04x)\n", + speed, adv_readback, adv_want); + ret = -EIO; + goto out_restore; + } + } + + ret = phy_modify_mmd(phydev, MDIO_MMD_AN, + AS21XXX_MDIO_AN_C22 + MII_BMCR, mask, want); + if (ret < 0) + goto out_restore; + + val = phy_read_mmd(phydev, MDIO_MMD_AN, + AS21XXX_MDIO_AN_C22 + MII_BMCR); + if (val < 0) { + ret = val; + goto out_restore; + } + if ((val & mask) != want) { + phydev_err(phydev, + "SerDes MII fixed control did not take for %d Mbps (read %04x, want %04x)\n", + speed, val, want); + ret = -EIO; + goto out_restore; + } + + want |= BMCR_ANENABLE; + ret = phy_modify_mmd(phydev, MDIO_MMD_AN, + AS21XXX_MDIO_AN_C22 + MII_BMCR, mask, + want | BMCR_ANRESTART); + if (ret < 0) + goto out_restore; + + ret = read_poll_timeout(phy_read_mmd, val, + val < 0 || !(val & BMCR_ANRESTART), + 1000, 100000, false, phydev, MDIO_MMD_AN, + AS21XXX_MDIO_AN_C22 + MII_BMCR); + if (val < 0) { + ret = val; + goto out_restore; + } + if (ret) { + phydev_err(phydev, + "SerDes MII AN restart did not self-clear for %d Mbps (read %04x)\n", + speed, val); + goto out_restore; + } + if ((val & mask) != want) { + phydev_err(phydev, + "SerDes MII control did not take for %d Mbps (read %04x, want %04x)\n", + speed, val, want); + ret = -EIO; + goto out_restore; + } + + adv_readback = phy_read_mmd(phydev, MDIO_MMD_AN, + AS21XXX_MDIO_AN_C22 + MII_ADVERTISE); + if (adv_readback < 0) { + ret = adv_readback; + goto out_restore; + } + if ((interface == PHY_INTERFACE_MODE_SGMII && + (adv_readback & AS22XXX_SDS_MII_ADV_CONTROL_MASK) != + (adv_want & AS22XXX_SDS_MII_ADV_CONTROL_MASK)) || + (interface == PHY_INTERFACE_MODE_USXGMII && + adv_readback != adv_want)) { + phydev_err(phydev, + "%s MII advertisement changed after AN restart (read %04x, want %04x)\n", + phy_modes(interface), adv_readback, adv_want); + ret = -EIO; + goto out_restore; + } + +out_restore: + restored = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_CHIP_CTRL, + orig); + if (!restored) + restored = phy_read_mmd(phydev, MDIO_MMD_VEND1, + VEND1_CHIP_CTRL); + if (restored != orig) { + phydev_err(phydev, + "SerDes MII config failed to restore chip_ctrl %04x (readback/err %d)\n", + orig, restored); + if (!ret) + ret = restored < 0 ? restored : -EIO; + } + +out_unlock: + mutex_unlock(&priv->sds_lock); + return ret; +} + +static int as22xxx_sds_pcs_reset(struct phy_device *phydev) +{ + struct as21xxx_priv *priv = phydev->priv; + int orig, paged, after, restored; + int ret = 0; + + mutex_lock(&priv->sds_lock); + + orig = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_CHIP_CTRL); + if (orig < 0) { + ret = orig; + goto out_unlock; + } + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_CHIP_CTRL, + orig | VEND1_CHIP_CTRL_XFI_ACCESS); + if (ret) + goto out_restore; + + paged = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_CHIP_CTRL); + if (paged < 0) { + ret = paged; + goto out_restore; + } + if (!(paged & VEND1_CHIP_CTRL_XFI_ACCESS)) { + ret = -EIO; + goto out_restore; + } + + ret = phy_modify_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL1, + MDIO_CTRL1_RESET, MDIO_CTRL1_RESET); + if (ret < 0) + goto out_restore; + + ret = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_PCS, MDIO_CTRL1, after, + !(after & MDIO_CTRL1_RESET), 5000, + 1000000, true); + if (ret) + goto out_restore; + +out_restore: + restored = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_CHIP_CTRL, orig); + if (!restored) + restored = + phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_CHIP_CTRL); + if (restored != orig) { + phydev_err(phydev, + "SerDes PCS reset failed to restore chip_ctrl %04x (readback/err %d)\n", + orig, restored); + if (!ret) + ret = restored < 0 ? restored : -EIO; + } + +out_unlock: + mutex_unlock(&priv->sds_lock); + return ret; +} + +static int as22xxx_sds_pma_reset(struct phy_device *phydev) +{ + struct as21xxx_priv *priv = phydev->priv; + int orig, paged, after, restored; + int ret = 0; + + mutex_lock(&priv->sds_lock); + + orig = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_CHIP_CTRL); + if (orig < 0) { + ret = orig; + goto out_unlock; + } + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_CHIP_CTRL, + orig | VEND1_CHIP_CTRL_XFI_ACCESS); + if (ret) + goto out_restore; + + paged = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_CHIP_CTRL); + if (paged < 0) { + ret = paged; + goto out_restore; + } + if (!(paged & VEND1_CHIP_CTRL_XFI_ACCESS)) { + ret = -EIO; + goto out_restore; + } + + ret = phy_modify_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1, + MDIO_CTRL1_RESET, MDIO_CTRL1_RESET); + if (ret < 0) + goto out_restore; + + ret = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1, + after, + !(after & MDIO_CTRL1_RESET), + 5000, 1000000, true); + if (ret) + goto out_restore; + +out_restore: + restored = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_CHIP_CTRL, orig); + if (!restored) + restored = + phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_CHIP_CTRL); + if (restored != orig) { + phydev_err(phydev, + "SerDes PMA reset failed to restore chip_ctrl %04x (readback/err %d)\n", + orig, restored); + if (!ret) + ret = restored < 0 ? restored : -EIO; + } + +out_unlock: + mutex_unlock(&priv->sds_lock); + return ret; +} + +static int as22xxx_sds_10g_ctrl_restore(struct phy_device *phydev) +{ + struct as21xxx_priv *priv = phydev->priv; + int orig, paged, pma_after, pcs_after, restored; + int ret = 0; + + mutex_lock(&priv->sds_lock); + + orig = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_CHIP_CTRL); + if (orig < 0) { + ret = orig; + goto out_unlock; + } + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_CHIP_CTRL, + orig | VEND1_CHIP_CTRL_XFI_ACCESS); + if (ret) + goto out_restore; + + paged = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_CHIP_CTRL); + if (paged < 0) { + ret = paged; + goto out_restore; + } + if (!(paged & VEND1_CHIP_CTRL_XFI_ACCESS)) { + ret = -EIO; + goto out_restore; + } + + ret = phy_modify_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1, + MDIO_CTRL1_SPEEDSEL, MDIO_CTRL1_SPEED10G); + if (ret < 0) + goto out_restore; + ret = phy_modify_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL1, + MDIO_CTRL1_SPEEDSEL, MDIO_CTRL1_SPEED10G); + if (ret < 0) + goto out_restore; + + pma_after = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1); + if (pma_after < 0) { + ret = pma_after; + goto out_restore; + } + pcs_after = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL1); + if (pcs_after < 0) { + ret = pcs_after; + goto out_restore; + } + if ((pma_after & MDIO_CTRL1_SPEEDSEL) != MDIO_CTRL1_SPEED10G || + (pcs_after & MDIO_CTRL1_SPEEDSEL) != MDIO_CTRL1_SPEED10G) { + phydev_err(phydev, + "SerDes 10G controls did not take: PMA %04x, PCS %04x\n", + pma_after, pcs_after); + ret = -EIO; + goto out_restore; + } + +out_restore: + restored = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_CHIP_CTRL, orig); + if (!restored) + restored = + phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_CHIP_CTRL); + if (restored != orig) { + phydev_err(phydev, + "SerDes 10G control restore failed to restore chip_ctrl %04x (readback/err %d)\n", + orig, restored); + if (!ret) + ret = restored < 0 ? restored : -EIO; + } + +out_unlock: + mutex_unlock(&priv->sds_lock); + return ret; +} + +static int as22xxx_check_sds_config(struct phy_device *phydev, u8 pcs_sel, + u8 sds_spd) +{ + u8 rb_pcs_sel, rb_sds_spd; + int ret; + + ret = aeon_dpc_sds_get(phydev, &rb_pcs_sel, &rb_sds_spd); + if (ret) + return ret; + + if (rb_pcs_sel != pcs_sel || rb_sds_spd != sds_spd) { + phydev_err(phydev, + "SDS config mismatch: expected %u/%u, read %u/%u\n", + pcs_sel, sds_spd, rb_pcs_sel, rb_sds_spd); + return -EIO; + } + + return 0; +} + +static int as22xxx_stage_system_side(struct phy_device *phydev, + phy_interface_t interface, + u8 pcs_sel, u8 sds_spd) +{ + int ret; + + ret = aeon_dpc_sds_set(phydev, pcs_sel, sds_spd); + if (ret) { + phydev_err(phydev, "failed to stage SDS config for %s: %d\n", + phy_modes(interface), ret); + return ret; + } + + return as22xxx_check_sds_config(phydev, pcs_sel, sds_spd); +} + +static int as22xxx_program_system_side(struct phy_device *phydev, + phy_interface_t interface, + u8 pcs_sel, u8 sds_spd) +{ + struct as21xxx_priv *priv = phydev->priv; + bool upshift = interface == PHY_INTERFACE_MODE_USXGMII && + priv->sds_interface == PHY_INTERFACE_MODE_SGMII; + u8 sds_id = 0; + int ret; + + ret = as22xxx_select_system_side(phydev, interface); + if (ret) + return ret; + + ret = as22xxx_stage_system_side(phydev, interface, pcs_sel, sds_spd); + if (ret) + return ret; + + if (interface == PHY_INTERFACE_MODE_SGMII || upshift) { + ret = as22xxx_sds_mii_config(phydev, interface, phydev->speed); + if (ret) + return ret; + } + + if (interface == PHY_INTERFACE_MODE_USXGMII) { + ret = as22xxx_select_system_side(phydev, interface); + if (ret) + return ret; + + if (upshift) { + ret = as22xxx_sds_pma_reset(phydev); + if (ret) + return ret; + + ret = as22xxx_check_sds_config(phydev, pcs_sel, sds_spd); + if (ret) + return ret; + } + + ret = aeon_cfg_xfer(phydev, IPC_DBGCMD_SDS, IPC_SDS_RST, + &sds_id, sizeof(sds_id), NULL, 0); + if (ret) + return ret; + + ret = aeon_dpc_ra_enable(phydev); + if (ret) { + phydev_err(phydev, + "failed to enable USXGMII rate adaptor: %d\n", + ret); + return ret; + } + + ret = as22xxx_check_sds_config(phydev, pcs_sel, sds_spd); + if (ret) + return ret; + } + + ret = aeon_dpc_fc_apply_mode(phydev, pcs_sel); + if (ret) { + phydev_err(phydev, "failed to set system-side flow control: %d\n", + ret); + return ret; + } + + ret = as22xxx_select_system_side(phydev, interface); + if (ret) + return ret; + + if (upshift) { + ret = as22xxx_sds_10g_ctrl_restore(phydev); + if (ret) + return ret; + + ret = as22xxx_sds_pcs_reset(phydev); + if (ret) + return ret; + + ret = as22xxx_check_sds_config(phydev, pcs_sel, sds_spd); + if (ret) + return ret; + + ret = aeon_dpc_ra_enable(phydev); + if (ret) { + phydev_err(phydev, + "failed to restore USXGMII rate adaptor: %d\n", + ret); + return ret; + } + + ret = aeon_dpc_fc_apply_mode(phydev, pcs_sel); + if (ret) + return ret; + + ret = as22xxx_select_system_side(phydev, interface); + if (ret) + return ret; + + ret = as22xxx_check_sds_config(phydev, pcs_sel, sds_spd); + if (ret) + return ret; + + ret = as22xxx_sds_10g_ctrl_restore(phydev); + if (ret) + return ret; + + ret = as22xxx_sds_mii_config(phydev, interface, phydev->speed); + if (ret) + return ret; + + priv->system_sync_state = + AS22XXX_SYSTEM_SYNC_NEEDS_PHY_ENABLE; + } else if (interface == PHY_INTERFACE_MODE_SGMII) { + priv->system_sync_state = AS22XXX_SYSTEM_SYNC_IDLE; + } + + return 0; +} + +static int as22xxx_usxgmii_ready_paged(struct phy_device *phydev, int *pcs_sts, + int *mii_sts) +{ + int val; + + *pcs_sts = phy_read_mmd(phydev, MDIO_MMD_PCS, AS22XXX_SDS_PCS_STS20); + if (*pcs_sts < 0) + return *pcs_sts; + + val = phy_read_mmd(phydev, MDIO_MMD_AN, AS21XXX_MDIO_AN_C22 + MII_BMSR); + if (val < 0) + return val; + *mii_sts = phy_read_mmd(phydev, MDIO_MMD_AN, + AS21XXX_MDIO_AN_C22 + MII_BMSR); + if (*mii_sts < 0) + return *mii_sts; + + return ((*pcs_sts & + (AS22XXX_SDS_PCS_LINK_UP | AS22XXX_SDS_PCS_HIGH_BER | + AS22XXX_SDS_PCS_BLOCK_LOCK)) == + (AS22XXX_SDS_PCS_LINK_UP | + AS22XXX_SDS_PCS_BLOCK_LOCK) && + (*mii_sts & (BMSR_ANEGCOMPLETE | BMSR_LSTATUS | BMSR_RFAULT)) == + (BMSR_ANEGCOMPLETE | BMSR_LSTATUS)); +} + +static int as22xxx_usxgmii_ready_once(struct phy_device *phydev, int *pcs_sts, + int *mii_sts) +{ + struct as21xxx_priv *priv = phydev->priv; + int orig, paged, chip_ctrl, restore, restored; + int restore_err = 0, ret; + + mutex_lock(&priv->sds_lock); + + orig = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_CHIP_CTRL); + if (orig < 0) { + ret = orig; + goto unlock; + } + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_CHIP_CTRL, + orig | VEND1_CHIP_CTRL_XFI_ACCESS); + if (ret) + goto out_restore; + + paged = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_CHIP_CTRL); + if (paged < 0) { + ret = paged; + goto out_restore; + } + if (!(paged & VEND1_CHIP_CTRL_XFI_ACCESS)) { + ret = -EIO; + goto out_restore; + } + + ret = as22xxx_usxgmii_ready_paged(phydev, pcs_sts, mii_sts); + +out_restore: + + chip_ctrl = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_CHIP_CTRL); + if (chip_ctrl < 0) { + restore_err = chip_ctrl; + restore = orig; + } else { + restore = (chip_ctrl & ~VEND1_CHIP_CTRL_XFI_ACCESS) | + (orig & VEND1_CHIP_CTRL_XFI_ACCESS); + } + + restored = + phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_CHIP_CTRL, restore); + if (!restored) + restored = + phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_CHIP_CTRL); + if (restored < 0 || ((restored ^ orig) & VEND1_CHIP_CTRL_XFI_ACCESS)) { + phydev_err(phydev, + "USXGMII readiness sample failed to restore XFI gate from chip_ctrl %04x (readback/err %d)\n", + orig, restored); + if (ret >= 0) + ret = restored < 0 ? restored : -EIO; + } else if (restore_err && ret >= 0) { + ret = restore_err; + } + +unlock: + mutex_unlock(&priv->sds_lock); + return ret; +} + +static int as22xxx_wait_usxgmii_ready(struct phy_device *phydev) +{ + int pcs_sts = 0, mii_sts = 0; + int ready = 0, ret; + + ret = read_poll_timeout(as22xxx_usxgmii_ready_once, ready, ready, + AS22XXX_SDS_READY_POLL_US, + AS22XXX_SDS_READY_TIMEOUT_US, false, + phydev, &pcs_sts, &mii_sts); + if (ready < 0) + ret = ready; + + if (ret) + phydev_err(phydev, + "system-side USXGMII did not become ready: PCS_STS20=%04x MII_STS=%04x: %d\n", + pcs_sts, mii_sts, ret); + + return ret; +} + +static int as22xxx_config_inband(struct phy_device *phydev, unsigned int modes) +{ + struct as21xxx_priv *priv = phydev->priv; + int ret; + + if (modes != LINK_INBAND_BYPASS) + return -EOPNOTSUPP; + + if (!priv->mode_switch || + priv->system_sync_state == AS22XXX_SYSTEM_SYNC_IDLE) + return 0; + + if (phydev->interface != PHY_INTERFACE_MODE_USXGMII || + priv->sds_interface != PHY_INTERFACE_MODE_USXGMII) + return -EINVAL; + + if (priv->system_sync_state == + AS22XXX_SYSTEM_SYNC_NEEDS_PHY_ENABLE) { + ret = aeon_ipc_phy_enable_mode(phydev, false); + if (ret) + return ret; + + ret = aeon_ipc_phy_enable_mode(phydev, true); + if (ret) + return ret; + + priv->system_sync_state = + AS22XXX_SYSTEM_SYNC_WAIT_PHY_ENABLE; + + ret = aeon_ipc_get_fw_version(phydev); + if (ret) + return ret; + + return as22xxx_stage_system_side(phydev, + PHY_INTERFACE_MODE_USXGMII, + AEON_SDS_PCS_SEL_64B66B, + AEON_SDS_SPD_10G); + } + + ret = as22xxx_wait_usxgmii_ready(phydev); + if (ret) + return ret; + + ret = as22xxx_check_sds_config(phydev, + AEON_SDS_PCS_SEL_64B66B, + AEON_SDS_SPD_10G); + if (ret) + return ret; + + priv->system_sync_state = AS22XXX_SYSTEM_SYNC_IDLE; + return 0; +} + +static int as22xxx_read_status(struct phy_device *phydev) +{ + struct as21xxx_priv *priv = phydev->priv; + int bmcr, old_link = phydev->link; + int ret; + + ret = as22xxx_read_link(phydev, &bmcr); + if (ret) + return ret; + + if (!priv->mode_switch && phydev->autoneg == AUTONEG_ENABLE && old_link && + phydev->link && + priv->sds_interface != PHY_INTERFACE_MODE_NA) + return 0; + + phydev->speed = SPEED_UNKNOWN; + phydev->duplex = DUPLEX_UNKNOWN; + phydev->pause = 0; + phydev->asym_pause = 0; + + if (phydev->autoneg == AUTONEG_ENABLE) { + ret = genphy_c45_read_lpa(phydev); + if (ret) + return ret; + + ret = as22xxx_read_lpa(phydev); + if (ret) + return ret; + + if (phydev->autoneg_complete) { + as22xxx_read_speed(phydev, bmcr); + phy_resolve_aneg_linkmode(phydev); + } + } else { + linkmode_zero(phydev->lp_advertising); + as22xxx_read_speed(phydev, bmcr); + } + + if (!as22xxx_update_interface(phydev)) + phydev->link = 0; + + return 0; +} + +static int as21xxx_led_brightness_set(struct phy_device *phydev, + u8 index, enum led_brightness value) +{ + u16 val = VEND1_LED_REG_A_EVENT_OFF; + + if (index > AEON_MAX_LEDS) + return -EINVAL; + + if (value) + val = VEND1_LED_REG_A_EVENT_ON; + + return phy_modify_mmd(phydev, MDIO_MMD_VEND1, + VEND1_LED_REG(index), + VEND1_LED_REG_A_EVENT, + FIELD_PREP(VEND1_LED_REG_A_EVENT, val)); +} + +static int as21xxx_led_hw_is_supported(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + int i; + + if (index > AEON_MAX_LEDS) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(as21xxx_led_supported_pattern); i++) + if (rules == as21xxx_led_supported_pattern[i].pattern) + return 0; + + return -EOPNOTSUPP; +} + +static int as21xxx_led_hw_control_get(struct phy_device *phydev, u8 index, + unsigned long *rules) +{ + int i, val; + + if (index > AEON_MAX_LEDS) + return -EINVAL; + + val = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_LED_REG(index)); + if (val < 0) + return val; + + val = FIELD_GET(VEND1_LED_REG_A_EVENT, val); + for (i = 0; i < ARRAY_SIZE(as21xxx_led_supported_pattern); i++) + if (val == as21xxx_led_supported_pattern[i].val) { + *rules = as21xxx_led_supported_pattern[i].pattern; + return 0; + } + + return -EINVAL; +} + +static int as21xxx_led_hw_control_set(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + u16 val = 0; + int i; + + if (index > AEON_MAX_LEDS) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(as21xxx_led_supported_pattern); i++) + if (rules == as21xxx_led_supported_pattern[i].pattern) { + val = as21xxx_led_supported_pattern[i].val; + break; + } + + return phy_modify_mmd(phydev, MDIO_MMD_VEND1, + VEND1_LED_REG(index), + VEND1_LED_REG_A_EVENT, + FIELD_PREP(VEND1_LED_REG_A_EVENT, val)); +} + +static int as21xxx_led_polarity_set(struct phy_device *phydev, int index, + unsigned long modes) +{ + bool led_active_low = false; + u16 mask, val = 0; u32 mode; if (index > AEON_MAX_LEDS) @@ -943,6 +2271,124 @@ static int as21xxx_match_phy_device(struct phy_device *phydev, return ret; } +static int as22xxx_match_phy_device(struct phy_device *phydev, + const struct phy_driver *phydrv) +{ + u32 phy_id; + int ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MII_PHYSID1); + if (ret < 0) + return ret; + phy_id = ret << 16; + + ret = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MII_PHYSID2); + if (ret < 0) + return ret; + phy_id |= ret; + + return phy_id == PHY_ID_AS22XXX; +} + +static int as22xxx_bringup(struct phy_device *phydev) +{ + struct as21xxx_priv *priv = phydev->priv; + int ret; + + priv->sds_interface = PHY_INTERFACE_MODE_NA; + priv->sds_speed = SPEED_UNKNOWN; + priv->system_sync_state = AS22XXX_SYSTEM_SYNC_IDLE; + + ret = aeon_firmware_load(phydev); + if (ret) + return ret; + + ret = read_poll_timeout(aeon_ipc_sync_parity, ret, !ret, + 0, 10000000, false, phydev, priv); + if (ret) { + phydev_err(phydev, "timed out waiting for firmware boot\n"); + return ret; + } + + ret = aeon_ipc_get_fw_version(phydev); + if (ret) + return ret; + + ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, VEND1_PTP_CLK, + VEND1_PTP_CLK_EN); + if (ret) + return ret; + + if (priv->mode_switch) { + ret = as22xxx_program_system_side(phydev, + PHY_INTERFACE_MODE_USXGMII, + AEON_SDS_PCS_SEL_64B66B, + AEON_SDS_SPD_10G); + if (ret) + return ret; + + priv->sds_interface = PHY_INTERFACE_MODE_USXGMII; + priv->sds_speed = SPEED_UNKNOWN; + } + + if (priv->mode_switch) { + ret = aeon_dpc_eth_sts_hw_update(phydev, true); + if (ret) { + phydev_err(phydev, + "failed to enable DPC Ethernet-status hardware follow: %d\n", + ret); + return ret; + } + } + + return 0; +} + +static int as22xxx_config_init(struct phy_device *phydev) +{ + struct as21xxx_priv *priv = phydev->priv; + + if (priv->mode_switch) { + phy_interface_zero(phydev->possible_interfaces); + __set_bit(PHY_INTERFACE_MODE_USXGMII, + phydev->possible_interfaces); + __set_bit(PHY_INTERFACE_MODE_SGMII, + phydev->possible_interfaces); + } + + if (!aeon_ipc_sync_parity(phydev, priv)) + return 0; + + return as22xxx_bringup(phydev); +} + +static int as22xxx_probe(struct phy_device *phydev) +{ + struct as21xxx_priv *priv; + int ret; + + phydev->c45_ids.mmds_present |= MDIO_DEVS_PMAPMD | MDIO_DEVS_PCS | + MDIO_DEVS_AN; + + priv = devm_kzalloc(&phydev->mdio.dev, + sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + phydev->priv = priv; + priv->mode_switch = device_property_read_bool(&phydev->mdio.dev, + "sgmii-usxgmii-switch-quirk"); + + ret = devm_mutex_init(&phydev->mdio.dev, + &priv->ipc_lock); + if (ret) + return ret; + ret = devm_mutex_init(&phydev->mdio.dev, &priv->sds_lock); + if (ret) + return ret; + + return as22xxx_bringup(phydev); +} + static struct phy_driver as21xxx_drivers[] = { { /* PHY expose in C45 as 0x7500 0x9410 @@ -1074,6 +2520,21 @@ static struct phy_driver as21xxx_drivers[] = { .led_hw_control_get = as21xxx_led_hw_control_get, .led_polarity_set = as21xxx_led_polarity_set, }, + { + PHY_ID_MATCH_EXACT(PHY_ID_AS22XXX), + .name = "Aeonsemi AS22XXX", + .probe = as22xxx_probe, + .match_phy_device = as22xxx_match_phy_device, + .config_init = as22xxx_config_init, + .config_aneg = as22xxx_config_aneg, + .config_inband = as22xxx_config_inband, + .read_status = as22xxx_read_status, + .led_brightness_set = as21xxx_led_brightness_set, + .led_hw_is_supported = as21xxx_led_hw_is_supported, + .led_hw_control_set = as21xxx_led_hw_control_set, + .led_hw_control_get = as21xxx_led_hw_control_get, + .led_polarity_set = as21xxx_led_polarity_set, + }, }; module_phy_driver(as21xxx_drivers); diff --git a/include/linux/pcs/pcs-xpcs.h b/include/linux/pcs/pcs-xpcs.h index 36073f7b6bb40..1950d93b350b9 100644 --- a/include/linux/pcs/pcs-xpcs.h +++ b/include/linux/pcs/pcs-xpcs.h @@ -53,6 +53,8 @@ struct dw_xpcs; struct phylink_pcs *xpcs_to_phylink_pcs(struct dw_xpcs *xpcs); int xpcs_get_an_mode(struct dw_xpcs *xpcs, phy_interface_t interface); void xpcs_config_eee_mult_fact(struct dw_xpcs *xpcs, u8 mult_fact); +void xpcs_config_qps615(struct dw_xpcs *xpcs, bool out_of_band, + bool sgmii_plus); struct dw_xpcs *xpcs_create_mdiodev(struct mii_bus *bus, int addr); struct dw_xpcs *xpcs_create_fwnode(struct fwnode_handle *fwnode); void xpcs_destroy(struct dw_xpcs *xpcs); diff --git a/include/linux/stmmac.h b/include/linux/stmmac.h index 76dd6b4ad997f..0150e33ac18a7 100644 --- a/include/linux/stmmac.h +++ b/include/linux/stmmac.h @@ -258,9 +258,16 @@ struct plat_stmmacenet_data { int (*set_clk_tx_rate)(void *priv, struct clk *clk_tx_i, phy_interface_t interface, int speed); void (*fix_mac_speed)(void *priv, int speed, unsigned int mode); + void (*post_mac_link_up)(struct net_device *ndev, void *priv, + struct phy_device *phydev, unsigned int mode, + phy_interface_t interface, int speed); int (*fix_soc_reset)(struct stmmac_priv *priv); int (*serdes_powerup)(struct net_device *ndev, void *priv); void (*serdes_powerdown)(struct net_device *ndev, void *priv); + int (*mac_prepare)(struct net_device *ndev, + void *priv, + unsigned int mode, + phy_interface_t interface); int (*mac_finish)(struct net_device *ndev, void *priv, unsigned int mode, diff --git a/include/soc/toshiba/tc956x-dwmac.h b/include/soc/toshiba/tc956x-dwmac.h index 5ca39cf764be9..5840c1a9a51d7 100644 --- a/include/soc/toshiba/tc956x-dwmac.h +++ b/include/soc/toshiba/tc956x-dwmac.h @@ -32,13 +32,21 @@ enum tc956x_clock_id { MAC_CLOCK_RMII = 15, /* eMAC 1 only */ }; +enum tc956x_common_clock_id { + COMMON_CLOCK_PLL = 24, /* POEPLLCEN */ + COMMON_CLOCK_SGMII = 25, /* SGMPCIEN */ + COMMON_CLOCK_REFCLK = 26, /* REFCLKOCEN (PHY refclk output) */ +}; + /** * struct tc956x_dwmac_data - Structure passed to stmmac auxiliary devices. * @chip: Context pointer needed for reset and clock operations * @emac: I/O mapped address used by eMAC * @emac_ctl: I/O mapped address used for eMAC control + * @sfr: I/O mapped address used for TC956X SFR access * @msigen: I/O mapped address used by MSIGEN - * @msigen_irq: IRQ number used by MSIGEN + * @msigen_irq: IRQ number of the first MSIGEN vector + * @msigen_nvec: Number of consecutive MSIGEN vectors from @msigen_irq * @rev_id: Chip revision ID (for quirks) * @mac_id: Unique device ID (0 or 1) * @@ -48,8 +56,10 @@ struct tc956x_dwmac_data { const struct tc956x_chip *chip; void __iomem *emac; void __iomem *emac_ctl; + void __iomem *sfr; void __iomem *msigen; unsigned int msigen_irq; + unsigned int msigen_nvec; u8 rev_id; u8 mac_id; }; @@ -81,4 +91,16 @@ static inline void tc956x_clock_disable(const struct tc956x_chip *chip, tc956x_reset_clock_set(chip, false, !mac_id, false, (u8)id); } +static inline void tc956x_common_clock_enable(const struct tc956x_chip *chip, + enum tc956x_common_clock_id id) +{ + tc956x_reset_clock_set(chip, false, true, true, (u8)id); +} + +static inline void tc956x_common_clock_disable(const struct tc956x_chip *chip, + enum tc956x_common_clock_id id) +{ + tc956x_reset_clock_set(chip, false, true, false, (u8)id); +} + #endif /* __TOSHIBA_TC956X_DWMAC_H__*/