| // 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 <i2c.h> |
| #include <log.h> |
| #include <malloc.h> |
| #include <net.h> |
| #include <linux/delay.h> |
| |
| #include <mach/cvmx-regs.h> |
| #include <mach/cvmx-csr.h> |
| #include <mach/cvmx-bootmem.h> |
| #include <mach/octeon-model.h> |
| #include <mach/octeon_fdt.h> |
| #include <mach/cvmx-helper.h> |
| #include <mach/cvmx-helper-board.h> |
| #include <mach/cvmx-helper-cfg.h> |
| #include <mach/cvmx-helper-fdt.h> |
| #include <mach/cvmx-helper-gpio.h> |
| |
| #include <mach/cvmx-smix-defs.h> |
| #include <mach/cvmx-mdio.h> |
| #include <mach/cvmx-qlm.h> |
| |
| 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; |
| } |
| } |
| } |