// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2018-2022 Marvell International Ltd. * * Helper functions to abstract board specific data about * network ports from the rest of the cvmx-helper files. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include DECLARE_GLOBAL_DATA_PTR; static bool sfp_parsed; static int __cvmx_helper_78xx_parse_phy(struct cvmx_phy_info *phy_info, int ipd_port); static int __get_phy_info_from_dt(cvmx_phy_info_t *phy_info, int ipd_port); /** * Writes to a Microsemi VSC7224 16-bit register * * @param[in] i2c_bus i2c bus data structure (must be enabled) * @param addr Address of VSC7224 on the i2c bus * @param reg 8-bit register number to write to * @param val 16-bit value to write * * @return 0 for success */ static int cvmx_write_vsc7224_reg(const struct cvmx_fdt_i2c_bus_info *i2c_bus, u8 addr, u8 reg, u16 val) { struct udevice *dev; u8 buffer[2]; int ret; ret = i2c_get_chip(i2c_bus->i2c_bus, addr, 1, &dev); if (ret) { debug("Cannot find I2C device: %d\n", ret); return -1; } ret = dm_i2c_write(dev, reg, buffer, 2); if (ret) { debug("Cannot write I2C device: %d\n", ret); return -1; } return 0; } /** * Writes to a Microsemi VSC7224 16-bit register * * @param[in] i2c_bus i2c bus data structure (must be enabled) * @param addr Address of VSC7224 on the i2c bus * @param reg 8-bit register number to write to * * @return 16-bit value or error if < 0 */ static int cvmx_read_vsc7224_reg(const struct cvmx_fdt_i2c_bus_info *i2c_bus, u8 addr, u8 reg) { struct udevice *dev; u8 buffer[2]; int ret; ret = i2c_get_chip(i2c_bus->i2c_bus, addr, 1, &dev); if (ret) { debug("Cannot find I2C device: %d\n", ret); return -1; } ret = dm_i2c_read(dev, reg, buffer, 2); if (ret) { debug("Cannot read I2C device: %d\n", ret); return -1; } return (buffer[0] << 8) | buffer[1]; } /** * Function called whenever mod_abs/mod_prs has changed for Microsemi VSC7224 * * @param sfp pointer to SFP data structure * @param val 1 if absent, 0 if present, otherwise not set * @param data user-defined data * * @return 0 for success, -1 on error */ int cvmx_sfp_vsc7224_mod_abs_changed(struct cvmx_fdt_sfp_info *sfp, int val, void *data) { int err; struct cvmx_sfp_mod_info *mod_info; int length; struct cvmx_vsc7224 *vsc7224; struct cvmx_vsc7224_chan *vsc7224_chan; struct cvmx_vsc7224_tap *taps, *match = NULL; int i; debug("%s(%s, %d, %p): Module %s\n", __func__, sfp->name, val, data, val ? "absent" : "present"); if (val) return 0; /* We're here if we detect that the module is now present */ err = cvmx_sfp_read_i2c_eeprom(sfp); if (err) { debug("%s: Error reading the SFP module eeprom for %s\n", __func__, sfp->name); return err; } mod_info = &sfp->sfp_info; if (!mod_info->valid || !sfp->valid) { debug("%s: Module data is invalid\n", __func__); return -1; } vsc7224_chan = sfp->vsc7224_chan; while (vsc7224_chan) { /* We don't do any rx tuning */ if (!vsc7224_chan->is_tx) { vsc7224_chan = vsc7224_chan->next; continue; } /* Walk through all the channels */ taps = vsc7224_chan->taps; if (mod_info->limiting) length = 0; else length = mod_info->max_copper_cable_len; debug("%s: limiting: %d, length: %d\n", __func__, mod_info->limiting, length); /* Find a matching length in the taps table */ for (i = 0; i < vsc7224_chan->num_taps; i++) { if (length >= taps->len) match = taps; taps++; } if (!match) { debug("%s(%s, %d, %p): Error: no matching tap for length %d\n", __func__, sfp->name, val, data, length); return -1; } debug("%s(%s): Applying %cx taps to vsc7224 %s:%d for cable length %d+\n", __func__, sfp->name, vsc7224_chan->is_tx ? 't' : 'r', vsc7224_chan->vsc7224->name, vsc7224_chan->lane, match->len); /* Program the taps */ vsc7224 = vsc7224_chan->vsc7224; cvmx_write_vsc7224_reg(vsc7224->i2c_bus, vsc7224->i2c_addr, 0x7f, vsc7224_chan->lane); if (!vsc7224_chan->maintap_disable) cvmx_write_vsc7224_reg(vsc7224->i2c_bus, vsc7224->i2c_addr, 0x99, match->main_tap); if (!vsc7224_chan->pretap_disable) cvmx_write_vsc7224_reg(vsc7224->i2c_bus, vsc7224->i2c_addr, 0x9a, match->pre_tap); if (!vsc7224_chan->posttap_disable) cvmx_write_vsc7224_reg(vsc7224->i2c_bus, vsc7224->i2c_addr, 0x9b, match->post_tap); /* Re-use val and disable taps if needed */ if (vsc7224_chan->maintap_disable || vsc7224_chan->pretap_disable || vsc7224_chan->posttap_disable) { val = cvmx_read_vsc7224_reg(vsc7224->i2c_bus, vsc7224->i2c_addr, 0x97); if (vsc7224_chan->maintap_disable) val |= 0x800; if (vsc7224_chan->pretap_disable) val |= 0x1000; if (vsc7224_chan->posttap_disable) val |= 0x400; cvmx_write_vsc7224_reg(vsc7224->i2c_bus, vsc7224->i2c_addr, 0x97, val); } vsc7224_chan = vsc7224_chan->next; } return err; } /** * Update the mod_abs and error LED * * @param ipd_port ipd port number * @param link link information */ static void __cvmx_helper_update_sfp(int ipd_port, struct cvmx_fdt_sfp_info *sfp_info, cvmx_helper_link_info_t link) { debug("%s(%d): checking mod_abs\n", __func__, ipd_port); cvmx_sfp_check_mod_abs(sfp_info, sfp_info->mod_abs_data); } static void cvmx_sfp_update_link(struct cvmx_fdt_sfp_info *sfp, cvmx_helper_link_info_t link) { while (sfp) { debug("%s(%s): checking mod_abs\n", __func__, sfp->name); if (link.s.link_up && sfp->last_mod_abs) cvmx_sfp_check_mod_abs(sfp, sfp->mod_abs_data); sfp = sfp->next_iface_sfp; } } /** * @INTERNAL * This function is used ethernet ports link speed. This functions uses the * device tree information to determine the phy address and type of PHY. * The only supproted PHYs are Marvell and Broadcom. * * @param ipd_port IPD input port associated with the port we want to get link * status for. * * @return The ports link status. If the link isn't fully resolved, this must * return zero. */ cvmx_helper_link_info_t __cvmx_helper_board_link_get_from_dt(int ipd_port) { cvmx_helper_link_info_t result; cvmx_phy_info_t *phy_info = NULL; cvmx_phy_info_t local_phy_info; int xiface = 0, index = 0; bool use_inband = false; struct cvmx_fdt_sfp_info *sfp_info; const void *fdt_addr = CASTPTR(const void *, gd->fdt_blob); result.u64 = 0; if (ipd_port >= 0) { int mode; xiface = cvmx_helper_get_interface_num(ipd_port); index = cvmx_helper_get_interface_index_num(ipd_port); mode = cvmx_helper_interface_get_mode(xiface); if (!cvmx_helper_get_port_autonegotiation(xiface, index)) { result.s.link_up = 1; result.s.full_duplex = 1; switch (mode) { case CVMX_HELPER_INTERFACE_MODE_RGMII: case CVMX_HELPER_INTERFACE_MODE_GMII: case CVMX_HELPER_INTERFACE_MODE_SGMII: case CVMX_HELPER_INTERFACE_MODE_QSGMII: case CVMX_HELPER_INTERFACE_MODE_AGL: case CVMX_HELPER_INTERFACE_MODE_SPI: if (OCTEON_IS_MODEL(OCTEON_CN70XX)) { struct cvmx_xiface xi = cvmx_helper_xiface_to_node_interface( xiface); u64 gbaud = cvmx_qlm_get_gbaud_mhz(0); result.s.speed = gbaud * 8 / 10; if (cvmx_qlm_get_dlm_mode( 0, xi.interface) == CVMX_QLM_MODE_SGMII) result.s.speed >>= 1; else result.s.speed >>= 2; } else { result.s.speed = 1000; } break; case CVMX_HELPER_INTERFACE_MODE_RXAUI: case CVMX_HELPER_INTERFACE_MODE_XAUI: case CVMX_HELPER_INTERFACE_MODE_10G_KR: case CVMX_HELPER_INTERFACE_MODE_XFI: result.s.speed = 10000; break; case CVMX_HELPER_INTERFACE_MODE_XLAUI: case CVMX_HELPER_INTERFACE_MODE_40G_KR4: result.s.speed = 40000; break; default: break; } sfp_info = cvmx_helper_cfg_get_sfp_info(xiface, index); /* Initialize the SFP info if it hasn't already been * done. */ if (!sfp_info && !sfp_parsed) { cvmx_sfp_parse_device_tree(fdt_addr); sfp_parsed = true; cvmx_sfp_read_all_modules(); sfp_info = cvmx_helper_cfg_get_sfp_info(xiface, index); } /* If the link is down or the link is up but we still * register the module as being absent, re-check * mod_abs. */ cvmx_sfp_update_link(sfp_info, result); cvmx_helper_update_link_led(xiface, index, result); return result; } phy_info = cvmx_helper_get_port_phy_info(xiface, index); if (!phy_info) { debug("%s: phy info not saved in config, allocating for 0x%x:%d\n", __func__, xiface, index); phy_info = (cvmx_phy_info_t *)cvmx_bootmem_alloc( sizeof(*phy_info), 0); if (!phy_info) { debug("%s: Out of memory\n", __func__); return result; } memset(phy_info, 0, sizeof(*phy_info)); phy_info->phy_addr = -1; debug("%s: Setting phy info for 0x%x:%d to %p\n", __func__, xiface, index, phy_info); cvmx_helper_set_port_phy_info(xiface, index, phy_info); } } else { /* For management ports we don't store the PHY information * so we use a local copy instead. */ phy_info = &local_phy_info; memset(phy_info, 0, sizeof(*phy_info)); phy_info->phy_addr = -1; } if (phy_info->phy_addr == -1) { if (octeon_has_feature(OCTEON_FEATURE_BGX)) { if (__cvmx_helper_78xx_parse_phy(phy_info, ipd_port)) { phy_info->phy_addr = -1; use_inband = true; } } else if (__get_phy_info_from_dt(phy_info, ipd_port) < 0) { phy_info->phy_addr = -1; use_inband = true; } } /* If we can't get the PHY info from the device tree then try * the inband state. */ if (use_inband) { result.s.full_duplex = 1; result.s.link_up = 1; result.s.speed = 1000; return result; } if (phy_info->phy_addr < 0) return result; if (phy_info->link_function) result = phy_info->link_function(phy_info); else result = cvmx_helper_link_get(ipd_port); sfp_info = cvmx_helper_cfg_get_sfp_info(xiface, index); while (sfp_info) { /* If the link is down or the link is up but we still register * the module as being absent, re-check mod_abs. */ if (!result.s.link_up || (result.s.link_up && sfp_info->last_mod_abs)) __cvmx_helper_update_sfp(ipd_port, sfp_info, result); sfp_info = sfp_info->next_iface_sfp; } return result; } cvmx_helper_link_info_t __cvmx_helper_board_link_get(int ipd_port) { cvmx_helper_link_info_t result; /* Unless we fix it later, all links are defaulted to down */ result.u64 = 0; return __cvmx_helper_board_link_get_from_dt(ipd_port); } void cvmx_helper_update_link_led(int xiface, int index, cvmx_helper_link_info_t result) { } void cvmx_helper_leds_show_error(struct cvmx_phy_gpio_leds *leds, bool error) { } int __cvmx_helper_board_interface_probe(int interface, int supported_ports) { return supported_ports; } /** * Returns the Ethernet node offset in the device tree * * @param fdt_addr - pointer to flat device tree in memory * @param aliases - offset of alias in device tree * @param ipd_port - ipd port number to look up * * @returns offset of Ethernet node if >= 0, error if -1 */ int __pip_eth_node(const void *fdt_addr, int aliases, int ipd_port) { char name_buffer[20]; const char *pip_path; int pip, iface, eth; int interface_num = cvmx_helper_get_interface_num(ipd_port); int interface_index = cvmx_helper_get_interface_index_num(ipd_port); cvmx_helper_interface_mode_t interface_mode = cvmx_helper_interface_get_mode(interface_num); /* The following are not found in the device tree */ switch (interface_mode) { case CVMX_HELPER_INTERFACE_MODE_ILK: case CVMX_HELPER_INTERFACE_MODE_LOOP: case CVMX_HELPER_INTERFACE_MODE_SRIO: debug("ERROR: No node expected for interface: %d, port: %d, mode: %s\n", interface_index, ipd_port, cvmx_helper_interface_mode_to_string(interface_mode)); return -1; default: break; } pip_path = (const char *)fdt_getprop(fdt_addr, aliases, "pip", NULL); if (!pip_path) { debug("ERROR: pip path not found in device tree\n"); return -1; } pip = fdt_path_offset(fdt_addr, pip_path); debug("ipdd_port=%d pip_path=%s pip=%d ", ipd_port, pip_path, pip); if (pip < 0) { debug("ERROR: pip not found in device tree\n"); return -1; } snprintf(name_buffer, sizeof(name_buffer), "interface@%d", interface_num); iface = fdt_subnode_offset(fdt_addr, pip, name_buffer); debug("iface=%d ", iface); if (iface < 0) { debug("ERROR : pip intf %d not found in device tree\n", interface_num); return -1; } snprintf(name_buffer, sizeof(name_buffer), "ethernet@%x", interface_index); eth = fdt_subnode_offset(fdt_addr, iface, name_buffer); debug("eth=%d\n", eth); if (eth < 0) { debug("ERROR : pip interface@%d ethernet@%d not found in device tree\n", interface_num, interface_index); return -1; } return eth; } int __mix_eth_node(const void *fdt_addr, int aliases, int interface_index) { char name_buffer[20]; const char *mix_path; int mix; snprintf(name_buffer, sizeof(name_buffer), "mix%d", interface_index); mix_path = (const char *)fdt_getprop(fdt_addr, aliases, name_buffer, NULL); if (!mix_path) { debug("ERROR: mix%d path not found in device tree\n", interface_index); } mix = fdt_path_offset(fdt_addr, mix_path); if (mix < 0) { debug("ERROR: %s not found in device tree\n", mix_path); return -1; } return mix; } static int __mdiobus_addr_to_unit(u32 addr) { int unit = (addr >> 7) & 3; if (!OCTEON_IS_MODEL(OCTEON_CN68XX) && !OCTEON_IS_MODEL(OCTEON_CN78XX)) unit >>= 1; return unit; } /** * Parse the muxed MDIO interface information from the device tree * * @param phy_info - pointer to phy info data structure to update * @param mdio_offset - offset of MDIO bus * @param mux_offset - offset of MUX, parent of mdio_offset * * @return 0 for success or -1 */ static int __get_muxed_mdio_info_from_dt(cvmx_phy_info_t *phy_info, int mdio_offset, int mux_offset) { const void *fdt_addr = CASTPTR(const void *, gd->fdt_blob); int phandle; int smi_offset; int gpio_offset; u64 smi_addr = 0; int len; u32 *pgpio_handle; int gpio_count = 0; u32 *prop_val; int offset; const char *prop_name; debug("%s(%p, 0x%x, 0x%x)\n", __func__, phy_info, mdio_offset, mux_offset); /* Get register value to put onto the GPIO lines to select */ phy_info->gpio_value = cvmx_fdt_get_int(fdt_addr, mdio_offset, "reg", -1); if (phy_info->gpio_value < 0) { debug("Could not get register value for muxed MDIO bus from DT\n"); return -1; } smi_offset = cvmx_fdt_lookup_phandle(fdt_addr, mux_offset, "mdio-parent-bus"); if (smi_offset < 0) { debug("Invalid SMI offset for muxed MDIO interface in device tree\n"); return -1; } smi_addr = cvmx_fdt_get_uint64(fdt_addr, smi_offset, "reg", 0); /* Convert SMI address to a MDIO interface */ switch (smi_addr) { case 0x1180000001800: case 0x1180000003800: /* 68XX address */ phy_info->mdio_unit = 0; break; case 0x1180000001900: case 0x1180000003880: phy_info->mdio_unit = 1; break; case 0x1180000003900: phy_info->mdio_unit = 2; break; case 0x1180000003980: phy_info->mdio_unit = 3; break; default: phy_info->mdio_unit = 1; break; } /* Find the GPIO MUX controller */ pgpio_handle = (u32 *)fdt_getprop(fdt_addr, mux_offset, "gpios", &len); if (!pgpio_handle || len < 12 || (len % 12) != 0 || len > CVMX_PHY_MUX_MAX_GPIO * 12) { debug("Invalid GPIO for muxed MDIO controller in DT\n"); return -1; } for (gpio_count = 0; gpio_count < len / 12; gpio_count++) { phandle = fdt32_to_cpu(pgpio_handle[gpio_count * 3]); phy_info->gpio[gpio_count] = fdt32_to_cpu(pgpio_handle[gpio_count * 3 + 1]); gpio_offset = fdt_node_offset_by_phandle(fdt_addr, phandle); if (gpio_offset < 0) { debug("Cannot access parent GPIO node in DT\n"); return -1; } if (!fdt_node_check_compatible(fdt_addr, gpio_offset, "cavium,octeon-3860-gpio")) { phy_info->gpio_type[gpio_count] = GPIO_OCTEON; } else if (!fdt_node_check_compatible(fdt_addr, gpio_offset, "nxp,pca8574")) { /* GPIO is a TWSI GPIO unit which might sit behind * another mux. */ phy_info->gpio_type[gpio_count] = GPIO_PCA8574; prop_val = (u32 *)fdt_getprop( fdt_addr, gpio_offset, "reg", NULL); if (!prop_val) { debug("Could not find TWSI address of npx pca8574 GPIO from DT\n"); return -1; } /* Get the TWSI address of the GPIO unit */ phy_info->cvmx_gpio_twsi[gpio_count] = fdt32_to_cpu(*prop_val); /* Get the selector on the GPIO mux if present */ offset = fdt_parent_offset(fdt_addr, gpio_offset); prop_val = (u32 *)fdt_getprop(fdt_addr, offset, "reg", NULL); if (prop_val) { phy_info->gpio_parent_mux_select = fdt32_to_cpu(*prop_val); /* Go up another level */ offset = fdt_parent_offset(fdt_addr, offset); if (!fdt_node_check_compatible(fdt_addr, offset, "nxp,pca9548")) { prop_val = (u32 *)fdt_getprop( fdt_addr, offset, "reg", NULL); if (!prop_val) { debug("Could not read MUX TWSI address from DT\n"); return -1; } phy_info->gpio_parent_mux_twsi = fdt32_to_cpu(*prop_val); } } } else { prop_name = (char *)fdt_getprop(fdt_addr, gpio_offset, "compatible", NULL); debug("Unknown GPIO type %s\n", prop_name); return -1; } } 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 cvmx_helper_link_info_t __get_marvell_phy_link_state(cvmx_phy_info_t *phy_info) { cvmx_helper_link_info_t result; int phy_status; u32 phy_addr = phy_info->phy_addr; result.u64 = 0; /* Set to page 0 */ cvmx_mdio_write(phy_addr >> 8, phy_addr & 0xff, 22, 0); /* All the speed information can be read from register 17 in one go. */ phy_status = cvmx_mdio_read(phy_addr >> 8, phy_addr & 0xff, 17); /* If the resolve bit 11 isn't set, see if autoneg is turned off * (bit 12, reg 0). The resolve bit doesn't get set properly when * autoneg is off, so force it */ if ((phy_status & (1 << 11)) == 0) { int auto_status = cvmx_mdio_read(phy_addr >> 8, phy_addr & 0xff, 0); if ((auto_status & (1 << 12)) == 0) phy_status |= 1 << 11; } /* Link is up = Speed/Duplex Resolved + RT-Link Up + G-Link Up. */ if ((phy_status & 0x0c08) == 0x0c08) { result.s.link_up = 1; result.s.full_duplex = ((phy_status >> 13) & 1); switch ((phy_status >> 14) & 3) { case 0: /* 10 Mbps */ result.s.speed = 10; break; case 1: /* 100 Mbps */ result.s.speed = 100; break; case 2: /* 1 Gbps */ result.s.speed = 1000; break; case 3: /* Illegal */ result.u64 = 0; break; } } return result; } /** * @INTERNAL * Get link state of broadcom PHY * * @param phy_info PHY information */ static cvmx_helper_link_info_t __get_broadcom_phy_link_state(cvmx_phy_info_t *phy_info) { cvmx_helper_link_info_t result; u32 phy_addr = phy_info->phy_addr; int phy_status; result.u64 = 0; /* Below we are going to read SMI/MDIO register 0x19 which works * on Broadcom parts */ phy_status = cvmx_mdio_read(phy_addr >> 8, phy_addr & 0xff, 0x19); switch ((phy_status >> 8) & 0x7) { case 0: result.u64 = 0; break; case 1: result.s.link_up = 1; result.s.full_duplex = 0; result.s.speed = 10; break; case 2: result.s.link_up = 1; result.s.full_duplex = 1; result.s.speed = 10; break; case 3: result.s.link_up = 1; result.s.full_duplex = 0; result.s.speed = 100; break; case 4: result.s.link_up = 1; result.s.full_duplex = 1; result.s.speed = 100; break; case 5: result.s.link_up = 1; result.s.full_duplex = 1; result.s.speed = 100; break; case 6: result.s.link_up = 1; result.s.full_duplex = 0; result.s.speed = 1000; break; case 7: result.s.link_up = 1; result.s.full_duplex = 1; result.s.speed = 1000; break; } return result; } /** * @INTERNAL * Get link state of generic gigabit PHY * * @param phy_info - information about the PHY * * @returns link status of the PHY */ static cvmx_helper_link_info_t __cvmx_get_generic_8023_c22_phy_link_state(cvmx_phy_info_t *phy_info) { cvmx_helper_link_info_t result; u32 phy_addr = phy_info->phy_addr; int phy_basic_control; /* Register 0x0 */ int phy_basic_status; /* Register 0x1 */ int phy_anog_adv; /* Register 0x4 */ int phy_link_part_avail; /* Register 0x5 */ int phy_control; /* Register 0x9 */ int phy_status; /* Register 0xA */ result.u64 = 0; phy_basic_status = cvmx_mdio_read(phy_addr >> 8, phy_addr & 0xff, 1); if (!(phy_basic_status & 0x4)) /* Check if link is up */ return result; /* Link is down, return link down */ result.s.link_up = 1; phy_basic_control = cvmx_mdio_read(phy_addr >> 8, phy_addr & 0xff, 0); /* Check if autonegotiation is enabled and completed */ if ((phy_basic_control & (1 << 12)) && (phy_basic_status & (1 << 5))) { phy_status = cvmx_mdio_read(phy_addr >> 8, phy_addr & 0xff, 0xA); phy_control = cvmx_mdio_read(phy_addr >> 8, phy_addr & 0xff, 0x9); phy_status &= phy_control << 2; phy_link_part_avail = cvmx_mdio_read(phy_addr >> 8, phy_addr & 0xff, 0x5); phy_anog_adv = cvmx_mdio_read(phy_addr >> 8, phy_addr & 0xff, 0x4); phy_link_part_avail &= phy_anog_adv; if (phy_status & 0xC00) { /* Gigabit full or half */ result.s.speed = 1000; result.s.full_duplex = !!(phy_status & 0x800); } else if (phy_link_part_avail & 0x0180) { /* 100 full or half */ result.s.speed = 100; result.s.full_duplex = !!(phy_link_part_avail & 0x100); } else if (phy_link_part_avail & 0x0060) { result.s.speed = 10; result.s.full_duplex = !!(phy_link_part_avail & 0x0040); } } else { /* Not autonegotiated */ result.s.full_duplex = !!(phy_basic_control & (1 << 8)); if (phy_basic_control & (1 << 6)) result.s.speed = 1000; else if (phy_basic_control & (1 << 13)) result.s.speed = 100; else result.s.speed = 10; } return result; } static cvmx_helper_link_info_t __cvmx_get_qualcomm_s17_phy_link_state(cvmx_phy_info_t *phy_info) { cvmx_helper_link_info_t result; u32 phy_addr = phy_info->phy_addr; int phy_status; int auto_status; result.u64 = 0; phy_status = cvmx_mdio_read(phy_addr >> 8, phy_addr & 0xff, 17); /* If bit 11 isn't set see if autonegotiation is turned off * (bit 12, reg 0). The resolved bit doesn't get set properly when * autonegotiation is off, so force it. */ if ((phy_status & (1 << 11)) == 0) { auto_status = cvmx_mdio_read(phy_addr >> 8, phy_addr & 0xff, 0); if ((auto_status & (1 << 12)) == 0) phy_status |= 1 << 11; } /* Only return a link if the PHY has finished autonegotiation and set * the resolved bit (bit 11). */ if (phy_status & (1 << 11)) { result.s.link_up = 1; result.s.full_duplex = !!(phy_status & (1 << 13)); switch ((phy_status >> 14) & 3) { case 0: /* 10Mbps */ result.s.speed = 10; break; case 1: /* 100Mbps */ result.s.speed = 100; break; case 2: /* 1Gbps */ result.s.speed = 1000; break; default: /* Illegal */ result.u64 = 0; break; } } debug(" link: %s, duplex: %s, speed: %lu\n", result.s.link_up ? "up" : "down", result.s.full_duplex ? "full" : "half", (unsigned long)result.s.speed); return result; } static cvmx_helper_link_info_t __get_generic_8023_c45_phy_link_state(cvmx_phy_info_t *phy_info) { cvmx_helper_link_info_t result; int phy_status; int pma_ctrl1; u32 phy_addr = phy_info->phy_addr; result.u64 = 0; pma_ctrl1 = cvmx_mdio_45_read(phy_addr >> 8, phy_addr & 0xff, 1, 0); if ((pma_ctrl1 & 0x207c) == 0x2040) result.s.speed = 10000; /* PMA Status 1 (1x0001) */ phy_status = cvmx_mdio_45_read(phy_addr >> 8, phy_addr & 0xff, 1, 0xa); if (phy_status < 0) return result; result.s.full_duplex = 1; if ((phy_status & 1) == 0) return result; phy_status = cvmx_mdio_45_read(phy_addr >> 8, phy_addr & 0xff, 4, 0x18); if (phy_status < 0) return result; result.s.link_up = (phy_status & 0x1000) ? 1 : 0; return result; } static cvmx_helper_link_info_t __cvmx_get_cortina_phy_link_state(cvmx_phy_info_t *phy_info) { cvmx_helper_link_info_t result; result.s.link_up = 1; result.s.full_duplex = 1; result.s.speed = 1000; return result; } static cvmx_helper_link_info_t __get_vitesse_vsc8490_phy_link_state(cvmx_phy_info_t *phy_info) { cvmx_helper_link_info_t result; result.s.link_up = 1; result.s.full_duplex = 1; result.s.speed = 1000; return result; } static cvmx_helper_link_info_t __get_aquantia_phy_link_state(cvmx_phy_info_t *phy_info) { cvmx_helper_link_info_t result; result.s.link_up = 1; result.s.full_duplex = 1; result.s.speed = 1000; return result; } static int __cvmx_helper_78xx_parse_phy(struct cvmx_phy_info *phy_info, int ipd_port) { const void *fdt_addr = CASTPTR(const void *, gd->fdt_blob); const char *compat; int phy; int parent; u64 mdio_base; int node, bus; int phy_addr; int index = cvmx_helper_get_interface_index_num(ipd_port); int xiface = cvmx_helper_get_interface_num(ipd_port); int compat_len = 0; debug("%s(0x%p, %d) ENTER\n", __func__, phy_info, ipd_port); phy = cvmx_helper_get_phy_fdt_node_offset(xiface, index); debug("%s: xiface: 0x%x, index: %d, ipd_port: %d, phy fdt offset: %d\n", __func__, xiface, index, ipd_port, phy); if (phy < 0) { /* If this is the first time through we need to first parse the * device tree to get the node offsets. */ debug("No config present, calling __cvmx_helper_parse_bgx_dt\n"); if (__cvmx_helper_parse_bgx_dt(fdt_addr)) { printf("Error: could not parse BGX device tree\n"); return -1; } if (__cvmx_fdt_parse_vsc7224(fdt_addr)) { debug("Error: could not parse Microsemi VSC7224 in DT\n"); return -1; } if (octeon_has_feature(OCTEON_FEATURE_BGX_XCV) && __cvmx_helper_parse_bgx_rgmii_dt(fdt_addr)) { printf("Error: could not parse BGX XCV device tree\n"); return -1; } phy = cvmx_helper_get_phy_fdt_node_offset(xiface, index); if (phy < 0) { debug("%s: Could not get PHY node offset for IPD port 0x%x, xiface: 0x%x, index: %d\n", __func__, ipd_port, xiface, index); return -1; } debug("%s: phy: %d (%s)\n", __func__, phy, fdt_get_name(fdt_addr, phy, NULL)); } compat = (const char *)fdt_getprop(fdt_addr, phy, "compatible", &compat_len); if (!compat) { printf("ERROR: %d:%d:no compatible prop in phy\n", xiface, index); return -1; } debug(" compatible: %s\n", compat); phy_info->fdt_offset = phy; phy_addr = cvmx_fdt_get_int(fdt_addr, phy, "reg", -1); if (phy_addr == -1) { printf("Error: %d:%d:could not get PHY address\n", xiface, index); return -1; } debug(" PHY address: %d, compat: %s\n", phy_addr, compat); if (!memcmp("marvell", compat, strlen("marvell"))) { phy_info->phy_type = MARVELL_GENERIC_PHY; phy_info->link_function = __get_marvell_phy_link_state; } else if (!memcmp("broadcom", compat, strlen("broadcom"))) { phy_info->phy_type = BROADCOM_GENERIC_PHY; phy_info->link_function = __get_broadcom_phy_link_state; } else if (!memcmp("cortina", compat, strlen("cortina"))) { phy_info->phy_type = CORTINA_PHY; phy_info->link_function = __cvmx_get_cortina_phy_link_state; } else if (!strcmp("vitesse,vsc8490", compat)) { phy_info->phy_type = VITESSE_VSC8490_PHY; phy_info->link_function = __get_vitesse_vsc8490_phy_link_state; } else if (fdt_stringlist_contains(compat, compat_len, "ethernet-phy-ieee802.3-c22")) { phy_info->phy_type = GENERIC_8023_C22_PHY; phy_info->link_function = __cvmx_get_generic_8023_c22_phy_link_state; } else if (fdt_stringlist_contains(compat, compat_len, "ethernet-phy-ieee802.3-c45")) { phy_info->phy_type = GENERIC_8023_C22_PHY; phy_info->link_function = __get_generic_8023_c45_phy_link_state; } phy_info->ipd_port = ipd_port; phy_info->phy_sub_addr = 0; phy_info->direct_connect = 1; parent = fdt_parent_offset(fdt_addr, phy); if (!fdt_node_check_compatible(fdt_addr, parent, "ethernet-phy-nexus")) { debug(" nexus PHY found\n"); if (phy_info->phy_type == CORTINA_PHY) { /* The Cortina CS422X uses the same PHY device for * multiple ports for XFI. In this case we use a * nexus and each PHY address is the slice or * sub-address and the actual PHY address is the * nexus address. */ phy_info->phy_sub_addr = phy_addr; phy_addr = cvmx_fdt_get_int(fdt_addr, parent, "reg", -1); debug(" Cortina PHY real address: 0x%x\n", phy_addr); } parent = fdt_parent_offset(fdt_addr, parent); } debug(" Parent: %s\n", fdt_get_name(fdt_addr, parent, NULL)); if (!fdt_node_check_compatible(fdt_addr, parent, "cavium,octeon-3860-mdio")) { debug(" Found Octeon MDIO\n"); mdio_base = cvmx_fdt_get_uint64(fdt_addr, parent, "reg", FDT_ADDR_T_NONE); debug(" MDIO address: 0x%llx\n", (unsigned long long)mdio_base); mdio_base = cvmx_fdt_translate_address(fdt_addr, parent, (u32 *)&mdio_base); debug(" Translated: 0x%llx\n", (unsigned long long)mdio_base); if (mdio_base == FDT_ADDR_T_NONE) { printf("Could not get MDIO base address from reg field\n"); return -1; } __cvmx_mdio_addr_to_node_bus(mdio_base, &node, &bus); if (bus < 0) { printf("Invalid MDIO address 0x%llx, could not detect bus and node\n", (unsigned long long)mdio_base); return -1; } debug(" MDIO node: %d, bus: %d\n", node, bus); phy_info->mdio_unit = (node << 2) | (bus & 3); phy_info->phy_addr = phy_addr | (phy_info->mdio_unit << 8); } else { printf("%s: Error: incompatible MDIO bus %s for IPD port %d\n", __func__, (const char *)fdt_get_name(fdt_addr, parent, NULL), ipd_port); return -1; } debug("%s: EXIT 0\n", __func__); return 0; } /** * Return the MII PHY address associated with the given IPD * port. The phy address is obtained from the device tree. * * @param[out] phy_info - PHY information data structure updated * @param ipd_port Octeon IPD port to get the MII address for. * * @return MII PHY address and bus number, -1 on error, -2 if PHY info missing (OK). */ static int __get_phy_info_from_dt(cvmx_phy_info_t *phy_info, int ipd_port) { const void *fdt_addr = CASTPTR(const void *, gd->fdt_blob); int aliases, eth, phy, phy_parent, ret, i; int mdio_parent; const char *phy_compatible_str; const char *host_mode_str = NULL; int interface; int phy_addr_offset = 0; debug("%s(%p, %d)\n", __func__, phy_info, ipd_port); if (octeon_has_feature(OCTEON_FEATURE_BGX)) return __cvmx_helper_78xx_parse_phy(phy_info, ipd_port); phy_info->phy_addr = -1; phy_info->phy_sub_addr = 0; phy_info->ipd_port = ipd_port; phy_info->direct_connect = -1; phy_info->phy_type = (cvmx_phy_type_t)-1; for (i = 0; i < CVMX_PHY_MUX_MAX_GPIO; i++) phy_info->gpio[i] = -1; phy_info->mdio_unit = -1; phy_info->gpio_value = -1; phy_info->gpio_parent_mux_twsi = -1; phy_info->gpio_parent_mux_select = -1; phy_info->link_function = NULL; phy_info->fdt_offset = -1; if (!fdt_addr) { debug("No device tree found.\n"); return -1; } aliases = fdt_path_offset(fdt_addr, "/aliases"); if (aliases < 0) { debug("Error: No /aliases node in device tree.\n"); return -1; } if (ipd_port < 0) { int interface_index = ipd_port - CVMX_HELPER_BOARD_MGMT_IPD_PORT; eth = __mix_eth_node(fdt_addr, aliases, interface_index); } else { eth = __pip_eth_node(fdt_addr, aliases, ipd_port); } if (eth < 0) { debug("ERROR : cannot find interface for ipd_port=%d\n", ipd_port); return -1; } interface = cvmx_helper_get_interface_num(ipd_port); /* Get handle to phy */ phy = cvmx_fdt_lookup_phandle(fdt_addr, eth, "phy-handle"); if (phy < 0) { cvmx_helper_interface_mode_t if_mode; /* Note that it's OK for RXAUI and ILK to not have a PHY * connected (i.e. EBB boards in loopback). */ debug("Cannot get phy-handle for ipd_port: %d\n", ipd_port); if_mode = cvmx_helper_interface_get_mode(interface); if (if_mode != CVMX_HELPER_INTERFACE_MODE_RXAUI && if_mode != CVMX_HELPER_INTERFACE_MODE_ILK) { debug("ERROR : phy handle not found in device tree ipd_port=%d\n", ipd_port); return -1; } else { return -2; } } phy_compatible_str = (const char *)fdt_getprop(fdt_addr, phy, "compatible", NULL); if (!phy_compatible_str) { debug("ERROR: no compatible prop in phy\n"); return -1; } debug("Checking compatible string \"%s\" for ipd port %d\n", phy_compatible_str, ipd_port); phy_info->fdt_offset = phy; if (!memcmp("marvell", phy_compatible_str, strlen("marvell"))) { debug("Marvell PHY detected for ipd_port %d\n", ipd_port); phy_info->phy_type = MARVELL_GENERIC_PHY; phy_info->link_function = __get_marvell_phy_link_state; } else if (!memcmp("broadcom", phy_compatible_str, strlen("broadcom"))) { phy_info->phy_type = BROADCOM_GENERIC_PHY; phy_info->link_function = __get_broadcom_phy_link_state; debug("Broadcom PHY detected for ipd_port %d\n", ipd_port); } else if (!memcmp("vitesse", phy_compatible_str, strlen("vitesse"))) { debug("Vitesse PHY detected for ipd_port %d\n", ipd_port); if (!fdt_node_check_compatible(fdt_addr, phy, "vitesse,vsc8490")) { phy_info->phy_type = VITESSE_VSC8490_PHY; debug("Vitesse VSC8490 detected\n"); phy_info->link_function = __get_vitesse_vsc8490_phy_link_state; } else if (!fdt_node_check_compatible( fdt_addr, phy, "ethernet-phy-ieee802.3-c22")) { phy_info->phy_type = GENERIC_8023_C22_PHY; phy_info->link_function = __cvmx_get_generic_8023_c22_phy_link_state; debug("Vitesse 802.3 c22 detected\n"); } else { phy_info->phy_type = GENERIC_8023_C45_PHY; phy_info->link_function = __get_generic_8023_c45_phy_link_state; debug("Vitesse 802.3 c45 detected\n"); } } else if (!memcmp("aquantia", phy_compatible_str, strlen("aquantia"))) { phy_info->phy_type = AQUANTIA_PHY; phy_info->link_function = __get_aquantia_phy_link_state; debug("Aquantia c45 PHY detected\n"); } else if (!memcmp("cortina", phy_compatible_str, strlen("cortina"))) { phy_info->phy_type = CORTINA_PHY; phy_info->link_function = __cvmx_get_cortina_phy_link_state; host_mode_str = (const char *)fdt_getprop( fdt_addr, phy, "cortina,host-mode", NULL); debug("Cortina PHY detected for ipd_port %d\n", ipd_port); } else if (!memcmp("ti", phy_compatible_str, strlen("ti"))) { phy_info->phy_type = GENERIC_8023_C45_PHY; phy_info->link_function = __get_generic_8023_c45_phy_link_state; debug("TI PHY detected for ipd_port %d\n", ipd_port); } else if (!fdt_node_check_compatible(fdt_addr, phy, "atheros,ar8334") || !fdt_node_check_compatible(fdt_addr, phy, "qualcomm,qca8334") || !fdt_node_check_compatible(fdt_addr, phy, "atheros,ar8337") || !fdt_node_check_compatible(fdt_addr, phy, "qualcomm,qca8337")) { phy_info->phy_type = QUALCOMM_S17; phy_info->link_function = __cvmx_get_qualcomm_s17_phy_link_state; debug("Qualcomm QCA833X switch detected\n"); } else if (!fdt_node_check_compatible(fdt_addr, phy, "ethernet-phy-ieee802.3-c22")) { phy_info->phy_type = GENERIC_8023_C22_PHY; phy_info->link_function = __cvmx_get_generic_8023_c22_phy_link_state; debug("Generic 802.3 c22 PHY detected\n"); } else if (!fdt_node_check_compatible(fdt_addr, phy, "ethernet-phy-ieee802.3-c45")) { phy_info->phy_type = GENERIC_8023_C45_PHY; phy_info->link_function = __get_generic_8023_c45_phy_link_state; debug("Generic 802.3 c45 PHY detected\n"); } else { debug("Unknown PHY compatibility\n"); phy_info->phy_type = (cvmx_phy_type_t)-1; phy_info->link_function = NULL; } phy_info->host_mode = CVMX_PHY_HOST_MODE_UNKNOWN; if (host_mode_str) { if (strcmp(host_mode_str, "rxaui") == 0) phy_info->host_mode = CVMX_PHY_HOST_MODE_RXAUI; else if (strcmp(host_mode_str, "xaui") == 0) phy_info->host_mode = CVMX_PHY_HOST_MODE_XAUI; else if (strcmp(host_mode_str, "sgmii") == 0) phy_info->host_mode = CVMX_PHY_HOST_MODE_SGMII; else if (strcmp(host_mode_str, "qsgmii") == 0) phy_info->host_mode = CVMX_PHY_HOST_MODE_QSGMII; else debug("Unknown PHY host mode\n"); } /* Check if PHY parent is the octeon MDIO bus. Some boards are connected * though a MUX and for them direct_connect_to_phy will be 0 */ phy_parent = fdt_parent_offset(fdt_addr, phy); if (phy_parent < 0) { debug("ERROR : cannot find phy parent for ipd_port=%d ret=%d\n", ipd_port, phy_parent); return -1; } /* For multi-phy devices and devices on a MUX, go to the parent */ ret = fdt_node_check_compatible(fdt_addr, phy_parent, "ethernet-phy-nexus"); if (ret == 0) { /* It's a nexus so check the grandparent. */ phy_addr_offset = cvmx_fdt_get_int(fdt_addr, phy_parent, "reg", 0); phy_parent = fdt_parent_offset(fdt_addr, phy_parent); } /* Check for a muxed MDIO interface */ mdio_parent = fdt_parent_offset(fdt_addr, phy_parent); ret = fdt_node_check_compatible(fdt_addr, mdio_parent, "cavium,mdio-mux"); if (ret == 0) { ret = __get_muxed_mdio_info_from_dt(phy_info, phy_parent, mdio_parent); if (ret) { printf("Error reading mdio mux information for ipd port %d\n", ipd_port); return -1; } } ret = fdt_node_check_compatible(fdt_addr, phy_parent, "cavium,octeon-3860-mdio"); if (ret == 0) { u32 *mdio_reg_base = (u32 *)fdt_getprop(fdt_addr, phy_parent, "reg", 0); phy_info->direct_connect = 1; if (mdio_reg_base == 0) { debug("ERROR : unable to get reg property in phy mdio\n"); return -1; } phy_info->mdio_unit = __mdiobus_addr_to_unit(fdt32_to_cpu(mdio_reg_base[1])); debug("phy parent=%s reg_base=%08x mdio_unit=%d\n", fdt_get_name(fdt_addr, phy_parent, NULL), (int)mdio_reg_base[1], phy_info->mdio_unit); } else { phy_info->direct_connect = 0; /* The PHY is not directly connected to the Octeon MDIO bus. * SE doesn't have abstractions for MDIO MUX or MDIO MUX * drivers and hence for the non direct cases code will be * needed which is board specific. * For now the MDIO Unit is defaulted to 1. */ debug("%s PHY at address: %d is not directly connected\n", __func__, phy_info->phy_addr); } phy_info->phy_addr = cvmx_fdt_get_int(fdt_addr, phy, "reg", -1); if (phy_info->phy_addr < 0) { debug("ERROR: Could not read phy address from reg in DT\n"); return -1; } phy_info->phy_addr += phy_addr_offset; phy_info->phy_addr |= phy_info->mdio_unit << 8; debug("%s(%p, %d) => 0x%x\n", __func__, phy_info, ipd_port, phy_info->phy_addr); return phy_info->phy_addr; } /** * @INTERNAL * Parse the device tree and set whether a port is valid or not. * * @param fdt_addr Pointer to device tree * * @return 0 for success, -1 on error. */ int __cvmx_helper_parse_bgx_dt(const void *fdt_addr) { int port_index; struct cvmx_xiface xi; int fdt_port_node = -1; int fdt_interface_node; int fdt_phy_node; u64 reg_addr; int xiface; struct cvmx_phy_info *phy_info; static bool parsed; int err; int ipd_port; if (parsed) { debug("%s: Already parsed\n", __func__); return 0; } while ((fdt_port_node = fdt_node_offset_by_compatible( fdt_addr, fdt_port_node, "cavium,octeon-7890-bgx-port")) >= 0) { /* Get the port number */ port_index = cvmx_fdt_get_int(fdt_addr, fdt_port_node, "reg", -1); if (port_index < 0) { debug("Error: missing reg field for bgx port in device tree\n"); return -1; } debug("%s: Parsing BGX port %d\n", __func__, port_index); /* Get the interface number */ fdt_interface_node = fdt_parent_offset(fdt_addr, fdt_port_node); if (fdt_interface_node < 0) { debug("Error: device tree corrupt!\n"); return -1; } if (fdt_node_check_compatible(fdt_addr, fdt_interface_node, "cavium,octeon-7890-bgx")) { debug("Error: incompatible Ethernet MAC Nexus in device tree!\n"); return -1; } reg_addr = cvmx_fdt_get_addr(fdt_addr, fdt_interface_node, "reg"); debug("%s: BGX interface address: 0x%llx\n", __func__, (unsigned long long)reg_addr); if (reg_addr == FDT_ADDR_T_NONE) { debug("Device tree BGX node has invalid address 0x%llx\n", (unsigned long long)reg_addr); return -1; } reg_addr = cvmx_fdt_translate_address(fdt_addr, fdt_interface_node, (u32 *)®_addr); xi = __cvmx_bgx_reg_addr_to_xiface(reg_addr); if (xi.node < 0) { debug("Device tree BGX node has invalid address 0x%llx\n", (unsigned long long)reg_addr); return -1; } debug("%s: Found BGX node %d, interface %d\n", __func__, xi.node, xi.interface); xiface = cvmx_helper_node_interface_to_xiface(xi.node, xi.interface); cvmx_helper_set_port_fdt_node_offset(xiface, port_index, fdt_port_node); cvmx_helper_set_port_valid(xiface, port_index, true); cvmx_helper_set_port_fdt_node_offset(xiface, port_index, fdt_port_node); if (fdt_getprop(fdt_addr, fdt_port_node, "cavium,sgmii-mac-phy-mode", NULL)) cvmx_helper_set_mac_phy_mode(xiface, port_index, true); else cvmx_helper_set_mac_phy_mode(xiface, port_index, false); if (fdt_getprop(fdt_addr, fdt_port_node, "cavium,force-link-up", NULL)) cvmx_helper_set_port_force_link_up(xiface, port_index, true); else cvmx_helper_set_port_force_link_up(xiface, port_index, false); if (fdt_getprop(fdt_addr, fdt_port_node, "cavium,sgmii-mac-1000x-mode", NULL)) cvmx_helper_set_1000x_mode(xiface, port_index, true); else cvmx_helper_set_1000x_mode(xiface, port_index, false); if (fdt_getprop(fdt_addr, fdt_port_node, "cavium,disable-autonegotiation", NULL)) cvmx_helper_set_port_autonegotiation(xiface, port_index, false); else cvmx_helper_set_port_autonegotiation(xiface, port_index, true); fdt_phy_node = cvmx_fdt_lookup_phandle(fdt_addr, fdt_port_node, "phy-handle"); if (fdt_phy_node >= 0) { cvmx_helper_set_phy_fdt_node_offset(xiface, port_index, fdt_phy_node); debug("%s: Setting PHY fdt node offset for interface 0x%x, port %d to %d\n", __func__, xiface, port_index, fdt_phy_node); debug("%s: PHY node name: %s\n", __func__, fdt_get_name(fdt_addr, fdt_phy_node, NULL)); cvmx_helper_set_port_phy_present(xiface, port_index, true); ipd_port = cvmx_helper_get_ipd_port(xiface, port_index); if (ipd_port >= 0) { debug("%s: Allocating phy info for 0x%x:%d\n", __func__, xiface, port_index); phy_info = (cvmx_phy_info_t *)cvmx_bootmem_alloc( sizeof(*phy_info), 0); if (!phy_info) { debug("%s: Out of memory\n", __func__); return -1; } memset(phy_info, 0, sizeof(*phy_info)); phy_info->phy_addr = -1; err = __get_phy_info_from_dt(phy_info, ipd_port); if (err) { debug("%s: Error parsing phy info for ipd port %d\n", __func__, ipd_port); return -1; } cvmx_helper_set_port_phy_info( xiface, port_index, phy_info); debug("%s: Saved phy info\n", __func__); } } else { cvmx_helper_set_phy_fdt_node_offset(xiface, port_index, -1); debug("%s: No PHY fdt node offset for interface 0x%x, port %d to %d\n", __func__, xiface, port_index, fdt_phy_node); cvmx_helper_set_port_phy_present(xiface, port_index, false); } } if (!sfp_parsed) if (cvmx_sfp_parse_device_tree(fdt_addr)) debug("%s: Error parsing SFP device tree\n", __func__); parsed = true; return 0; } int __cvmx_helper_parse_bgx_rgmii_dt(const void *fdt_addr) { u64 reg_addr; struct cvmx_xiface xi; int fdt_port_node = -1; int fdt_interface_node; int fdt_phy_node; int port_index; int xiface; /* There's only one xcv (RGMII) interface, so just search for the one * that's part of a BGX entry. */ while ((fdt_port_node = fdt_node_offset_by_compatible( fdt_addr, fdt_port_node, "cavium,octeon-7360-xcv")) >= 0) { fdt_interface_node = fdt_parent_offset(fdt_addr, fdt_port_node); if (fdt_interface_node < 0) { printf("Error: device tree corrupt!\n"); return -1; } debug("%s: XCV parent node compatible: %s\n", __func__, (char *)fdt_getprop(fdt_addr, fdt_interface_node, "compatible", NULL)); if (!fdt_node_check_compatible(fdt_addr, fdt_interface_node, "cavium,octeon-7890-bgx")) break; } if (fdt_port_node == -FDT_ERR_NOTFOUND) { debug("No XCV/RGMII interface found in device tree\n"); return 0; } else if (fdt_port_node < 0) { debug("%s: Error %d parsing device tree\n", __func__, fdt_port_node); return -1; } port_index = cvmx_fdt_get_int(fdt_addr, fdt_port_node, "reg", -1); if (port_index != 0) { printf("%s: Error: port index (reg) must be 0, not %d.\n", __func__, port_index); return -1; } reg_addr = cvmx_fdt_get_addr(fdt_addr, fdt_interface_node, "reg"); if (reg_addr == FDT_ADDR_T_NONE) { printf("%s: Error: could not get BGX interface address\n", __func__); return -1; } /* We don't have to bother translating since only 78xx supports OCX and * doesn't support RGMII. */ xi = __cvmx_bgx_reg_addr_to_xiface(reg_addr); debug("%s: xi.node: %d, xi.interface: 0x%x, addr: 0x%llx\n", __func__, xi.node, xi.interface, (unsigned long long)reg_addr); if (xi.node < 0) { printf("%s: Device tree BGX node has invalid address 0x%llx\n", __func__, (unsigned long long)reg_addr); return -1; } debug("%s: Found XCV (RGMII) interface on interface %d\n", __func__, xi.interface); debug(" phy handle: 0x%x\n", cvmx_fdt_get_int(fdt_addr, fdt_port_node, "phy-handle", -1)); fdt_phy_node = cvmx_fdt_lookup_phandle(fdt_addr, fdt_port_node, "phy-handle"); debug("%s: phy-handle node: 0x%x\n", __func__, fdt_phy_node); xiface = cvmx_helper_node_interface_to_xiface(xi.node, xi.interface); cvmx_helper_set_port_fdt_node_offset(xiface, port_index, fdt_port_node); if (fdt_phy_node >= 0) { debug("%s: Setting PHY fdt node offset for interface 0x%x, port %d to %d\n", __func__, xiface, port_index, fdt_phy_node); debug("%s: PHY node name: %s\n", __func__, fdt_get_name(fdt_addr, fdt_phy_node, NULL)); cvmx_helper_set_phy_fdt_node_offset(xiface, port_index, fdt_phy_node); cvmx_helper_set_port_phy_present(xiface, port_index, true); } else { cvmx_helper_set_phy_fdt_node_offset(xiface, port_index, -1); debug("%s: No PHY fdt node offset for interface 0x%x, port %d to %d\n", __func__, xiface, port_index, fdt_phy_node); cvmx_helper_set_port_phy_present(xiface, port_index, false); } return 0; } /** * Returns if a port is present on an interface * * @param fdt_addr - address fo flat device tree * @param ipd_port - IPD port number * * @return 1 if port is present, 0 if not present, -1 if error */ int __cvmx_helper_board_get_port_from_dt(void *fdt_addr, int ipd_port) { int port_index; int aliases; const char *pip_path; char name_buffer[24]; int pip, iface, eth; cvmx_helper_interface_mode_t mode; int xiface = cvmx_helper_get_interface_num(ipd_port); struct cvmx_xiface xi = cvmx_helper_xiface_to_node_interface(xiface); u32 val; int phy_node_offset; int parse_bgx_dt_err; int parse_vsc7224_err; debug("%s(%p, %d)\n", __func__, fdt_addr, ipd_port); if (octeon_has_feature(OCTEON_FEATURE_BGX)) { static int fdt_ports_initialized; port_index = cvmx_helper_get_interface_index_num(ipd_port); if (!fdt_ports_initialized) { if (octeon_has_feature(OCTEON_FEATURE_BGX_XCV)) { if (!__cvmx_helper_parse_bgx_rgmii_dt(fdt_addr)) fdt_ports_initialized = 1; parse_bgx_dt_err = __cvmx_helper_parse_bgx_dt(fdt_addr); parse_vsc7224_err = __cvmx_fdt_parse_vsc7224(fdt_addr); if (!parse_bgx_dt_err && !parse_vsc7224_err) fdt_ports_initialized = 1; } else { debug("%s: Error parsing FDT\n", __func__); return -1; } } return cvmx_helper_is_port_valid(xiface, port_index); } mode = cvmx_helper_interface_get_mode(xiface); switch (mode) { /* Device tree has information about the following mode types. */ case CVMX_HELPER_INTERFACE_MODE_RGMII: case CVMX_HELPER_INTERFACE_MODE_GMII: case CVMX_HELPER_INTERFACE_MODE_SPI: case CVMX_HELPER_INTERFACE_MODE_XAUI: case CVMX_HELPER_INTERFACE_MODE_SGMII: case CVMX_HELPER_INTERFACE_MODE_QSGMII: case CVMX_HELPER_INTERFACE_MODE_RXAUI: case CVMX_HELPER_INTERFACE_MODE_AGL: case CVMX_HELPER_INTERFACE_MODE_XLAUI: case CVMX_HELPER_INTERFACE_MODE_XFI: aliases = 1; break; default: aliases = 0; break; } /* The device tree information is present on interfaces that have phy */ if (!aliases) return 1; port_index = cvmx_helper_get_interface_index_num(ipd_port); aliases = fdt_path_offset(fdt_addr, "/aliases"); if (aliases < 0) { debug("%s: ERROR: /aliases not found in device tree fdt_addr=%p\n", __func__, fdt_addr); return -1; } pip_path = (const char *)fdt_getprop(fdt_addr, aliases, "pip", NULL); if (!pip_path) { debug("%s: ERROR: interface %x pip path not found in device tree\n", __func__, xiface); return -1; } pip = fdt_path_offset(fdt_addr, pip_path); if (pip < 0) { debug("%s: ERROR: interface %x pip not found in device tree\n", __func__, xiface); return -1; } snprintf(name_buffer, sizeof(name_buffer), "interface@%d", xi.interface); iface = fdt_subnode_offset(fdt_addr, pip, name_buffer); if (iface < 0) return 0; snprintf(name_buffer, sizeof(name_buffer), "ethernet@%x", port_index); eth = fdt_subnode_offset(fdt_addr, iface, name_buffer); debug("%s: eth subnode offset %d from %s\n", __func__, eth, name_buffer); if (eth < 0) return -1; cvmx_helper_set_port_fdt_node_offset(xiface, port_index, eth); phy_node_offset = cvmx_fdt_get_int(fdt_addr, eth, "phy", -1); cvmx_helper_set_phy_fdt_node_offset(xiface, port_index, phy_node_offset); if (fdt_getprop(fdt_addr, eth, "cavium,sgmii-mac-phy-mode", NULL)) cvmx_helper_set_mac_phy_mode(xiface, port_index, true); else cvmx_helper_set_mac_phy_mode(xiface, port_index, false); if (fdt_getprop(fdt_addr, eth, "cavium,force-link-up", NULL)) cvmx_helper_set_port_force_link_up(xiface, port_index, true); else cvmx_helper_set_port_force_link_up(xiface, port_index, false); if (fdt_getprop(fdt_addr, eth, "cavium,sgmii-mac-1000x-mode", NULL)) cvmx_helper_set_1000x_mode(xiface, port_index, true); else cvmx_helper_set_1000x_mode(xiface, port_index, false); if (fdt_getprop(fdt_addr, eth, "cavium,disable-autonegotiation", NULL)) cvmx_helper_set_port_autonegotiation(xiface, port_index, false); else cvmx_helper_set_port_autonegotiation(xiface, port_index, true); if (mode == CVMX_HELPER_INTERFACE_MODE_AGL) { bool tx_bypass = false; if (fdt_getprop(fdt_addr, eth, "cavium,rx-clk-delay-bypass", NULL)) cvmx_helper_set_agl_rx_clock_delay_bypass( xiface, port_index, true); else cvmx_helper_set_agl_rx_clock_delay_bypass( xiface, port_index, false); val = cvmx_fdt_get_int(fdt_addr, eth, "cavium,rx-clk-skew", 0); cvmx_helper_set_agl_rx_clock_skew(xiface, port_index, val); if (fdt_getprop(fdt_addr, eth, "cavium,tx-clk-delay-bypass", NULL)) tx_bypass = true; val = cvmx_fdt_get_int(fdt_addr, eth, "tx-clk-delay", 0); cvmx_helper_cfg_set_rgmii_tx_clk_delay(xiface, port_index, tx_bypass, val); val = cvmx_fdt_get_int(fdt_addr, eth, "cavium,refclk-sel", 0); cvmx_helper_set_agl_refclk_sel(xiface, port_index, val); } return (eth >= 0); } /** * Given the address of the MDIO registers, output the CPU node and MDIO bus * * @param addr 64-bit address of MDIO registers (from device tree) * @param[out] node CPU node number (78xx) * @param[out] bus MDIO bus number */ void __cvmx_mdio_addr_to_node_bus(u64 addr, int *node, int *bus) { if (OCTEON_IS_MODEL(OCTEON_CN78XX)) { if (node) *node = cvmx_csr_addr_to_node(addr); addr = cvmx_csr_addr_strip_node(addr); } else { if (node) *node = 0; } if (OCTEON_IS_MODEL(OCTEON_CN68XX) || OCTEON_IS_MODEL(OCTEON_CN78XX)) { switch (addr) { case 0x0001180000003800: *bus = 0; break; case 0x0001180000003880: *bus = 1; break; case 0x0001180000003900: *bus = 2; break; case 0x0001180000003980: *bus = 3; break; default: *bus = -1; printf("%s: Invalid SMI bus address 0x%llx\n", __func__, (unsigned long long)addr); break; } } else if (OCTEON_IS_MODEL(OCTEON_CN73XX) || OCTEON_IS_MODEL(OCTEON_CNF75XX)) { switch (addr) { case 0x0001180000003800: *bus = 0; break; case 0x0001180000003880: *bus = 1; break; default: *bus = -1; printf("%s: Invalid SMI bus address 0x%llx\n", __func__, (unsigned long long)addr); break; } } else { switch (addr) { case 0x0001180000001800: *bus = 0; break; case 0x0001180000001900: *bus = 1; break; default: *bus = -1; printf("%s: Invalid SMI bus address 0x%llx\n", __func__, (unsigned long long)addr); break; } } }