From 8e0f8efe2a8b1cded4f4949a8d71e99580600681 Mon Sep 17 00:00:00 2001 From: Stefan Roese Date: Thu, 7 Apr 2022 09:11:52 +0200 Subject: [PATCH] net: Add ethernet support for MIPS Octeon This patchs adds the ethernet & MDIO driver for the MIPS Octeon II / III SoC platform. Please note that these drivers are based on the 2013 U-Boot version from Marvell and make use of the platform supported helper functions for the ethernet functionality, including stuff like SFP handling. Signed-off-by: Stefan Roese Cc: Ramon Fried Cc: Joe Hershberger --- drivers/net/Kconfig | 7 + drivers/net/Makefile | 1 + drivers/net/octeon/Makefile | 6 + drivers/net/octeon/octeon_eth.c | 1060 ++++++++++++++++++++++++++++++ drivers/net/octeon/octeon_mdio.c | 226 +++++++ 5 files changed, 1300 insertions(+) create mode 100644 drivers/net/octeon/Makefile create mode 100644 drivers/net/octeon/octeon_eth.c create mode 100644 drivers/net/octeon/octeon_mdio.c diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 347fe8aa42..da56326ad3 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -495,6 +495,13 @@ config MT7628_ETH The MediaTek MT7628 ethernet interface is used on MT7628 and MT7688 based boards. +config NET_OCTEON + bool "MIPS Octeon ethernet support" + depends on ARCH_OCTEON + help + You must select Y to enable network device support for + MIPS Octeon SoCs. If unsure, say n + config NET_OCTEONTX bool "OcteonTX Ethernet support" depends on ARCH_OCTEONTX diff --git a/drivers/net/Makefile b/drivers/net/Makefile index 16733d2d1f..69fb3bbbf7 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -64,6 +64,7 @@ obj-$(CONFIG_MVMDIO) += mvmdio.o obj-$(CONFIG_MVNETA) += mvneta.o obj-$(CONFIG_MVPP2) += mvpp2.o obj-$(CONFIG_NETCONSOLE) += netconsole.o +obj-$(CONFIG_NET_OCTEON) += octeon/ obj-$(CONFIG_NET_OCTEONTX) += octeontx/ obj-$(CONFIG_NET_OCTEONTX2) += octeontx2/ obj-$(CONFIG_OCTEONTX2_CGX_INTF) += octeontx2/cgx_intf.o diff --git a/drivers/net/octeon/Makefile b/drivers/net/octeon/Makefile new file mode 100644 index 0000000000..c573411a62 --- /dev/null +++ b/drivers/net/octeon/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# Copyright (C) 2018-2022 Marvell International Ltd. +# + +obj-$(CONFIG_NET_OCTEON) += octeon_eth.o octeon_mdio.o diff --git a/drivers/net/octeon/octeon_eth.c b/drivers/net/octeon/octeon_eth.c new file mode 100644 index 0000000000..fbb1afc08a --- /dev/null +++ b/drivers/net/octeon/octeon_eth.c @@ -0,0 +1,1060 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018-2022 Marvell International Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/** Maximum receive packet size (hardware default is 1536) */ +#define CONFIG_OCTEON_NETWORK_MRU 1536 + +#define OCTEON_BOOTLOADER_NAMED_BLOCK_TMP_PREFIX "__tmp" + +/** + * Enables RX packet debugging if octeon_debug_rx_packets is set in the + * environment. + */ +#define DEBUG_RX_PACKET + +/** + * Enables TX packet debugging if octeon_debug_tx_packets is set in the + * environment. + */ +#define DEBUG_TX_PACKET + +/* Global flag indicating common hw has been set up */ +static int octeon_global_hw_inited; + +#if defined(DEBUG_RX_PACKET) || defined(DEBUG_TX_PACKET) +static int packet_rx_debug; +static int packet_tx_debug; +#endif + +/* Make sure that we have enough buffers to keep prefetching blocks happy. + * Absolute minimum is probably about 200. + */ +#define NUM_PACKET_BUFFERS 1000 + +#define PKO_SHUTDOWN_TIMEOUT_VAL 100 + +/* Define the offsets from the base CSR */ +#define GMX_PRT_CFG 0x10 + +#define GMX_RX_FRM_MAX 0x30 +#define GMX_RX_JABBER 0x38 + +#define GMX_RX_ADR_CTL 0x100 +#define GMX_RX_ADR_CAM_EN 0x108 +#define GMX_RX_ADR_CAM0 0x180 +#define GMX_RX_ADR_CAM1 0x188 +#define GMX_RX_ADR_CAM2 0x190 +#define GMX_RX_ADR_CAM3 0x198 +#define GMX_RX_ADR_CAM4 0x1a0 +#define GMX_RX_ADR_CAM5 0x1a8 +#define GMX_TX_OVR_BP 0x4c8 + +/** + * Set the hardware MAC address for a device + * + * @param interface interface of port to set + * @param index index of port to set MAC address for + * @param addr Address structure to change it too. + * @return Zero on success + */ +static int cvm_oct_set_mac_address(struct udevice *dev) +{ + struct octeon_eth_info *priv = dev_get_priv(dev); + struct eth_pdata *pdata = dev_get_plat(dev); + cvmx_gmxx_prtx_cfg_t gmx_cfg; + cvmx_helper_interface_mode_t mode; + cvmx_gmxx_rxx_adr_ctl_t control; + u8 *ptr = (uint8_t *)pdata->enetaddr; + int interface = priv->interface; + int index = priv->index; + u64 mac = 0; + u64 gmx_reg; + int xipd_port; + int i; + + for (i = 0; i < 6; i++) + mac = (mac << 8) | (u64)(ptr[i]); + + debug("%s(%s (%pM))\n", __func__, dev->name, ptr); + mode = cvmx_helper_interface_get_mode(interface); + + /* It's rather expensive to change the MAC address for BGX so we only + * do this if it has changed or not been set previously. + */ + if (octeon_has_feature(OCTEON_FEATURE_BGX)) { + xipd_port = cvmx_helper_get_ipd_port(interface, index); + if (priv->last_bgx_mac != mac || !priv->bgx_mac_set) { + cvmx_helper_bgx_set_mac(xipd_port, 1, 2, mac); + priv->last_bgx_mac = mac; + priv->bgx_mac_set = 1; + } + return 0; + } + + if (mode == CVMX_HELPER_INTERFACE_MODE_AGL) { + gmx_reg = CVMX_AGL_GMX_RXX_INT_REG(0); + } else { + gmx_reg = CVMX_GMXX_RXX_INT_REG(index, interface); + csr_wr(CVMX_GMXX_SMACX(index, interface), mac); + } + + /* Disable interface */ + gmx_cfg.u64 = csr_rd(gmx_reg + GMX_PRT_CFG); + csr_wr(gmx_reg + GMX_PRT_CFG, gmx_cfg.u64 & ~1ull); + debug("%s: gmx reg: 0x%llx\n", __func__, gmx_reg); + + csr_wr(gmx_reg + GMX_RX_ADR_CAM0, ptr[0]); + csr_wr(gmx_reg + GMX_RX_ADR_CAM1, ptr[1]); + csr_wr(gmx_reg + GMX_RX_ADR_CAM2, ptr[2]); + csr_wr(gmx_reg + GMX_RX_ADR_CAM3, ptr[3]); + csr_wr(gmx_reg + GMX_RX_ADR_CAM4, ptr[4]); + csr_wr(gmx_reg + GMX_RX_ADR_CAM5, ptr[5]); + + control.u64 = 0; + control.s.bcst = 1; /* Allow broadcast MAC addresses */ + control.s.mcst = 1; /* Force reject multicast packets */ + control.s.cam_mode = 1; /* Filter packets based on the CAM */ + + csr_wr(gmx_reg + GMX_RX_ADR_CTL, control.u64); + + csr_wr(gmx_reg + GMX_RX_ADR_CAM_EN, 1); + + /* Return interface to previous enable state */ + csr_wr(gmx_reg + GMX_PRT_CFG, gmx_cfg.u64); + + return 0; +} + +static void cvm_oct_fill_hw_memory(u64 pool, u64 size, u64 elements) +{ + static int alloc_count; + char tmp_name[64]; + int ret; + + debug("%s: pool: 0x%llx, size: 0xx%llx, count: 0x%llx\n", + __func__, pool, size, elements); + sprintf(tmp_name, "%s_fpa_alloc_%d", + OCTEON_BOOTLOADER_NAMED_BLOCK_TMP_PREFIX, alloc_count++); + ret = cvmx_fpa_setup_pool(pool, tmp_name, NULL, size, elements); +} + +/** + * Configure common hardware for all interfaces + */ +static void cvm_oct_configure_common_hw(void) +{ + int mru = env_get_ulong("octeon_mru", 0, CONFIG_OCTEON_NETWORK_MRU); + int packet_pool_size = CVMX_FPA_PACKET_POOL_SIZE; + + if (mru > packet_pool_size) + packet_pool_size = (mru + CVMX_CACHE_LINE_SIZE - 1) & + ~(CVMX_CACHE_LINE_SIZE - 1); + + /* Setup the FPA */ + cvmx_fpa_enable(); + + cvm_oct_fill_hw_memory(CVMX_FPA_WQE_POOL, CVMX_FPA_WQE_POOL_SIZE, + NUM_PACKET_BUFFERS); +#if CVMX_FPA_OUTPUT_BUFFER_POOL != CVMX_FPA_PACKET_POOL + if (!octeon_has_feature(OCTEON_FEATURE_CN78XX_WQE)) { + cvm_oct_fill_hw_memory(CVMX_FPA_OUTPUT_BUFFER_POOL, + CVMX_FPA_OUTPUT_BUFFER_POOL_SIZE, 128); + } +#endif + cvm_oct_fill_hw_memory(CVMX_FPA_PACKET_POOL, packet_pool_size, + NUM_PACKET_BUFFERS); + + cvmx_helper_initialize_packet_io_global(); + cvmx_helper_initialize_packet_io_local(); + + /* The MRU defaults to 1536 bytes by the hardware. Setting + * CONFIG_OCTEON_NETWORK_MRU allows this to be overridden. + */ + if (octeon_has_feature(OCTEON_FEATURE_PKI)) { + struct cvmx_pki_global_config gbl_cfg; + int i; + + cvmx_pki_read_global_config(0, &gbl_cfg); + for (i = 0; i < CVMX_PKI_NUM_FRAME_CHECK; i++) + gbl_cfg.frm_len[i].maxlen = mru; + cvmx_pki_write_global_config(0, &gbl_cfg); + } + + /* Set POW get work timeout to maximum value */ + if (octeon_has_feature(OCTEON_FEATURE_CN68XX_WQE) || + octeon_has_feature(OCTEON_FEATURE_CN78XX_WQE)) + csr_wr(CVMX_SSO_NW_TIM, 0x3ff); + else + csr_wr(CVMX_POW_NW_TIM, 0x3ff); +} + +/** + * Enables Ethernet devices to allow packets to be transmitted and received. + * For example, this is activated when the DHCP command is issued. + * + * @param dev Ethernet device to initialize + * @param bis board data structure, not used. + * + * @return 1 for success + */ +int octeon_eth_init(struct udevice *dev) +{ + struct octeon_eth_info *priv = dev_get_priv(dev); + + debug("%s(), dev_ptr: %p, dev: %s, port: %d\n", __func__, dev, + dev->name, priv->port); + + if (priv->initted_flag) { + debug("%s already initialized\n", dev->name); + return 1; + } + + if (!octeon_global_hw_inited) { + debug("Initializing common hardware\n"); + cvm_oct_configure_common_hw(); + } + + /* Ignore backpressure on RGMII ports */ + if (!octeon_has_feature(OCTEON_FEATURE_BGX)) + csr_wr(priv->gmx_base + GMX_TX_OVR_BP, 0xf << 8 | 0xf); + + debug("%s: Setting MAC address\n", __func__); + cvm_oct_set_mac_address(dev); + + if (!octeon_global_hw_inited) { + debug("Enabling packet input\n"); + cvmx_helper_ipd_and_packet_input_enable(); + octeon_global_hw_inited = 1; + + /* Connect, configure and start the PHY, if the device is + * connected to one. If not, then it's most likely an SPF + * enabled port, which does not have such PHY setup here. + */ + if (priv->mdio_dev) { + priv->phy_dev = dm_eth_phy_connect(dev); + phy_config(priv->phy_dev); + phy_startup(priv->phy_dev); + } + } + priv->enabled = 0; + priv->initted_flag = 1; + + debug("%s exiting successfully\n", __func__); + return 1; +} + +/** + * Initializes the specified interface and port + * + * @param interface interface to initialize + * @param index port index on interface + * @param port ipd port number + * @param if_mode interface mode + * + * @return 0 for success, -1 if out of memory, 1 if port is invalid + */ +static int octeon_eth_initialize(struct udevice *dev, int interface, + int index, int port, + cvmx_helper_interface_mode_t if_mode) +{ + struct octeon_eth_info *oct_eth_info = dev_get_priv(dev); + int eth; + + eth = cvmx_helper_get_port_fdt_node_offset(interface, index); + if (eth <= 0) { + debug("ERROR: No fdt node for interface %d, index %d\n", + interface, index); + return 1; + } + + oct_eth_info->is_c45 = (if_mode == CVMX_HELPER_INTERFACE_MODE_XAUI) || + (if_mode == CVMX_HELPER_INTERFACE_MODE_RXAUI) || + (if_mode == CVMX_HELPER_INTERFACE_MODE_XFI) || + (if_mode == CVMX_HELPER_INTERFACE_MODE_XLAUI) || + (if_mode == CVMX_HELPER_INTERFACE_MODE_10G_KR) || + (if_mode == CVMX_HELPER_INTERFACE_MODE_10G_KR); + oct_eth_info->port = port; + oct_eth_info->index = index; + oct_eth_info->interface = interface; + oct_eth_info->initted_flag = 0; + /* This is guaranteed to force the link state to be printed out */ + oct_eth_info->link_state = 0xffffffffffffffffULL; + debug("Setting up port: %d, int: %d, index: %d, device: octeth%d\n", + oct_eth_info->port, oct_eth_info->interface, oct_eth_info->index, + dev_seq(dev)); + if (if_mode == CVMX_HELPER_INTERFACE_MODE_AGL) { + oct_eth_info->gmx_base = CVMX_AGL_GMX_RXX_INT_REG(0); + } else { + if (!octeon_has_feature(OCTEON_FEATURE_BGX)) + oct_eth_info->gmx_base = + CVMX_GMXX_RXX_INT_REG(index, interface); + } + + return 0; +} + +/** + * @INTERNAL + * Converts a BGX address to the node, interface and port number + * + * @param bgx_addr Address of CSR register + * + * @return node, interface and port number, will be -1 for invalid address. + */ +static struct cvmx_xiface __cvmx_bgx_reg_addr_to_xiface(u64 bgx_addr) +{ + struct cvmx_xiface xi = { -1, -1 }; + + xi.node = cvmx_csr_addr_to_node(bgx_addr); + bgx_addr = cvmx_csr_addr_strip_node(bgx_addr); + if ((bgx_addr & 0xFFFFFFFFF0000000) != 0x00011800E0000000) { + debug("%s: Invalid BGX address 0x%llx\n", __func__, + (unsigned long long)bgx_addr); + xi.node = -1; + return xi; + } + xi.interface = (bgx_addr >> 24) & 0x0F; + + return xi; +} + +static int octeon_nic_probe(struct udevice *dev) +{ + struct octeon_eth_info *info = dev_get_priv(dev); + struct ofnode_phandle_args phandle; + struct cvmx_xiface xi; + ofnode node, mdio_node; + int ipd_port; + int intf; + int ret; + + /* The empty stub is to keep cvmx_user_app_init() happy. */ + cvmx_npi_max_pknds = 1; + __cvmx_helper_init_port_valid(); + + xi = __cvmx_bgx_reg_addr_to_xiface(dev_read_addr(dev)); + intf = xi.interface; + debug("%s: Found BGX node %d, interface %d\n", __func__, xi.node, intf); + + ipd_port = cvmx_helper_get_ipd_port(intf, xi.node); + ret = octeon_eth_initialize(dev, intf, xi.node, ipd_port, + cvmx_helper_interface_get_mode(intf)); + + /* Move to subnode, as this includes the "phy-handle" */ + node = dev_read_first_subnode(dev); + + /* Check if an SPF module is conneted, then no MDIO is probed */ + ret = ofnode_parse_phandle_with_args(node, "sfp-slot", NULL, 0, 0, + &phandle); + if (!ret) { + dev_dbg(dev, "sfp-slot found, not probing for MDIO\n"); + return 0; + } + + /* Continue with MDIO probing */ + ret = ofnode_parse_phandle_with_args(node, "phy-handle", NULL, 0, 0, + &phandle); + if (ret) { + dev_err(dev, "phy-handle not found in subnode\n"); + return -ENODEV; + } + + /* Get MDIO node */ + mdio_node = ofnode_get_parent(phandle.node); + ret = uclass_get_device_by_ofnode(UCLASS_MDIO, mdio_node, + &info->mdio_dev); + if (ret) { + dev_err(dev, "mdio_dev not found\n"); + return -ENODEV; + } + + return 0; +} + +/** + * Sets the hardware MAC address of the Ethernet device + * + * @param dev - Ethernet device + * + * @return 0 for success + */ +int octeon_eth_write_hwaddr(struct udevice *dev) +{ + struct octeon_eth_info *oct_eth_info = dev_get_priv(dev); + struct eth_pdata *pdata = dev_get_plat(dev); + + /* Skip if the interface isn't yet enabled */ + if (!oct_eth_info->enabled) { + debug("%s: Interface not enabled, not setting MAC address\n", + __func__); + return 0; + } + debug("%s: Setting %s address to %02x:%02x:%02x:%02x:%02x:%02x\n", + __func__, dev->name, pdata->enetaddr[0], pdata->enetaddr[1], + pdata->enetaddr[2], pdata->enetaddr[3], pdata->enetaddr[4], + pdata->enetaddr[5]); + return cvm_oct_set_mac_address(dev); +} + +/** + * Enables and disables the XCV RGMII interface + * + * @param interface Interface number + * @param index Port index (should be 0 for RGMII) + * @param enable True to enable it, false to disable it + */ +static void octeon_bgx_xcv_rgmii_enable(int interface, int index, bool enable) +{ + union cvmx_xcv_reset xcv_reset; + + debug("%s(%d, %d, %sable)\n", __func__, interface, index, + enable ? "en" : "dis"); + xcv_reset.u64 = csr_rd(CVMX_XCV_RESET); + xcv_reset.s.rx_pkt_rst_n = enable ? 1 : 0; + csr_wr(CVMX_XCV_RESET, xcv_reset.u64); +} + +/** + * Enables a SGMII interface + * + * @param dev - Ethernet device to initialize + */ +void octeon_eth_sgmii_enable(struct udevice *dev) +{ + struct octeon_eth_info *oct_eth_info; + cvmx_gmxx_prtx_cfg_t gmx_cfg; + int index, interface; + cvmx_helper_interface_mode_t if_mode; + + oct_eth_info = dev_get_priv(dev); + interface = oct_eth_info->interface; + index = oct_eth_info->index; + + debug("%s(%s) (%d.%d)\n", __func__, dev->name, interface, index); + if (octeon_has_feature(OCTEON_FEATURE_BGX)) { + cvmx_bgxx_cmrx_config_t cmr_config; + + cmr_config.u64 = + csr_rd(CVMX_BGXX_CMRX_CONFIG(index, interface)); + cmr_config.s.enable = 1; + cmr_config.s.data_pkt_tx_en = 1; + cmr_config.s.data_pkt_rx_en = 1; + csr_wr(CVMX_BGXX_CMRX_CONFIG(index, interface), cmr_config.u64); + mdelay(100); + if (cvmx_helper_bgx_is_rgmii(interface, index)) + octeon_bgx_xcv_rgmii_enable(interface, index, true); + } else { + if_mode = cvmx_helper_interface_get_mode(interface); + /* Normal operating mode. */ + + if (if_mode == CVMX_HELPER_INTERFACE_MODE_SGMII || + if_mode == CVMX_HELPER_INTERFACE_MODE_QSGMII) { + cvmx_pcsx_miscx_ctl_reg_t pcsx_miscx_ctl_reg; + + debug(" if mode: (Q)SGMII\n"); + pcsx_miscx_ctl_reg.u64 = csr_rd(CVMX_PCSX_MISCX_CTL_REG(index, interface)); + pcsx_miscx_ctl_reg.s.gmxeno = 0; + csr_wr(CVMX_PCSX_MISCX_CTL_REG(index, interface), + pcsx_miscx_ctl_reg.u64); + } else if (if_mode != CVMX_HELPER_INTERFACE_MODE_AGL) { + cvmx_pcsxx_misc_ctl_reg_t pcsxx_misc_ctl_reg; + + debug(" if mode: AGM\n"); + pcsxx_misc_ctl_reg.u64 = + csr_rd(CVMX_PCSXX_MISC_CTL_REG(interface)); + pcsxx_misc_ctl_reg.s.gmxeno = 0; + csr_wr(CVMX_PCSXX_MISC_CTL_REG(interface), + pcsxx_misc_ctl_reg.u64); + } + + gmx_cfg.u64 = csr_rd(oct_eth_info->gmx_base + GMX_PRT_CFG); + gmx_cfg.s.en = 1; + csr_wr(oct_eth_info->gmx_base + GMX_PRT_CFG, gmx_cfg.u64); + gmx_cfg.u64 = csr_rd(oct_eth_info->gmx_base + GMX_PRT_CFG); + } +} + +/** + * Enables an Ethernet interface + * + * @param dev - Ethernet device to enable + */ +void octeon_eth_enable(struct udevice *dev) +{ + struct octeon_eth_info *oct_eth_info; + u64 tmp; + int interface; + cvmx_helper_interface_mode_t if_mode; + + oct_eth_info = dev_get_priv(dev); + interface = oct_eth_info->interface; + if_mode = cvmx_helper_interface_get_mode(interface); + + switch (if_mode) { + case CVMX_HELPER_INTERFACE_MODE_RGMII: + case CVMX_HELPER_INTERFACE_MODE_GMII: + debug(" rgmii/gmii mode\n"); + tmp = csr_rd(CVMX_ASXX_RX_PRT_EN(interface)); + tmp |= (1ull << (oct_eth_info->port & 0x3)); + csr_wr(CVMX_ASXX_RX_PRT_EN(interface), tmp); + tmp = csr_rd(CVMX_ASXX_TX_PRT_EN(interface)); + tmp |= (1ull << (oct_eth_info->port & 0x3)); + csr_wr(CVMX_ASXX_TX_PRT_EN(interface), tmp); + octeon_eth_write_hwaddr(dev); + break; + + case CVMX_HELPER_INTERFACE_MODE_SGMII: + case CVMX_HELPER_INTERFACE_MODE_XAUI: + case CVMX_HELPER_INTERFACE_MODE_RXAUI: + case CVMX_HELPER_INTERFACE_MODE_XLAUI: + case CVMX_HELPER_INTERFACE_MODE_XFI: + case CVMX_HELPER_INTERFACE_MODE_10G_KR: + case CVMX_HELPER_INTERFACE_MODE_40G_KR4: + case CVMX_HELPER_INTERFACE_MODE_MIXED: + case CVMX_HELPER_INTERFACE_MODE_AGL: + debug(" SGMII/XAUI/etc.\n"); + octeon_eth_sgmii_enable(dev); + octeon_eth_write_hwaddr(dev); + break; + + default: + break; + } +} + +void octeon_phy_port_check(struct udevice *dev) +{ + struct octeon_eth_info *oct_eth_info = dev_get_priv(dev); + struct phy_device *phydev = oct_eth_info->phydev; + + if (oct_eth_info->phy_port_check) + oct_eth_info->phy_port_check(phydev); +} + +/** + * Configure the RGMII port for the negotiated speed + * + * @param dev Linux device for the RGMII port + */ +static void cvm_oct_configure_rgmii_speed(struct udevice *dev) +{ + struct octeon_eth_info *priv = dev_get_priv(dev); + int port = priv->port; + cvmx_helper_link_info_t link_state = cvmx_helper_link_get(port); + + /* If the port is down some PHYs we need to check modules, etc. */ + if (!link_state.s.link_up) + octeon_phy_port_check(dev); + + if (link_state.u64 != priv->link_state) { + cvmx_helper_interface_mode_t mode; + + octeon_phy_port_check(dev); + + debug("%s(%s): Link state changed\n", __func__, dev->name); + printf("%s: ", dev->name); + if (!link_state.s.link_up) { + puts("Down "); + } else { + printf("Up %d Mbps ", link_state.s.speed); + if (link_state.s.full_duplex) + puts("Full duplex "); + else + puts("Half duplex "); + } + mode = cvmx_helper_interface_get_mode(priv->interface); + printf("(port %2d) (%s)\n", port, + cvmx_helper_interface_mode_to_string(mode)); + debug("%s: Setting link state\n", __func__); + cvmx_helper_link_set(priv->port, link_state); + priv->link_state = link_state.u64; + } +} + +#if defined(DEBUG_TX_PACKET) || defined(DEBUG_RX_PACKET) +static void print_mac(const char *label, const uint8_t *mac_addr) +{ + printf("%s: %02x:%02x:%02x:%02x:%02x:%02x", label, mac_addr[0], + mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]); +} + +static void print_ip(const void *packet) +{ + u8 *p = (uint8_t *)packet; + u16 length; + u8 hdr_len; + + puts("IP Header:\n"); + if ((p[0] & 0xF0) != 0x40) { + printf("Invalid IP version %d\n", *p >> 4); + return; + } + hdr_len = *p & 0x0F; + if (hdr_len < 5) + printf("Invalid IP header length %d\n", hdr_len); + printf(" Version: 4, Header length: %d\n", hdr_len); + length = (p[2] << 8) | p[3]; + printf(" TOS: 0x%02x, length: %d\n", p[1], length); + printf(" ID: %d, %s%s%s fragment offset: %d\n", (p[4] << 8) | p[5], + p[6] & 0x80 ? "congested, " : "", p[6] & 0x40 ? "DF, " : "", + p[6] & 0x20 ? "MF, " : "", ((p[6] & 0x1F) << 8) | p[7]); + printf(" TTL: %d, Protocol: %d, Header Checksum: 0x%x\n", p[8], p[9], + (p[10] << 8) | p[11]); + printf(" Source IP: %d.%d.%d.%d\n Destination IP: %d.%d.%d.%d\n", + p[12], p[13], p[14], p[15], p[16], p[17], p[18], p[19]); + if (p[9] == 17 || p[9] == 6) + printf(" Source port: %u, Destination port: %u\n", + (p[20] << 8) | p[21], (p[22] << 8) | p[23]); + puts("\n"); +} + +/** + * Prints out a packet for debugging purposes + * + * @param[in] packet - pointer to packet data + * @param length - length of packet in bytes + */ +static void print_packet(const void *packet, int length) +{ + int i, j; + const unsigned char *up = packet; + u16 type = (up[12] << 8 | up[13]); + int start = 14; + + print_mac("DMAC", &up[0]); + puts(" "); + print_mac("SMAC", &up[6]); + printf(" TYPE: %04x\n", type); + + if (type == 0x0800) + print_ip(&up[start]); + + for (i = start; (i + 16) < length; i += 16) { + printf("%04x ", i); + for (j = 0; j < 16; ++j) + printf("%02x ", up[i + j]); + + printf(" "); + for (j = 0; j < 16; ++j) + printf("%c", + ((up[i + j] >= ' ') && (up[i + j] <= '~')) ? + up[i + j] : + '.'); + printf("\n"); + } + printf("%04x ", i); + for (j = 0; i + j < length; ++j) + printf("%02x ", up[i + j]); + + for (; j < 16; ++j) + printf(" "); + + printf(" "); + for (j = 0; i + j < length; ++j) + printf("%c", ((up[i + j] >= ' ') && (up[i + j] <= '~')) ? + up[i + j] : + '.'); + + printf("\n"); +} +#endif + +/** + * String representation of error codes. + */ +static const char * const rx_error_codes[] = { + "OK", + "partial", + "jabber", + "overrun", + "oversize", + "alignment", + "fragment", + "fcs", + "undersize", + "extend", + "length mismatch", + "rgmii rx", + "skip error", + "nibble error (studder)", + "(undefined)", + "(undefined)", + "SPI 4.2 FCS", + "skip", + "L2 malformed", +}; + +/** + * Called to receive a packet + * + * @param dev - device to receive on + * + * @return - length of packet + * + * This function is used to poll packets. In turn it calls NetReceive + * to process the packets. + */ +static int nic_recv(struct udevice *dev, int flags, uchar **packetp) +{ + cvmx_wqe_t *work = cvmx_pow_work_request_sync(CVMX_POW_WAIT); + struct octeon_eth_info *oct_eth_info = dev_get_priv(dev); + cvmx_buf_ptr_t buf_ptr; + void *packet_data; + int length; + int error_code; + + if (!oct_eth_info->enabled) { + oct_eth_info->enabled = 1; + debug("%s: Enabling interface %s\n", __func__, dev->name); + octeon_eth_enable(dev); + } + + if (!work) { + /* + * Somtimes the link is not up yet. Return here in this + * case, this function will be called again later. + */ + return 0; + } + + error_code = cvmx_wqe_get_rcv_err(work); + if (error_code) { + /* Work has error, so drop */ + cvmx_helper_free_packet_data(work); + cvmx_wqe_free(work); + if (error_code < ARRAY_SIZE(rx_error_codes) && + !octeon_has_feature(OCTEON_FEATURE_BGX)) + printf("Receive error (code %d: %s), dropping\n", + error_code, rx_error_codes[error_code]); + else + printf("Receive error (code %d (unknown), dropping\n", + error_code); + return 0; + } + if (cvmx_wqe_get_bufs(work) != 1) { + /* can only support single-buffer packets */ + printf("Abnormal packet received in %u bufs, dropping\n", + cvmx_wqe_get_bufs(work)); + length = cvmx_wqe_get_len(work); + buf_ptr = cvmx_wqe_get_packet_ptr(work); + packet_data = cvmx_phys_to_ptr(buf_ptr.s.addr); + print_packet(packet_data, length); + cvmx_helper_free_packet_data(work); + cvmx_wqe_free(work); + return 0; + } + + buf_ptr = cvmx_wqe_get_packet_ptr(work); + packet_data = cvmx_phys_to_ptr(buf_ptr.s.addr); + length = cvmx_wqe_get_len(work); + + oct_eth_info->packets_received++; + debug("############# got work: %p, len: %d, packet_ptr: %p\n", work, + length, packet_data); +#if defined(DEBUG_RX_PACKET) + if (packet_rx_debug) { + printf("\nRX packet: interface: %d, index: %d\n", + oct_eth_info->interface, oct_eth_info->index); + print_packet(packet_data, length); + } +#endif + *packetp = (uchar *)packet_data; + + /* Save work for free_pkt() */ + oct_eth_info->work = work; + + /* Free WQE and packet data */ + return length; +} + +static int nic_free_pkt(struct udevice *dev, uchar *pkt, int pkt_len) +{ + struct octeon_eth_info *oct_eth_info = dev_get_priv(dev); + cvmx_wqe_t *work = oct_eth_info->work; + + if (!work) + return 0; + + cvmx_helper_free_packet_data(work); + cvmx_wqe_free(work); + oct_eth_info->work = NULL; + + return 0; +} + +/** + * Packet transmit + * + * @param skb Packet to send + * @param dev Device info structure + * @return Always returns zero + */ +static int cvm_oct_xmit(struct udevice *dev, void *packet, int len) +{ + struct octeon_eth_info *priv = dev_get_priv(dev); + int queue = cvmx_pko_get_base_queue(priv->port); + cvmx_pko_command_word0_t pko_command; + cvmx_buf_ptr_t hw_buffer; + int rv; + + debug("%s: addr: %p, len: %d\n", __func__, packet, len); + + hw_buffer.u64 = 0; + hw_buffer.s.addr = cvmx_ptr_to_phys(packet); + hw_buffer.s.pool = CVMX_FPA_PACKET_POOL; + hw_buffer.s.size = len; + hw_buffer.s.back = 0; + + /* Build the PKO command */ + pko_command.u64 = 0; + pko_command.s.subone0 = 1; + pko_command.s.dontfree = 0; + pko_command.s.segs = 1; + pko_command.s.total_bytes = len; + /* Send the packet to the output queue */ + + debug("%s: port: %d, queue: %d\n", __func__, priv->port, queue); + cvmx_pko_send_packet_prepare(priv->port, queue, 0); + rv = cvmx_pko_send_packet_finish(priv->port, queue, pko_command, + hw_buffer, 0); + if (rv) + printf("Failed to send the packet rv=%d\n", rv); + + return 0; +} + +static int nic_xmit(struct udevice *dev, void *pkt, int pkt_len) +{ + struct octeon_eth_info *oct_eth_info = dev_get_priv(dev); + void *fpa_buf = cvmx_fpa_alloc(CVMX_FPA_PACKET_POOL); + + if (!oct_eth_info->enabled) { + oct_eth_info->enabled = 1; + octeon_eth_enable(dev); + } + + /* We need to copy this to a FPA buffer, then give that to TX */ + + if (oct_eth_info->packets_sent == 0 && + !octeon_has_feature(OCTEON_FEATURE_BGX)) + cvm_oct_configure_rgmii_speed(dev); + + if (!fpa_buf) { + printf("ERROR allocating buffer for packet!\n"); + return -1; + } + + memcpy(fpa_buf, pkt, pkt_len); +#ifdef DEBUG_TX_PACKET + if (packet_tx_debug) { + printf("\nTX packet: interface: %d, index: %d\n", + oct_eth_info->interface, oct_eth_info->index); + print_packet(pkt, pkt_len); + } +#endif + cvm_oct_xmit(dev, fpa_buf, pkt_len); + oct_eth_info->packets_sent++; + + return 0; +} + +int nic_open(struct udevice *dev) +{ + octeon_eth_init(dev); + + return 0; +} + +static void octeon_eth_halt_bgx(struct udevice *dev, + cvmx_helper_interface_mode_t mode) +{ + union cvmx_bgxx_cmrx_config cmr_config; + union cvmx_bgxx_cmr_rx_adrx_cam cmr_cam; + struct octeon_eth_info *oct_eth_info = dev_get_priv(dev); + int index = oct_eth_info->index; + int xiface = oct_eth_info->interface; + struct cvmx_xiface xi = cvmx_helper_xiface_to_node_interface(xiface); + + debug("%s(%s(%d.%d), %d)\n", __func__, dev->name, xiface, index, mode); + + /* For RGMII we need to properly shut down the XCV interface */ + if (cvmx_helper_bgx_is_rgmii(xiface, index)) { + debug(" Shut down XCV RGMII\n"); + octeon_bgx_xcv_rgmii_enable(xi.interface, index, false); + } else { + cmr_config.u64 = csr_rd_node(xi.node, + CVMX_BGXX_CMRX_CONFIG(index, xi.interface)); + cmr_config.s.data_pkt_tx_en = 0; + cmr_config.s.data_pkt_rx_en = 0; + csr_wr_node(xi.node, CVMX_BGXX_CMRX_CONFIG(index, xi.interface), + cmr_config.u64); + + cmr_cam.u64 = csr_rd_node(xi.node, + CVMX_BGXX_CMR_RX_ADRX_CAM(index * 8, xi.interface)); + cmr_cam.s.en = 0; + csr_wr_node(xi.node, + CVMX_BGXX_CMR_RX_ADRX_CAM(index * 8, xi.interface), + cmr_cam.u64); + oct_eth_info->last_bgx_mac = 0; + oct_eth_info->bgx_mac_set = 0; + } +} + +/** + * Halts the specified Ethernet interface preventing it from receiving any more + * packets. + * + * @param dev - Ethernet device to shut down. + */ +void octeon_eth_halt(struct udevice *dev) +{ + struct octeon_eth_info *oct_eth_info = dev_get_priv(dev); + int index = oct_eth_info->index; + int interface = oct_eth_info->interface; + cvmx_helper_interface_mode_t mode; + union cvmx_gmxx_rxx_adr_ctl adr_ctl; + cvmx_gmxx_prtx_cfg_t gmx_cfg; + u64 tmp; + + debug("%s(%s): Halting\n", __func__, dev->name); + + oct_eth_info->enabled = 0; + + mode = cvmx_helper_interface_get_mode(oct_eth_info->interface); + if (octeon_has_feature(OCTEON_FEATURE_BGX)) { + octeon_eth_halt_bgx(dev, mode); + return; + } + + /* Stop SCC */ + /* Disable reception on this port at the GMX block */ + switch (mode) { + case CVMX_HELPER_INTERFACE_MODE_RGMII: + case CVMX_HELPER_INTERFACE_MODE_GMII: + debug(" RGMII/GMII\n"); + tmp = csr_rd(CVMX_ASXX_RX_PRT_EN(oct_eth_info->interface)); + tmp &= ~(1ull << index); + /* Disable the RGMII RX ports */ + csr_wr(CVMX_ASXX_RX_PRT_EN(oct_eth_info->interface), tmp); + tmp = csr_rd(CVMX_ASXX_TX_PRT_EN(oct_eth_info->interface)); + tmp &= ~(1ull << index); + /* Disable the RGMII TX ports */ + csr_wr(CVMX_ASXX_TX_PRT_EN(oct_eth_info->interface), tmp); + /* No break! */ + case CVMX_HELPER_INTERFACE_MODE_SGMII: + case CVMX_HELPER_INTERFACE_MODE_QSGMII: + case CVMX_HELPER_INTERFACE_MODE_XAUI: + case CVMX_HELPER_INTERFACE_MODE_RXAUI: + case CVMX_HELPER_INTERFACE_MODE_XLAUI: + case CVMX_HELPER_INTERFACE_MODE_XFI: + case CVMX_HELPER_INTERFACE_MODE_10G_KR: + case CVMX_HELPER_INTERFACE_MODE_40G_KR4: + case CVMX_HELPER_INTERFACE_MODE_MIXED: + case CVMX_HELPER_INTERFACE_MODE_AGL: + /* Disable MAC filtering */ + gmx_cfg.u64 = csr_rd(oct_eth_info->gmx_base + GMX_PRT_CFG); + csr_wr(oct_eth_info->gmx_base + GMX_PRT_CFG, + gmx_cfg.u64 & ~1ull); + adr_ctl.u64 = 0; + adr_ctl.s.bcst = 1; /* Reject broadcast */ + csr_wr(oct_eth_info->gmx_base + GMX_RX_ADR_CTL, adr_ctl.u64); + csr_wr(oct_eth_info->gmx_base + GMX_RX_ADR_CAM_EN, 0); + csr_wr(oct_eth_info->gmx_base + GMX_PRT_CFG, gmx_cfg.u64); + break; + default: + printf("%s: Unknown mode %d for interface 0x%x:%d\n", __func__, + mode, interface, index); + break; + } +} + +void nic_stop(struct udevice *dev) +{ + octeon_eth_halt(dev); +} + +int nic_write_hwaddr(struct udevice *dev) +{ + cvm_oct_set_mac_address(dev); + + return 0; +} + +static const struct eth_ops octeon_nic_ops = { + .start = nic_open, + .stop = nic_stop, + .send = nic_xmit, + .recv = nic_recv, + .free_pkt = nic_free_pkt, + .write_hwaddr = nic_write_hwaddr, +}; + +static const struct udevice_id octeon_nic_ids[] = { + { .compatible = "cavium,octeon-7890-bgx" }, + {} +}; + +U_BOOT_DRIVER(octeon_nic) = { + .name = "octeon_nic", + .id = UCLASS_ETH, + .probe = octeon_nic_probe, + .of_match = octeon_nic_ids, + .ops = &octeon_nic_ops, + .priv_auto = sizeof(struct octeon_eth_info), +}; diff --git a/drivers/net/octeon/octeon_mdio.c b/drivers/net/octeon/octeon_mdio.c new file mode 100644 index 0000000000..34ee80901f --- /dev/null +++ b/drivers/net/octeon/octeon_mdio.c @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018-2022 Marvell International Ltd. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CVMX_SMI_DRV_CTL 0x0001180000001828ull +#define DEFAULT_MDIO_SPEED 2500000 /** 2.5 MHz default speed */ + +/** + * cvmx_smi_drv_ctl + * + * Enables the SMI interface. + * + */ +union cvmx_smi_drv_ctl { + u64 u64; + struct cvmx_smi_drv_ctl_s { + u64 reserved_14_63 : 50; + u64 pctl : 6; + u64 reserved_6_7 : 2; + u64 nctl : 6; + } s; +}; + +struct octeon_mdiobus { + struct mii_dev *mii_dev; + /** + * The local bus is in the lower 8 bits, followed by the remote bus in + * the top 8 bits. Bit 16 will be set if the bus is non-local. + */ + u32 bus_id; + + int node; /** Node number */ + int speed; /** Bus speed, normally 2.5 MHz */ + int fdt_node; /** Node in FDT */ + bool local; /** true if local MDIO bus */ +}; + +static int octeon_mdio_read(struct udevice *mdio_dev, int phy_addr, + int dev_addr, int reg_addr) +{ + struct octeon_mdiobus *p = dev_get_priv(mdio_dev); + struct mii_dev *dev = p->mii_dev; + int value; + + debug("%s(0x%p(%s): bus_id=%d phy_addr=%d, 0x%x, 0x%x) - ", __func__, + dev, dev->name, p->bus_id, phy_addr, dev_addr, reg_addr); + if (IS_ENABLED(CONFIG_PHYLIB_10G) && dev_addr != MDIO_DEVAD_NONE) { + debug("clause 45 mode\n"); + value = cvmx_mdio_45_read(p->bus_id & 0xff, phy_addr, dev_addr, + reg_addr); + } else { + value = cvmx_mdio_read(p->bus_id & 0xff, phy_addr, reg_addr); + } + + debug("Return value: 0x%x\n", value); + return value; +} + +static int octeon_mdio_write(struct udevice *mdio_dev, int phy_addr, + int dev_addr, int reg_addr, u16 value) +{ + struct octeon_mdiobus *p = dev_get_priv(mdio_dev); + struct mii_dev *dev = p->mii_dev; + + debug("%s(0x%p(%s): bus_id=%d phy_addr=%d, 0x%x, 0x%x, 0x%x)\n", + __func__, dev, dev->name, p->bus_id, phy_addr, dev_addr, reg_addr, + value); + + if (IS_ENABLED(CONFIG_PHYLIB_10G) && dev_addr != MDIO_DEVAD_NONE) { + debug("clause 45 mode\n"); + return cvmx_mdio_45_write(p->bus_id & 0xff, phy_addr, dev_addr, + reg_addr, value); + } + + return cvmx_mdio_write(p->bus_id & 0xff, phy_addr, reg_addr, value); +} + +/** + * Converts a MDIO register address to a bus number + * + * @param reg_addr MDIO base register address + * + * @return MDIO bus number or -1 if invalid address + */ +int octeon_mdio_reg_addr_to_bus(u64 reg_addr) +{ + int bus_base; + int bus; + + /* Adjust the bus number based on the node number */ + bus_base = cvmx_csr_addr_to_node(reg_addr) * 4; + reg_addr = cvmx_csr_addr_strip_node(reg_addr); + + switch (reg_addr) { + case 0x1180000001800: + case 0x1180000003800: /* 68XX/78XX address */ + bus = 0; + break; + case 0x1180000001900: + case 0x1180000003880: + bus = 1; + break; + case 0x1180000003900: + bus = 2; + break; + case 0x1180000003980: + bus = 3; + break; + default: + printf("%s: Unknown register address 0x%llx\n", __func__, + reg_addr); + return -1; + } + bus += bus_base; + debug("%s: address 0x%llx is bus %d\n", __func__, reg_addr, bus); + return bus; +} + +static int octeon_mdio_probe(struct udevice *dev) +{ + struct octeon_mdiobus *p = dev_get_priv(dev); + union cvmx_smi_drv_ctl drv_ctl; + cvmx_smix_clk_t smi_clk; + u64 mdio_addr; + int bus; + u64 sclock; + u32 sample_dly; + u64 denom; + + mdio_addr = dev_read_addr(dev); + debug("%s: Translated address: 0x%llx\n", __func__, mdio_addr); + bus = octeon_mdio_reg_addr_to_bus(mdio_addr); + p->bus_id = bus; + debug("%s: bus: %d\n", __func__, bus); + + drv_ctl.u64 = csr_rd(CVMX_SMI_DRV_CTL); + drv_ctl.s.pctl = dev_read_u32_default(dev, "cavium,pctl-drive-strength", + drv_ctl.s.pctl); + drv_ctl.s.nctl = dev_read_u32_default(dev, "cavium,nctl-drive-strength", + drv_ctl.s.nctl); + debug("%s: Set MDIO PCTL drive strength to 0x%x and NCTL drive strength to 0x%x\n", + __func__, drv_ctl.s.pctl, drv_ctl.s.nctl); + csr_wr(CVMX_SMI_DRV_CTL, drv_ctl.u64); + + /* Set the bus speed, default is 2.5MHz */ + p->speed = dev_read_u32_default(dev, "cavium,max-speed", + DEFAULT_MDIO_SPEED); + sclock = gd->bus_clk; + smi_clk.u64 = csr_rd(CVMX_SMIX_CLK(bus & 3)); + smi_clk.s.phase = sclock / (p->speed * 2); + + /* Allow sample delay to be specified */ + sample_dly = dev_read_u32_default(dev, "cavium,sample-delay", 0); + /* Only change the sample delay if it is set, otherwise use + * the default value of 2. + */ + if (sample_dly) { + u32 sample; + + denom = (sclock * 1000ULL) / sample_dly; + debug("%s: sclock: %llu, sample_dly: %u ps, denom: %llu\n", + __func__, sclock, sample_dly, denom); + sample = (sclock + denom - 1) / denom; + debug("%s: sample: %u\n", __func__, smi_clk.s.sample); + if (sample < 2) { + printf("%s: warning: cavium,sample-delay %u ps is too small in device tree for %s\n", + __func__, sample_dly, dev->name); + sample = 2; + } + if (sample > (2 * smi_clk.s.phase - 3)) { + printf("%s: warning: cavium,sample-delay %u ps is too large in device tree for %s\n", + __func__, sample_dly, dev->name); + sample = 2 * smi_clk.s.phase - 3; + } + smi_clk.s.sample = sample & 0xf; + smi_clk.s.sample_hi = (sample >> 4) & 0xf; + debug("%s(%s): sample delay: %u ps (%d clocks)\n", __func__, + dev->name, sample_dly, smi_clk.s.sample); + } + csr_wr(CVMX_SMIX_CLK(bus & 3), smi_clk.u64); + + debug("mdio clock phase: %d clocks\n", smi_clk.s.phase); + csr_wr(CVMX_SMIX_CLK(bus & 3), smi_clk.u64); + debug("Enabling SMI interface %s\n", dev->name); + csr_wr(CVMX_SMIX_EN(bus & 3), 1); + + /* Muxed MDIO bus support removed for now! */ + return 0; +} + +static const struct mdio_ops octeon_mdio_ops = { + .read = octeon_mdio_read, + .write = octeon_mdio_write, +}; + +static const struct udevice_id octeon_mdio_ids[] = { + { .compatible = "cavium,octeon-3860-mdio" }, + {} +}; + +U_BOOT_DRIVER(octeon_mdio) = { + .name = "octeon_mdio", + .id = UCLASS_MDIO, + .of_match = octeon_mdio_ids, + .probe = octeon_mdio_probe, + .ops = &octeon_mdio_ops, + .priv_auto = sizeof(struct octeon_mdiobus), +}; -- 2.39.5