| // SPDX-License-Identifier: GPL-2.0 |
| /* Copyright (c) 2015 - 2025 Beijing WangXun Technology Co., Ltd. */ |
| |
| #include <linux/phylink.h> |
| #include <linux/iopoll.h> |
| #include <linux/pci.h> |
| #include <linux/phy.h> |
| |
| #include "../libwx/wx_type.h" |
| #include "../libwx/wx_lib.h" |
| #include "../libwx/wx_ptp.h" |
| #include "../libwx/wx_hw.h" |
| #include "../libwx/wx_sriov.h" |
| #include "txgbe_type.h" |
| #include "txgbe_aml.h" |
| #include "txgbe_hw.h" |
| |
| void txgbe_gpio_init_aml(struct wx *wx) |
| { |
| u32 status, mod_rst; |
| |
| if (wx->mac.type == wx_mac_aml40) |
| mod_rst = TXGBE_GPIOBIT_4; |
| else |
| mod_rst = TXGBE_GPIOBIT_2; |
| |
| wr32(wx, WX_GPIO_INTTYPE_LEVEL, mod_rst); |
| wr32(wx, WX_GPIO_INTEN, mod_rst); |
| |
| status = rd32(wx, WX_GPIO_INTSTATUS); |
| for (int i = 0; i < 6; i++) { |
| if (status & BIT(i)) |
| wr32(wx, WX_GPIO_EOI, BIT(i)); |
| } |
| } |
| |
| irqreturn_t txgbe_gpio_irq_handler_aml(int irq, void *data) |
| { |
| struct txgbe *txgbe = data; |
| struct wx *wx = txgbe->wx; |
| u32 status, mod_rst; |
| |
| if (wx->mac.type == wx_mac_aml40) |
| mod_rst = TXGBE_GPIOBIT_4; |
| else |
| mod_rst = TXGBE_GPIOBIT_2; |
| |
| wr32(wx, WX_GPIO_INTMASK, 0xFF); |
| status = rd32(wx, WX_GPIO_INTSTATUS); |
| if (status & mod_rst) { |
| set_bit(WX_FLAG_NEED_MODULE_RESET, wx->flags); |
| wr32(wx, WX_GPIO_EOI, mod_rst); |
| wx_service_event_schedule(wx); |
| } |
| |
| wr32(wx, WX_GPIO_INTMASK, 0); |
| return IRQ_HANDLED; |
| } |
| |
| int txgbe_test_hostif(struct wx *wx) |
| { |
| struct txgbe_hic_ephy_getlink buffer; |
| |
| if (wx->mac.type == wx_mac_sp) |
| return 0; |
| |
| buffer.hdr.cmd = FW_PHY_GET_LINK_CMD; |
| buffer.hdr.buf_len = sizeof(struct txgbe_hic_ephy_getlink) - |
| sizeof(struct wx_hic_hdr); |
| buffer.hdr.cmd_or_resp.cmd_resv = FW_CEM_CMD_RESERVED; |
| |
| return wx_host_interface_command(wx, (u32 *)&buffer, sizeof(buffer), |
| WX_HI_COMMAND_TIMEOUT, false); |
| } |
| |
| int txgbe_read_eeprom_hostif(struct wx *wx, |
| struct txgbe_hic_i2c_read *buffer, |
| u32 length, u8 *data) |
| { |
| u32 dword_len, offset, value, i; |
| int err; |
| |
| buffer->hdr.cmd = FW_READ_EEPROM_CMD; |
| buffer->hdr.buf_len = sizeof(struct txgbe_hic_i2c_read) - |
| sizeof(struct wx_hic_hdr); |
| buffer->hdr.cmd_or_resp.cmd_resv = FW_CEM_CMD_RESERVED; |
| |
| err = wx_host_interface_command(wx, (u32 *)buffer, |
| sizeof(struct txgbe_hic_i2c_read), |
| WX_HI_COMMAND_TIMEOUT, false); |
| if (err != 0) |
| return err; |
| |
| /* buffer length offset to read return data */ |
| offset = sizeof(struct txgbe_hic_i2c_read) >> 2; |
| dword_len = round_up(length, 4) >> 2; |
| |
| for (i = 0; i < dword_len; i++) { |
| u32 copy_len = min_t(u32, 4, length - i * 4); |
| |
| value = rd32a(wx, WX_FW2SW_MBOX, i + offset); |
| le32_to_cpus(&value); |
| |
| memcpy(data, &value, copy_len); |
| data += copy_len; |
| } |
| |
| return 0; |
| } |
| |
| static int txgbe_identify_module_hostif(struct wx *wx, |
| struct txgbe_hic_get_module_info *buffer) |
| { |
| buffer->hdr.cmd = FW_GET_MODULE_INFO_CMD; |
| buffer->hdr.buf_len = sizeof(struct txgbe_hic_get_module_info) - |
| sizeof(struct wx_hic_hdr); |
| buffer->hdr.cmd_or_resp.cmd_resv = FW_CEM_CMD_RESERVED; |
| |
| return wx_host_interface_command(wx, (u32 *)buffer, |
| sizeof(struct txgbe_hic_get_module_info), |
| WX_HI_COMMAND_TIMEOUT, true); |
| } |
| |
| static int txgbe_set_phy_link_hostif(struct wx *wx, int speed, int autoneg, int duplex) |
| { |
| struct txgbe_hic_ephy_setlink buffer; |
| |
| buffer.hdr.cmd = FW_PHY_SET_LINK_CMD; |
| buffer.hdr.buf_len = sizeof(struct txgbe_hic_ephy_setlink) - |
| sizeof(struct wx_hic_hdr); |
| buffer.hdr.cmd_or_resp.cmd_resv = FW_CEM_CMD_RESERVED; |
| |
| switch (speed) { |
| case SPEED_40000: |
| buffer.speed = TXGBE_LINK_SPEED_40GB_FULL; |
| break; |
| case SPEED_25000: |
| buffer.speed = TXGBE_LINK_SPEED_25GB_FULL; |
| break; |
| case SPEED_10000: |
| buffer.speed = TXGBE_LINK_SPEED_10GB_FULL; |
| break; |
| default: |
| buffer.speed = TXGBE_LINK_SPEED_UNKNOWN; |
| break; |
| } |
| |
| buffer.fec_mode = TXGBE_PHY_FEC_AUTO; |
| buffer.autoneg = autoneg; |
| buffer.duplex = duplex; |
| |
| return wx_host_interface_command(wx, (u32 *)&buffer, sizeof(buffer), |
| WX_HI_COMMAND_TIMEOUT, false); |
| } |
| |
| static void txgbe_get_link_capabilities(struct wx *wx, int *speed, |
| int *autoneg, int *duplex) |
| { |
| struct txgbe *txgbe = wx->priv; |
| |
| if (test_bit(PHY_INTERFACE_MODE_XLGMII, txgbe->link_interfaces)) |
| *speed = SPEED_40000; |
| else if (test_bit(PHY_INTERFACE_MODE_25GBASER, txgbe->link_interfaces)) |
| *speed = SPEED_25000; |
| else if (test_bit(PHY_INTERFACE_MODE_10GBASER, txgbe->link_interfaces)) |
| *speed = SPEED_10000; |
| else |
| *speed = SPEED_UNKNOWN; |
| |
| *autoneg = phylink_test(txgbe->advertising, Autoneg); |
| *duplex = *speed == SPEED_UNKNOWN ? DUPLEX_HALF : DUPLEX_FULL; |
| } |
| |
| static void txgbe_get_mac_link(struct wx *wx, int *speed) |
| { |
| u32 status; |
| |
| status = rd32(wx, TXGBE_CFG_PORT_ST); |
| if (!(status & TXGBE_CFG_PORT_ST_LINK_UP)) |
| *speed = SPEED_UNKNOWN; |
| else if (status & TXGBE_CFG_PORT_ST_LINK_AML_40G) |
| *speed = SPEED_40000; |
| else if (status & TXGBE_CFG_PORT_ST_LINK_AML_25G) |
| *speed = SPEED_25000; |
| else if (status & TXGBE_CFG_PORT_ST_LINK_AML_10G) |
| *speed = SPEED_10000; |
| else |
| *speed = SPEED_UNKNOWN; |
| } |
| |
| void txgbe_set_phy_link(struct wx *wx) |
| { |
| int speed, autoneg, duplex, err; |
| |
| txgbe_get_link_capabilities(wx, &speed, &autoneg, &duplex); |
| |
| err = txgbe_set_phy_link_hostif(wx, speed, autoneg, duplex); |
| if (err) |
| wx_err(wx, "Failed to setup link\n"); |
| } |
| |
| static int txgbe_sfp_to_linkmodes(struct wx *wx, struct txgbe_sff_id *id) |
| { |
| __ETHTOOL_DECLARE_LINK_MODE_MASK(modes) = { 0, }; |
| DECLARE_PHY_INTERFACE_MASK_ZERO(interfaces); |
| struct txgbe *txgbe = wx->priv; |
| |
| if (id->cable_tech & TXGBE_SFF_DA_PASSIVE_CABLE) { |
| txgbe->link_port = PORT_DA; |
| phylink_set(modes, Autoneg); |
| if (id->com_25g_code == TXGBE_SFF_25GBASECR_91FEC || |
| id->com_25g_code == TXGBE_SFF_25GBASECR_74FEC || |
| id->com_25g_code == TXGBE_SFF_25GBASECR_NOFEC) { |
| phylink_set(modes, 25000baseCR_Full); |
| phylink_set(modes, 10000baseCR_Full); |
| __set_bit(PHY_INTERFACE_MODE_25GBASER, interfaces); |
| __set_bit(PHY_INTERFACE_MODE_10GBASER, interfaces); |
| } else { |
| phylink_set(modes, 10000baseCR_Full); |
| __set_bit(PHY_INTERFACE_MODE_10GBASER, interfaces); |
| } |
| } else if (id->cable_tech & TXGBE_SFF_DA_ACTIVE_CABLE) { |
| txgbe->link_port = PORT_DA; |
| phylink_set(modes, Autoneg); |
| phylink_set(modes, 25000baseCR_Full); |
| __set_bit(PHY_INTERFACE_MODE_25GBASER, interfaces); |
| } else { |
| if (id->com_25g_code == TXGBE_SFF_25GBASESR_CAPABLE || |
| id->com_25g_code == TXGBE_SFF_25GBASEER_CAPABLE || |
| id->com_25g_code == TXGBE_SFF_25GBASELR_CAPABLE) { |
| txgbe->link_port = PORT_FIBRE; |
| phylink_set(modes, 25000baseSR_Full); |
| __set_bit(PHY_INTERFACE_MODE_25GBASER, interfaces); |
| } |
| if (id->com_10g_code & TXGBE_SFF_10GBASESR_CAPABLE) { |
| txgbe->link_port = PORT_FIBRE; |
| phylink_set(modes, 10000baseSR_Full); |
| __set_bit(PHY_INTERFACE_MODE_10GBASER, interfaces); |
| } |
| if (id->com_10g_code & TXGBE_SFF_10GBASELR_CAPABLE) { |
| txgbe->link_port = PORT_FIBRE; |
| phylink_set(modes, 10000baseLR_Full); |
| __set_bit(PHY_INTERFACE_MODE_10GBASER, interfaces); |
| } |
| } |
| |
| if (phy_interface_empty(interfaces)) { |
| wx_err(wx, "unsupported SFP module\n"); |
| return -EINVAL; |
| } |
| |
| phylink_set(modes, Pause); |
| phylink_set(modes, Asym_Pause); |
| phylink_set(modes, FIBRE); |
| |
| if (!linkmode_equal(txgbe->link_support, modes)) { |
| linkmode_copy(txgbe->link_support, modes); |
| phy_interface_and(txgbe->link_interfaces, |
| wx->phylink_config.supported_interfaces, |
| interfaces); |
| linkmode_copy(txgbe->advertising, modes); |
| |
| set_bit(WX_FLAG_NEED_LINK_CONFIG, wx->flags); |
| } |
| |
| return 0; |
| } |
| |
| static int txgbe_qsfp_to_linkmodes(struct wx *wx, struct txgbe_sff_id *id) |
| { |
| __ETHTOOL_DECLARE_LINK_MODE_MASK(modes) = { 0, }; |
| DECLARE_PHY_INTERFACE_MASK_ZERO(interfaces); |
| struct txgbe *txgbe = wx->priv; |
| |
| if (id->transceiver_type & TXGBE_SFF_ETHERNET_40G_CR4) { |
| txgbe->link_port = PORT_DA; |
| phylink_set(modes, Autoneg); |
| phylink_set(modes, 40000baseCR4_Full); |
| phylink_set(modes, 10000baseCR_Full); |
| __set_bit(PHY_INTERFACE_MODE_XLGMII, interfaces); |
| __set_bit(PHY_INTERFACE_MODE_10GBASER, interfaces); |
| } |
| if (id->transceiver_type & TXGBE_SFF_ETHERNET_40G_SR4) { |
| txgbe->link_port = PORT_FIBRE; |
| phylink_set(modes, 40000baseSR4_Full); |
| __set_bit(PHY_INTERFACE_MODE_XLGMII, interfaces); |
| } |
| if (id->transceiver_type & TXGBE_SFF_ETHERNET_40G_LR4) { |
| txgbe->link_port = PORT_FIBRE; |
| phylink_set(modes, 40000baseLR4_Full); |
| __set_bit(PHY_INTERFACE_MODE_XLGMII, interfaces); |
| } |
| if (id->transceiver_type & TXGBE_SFF_ETHERNET_40G_ACTIVE) { |
| txgbe->link_port = PORT_DA; |
| phylink_set(modes, Autoneg); |
| phylink_set(modes, 40000baseCR4_Full); |
| __set_bit(PHY_INTERFACE_MODE_XLGMII, interfaces); |
| } |
| if (id->transceiver_type & TXGBE_SFF_ETHERNET_RSRVD) { |
| if (id->sff_opt1 & TXGBE_SFF_ETHERNET_100G_CR4) { |
| txgbe->link_port = PORT_DA; |
| phylink_set(modes, Autoneg); |
| phylink_set(modes, 40000baseCR4_Full); |
| phylink_set(modes, 25000baseCR_Full); |
| phylink_set(modes, 10000baseCR_Full); |
| __set_bit(PHY_INTERFACE_MODE_XLGMII, interfaces); |
| __set_bit(PHY_INTERFACE_MODE_25GBASER, interfaces); |
| __set_bit(PHY_INTERFACE_MODE_10GBASER, interfaces); |
| } |
| } |
| |
| if (phy_interface_empty(interfaces)) { |
| wx_err(wx, "unsupported QSFP module\n"); |
| return -EINVAL; |
| } |
| |
| phylink_set(modes, Pause); |
| phylink_set(modes, Asym_Pause); |
| phylink_set(modes, FIBRE); |
| |
| if (!linkmode_equal(txgbe->link_support, modes)) { |
| linkmode_copy(txgbe->link_support, modes); |
| phy_interface_and(txgbe->link_interfaces, |
| wx->phylink_config.supported_interfaces, |
| interfaces); |
| linkmode_copy(txgbe->advertising, modes); |
| |
| set_bit(WX_FLAG_NEED_LINK_CONFIG, wx->flags); |
| } |
| |
| return 0; |
| } |
| |
| int txgbe_identify_module(struct wx *wx) |
| { |
| struct txgbe_hic_get_module_info buffer = { 0 }; |
| struct txgbe_sff_id *id; |
| int err = 0; |
| u32 mod_abs; |
| u32 gpio; |
| |
| if (wx->mac.type == wx_mac_aml40) |
| mod_abs = TXGBE_GPIOBIT_4; |
| else |
| mod_abs = TXGBE_GPIOBIT_2; |
| |
| gpio = rd32(wx, WX_GPIO_EXT); |
| if (gpio & mod_abs) |
| return -ENODEV; |
| |
| err = txgbe_identify_module_hostif(wx, &buffer); |
| if (err) { |
| wx_err(wx, "Failed to identify module\n"); |
| return err; |
| } |
| |
| id = &buffer.id; |
| if (id->identifier == TXGBE_SFF_IDENTIFIER_SFP) |
| return txgbe_sfp_to_linkmodes(wx, id); |
| |
| if (id->identifier == TXGBE_SFF_IDENTIFIER_QSFP || |
| id->identifier == TXGBE_SFF_IDENTIFIER_QSFP_PLUS || |
| id->identifier == TXGBE_SFF_IDENTIFIER_QSFP28) |
| return txgbe_qsfp_to_linkmodes(wx, id); |
| |
| wx_err(wx, "Invalid module\n"); |
| return -EINVAL; |
| } |
| |
| void txgbe_setup_link(struct wx *wx) |
| { |
| struct txgbe *txgbe = wx->priv; |
| |
| phy_interface_zero(txgbe->link_interfaces); |
| linkmode_zero(txgbe->link_support); |
| |
| set_bit(WX_FLAG_NEED_MODULE_RESET, wx->flags); |
| wx_service_event_schedule(wx); |
| } |
| |
| static void txgbe_get_link_state(struct phylink_config *config, |
| struct phylink_link_state *state) |
| { |
| struct wx *wx = phylink_to_wx(config); |
| int speed; |
| |
| txgbe_get_mac_link(wx, &speed); |
| state->link = speed != SPEED_UNKNOWN; |
| state->speed = speed; |
| state->duplex = state->link ? DUPLEX_FULL : DUPLEX_UNKNOWN; |
| } |
| |
| static void txgbe_reconfig_mac(struct wx *wx) |
| { |
| u32 wdg, fc; |
| |
| wdg = rd32(wx, WX_MAC_WDG_TIMEOUT); |
| fc = rd32(wx, WX_MAC_RX_FLOW_CTRL); |
| |
| wr32(wx, WX_MIS_RST, TXGBE_MIS_RST_MAC_RST(wx->bus.func)); |
| /* wait for MAC reset complete */ |
| usleep_range(1000, 1500); |
| |
| wr32m(wx, TXGBE_MAC_MISC_CTL, TXGBE_MAC_MISC_CTL_LINK_STS_MOD, |
| TXGBE_MAC_MISC_CTL_LINK_BOTH); |
| wx_reset_mac(wx); |
| |
| wr32(wx, WX_MAC_WDG_TIMEOUT, wdg); |
| wr32(wx, WX_MAC_RX_FLOW_CTRL, fc); |
| } |
| |
| static void txgbe_mac_link_up_aml(struct phylink_config *config, |
| struct phy_device *phy, |
| unsigned int mode, |
| phy_interface_t interface, |
| int speed, int duplex, |
| bool tx_pause, bool rx_pause) |
| { |
| struct wx *wx = phylink_to_wx(config); |
| u32 txcfg; |
| |
| wx_fc_enable(wx, tx_pause, rx_pause); |
| |
| txgbe_reconfig_mac(wx); |
| txgbe_enable_sec_tx_path(wx); |
| |
| txcfg = rd32(wx, TXGBE_AML_MAC_TX_CFG); |
| txcfg &= ~TXGBE_AML_MAC_TX_CFG_SPEED_MASK; |
| |
| switch (speed) { |
| case SPEED_40000: |
| txcfg |= TXGBE_AML_MAC_TX_CFG_SPEED_40G; |
| break; |
| case SPEED_25000: |
| txcfg |= TXGBE_AML_MAC_TX_CFG_SPEED_25G; |
| break; |
| case SPEED_10000: |
| txcfg |= TXGBE_AML_MAC_TX_CFG_SPEED_10G; |
| break; |
| default: |
| break; |
| } |
| |
| wr32m(wx, WX_MAC_RX_CFG, WX_MAC_RX_CFG_RE, WX_MAC_RX_CFG_RE); |
| wr32(wx, TXGBE_AML_MAC_TX_CFG, txcfg | TXGBE_AML_MAC_TX_CFG_TE); |
| |
| wx->speed = speed; |
| wx->last_rx_ptp_check = jiffies; |
| if (test_bit(WX_STATE_PTP_RUNNING, wx->state)) |
| wx_ptp_reset_cyclecounter(wx); |
| /* ping all the active vfs to let them know we are going up */ |
| wx_ping_all_vfs_with_link_status(wx, true); |
| } |
| |
| static void txgbe_mac_link_down_aml(struct phylink_config *config, |
| unsigned int mode, |
| phy_interface_t interface) |
| { |
| struct wx *wx = phylink_to_wx(config); |
| |
| wr32m(wx, TXGBE_AML_MAC_TX_CFG, TXGBE_AML_MAC_TX_CFG_TE, 0); |
| wr32m(wx, WX_MAC_RX_CFG, WX_MAC_RX_CFG_RE, 0); |
| |
| wx->speed = SPEED_UNKNOWN; |
| if (test_bit(WX_STATE_PTP_RUNNING, wx->state)) |
| wx_ptp_reset_cyclecounter(wx); |
| /* ping all the active vfs to let them know we are going down */ |
| wx_ping_all_vfs_with_link_status(wx, false); |
| } |
| |
| static void txgbe_mac_config_aml(struct phylink_config *config, unsigned int mode, |
| const struct phylink_link_state *state) |
| { |
| } |
| |
| static const struct phylink_mac_ops txgbe_mac_ops_aml = { |
| .mac_config = txgbe_mac_config_aml, |
| .mac_link_down = txgbe_mac_link_down_aml, |
| .mac_link_up = txgbe_mac_link_up_aml, |
| }; |
| |
| int txgbe_phylink_init_aml(struct txgbe *txgbe) |
| { |
| struct phylink_link_state state; |
| struct phylink_config *config; |
| struct wx *wx = txgbe->wx; |
| phy_interface_t phy_mode; |
| struct phylink *phylink; |
| int err; |
| |
| config = &wx->phylink_config; |
| config->dev = &wx->netdev->dev; |
| config->type = PHYLINK_NETDEV; |
| config->mac_capabilities = MAC_25000FD | MAC_10000FD | |
| MAC_SYM_PAUSE | MAC_ASYM_PAUSE; |
| config->get_fixed_state = txgbe_get_link_state; |
| |
| if (wx->mac.type == wx_mac_aml40) { |
| config->mac_capabilities |= MAC_40000FD; |
| phy_mode = PHY_INTERFACE_MODE_XLGMII; |
| __set_bit(PHY_INTERFACE_MODE_XLGMII, config->supported_interfaces); |
| state.speed = SPEED_40000; |
| state.duplex = DUPLEX_FULL; |
| } else { |
| phy_mode = PHY_INTERFACE_MODE_25GBASER; |
| state.speed = SPEED_25000; |
| state.duplex = DUPLEX_FULL; |
| } |
| |
| __set_bit(PHY_INTERFACE_MODE_25GBASER, config->supported_interfaces); |
| __set_bit(PHY_INTERFACE_MODE_10GBASER, config->supported_interfaces); |
| |
| phylink = phylink_create(config, NULL, phy_mode, &txgbe_mac_ops_aml); |
| if (IS_ERR(phylink)) |
| return PTR_ERR(phylink); |
| |
| err = phylink_set_fixed_link(phylink, &state); |
| if (err) { |
| wx_err(wx, "Failed to set fixed link\n"); |
| phylink_destroy(phylink); |
| return err; |
| } |
| |
| wx->phylink = phylink; |
| |
| return 0; |
| } |