Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Steen Hegelund | 4979 | 100.00% | 1 | 100.00% |
Total | 4979 | 1 |
// SPDX-License-Identifier: GPL-2.0+ /* Microchip Sparx5 Switch driver * * Copyright (c) 2021 Microchip Technology Inc. and its subsidiaries. */ #include <linux/module.h> #include <linux/phy/phy.h> #include "sparx5_main_regs.h" #include "sparx5_main.h" #include "sparx5_port.h" #define SPX5_ETYPE_TAG_C 0x8100 #define SPX5_ETYPE_TAG_S 0x88a8 #define SPX5_WAIT_US 1000 #define SPX5_WAIT_MAX_US 2000 enum port_error { SPX5_PERR_SPEED, SPX5_PERR_IFTYPE, }; #define PAUSE_DISCARD 0xC #define ETH_MAXLEN (ETH_DATA_LEN + ETH_HLEN + ETH_FCS_LEN) static void decode_sgmii_word(u16 lp_abil, struct sparx5_port_status *status) { status->an_complete = true; if (!(lp_abil & LPA_SGMII_LINK)) { status->link = false; return; } switch (lp_abil & LPA_SGMII_SPD_MASK) { case LPA_SGMII_10: status->speed = SPEED_10; break; case LPA_SGMII_100: status->speed = SPEED_100; break; case LPA_SGMII_1000: status->speed = SPEED_1000; break; default: status->link = false; return; } if (lp_abil & LPA_SGMII_FULL_DUPLEX) status->duplex = DUPLEX_FULL; else status->duplex = DUPLEX_HALF; } static void decode_cl37_word(u16 lp_abil, uint16_t ld_abil, struct sparx5_port_status *status) { status->link = !(lp_abil & ADVERTISE_RFAULT) && status->link; status->an_complete = true; status->duplex = (ADVERTISE_1000XFULL & lp_abil) ? DUPLEX_FULL : DUPLEX_UNKNOWN; // 1G HDX not supported if ((ld_abil & ADVERTISE_1000XPAUSE) && (lp_abil & ADVERTISE_1000XPAUSE)) { status->pause = MLO_PAUSE_RX | MLO_PAUSE_TX; } else if ((ld_abil & ADVERTISE_1000XPSE_ASYM) && (lp_abil & ADVERTISE_1000XPSE_ASYM)) { status->pause |= (lp_abil & ADVERTISE_1000XPAUSE) ? MLO_PAUSE_TX : 0; status->pause |= (ld_abil & ADVERTISE_1000XPAUSE) ? MLO_PAUSE_RX : 0; } else { status->pause = MLO_PAUSE_NONE; } } static int sparx5_get_dev2g5_status(struct sparx5 *sparx5, struct sparx5_port *port, struct sparx5_port_status *status) { u32 portno = port->portno; u16 lp_adv, ld_adv; u32 value; /* Get PCS Link down sticky */ value = spx5_rd(sparx5, DEV2G5_PCS1G_STICKY(portno)); status->link_down = DEV2G5_PCS1G_STICKY_LINK_DOWN_STICKY_GET(value); if (status->link_down) /* Clear the sticky */ spx5_wr(value, sparx5, DEV2G5_PCS1G_STICKY(portno)); /* Get both current Link and Sync status */ value = spx5_rd(sparx5, DEV2G5_PCS1G_LINK_STATUS(portno)); status->link = DEV2G5_PCS1G_LINK_STATUS_LINK_STATUS_GET(value) && DEV2G5_PCS1G_LINK_STATUS_SYNC_STATUS_GET(value); if (port->conf.portmode == PHY_INTERFACE_MODE_1000BASEX) status->speed = SPEED_1000; else if (port->conf.portmode == PHY_INTERFACE_MODE_2500BASEX) status->speed = SPEED_2500; status->duplex = DUPLEX_FULL; /* Get PCS ANEG status register */ value = spx5_rd(sparx5, DEV2G5_PCS1G_ANEG_STATUS(portno)); /* Aneg complete provides more information */ if (DEV2G5_PCS1G_ANEG_STATUS_ANEG_COMPLETE_GET(value)) { lp_adv = DEV2G5_PCS1G_ANEG_STATUS_LP_ADV_ABILITY_GET(value); if (port->conf.portmode == PHY_INTERFACE_MODE_SGMII) { decode_sgmii_word(lp_adv, status); } else { value = spx5_rd(sparx5, DEV2G5_PCS1G_ANEG_CFG(portno)); ld_adv = DEV2G5_PCS1G_ANEG_CFG_ADV_ABILITY_GET(value); decode_cl37_word(lp_adv, ld_adv, status); } } return 0; } static int sparx5_get_sfi_status(struct sparx5 *sparx5, struct sparx5_port *port, struct sparx5_port_status *status) { bool high_speed_dev = sparx5_is_baser(port->conf.portmode); u32 portno = port->portno; u32 value, dev, tinst; void __iomem *inst; if (!high_speed_dev) { netdev_err(port->ndev, "error: low speed and SFI mode\n"); return -EINVAL; } dev = sparx5_to_high_dev(portno); tinst = sparx5_port_dev_index(portno); inst = spx5_inst_get(sparx5, dev, tinst); value = spx5_inst_rd(inst, DEV10G_MAC_TX_MONITOR_STICKY(0)); if (value != DEV10G_MAC_TX_MONITOR_STICKY_IDLE_STATE_STICKY) { /* The link is or has been down. Clear the sticky bit */ status->link_down = 1; spx5_inst_wr(0xffffffff, inst, DEV10G_MAC_TX_MONITOR_STICKY(0)); value = spx5_inst_rd(inst, DEV10G_MAC_TX_MONITOR_STICKY(0)); } status->link = (value == DEV10G_MAC_TX_MONITOR_STICKY_IDLE_STATE_STICKY); status->duplex = DUPLEX_FULL; if (port->conf.portmode == PHY_INTERFACE_MODE_5GBASER) status->speed = SPEED_5000; else if (port->conf.portmode == PHY_INTERFACE_MODE_10GBASER) status->speed = SPEED_10000; else status->speed = SPEED_25000; return 0; } /* Get link status of 1000Base-X/in-band and SFI ports. */ int sparx5_get_port_status(struct sparx5 *sparx5, struct sparx5_port *port, struct sparx5_port_status *status) { memset(status, 0, sizeof(*status)); status->speed = port->conf.speed; if (port->conf.power_down) { status->link = false; return 0; } switch (port->conf.portmode) { case PHY_INTERFACE_MODE_SGMII: case PHY_INTERFACE_MODE_QSGMII: case PHY_INTERFACE_MODE_1000BASEX: case PHY_INTERFACE_MODE_2500BASEX: return sparx5_get_dev2g5_status(sparx5, port, status); case PHY_INTERFACE_MODE_5GBASER: case PHY_INTERFACE_MODE_10GBASER: case PHY_INTERFACE_MODE_25GBASER: return sparx5_get_sfi_status(sparx5, port, status); case PHY_INTERFACE_MODE_NA: return 0; default: netdev_err(port->ndev, "Status not supported"); return -ENODEV; } return 0; } static int sparx5_port_error(struct sparx5_port *port, struct sparx5_port_config *conf, enum port_error errtype) { switch (errtype) { case SPX5_PERR_SPEED: netdev_err(port->ndev, "Interface does not support speed: %u: for %s\n", conf->speed, phy_modes(conf->portmode)); break; case SPX5_PERR_IFTYPE: netdev_err(port->ndev, "Switch port does not support interface type: %s\n", phy_modes(conf->portmode)); break; default: netdev_err(port->ndev, "Interface configuration error\n"); } return -EINVAL; } static int sparx5_port_verify_speed(struct sparx5 *sparx5, struct sparx5_port *port, struct sparx5_port_config *conf) { if ((sparx5_port_is_2g5(port->portno) && conf->speed > SPEED_2500) || (sparx5_port_is_5g(port->portno) && conf->speed > SPEED_5000) || (sparx5_port_is_10g(port->portno) && conf->speed > SPEED_10000)) return sparx5_port_error(port, conf, SPX5_PERR_SPEED); switch (conf->portmode) { case PHY_INTERFACE_MODE_NA: return -EINVAL; case PHY_INTERFACE_MODE_1000BASEX: if (conf->speed != SPEED_1000 || sparx5_port_is_2g5(port->portno)) return sparx5_port_error(port, conf, SPX5_PERR_SPEED); if (sparx5_port_is_2g5(port->portno)) return sparx5_port_error(port, conf, SPX5_PERR_IFTYPE); break; case PHY_INTERFACE_MODE_2500BASEX: if (conf->speed != SPEED_2500 || sparx5_port_is_2g5(port->portno)) return sparx5_port_error(port, conf, SPX5_PERR_SPEED); break; case PHY_INTERFACE_MODE_QSGMII: if (port->portno > 47) return sparx5_port_error(port, conf, SPX5_PERR_IFTYPE); fallthrough; case PHY_INTERFACE_MODE_SGMII: if (conf->speed != SPEED_1000 && conf->speed != SPEED_100 && conf->speed != SPEED_10 && conf->speed != SPEED_2500) return sparx5_port_error(port, conf, SPX5_PERR_SPEED); break; case PHY_INTERFACE_MODE_5GBASER: case PHY_INTERFACE_MODE_10GBASER: case PHY_INTERFACE_MODE_25GBASER: if ((conf->speed != SPEED_5000 && conf->speed != SPEED_10000 && conf->speed != SPEED_25000)) return sparx5_port_error(port, conf, SPX5_PERR_SPEED); break; default: return sparx5_port_error(port, conf, SPX5_PERR_IFTYPE); } return 0; } static bool sparx5_dev_change(struct sparx5 *sparx5, struct sparx5_port *port, struct sparx5_port_config *conf) { return sparx5_is_baser(port->conf.portmode) ^ sparx5_is_baser(conf->portmode); } static int sparx5_port_flush_poll(struct sparx5 *sparx5, u32 portno) { u32 value, resource, prio, delay_cnt = 0; bool poll_src = true; char *mem = ""; /* Resource == 0: Memory tracked per source (SRC-MEM) * Resource == 1: Frame references tracked per source (SRC-REF) * Resource == 2: Memory tracked per destination (DST-MEM) * Resource == 3: Frame references tracked per destination. (DST-REF) */ while (1) { bool empty = true; for (resource = 0; resource < (poll_src ? 2 : 1); resource++) { u32 base; base = (resource == 0 ? 2048 : 0) + SPX5_PRIOS * portno; for (prio = 0; prio < SPX5_PRIOS; prio++) { value = spx5_rd(sparx5, QRES_RES_STAT(base + prio)); if (value) { mem = resource == 0 ? "DST-MEM" : "SRC-MEM"; empty = false; } } } if (empty) break; if (delay_cnt++ == 2000) { dev_err(sparx5->dev, "Flush timeout port %u. %s queue not empty\n", portno, mem); return -EINVAL; } usleep_range(SPX5_WAIT_US, SPX5_WAIT_MAX_US); } return 0; } static int sparx5_port_disable(struct sparx5 *sparx5, struct sparx5_port *port, bool high_spd_dev) { u32 tinst = high_spd_dev ? sparx5_port_dev_index(port->portno) : port->portno; u32 dev = high_spd_dev ? sparx5_to_high_dev(port->portno) : TARGET_DEV2G5; void __iomem *devinst = spx5_inst_get(sparx5, dev, tinst); u32 spd = port->conf.speed; u32 spd_prm; int err; if (high_spd_dev) { /* 1: Reset the PCS Rx clock domain */ spx5_inst_rmw(DEV10G_DEV_RST_CTRL_PCS_RX_RST, DEV10G_DEV_RST_CTRL_PCS_RX_RST, devinst, DEV10G_DEV_RST_CTRL(0)); /* 2: Disable MAC frame reception */ spx5_inst_rmw(0, DEV10G_MAC_ENA_CFG_RX_ENA, devinst, DEV10G_MAC_ENA_CFG(0)); } else { /* 1: Reset the PCS Rx clock domain */ spx5_inst_rmw(DEV2G5_DEV_RST_CTRL_PCS_RX_RST, DEV2G5_DEV_RST_CTRL_PCS_RX_RST, devinst, DEV2G5_DEV_RST_CTRL(0)); /* 2: Disable MAC frame reception */ spx5_inst_rmw(0, DEV2G5_MAC_ENA_CFG_RX_ENA, devinst, DEV2G5_MAC_ENA_CFG(0)); } /* 3: Disable traffic being sent to or from switch port->portno */ spx5_rmw(0, QFWD_SWITCH_PORT_MODE_PORT_ENA, sparx5, QFWD_SWITCH_PORT_MODE(port->portno)); /* 4: Disable dequeuing from the egress queues */ spx5_rmw(HSCH_PORT_MODE_DEQUEUE_DIS, HSCH_PORT_MODE_DEQUEUE_DIS, sparx5, HSCH_PORT_MODE(port->portno)); /* 5: Disable Flowcontrol */ spx5_rmw(QSYS_PAUSE_CFG_PAUSE_STOP_SET(0xFFF - 1), QSYS_PAUSE_CFG_PAUSE_STOP, sparx5, QSYS_PAUSE_CFG(port->portno)); spd_prm = spd == SPEED_10 ? 1000 : spd == SPEED_100 ? 100 : 10; /* 6: Wait while the last frame is exiting the queues */ usleep_range(8 * spd_prm, 10 * spd_prm); /* 7: Flush the queues accociated with the port->portno */ spx5_rmw(HSCH_FLUSH_CTRL_FLUSH_PORT_SET(port->portno) | HSCH_FLUSH_CTRL_FLUSH_DST_SET(1) | HSCH_FLUSH_CTRL_FLUSH_SRC_SET(1) | HSCH_FLUSH_CTRL_FLUSH_ENA_SET(1), HSCH_FLUSH_CTRL_FLUSH_PORT | HSCH_FLUSH_CTRL_FLUSH_DST | HSCH_FLUSH_CTRL_FLUSH_SRC | HSCH_FLUSH_CTRL_FLUSH_ENA, sparx5, HSCH_FLUSH_CTRL); /* 8: Enable dequeuing from the egress queues */ spx5_rmw(0, HSCH_PORT_MODE_DEQUEUE_DIS, sparx5, HSCH_PORT_MODE(port->portno)); /* 9: Wait until flushing is complete */ err = sparx5_port_flush_poll(sparx5, port->portno); if (err) return err; /* 10: Reset the MAC clock domain */ if (high_spd_dev) { spx5_inst_rmw(DEV10G_DEV_RST_CTRL_PCS_TX_RST_SET(1) | DEV10G_DEV_RST_CTRL_MAC_RX_RST_SET(1) | DEV10G_DEV_RST_CTRL_MAC_TX_RST_SET(1), DEV10G_DEV_RST_CTRL_PCS_TX_RST | DEV10G_DEV_RST_CTRL_MAC_RX_RST | DEV10G_DEV_RST_CTRL_MAC_TX_RST, devinst, DEV10G_DEV_RST_CTRL(0)); } else { spx5_inst_rmw(DEV2G5_DEV_RST_CTRL_SPEED_SEL_SET(3) | DEV2G5_DEV_RST_CTRL_PCS_TX_RST_SET(1) | DEV2G5_DEV_RST_CTRL_PCS_RX_RST_SET(1) | DEV2G5_DEV_RST_CTRL_MAC_TX_RST_SET(1) | DEV2G5_DEV_RST_CTRL_MAC_RX_RST_SET(1), DEV2G5_DEV_RST_CTRL_SPEED_SEL | DEV2G5_DEV_RST_CTRL_PCS_TX_RST | DEV2G5_DEV_RST_CTRL_PCS_RX_RST | DEV2G5_DEV_RST_CTRL_MAC_TX_RST | DEV2G5_DEV_RST_CTRL_MAC_RX_RST, devinst, DEV2G5_DEV_RST_CTRL(0)); } /* 11: Clear flushing */ spx5_rmw(HSCH_FLUSH_CTRL_FLUSH_PORT_SET(port->portno) | HSCH_FLUSH_CTRL_FLUSH_ENA_SET(0), HSCH_FLUSH_CTRL_FLUSH_PORT | HSCH_FLUSH_CTRL_FLUSH_ENA, sparx5, HSCH_FLUSH_CTRL); if (high_spd_dev) { u32 pcs = sparx5_to_pcs_dev(port->portno); void __iomem *pcsinst = spx5_inst_get(sparx5, pcs, tinst); /* 12: Disable 5G/10G/25 BaseR PCS */ spx5_inst_rmw(PCS10G_BR_PCS_CFG_PCS_ENA_SET(0), PCS10G_BR_PCS_CFG_PCS_ENA, pcsinst, PCS10G_BR_PCS_CFG(0)); if (sparx5_port_is_25g(port->portno)) /* Disable 25G PCS */ spx5_rmw(DEV25G_PCS25G_CFG_PCS25G_ENA_SET(0), DEV25G_PCS25G_CFG_PCS25G_ENA, sparx5, DEV25G_PCS25G_CFG(tinst)); } else { /* 12: Disable 1G PCS */ spx5_rmw(DEV2G5_PCS1G_CFG_PCS_ENA_SET(0), DEV2G5_PCS1G_CFG_PCS_ENA, sparx5, DEV2G5_PCS1G_CFG(port->portno)); } /* The port is now flushed and disabled */ return 0; } static int sparx5_port_fifo_sz(struct sparx5 *sparx5, u32 portno, u32 speed) { u32 sys_clk = sparx5_clk_period(sparx5->coreclock); const u32 taxi_dist[SPX5_PORTS_ALL] = { 6, 8, 10, 6, 8, 10, 6, 8, 10, 6, 8, 10, 4, 4, 4, 4, 11, 12, 13, 14, 15, 16, 17, 18, 11, 12, 13, 14, 15, 16, 17, 18, 11, 12, 13, 14, 15, 16, 17, 18, 11, 12, 13, 14, 15, 16, 17, 18, 4, 6, 8, 4, 6, 8, 6, 8, 2, 2, 2, 2, 2, 2, 2, 4, 2 }; u32 mac_per = 6400, tmp1, tmp2, tmp3; u32 fifo_width = 16; u32 mac_width = 8; u32 addition = 0; switch (speed) { case SPEED_25000: return 0; case SPEED_10000: mac_per = 6400; mac_width = 8; addition = 1; break; case SPEED_5000: mac_per = 12800; mac_width = 8; addition = 0; break; case SPEED_2500: mac_per = 3200; mac_width = 1; addition = 0; break; case SPEED_1000: mac_per = 8000; mac_width = 1; addition = 0; break; case SPEED_100: case SPEED_10: return 1; default: break; } tmp1 = 1000 * mac_width / fifo_width; tmp2 = 3000 + ((12000 + 2 * taxi_dist[portno] * 1000) * sys_clk / mac_per); tmp3 = tmp1 * tmp2 / 1000; return (tmp3 + 2000 + 999) / 1000 + addition; } /* Configure port muxing: * QSGMII: 4x2G5 devices */ static int sparx5_port_mux_set(struct sparx5 *sparx5, struct sparx5_port *port, struct sparx5_port_config *conf) { u32 portno = port->portno; u32 inst; if (port->conf.portmode == conf->portmode) return 0; /* Nothing to do */ switch (conf->portmode) { case PHY_INTERFACE_MODE_QSGMII: /* QSGMII: 4x2G5 devices. Mode Q' */ inst = (portno - portno % 4) / 4; spx5_rmw(BIT(inst), BIT(inst), sparx5, PORT_CONF_QSGMII_ENA); if ((portno / 4 % 2) == 0) { /* Affects d0-d3,d8-d11..d40-d43 */ spx5_rmw(PORT_CONF_USGMII_CFG_BYPASS_SCRAM_SET(1) | PORT_CONF_USGMII_CFG_BYPASS_DESCRAM_SET(1) | PORT_CONF_USGMII_CFG_QUAD_MODE_SET(1), PORT_CONF_USGMII_CFG_BYPASS_SCRAM | PORT_CONF_USGMII_CFG_BYPASS_DESCRAM | PORT_CONF_USGMII_CFG_QUAD_MODE, sparx5, PORT_CONF_USGMII_CFG((portno / 8))); } break; default: break; } return 0; } static int sparx5_port_max_tags_set(struct sparx5 *sparx5, struct sparx5_port *port) { enum sparx5_port_max_tags max_tags = port->max_vlan_tags; int tag_ct = max_tags == SPX5_PORT_MAX_TAGS_ONE ? 1 : max_tags == SPX5_PORT_MAX_TAGS_TWO ? 2 : 0; bool dtag = max_tags == SPX5_PORT_MAX_TAGS_TWO; enum sparx5_vlan_port_type vlan_type = port->vlan_type; bool dotag = max_tags != SPX5_PORT_MAX_TAGS_NONE; u32 dev = sparx5_to_high_dev(port->portno); u32 tinst = sparx5_port_dev_index(port->portno); void __iomem *inst = spx5_inst_get(sparx5, dev, tinst); u32 etype; etype = (vlan_type == SPX5_VLAN_PORT_TYPE_S_CUSTOM ? port->custom_etype : vlan_type == SPX5_VLAN_PORT_TYPE_C ? SPX5_ETYPE_TAG_C : SPX5_ETYPE_TAG_S); spx5_wr(DEV2G5_MAC_TAGS_CFG_TAG_ID_SET(etype) | DEV2G5_MAC_TAGS_CFG_PB_ENA_SET(dtag) | DEV2G5_MAC_TAGS_CFG_VLAN_AWR_ENA_SET(dotag) | DEV2G5_MAC_TAGS_CFG_VLAN_LEN_AWR_ENA_SET(dotag), sparx5, DEV2G5_MAC_TAGS_CFG(port->portno)); if (sparx5_port_is_2g5(port->portno)) return 0; spx5_inst_rmw(DEV10G_MAC_TAGS_CFG_TAG_ID_SET(etype) | DEV10G_MAC_TAGS_CFG_TAG_ENA_SET(dotag), DEV10G_MAC_TAGS_CFG_TAG_ID | DEV10G_MAC_TAGS_CFG_TAG_ENA, inst, DEV10G_MAC_TAGS_CFG(0, 0)); spx5_inst_rmw(DEV10G_MAC_NUM_TAGS_CFG_NUM_TAGS_SET(tag_ct), DEV10G_MAC_NUM_TAGS_CFG_NUM_TAGS, inst, DEV10G_MAC_NUM_TAGS_CFG(0)); spx5_inst_rmw(DEV10G_MAC_MAXLEN_CFG_MAX_LEN_TAG_CHK_SET(dotag), DEV10G_MAC_MAXLEN_CFG_MAX_LEN_TAG_CHK, inst, DEV10G_MAC_MAXLEN_CFG(0)); return 0; } int sparx5_port_fwd_urg(struct sparx5 *sparx5, u32 speed) { u32 clk_period_ps = 1600; /* 625Mhz for now */ u32 urg = 672000; switch (speed) { case SPEED_10: case SPEED_100: case SPEED_1000: urg = 672000; break; case SPEED_2500: urg = 270000; break; case SPEED_5000: urg = 135000; break; case SPEED_10000: urg = 67200; break; case SPEED_25000: urg = 27000; break; } return urg / clk_period_ps - 1; } static u16 sparx5_wm_enc(u16 value) { if (value >= 2048) return 2048 + value / 16; return value; } static int sparx5_port_fc_setup(struct sparx5 *sparx5, struct sparx5_port *port, struct sparx5_port_config *conf) { bool fc_obey = conf->pause & MLO_PAUSE_RX ? 1 : 0; u32 pause_stop = 0xFFF - 1; /* FC gen disabled */ if (conf->pause & MLO_PAUSE_TX) pause_stop = sparx5_wm_enc(4 * (ETH_MAXLEN / SPX5_BUFFER_CELL_SZ)); /* Set HDX flowcontrol */ spx5_rmw(DSM_MAC_CFG_HDX_BACKPREASSURE_SET(conf->duplex == DUPLEX_HALF), DSM_MAC_CFG_HDX_BACKPREASSURE, sparx5, DSM_MAC_CFG(port->portno)); /* Obey flowcontrol */ spx5_rmw(DSM_RX_PAUSE_CFG_RX_PAUSE_EN_SET(fc_obey), DSM_RX_PAUSE_CFG_RX_PAUSE_EN, sparx5, DSM_RX_PAUSE_CFG(port->portno)); /* Disable forward pressure */ spx5_rmw(QSYS_FWD_PRESSURE_FWD_PRESSURE_DIS_SET(fc_obey), QSYS_FWD_PRESSURE_FWD_PRESSURE_DIS, sparx5, QSYS_FWD_PRESSURE(port->portno)); /* Generate pause frames */ spx5_rmw(QSYS_PAUSE_CFG_PAUSE_STOP_SET(pause_stop), QSYS_PAUSE_CFG_PAUSE_STOP, sparx5, QSYS_PAUSE_CFG(port->portno)); return 0; } static u16 sparx5_get_aneg_word(struct sparx5_port_config *conf) { if (conf->portmode == PHY_INTERFACE_MODE_1000BASEX) /* cl-37 aneg */ return (conf->pause_adv | ADVERTISE_LPACK | ADVERTISE_1000XFULL); else return 1; /* Enable SGMII Aneg */ } int sparx5_serdes_set(struct sparx5 *sparx5, struct sparx5_port *port, struct sparx5_port_config *conf) { int portmode, err, speed = conf->speed; if (conf->portmode == PHY_INTERFACE_MODE_QSGMII && ((port->portno % 4) != 0)) { return 0; } if (sparx5_is_baser(conf->portmode)) { if (conf->portmode == PHY_INTERFACE_MODE_25GBASER) speed = SPEED_25000; else if (conf->portmode == PHY_INTERFACE_MODE_10GBASER) speed = SPEED_10000; else speed = SPEED_5000; } err = phy_set_media(port->serdes, conf->media); if (err) return err; if (speed > 0) { err = phy_set_speed(port->serdes, speed); if (err) return err; } if (conf->serdes_reset) { err = phy_reset(port->serdes); if (err) return err; } /* Configure SerDes with port parameters * For BaseR, the serdes driver supports 10GGBASE-R and speed 5G/10G/25G */ portmode = conf->portmode; if (sparx5_is_baser(conf->portmode)) portmode = PHY_INTERFACE_MODE_10GBASER; err = phy_set_mode_ext(port->serdes, PHY_MODE_ETHERNET, portmode); if (err) return err; conf->serdes_reset = false; return err; } static int sparx5_port_pcs_low_set(struct sparx5 *sparx5, struct sparx5_port *port, struct sparx5_port_config *conf) { bool sgmii = false, inband_aneg = false; int err; if (port->conf.inband) { if (conf->portmode == PHY_INTERFACE_MODE_SGMII || conf->portmode == PHY_INTERFACE_MODE_QSGMII) inband_aneg = true; /* Cisco-SGMII in-band-aneg */ else if (conf->portmode == PHY_INTERFACE_MODE_1000BASEX && conf->autoneg) inband_aneg = true; /* Clause-37 in-band-aneg */ err = sparx5_serdes_set(sparx5, port, conf); if (err) return -EINVAL; } else { sgmii = true; /* Phy is connnected to the MAC */ } /* Choose SGMII or 1000BaseX/2500BaseX PCS mode */ spx5_rmw(DEV2G5_PCS1G_MODE_CFG_SGMII_MODE_ENA_SET(sgmii), DEV2G5_PCS1G_MODE_CFG_SGMII_MODE_ENA, sparx5, DEV2G5_PCS1G_MODE_CFG(port->portno)); /* Enable PCS */ spx5_wr(DEV2G5_PCS1G_CFG_PCS_ENA_SET(1), sparx5, DEV2G5_PCS1G_CFG(port->portno)); if (inband_aneg) { u16 abil = sparx5_get_aneg_word(conf); /* Enable in-band aneg */ spx5_wr(DEV2G5_PCS1G_ANEG_CFG_ADV_ABILITY_SET(abil) | DEV2G5_PCS1G_ANEG_CFG_SW_RESOLVE_ENA_SET(1) | DEV2G5_PCS1G_ANEG_CFG_ANEG_ENA_SET(1) | DEV2G5_PCS1G_ANEG_CFG_ANEG_RESTART_ONE_SHOT_SET(1), sparx5, DEV2G5_PCS1G_ANEG_CFG(port->portno)); } else { spx5_wr(0, sparx5, DEV2G5_PCS1G_ANEG_CFG(port->portno)); } /* Take PCS out of reset */ spx5_rmw(DEV2G5_DEV_RST_CTRL_SPEED_SEL_SET(2) | DEV2G5_DEV_RST_CTRL_PCS_TX_RST_SET(0) | DEV2G5_DEV_RST_CTRL_PCS_RX_RST_SET(0), DEV2G5_DEV_RST_CTRL_SPEED_SEL | DEV2G5_DEV_RST_CTRL_PCS_TX_RST | DEV2G5_DEV_RST_CTRL_PCS_RX_RST, sparx5, DEV2G5_DEV_RST_CTRL(port->portno)); return 0; } static int sparx5_port_pcs_high_set(struct sparx5 *sparx5, struct sparx5_port *port, struct sparx5_port_config *conf) { u32 clk_spd = conf->portmode == PHY_INTERFACE_MODE_5GBASER ? 1 : 0; u32 pix = sparx5_port_dev_index(port->portno); u32 dev = sparx5_to_high_dev(port->portno); u32 pcs = sparx5_to_pcs_dev(port->portno); void __iomem *devinst; void __iomem *pcsinst; int err; devinst = spx5_inst_get(sparx5, dev, pix); pcsinst = spx5_inst_get(sparx5, pcs, pix); /* SFI : No in-band-aneg. Speeds 5G/10G/25G */ err = sparx5_serdes_set(sparx5, port, conf); if (err) return -EINVAL; if (conf->portmode == PHY_INTERFACE_MODE_25GBASER) { /* Enable PCS for 25G device, speed 25G */ spx5_rmw(DEV25G_PCS25G_CFG_PCS25G_ENA_SET(1), DEV25G_PCS25G_CFG_PCS25G_ENA, sparx5, DEV25G_PCS25G_CFG(pix)); } else { /* Enable PCS for 5G/10G/25G devices, speed 5G/10G */ spx5_inst_rmw(PCS10G_BR_PCS_CFG_PCS_ENA_SET(1), PCS10G_BR_PCS_CFG_PCS_ENA, pcsinst, PCS10G_BR_PCS_CFG(0)); } /* Enable 5G/10G/25G MAC module */ spx5_inst_wr(DEV10G_MAC_ENA_CFG_RX_ENA_SET(1) | DEV10G_MAC_ENA_CFG_TX_ENA_SET(1), devinst, DEV10G_MAC_ENA_CFG(0)); /* Take the device out of reset */ spx5_inst_rmw(DEV10G_DEV_RST_CTRL_PCS_RX_RST_SET(0) | DEV10G_DEV_RST_CTRL_PCS_TX_RST_SET(0) | DEV10G_DEV_RST_CTRL_MAC_RX_RST_SET(0) | DEV10G_DEV_RST_CTRL_MAC_TX_RST_SET(0) | DEV10G_DEV_RST_CTRL_SPEED_SEL_SET(clk_spd), DEV10G_DEV_RST_CTRL_PCS_RX_RST | DEV10G_DEV_RST_CTRL_PCS_TX_RST | DEV10G_DEV_RST_CTRL_MAC_RX_RST | DEV10G_DEV_RST_CTRL_MAC_TX_RST | DEV10G_DEV_RST_CTRL_SPEED_SEL, devinst, DEV10G_DEV_RST_CTRL(0)); return 0; } /* Switch between 1G/2500 and 5G/10G/25G devices */ static void sparx5_dev_switch(struct sparx5 *sparx5, int port, bool hsd) { int bt_indx = BIT(sparx5_port_dev_index(port)); if (sparx5_port_is_5g(port)) { spx5_rmw(hsd ? 0 : bt_indx, bt_indx, sparx5, PORT_CONF_DEV5G_MODES); } else if (sparx5_port_is_10g(port)) { spx5_rmw(hsd ? 0 : bt_indx, bt_indx, sparx5, PORT_CONF_DEV10G_MODES); } else if (sparx5_port_is_25g(port)) { spx5_rmw(hsd ? 0 : bt_indx, bt_indx, sparx5, PORT_CONF_DEV25G_MODES); } } /* Configure speed/duplex dependent registers */ static int sparx5_port_config_low_set(struct sparx5 *sparx5, struct sparx5_port *port, struct sparx5_port_config *conf) { u32 clk_spd, gig_mode, tx_gap, hdx_gap_1, hdx_gap_2; bool fdx = conf->duplex == DUPLEX_FULL; int spd = conf->speed; clk_spd = spd == SPEED_10 ? 0 : spd == SPEED_100 ? 1 : 2; gig_mode = spd == SPEED_1000 || spd == SPEED_2500; tx_gap = spd == SPEED_1000 ? 4 : fdx ? 6 : 5; hdx_gap_1 = spd == SPEED_1000 ? 0 : spd == SPEED_100 ? 1 : 2; hdx_gap_2 = spd == SPEED_1000 ? 0 : spd == SPEED_100 ? 4 : 1; /* GIG/FDX mode */ spx5_rmw(DEV2G5_MAC_MODE_CFG_GIGA_MODE_ENA_SET(gig_mode) | DEV2G5_MAC_MODE_CFG_FDX_ENA_SET(fdx), DEV2G5_MAC_MODE_CFG_GIGA_MODE_ENA | DEV2G5_MAC_MODE_CFG_FDX_ENA, sparx5, DEV2G5_MAC_MODE_CFG(port->portno)); /* Set MAC IFG Gaps */ spx5_wr(DEV2G5_MAC_IFG_CFG_TX_IFG_SET(tx_gap) | DEV2G5_MAC_IFG_CFG_RX_IFG1_SET(hdx_gap_1) | DEV2G5_MAC_IFG_CFG_RX_IFG2_SET(hdx_gap_2), sparx5, DEV2G5_MAC_IFG_CFG(port->portno)); /* Disabling frame aging when in HDX (due to HDX issue) */ spx5_rmw(HSCH_PORT_MODE_AGE_DIS_SET(fdx == 0), HSCH_PORT_MODE_AGE_DIS, sparx5, HSCH_PORT_MODE(port->portno)); /* Enable MAC module */ spx5_wr(DEV2G5_MAC_ENA_CFG_RX_ENA | DEV2G5_MAC_ENA_CFG_TX_ENA, sparx5, DEV2G5_MAC_ENA_CFG(port->portno)); /* Select speed and take MAC out of reset */ spx5_rmw(DEV2G5_DEV_RST_CTRL_SPEED_SEL_SET(clk_spd) | DEV2G5_DEV_RST_CTRL_MAC_TX_RST_SET(0) | DEV2G5_DEV_RST_CTRL_MAC_RX_RST_SET(0), DEV2G5_DEV_RST_CTRL_SPEED_SEL | DEV2G5_DEV_RST_CTRL_MAC_TX_RST | DEV2G5_DEV_RST_CTRL_MAC_RX_RST, sparx5, DEV2G5_DEV_RST_CTRL(port->portno)); return 0; } int sparx5_port_pcs_set(struct sparx5 *sparx5, struct sparx5_port *port, struct sparx5_port_config *conf) { bool high_speed_dev = sparx5_is_baser(conf->portmode); int err; if (sparx5_dev_change(sparx5, port, conf)) { /* switch device */ sparx5_dev_switch(sparx5, port->portno, high_speed_dev); /* Disable the not-in-use device */ err = sparx5_port_disable(sparx5, port, !high_speed_dev); if (err) return err; } /* Disable the port before re-configuring */ err = sparx5_port_disable(sparx5, port, high_speed_dev); if (err) return -EINVAL; if (high_speed_dev) err = sparx5_port_pcs_high_set(sparx5, port, conf); else err = sparx5_port_pcs_low_set(sparx5, port, conf); if (err) return -EINVAL; if (port->conf.inband) { /* Enable/disable 1G counters in ASM */ spx5_rmw(ASM_PORT_CFG_CSC_STAT_DIS_SET(high_speed_dev), ASM_PORT_CFG_CSC_STAT_DIS, sparx5, ASM_PORT_CFG(port->portno)); /* Enable/disable 1G counters in DSM */ spx5_rmw(DSM_BUF_CFG_CSC_STAT_DIS_SET(high_speed_dev), DSM_BUF_CFG_CSC_STAT_DIS, sparx5, DSM_BUF_CFG(port->portno)); } port->conf = *conf; return 0; } int sparx5_port_config(struct sparx5 *sparx5, struct sparx5_port *port, struct sparx5_port_config *conf) { bool high_speed_dev = sparx5_is_baser(conf->portmode); int err, urgency, stop_wm; err = sparx5_port_verify_speed(sparx5, port, conf); if (err) return err; /* high speed device is already configured */ if (!high_speed_dev) sparx5_port_config_low_set(sparx5, port, conf); /* Configure flow control */ err = sparx5_port_fc_setup(sparx5, port, conf); if (err) return err; /* Set the DSM stop watermark */ stop_wm = sparx5_port_fifo_sz(sparx5, port->portno, conf->speed); spx5_rmw(DSM_DEV_TX_STOP_WM_CFG_DEV_TX_STOP_WM_SET(stop_wm), DSM_DEV_TX_STOP_WM_CFG_DEV_TX_STOP_WM, sparx5, DSM_DEV_TX_STOP_WM_CFG(port->portno)); /* Enable port in queue system */ urgency = sparx5_port_fwd_urg(sparx5, conf->speed); spx5_rmw(QFWD_SWITCH_PORT_MODE_PORT_ENA_SET(1) | QFWD_SWITCH_PORT_MODE_FWD_URGENCY_SET(urgency), QFWD_SWITCH_PORT_MODE_PORT_ENA | QFWD_SWITCH_PORT_MODE_FWD_URGENCY, sparx5, QFWD_SWITCH_PORT_MODE(port->portno)); /* Save the new values */ port->conf = *conf; return 0; } /* Initialize port config to default */ int sparx5_port_init(struct sparx5 *sparx5, struct sparx5_port *port, struct sparx5_port_config *conf) { u32 pause_start = sparx5_wm_enc(6 * (ETH_MAXLEN / SPX5_BUFFER_CELL_SZ)); u32 atop = sparx5_wm_enc(20 * (ETH_MAXLEN / SPX5_BUFFER_CELL_SZ)); u32 devhigh = sparx5_to_high_dev(port->portno); u32 pix = sparx5_port_dev_index(port->portno); u32 pcs = sparx5_to_pcs_dev(port->portno); bool sd_pol = port->signd_active_high; bool sd_sel = !port->signd_internal; bool sd_ena = port->signd_enable; u32 pause_stop = 0xFFF - 1; /* FC generate disabled */ void __iomem *devinst; void __iomem *pcsinst; int err; devinst = spx5_inst_get(sparx5, devhigh, pix); pcsinst = spx5_inst_get(sparx5, pcs, pix); /* Set the mux port mode */ err = sparx5_port_mux_set(sparx5, port, conf); if (err) return err; /* Configure MAC vlan awareness */ err = sparx5_port_max_tags_set(sparx5, port); if (err) return err; /* Set Max Length */ spx5_rmw(DEV2G5_MAC_MAXLEN_CFG_MAX_LEN_SET(ETH_MAXLEN), DEV2G5_MAC_MAXLEN_CFG_MAX_LEN, sparx5, DEV2G5_MAC_MAXLEN_CFG(port->portno)); /* 1G/2G5: Signal Detect configuration */ spx5_wr(DEV2G5_PCS1G_SD_CFG_SD_POL_SET(sd_pol) | DEV2G5_PCS1G_SD_CFG_SD_SEL_SET(sd_sel) | DEV2G5_PCS1G_SD_CFG_SD_ENA_SET(sd_ena), sparx5, DEV2G5_PCS1G_SD_CFG(port->portno)); /* Set Pause WM hysteresis */ spx5_rmw(QSYS_PAUSE_CFG_PAUSE_START_SET(pause_start) | QSYS_PAUSE_CFG_PAUSE_STOP_SET(pause_stop) | QSYS_PAUSE_CFG_PAUSE_ENA_SET(1), QSYS_PAUSE_CFG_PAUSE_START | QSYS_PAUSE_CFG_PAUSE_STOP | QSYS_PAUSE_CFG_PAUSE_ENA, sparx5, QSYS_PAUSE_CFG(port->portno)); /* Port ATOP. Frames are tail dropped when this WM is hit */ spx5_wr(QSYS_ATOP_ATOP_SET(atop), sparx5, QSYS_ATOP(port->portno)); /* Discard pause frame 01-80-C2-00-00-01 */ spx5_wr(PAUSE_DISCARD, sparx5, ANA_CL_CAPTURE_BPDU_CFG(port->portno)); if (conf->portmode == PHY_INTERFACE_MODE_QSGMII || conf->portmode == PHY_INTERFACE_MODE_SGMII) { err = sparx5_serdes_set(sparx5, port, conf); if (err) return err; if (!sparx5_port_is_2g5(port->portno)) /* Enable shadow device */ spx5_rmw(DSM_DEV_TX_STOP_WM_CFG_DEV10G_SHADOW_ENA_SET(1), DSM_DEV_TX_STOP_WM_CFG_DEV10G_SHADOW_ENA, sparx5, DSM_DEV_TX_STOP_WM_CFG(port->portno)); sparx5_dev_switch(sparx5, port->portno, false); } if (conf->portmode == PHY_INTERFACE_MODE_QSGMII) { // All ports must be PCS enabled in QSGMII mode spx5_rmw(DEV2G5_DEV_RST_CTRL_PCS_TX_RST_SET(0), DEV2G5_DEV_RST_CTRL_PCS_TX_RST, sparx5, DEV2G5_DEV_RST_CTRL(port->portno)); } /* Default IFGs for 1G */ spx5_wr(DEV2G5_MAC_IFG_CFG_TX_IFG_SET(6) | DEV2G5_MAC_IFG_CFG_RX_IFG1_SET(0) | DEV2G5_MAC_IFG_CFG_RX_IFG2_SET(0), sparx5, DEV2G5_MAC_IFG_CFG(port->portno)); if (sparx5_port_is_2g5(port->portno)) return 0; /* Low speed device only - return */ /* Now setup the high speed device */ if (conf->portmode == PHY_INTERFACE_MODE_NA) conf->portmode = PHY_INTERFACE_MODE_10GBASER; if (sparx5_is_baser(conf->portmode)) sparx5_dev_switch(sparx5, port->portno, true); /* Set Max Length */ spx5_inst_rmw(DEV10G_MAC_MAXLEN_CFG_MAX_LEN_SET(ETH_MAXLEN), DEV10G_MAC_MAXLEN_CFG_MAX_LEN, devinst, DEV10G_MAC_ENA_CFG(0)); /* Handle Signal Detect in 10G PCS */ spx5_inst_wr(PCS10G_BR_PCS_SD_CFG_SD_POL_SET(sd_pol) | PCS10G_BR_PCS_SD_CFG_SD_SEL_SET(sd_sel) | PCS10G_BR_PCS_SD_CFG_SD_ENA_SET(sd_ena), pcsinst, PCS10G_BR_PCS_SD_CFG(0)); if (sparx5_port_is_25g(port->portno)) { /* Handle Signal Detect in 25G PCS */ spx5_wr(DEV25G_PCS25G_SD_CFG_SD_POL_SET(sd_pol) | DEV25G_PCS25G_SD_CFG_SD_SEL_SET(sd_sel) | DEV25G_PCS25G_SD_CFG_SD_ENA_SET(sd_ena), sparx5, DEV25G_PCS25G_SD_CFG(pix)); } return 0; } void sparx5_port_enable(struct sparx5_port *port, bool enable) { struct sparx5 *sparx5 = port->sparx5; /* Enable port for frame transfer? */ spx5_rmw(QFWD_SWITCH_PORT_MODE_PORT_ENA_SET(enable), QFWD_SWITCH_PORT_MODE_PORT_ENA, sparx5, QFWD_SWITCH_PORT_MODE(port->portno)); }
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1