| /* |
| * |
| * Copyright (c) 2002-2005 Broadcom Corporation |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
| * |
| * |
| *File Name : bcmmii.c |
| * |
| *Description: Broadcom PHY/GPHY/Ethernet Switch Configuration |
| *Revision: 09/25/2008, L.Sun created. |
| */ |
| |
| #include "bcmgenet.h" |
| |
| #include <linux/types.h> |
| #include <linux/delay.h> |
| #include <linux/wait.h> |
| #include <linux/mii.h> |
| #include <linux/ethtool.h> |
| #include <linux/bitops.h> |
| #include <linux/netdevice.h> |
| #include <linux/platform_device.h> |
| #include <linux/phy.h> |
| #include <linux/phy_fixed.h> |
| #include <linux/brcmphy.h> |
| #include <linux/of.h> |
| #include <linux/of_net.h> |
| #include <linux/of_mdio.h> |
| |
| /* read a value from the MII */ |
| static int bcmgenet_mii_read(struct mii_bus *bus, int phy_id, int location) |
| { |
| int ret; |
| struct net_device *dev = bus->priv; |
| struct bcmgenet_priv *priv = netdev_priv(dev); |
| u32 reg; |
| |
| bcmgenet_umac_writel(priv, (MDIO_RD | (phy_id << MDIO_PMD_SHIFT) | |
| (location << MDIO_REG_SHIFT)), UMAC_MDIO_CMD); |
| /* Start MDIO transaction*/ |
| reg = bcmgenet_umac_readl(priv, UMAC_MDIO_CMD); |
| reg |= MDIO_START_BUSY; |
| bcmgenet_umac_writel(priv, reg, UMAC_MDIO_CMD); |
| wait_event_timeout(priv->wq, |
| !(bcmgenet_umac_readl(priv, UMAC_MDIO_CMD) |
| & MDIO_START_BUSY), |
| HZ/100); |
| ret = bcmgenet_umac_readl(priv, UMAC_MDIO_CMD); |
| |
| /* Don't check error codes from switches, as some of them are |
| * known to return MDIO_READ_FAIL on good transactions |
| */ |
| if (!(bus->phy_ignore_ta_mask & 1 << phy_id) && |
| (ret & MDIO_READ_FAIL)) { |
| netif_dbg(priv, hw, dev, "MDIO read failure\n"); |
| ret = 0; |
| } |
| return ret & 0xffff; |
| } |
| |
| /* write a value to the MII */ |
| static int bcmgenet_mii_write(struct mii_bus *bus, int phy_id, |
| int location, u16 val) |
| { |
| struct net_device *dev = bus->priv; |
| struct bcmgenet_priv *priv = netdev_priv(dev); |
| u32 reg; |
| |
| bcmgenet_umac_writel(priv, (MDIO_WR | (phy_id << MDIO_PMD_SHIFT) | |
| (location << MDIO_REG_SHIFT) | (0xffff & val)), |
| UMAC_MDIO_CMD); |
| reg = bcmgenet_umac_readl(priv, UMAC_MDIO_CMD); |
| reg |= MDIO_START_BUSY; |
| bcmgenet_umac_writel(priv, reg, UMAC_MDIO_CMD); |
| wait_event_timeout(priv->wq, |
| !(bcmgenet_umac_readl(priv, UMAC_MDIO_CMD) & |
| MDIO_START_BUSY), |
| HZ/100); |
| |
| return 0; |
| } |
| |
| /* setup netdev link state when PHY link status change and |
| * update UMAC and RGMII block when link up |
| */ |
| void bcmgenet_mii_setup(struct net_device *dev) |
| { |
| struct bcmgenet_priv *priv = netdev_priv(dev); |
| struct phy_device *phydev = priv->phydev; |
| u32 reg, cmd_bits = 0; |
| bool status_changed = false; |
| |
| if (priv->old_link != phydev->link) { |
| status_changed = 1; |
| priv->old_link = phydev->link; |
| } |
| |
| if (phydev->link) { |
| /* check speed/duplex/pause changes */ |
| if (priv->old_speed != phydev->speed) { |
| status_changed = true; |
| priv->old_speed = phydev->speed; |
| } |
| |
| if (priv->old_duplex != phydev->duplex) { |
| status_changed = true; |
| priv->old_duplex = phydev->duplex; |
| } |
| |
| if (priv->old_pause != phydev->pause) { |
| status_changed = true; |
| priv->old_pause = phydev->pause; |
| } |
| |
| /* done if nothing has changed */ |
| if (!status_changed) |
| return; |
| |
| /* program UMAC and RGMII block based on established link |
| * speed, pause, and duplex. |
| * the speed set in umac->cmd tell RGMII block which clock |
| * 25MHz(100Mbps)/125MHz(1Gbps) to use for transmit. |
| * receive clock is provided by PHY. |
| */ |
| reg = bcmgenet_ext_readl(priv, EXT_RGMII_OOB_CTRL); |
| reg &= ~OOB_DISABLE; |
| reg |= RGMII_LINK; |
| bcmgenet_ext_writel(priv, reg, EXT_RGMII_OOB_CTRL); |
| |
| /* speed */ |
| if (phydev->speed == SPEED_1000) |
| cmd_bits = UMAC_SPEED_1000; |
| else if (phydev->speed == SPEED_100) |
| cmd_bits = UMAC_SPEED_100; |
| else |
| cmd_bits = UMAC_SPEED_10; |
| cmd_bits <<= CMD_SPEED_SHIFT; |
| |
| /* duplex */ |
| if (phydev->duplex != DUPLEX_FULL) |
| cmd_bits |= CMD_HD_EN; |
| |
| if (priv->old_pause != phydev->pause) { |
| status_changed = 1; |
| priv->old_pause = phydev->pause; |
| } |
| |
| /* pause capability */ |
| if (!phydev->pause) |
| cmd_bits |= CMD_RX_PAUSE_IGNORE | CMD_TX_PAUSE_IGNORE; |
| |
| reg = bcmgenet_umac_readl(priv, UMAC_CMD); |
| reg &= ~((CMD_SPEED_MASK << CMD_SPEED_SHIFT) | |
| CMD_HD_EN | CMD_RX_PAUSE_IGNORE | |
| CMD_TX_PAUSE_IGNORE); |
| reg |= cmd_bits; |
| bcmgenet_umac_writel(priv, reg, UMAC_CMD); |
| } else { |
| /* done if nothing has changed */ |
| if (!status_changed) |
| return; |
| |
| /* needed for MoCA fixed PHY to reflect correct link status */ |
| netif_carrier_off(dev); |
| } |
| |
| phy_print_status(phydev); |
| } |
| |
| void bcmgenet_mii_reset(struct net_device *dev) |
| { |
| struct bcmgenet_priv *priv = netdev_priv(dev); |
| |
| /* call the PHY driver specific init routine */ |
| phy_init_hw(priv->phydev); |
| phy_start_aneg(priv->phydev); |
| } |
| |
| /* 7366a0 EXT GPHY block comes with the CFG_IDDQ_BIAS and CFG_EXT_PWR_DOWN |
| * bits set to 1 at reset, they need to be cleared. A reset must also be |
| * issued. An initial reset value of 1500 micro seconds was not enough, 2000 |
| * micro seconds always works. The post-reset delay of 20 micro seconds could |
| * be eliminated but better be safe than sorry. |
| */ |
| void bcmgenet_phy_power_set(struct net_device *dev, bool enable) |
| { |
| struct bcmgenet_priv *priv = netdev_priv(dev); |
| u32 reg = 0; |
| |
| /* EXT_GPHY_CTRL is only valid for GENETv4 and onward */ |
| if (!GENET_IS_V4(priv)) |
| return; |
| |
| reg = bcmgenet_ext_readl(priv, EXT_GPHY_CTRL); |
| if (enable) { |
| reg &= ~EXT_CK25_DIS; |
| bcmgenet_ext_writel(priv, reg, EXT_GPHY_CTRL); |
| mdelay(1); |
| |
| reg &= ~(EXT_CFG_IDDQ_BIAS | EXT_CFG_PWR_DOWN); |
| reg |= EXT_GPHY_RESET; |
| bcmgenet_ext_writel(priv, reg, EXT_GPHY_CTRL); |
| mdelay(1); |
| |
| reg &= ~EXT_GPHY_RESET; |
| } else { |
| reg |= EXT_CFG_IDDQ_BIAS | EXT_CFG_PWR_DOWN | EXT_GPHY_RESET; |
| bcmgenet_ext_writel(priv, reg, EXT_GPHY_CTRL); |
| mdelay(1); |
| reg |= EXT_CK25_DIS; |
| } |
| bcmgenet_ext_writel(priv, reg, EXT_GPHY_CTRL); |
| udelay(60); |
| } |
| |
| static int bcmgenet_mii_probe(struct net_device *dev) |
| { |
| struct bcmgenet_priv *priv = netdev_priv(dev); |
| struct phy_device *phydev; |
| char phy_id[MII_BUS_ID_SIZE + 3]; |
| const char *fixed_bus = NULL; |
| int phy_addr = priv->phy_addr; |
| u32 phy_flags; |
| |
| if (priv->phydev) { |
| pr_info("PHY already attached\n"); |
| return 0; |
| } |
| |
| if (priv->old_dt_binding) { |
| /* Bind to fixed-0 for MOCA and switches */ |
| if (priv->phy_type == BRCM_PHY_TYPE_MOCA || |
| priv->phy_addr == BRCM_PHY_ID_NONE) { |
| fixed_bus = "fixed-0"; |
| } |
| |
| /* Use the second fixed PHY */ |
| if (priv->phy_addr == BRCM_PHY_ID_NONE) |
| phy_addr = 1; |
| |
| /* Connect either on the real MII bus or on the fixed one */ |
| snprintf(phy_id, sizeof(phy_id), PHY_ID_FMT, |
| fixed_bus ? fixed_bus : priv->mii_bus->id, |
| phy_addr); |
| } |
| |
| phy_flags = priv->gphy_rev; |
| |
| /* Initialize link state variables that bcmgenet_mii_setup() uses */ |
| priv->old_link = -1; |
| priv->old_speed = -1; |
| priv->old_duplex = -1; |
| priv->old_pause = -1; |
| |
| if (priv->old_dt_binding) { |
| phydev = phy_connect(dev, phy_id, bcmgenet_mii_setup, |
| priv->phy_interface); |
| } else { |
| phydev = of_phy_connect(dev, priv->phy_dn, |
| bcmgenet_mii_setup, phy_flags, |
| priv->phy_interface); |
| } |
| |
| if (!phydev) { |
| pr_err("could not attach to PHY\n"); |
| return -ENODEV; |
| } |
| |
| phydev->supported &= priv->phy_supported; |
| /* Adjust advertised speeds based on configured speed */ |
| if (priv->phy_speed == SPEED_1000) |
| phydev->advertising = PHY_GBIT_FEATURES; |
| else |
| phydev->advertising = PHY_BASIC_FEATURES; |
| |
| pr_info("attached PHY at address %d [%s]\n", |
| phydev->addr, phydev->drv->name); |
| |
| priv->phydev = phydev; |
| |
| return 0; |
| } |
| |
| static int bcmgenet_mii_alloc(struct bcmgenet_priv *priv) |
| { |
| struct mii_bus *bus; |
| int ret = 0; |
| |
| if (priv->mii_bus) |
| return 0; |
| |
| priv->mii_bus = mdiobus_alloc(); |
| if (!priv->mii_bus) { |
| pr_err("failed to allocate\n"); |
| return -ENOMEM; |
| } |
| |
| bus = priv->mii_bus; |
| bus->priv = priv->dev; |
| bus->name = "bcmgenet MII bus"; |
| bus->parent = &priv->pdev->dev; |
| bus->read = bcmgenet_mii_read; |
| bus->write = bcmgenet_mii_write; |
| if (priv->old_dt_binding) |
| bus->phy_mask = ~(1 << priv->phy_addr); |
| snprintf(bus->id, MII_BUS_ID_SIZE, "%s-%d", |
| priv->pdev->name, priv->pdev->id); |
| |
| bus->irq = kzalloc(sizeof(int) * PHY_MAX_ADDR, GFP_KERNEL); |
| if (!bus->irq) { |
| ret = -ENOMEM; |
| goto out_mdio_free; |
| } |
| |
| /* This is the correct thing to do, but libphy needs fixing |
| * with respect to ignoring interrupts |
| */ |
| if (priv->phy_addr < PHY_MAX_ADDR) { |
| if (priv->phy_type == BRCM_PHY_TYPE_INT) |
| bus->irq[priv->phy_addr] = PHY_IGNORE_INTERRUPT; |
| else |
| bus->irq[priv->phy_addr] = PHY_POLL; |
| } |
| |
| return 0; |
| |
| out_mdio_free: |
| mdiobus_free(priv->mii_bus); |
| return ret; |
| } |
| |
| static void bcmgenet_mii_free(struct bcmgenet_priv *priv) |
| { |
| mdiobus_unregister(priv->mii_bus); |
| kfree(priv->mii_bus->irq); |
| mdiobus_free(priv->mii_bus); |
| } |
| |
| static void bcmgenet_internal_phy_setup(struct net_device *dev) |
| { |
| struct bcmgenet_priv *priv = netdev_priv(dev); |
| u32 reg; |
| |
| /* Power up PHY */ |
| bcmgenet_phy_power_set(dev, true); |
| /* enable APD */ |
| reg = bcmgenet_ext_readl(priv, EXT_EXT_PWR_MGMT); |
| reg |= EXT_PWR_DN_EN_LD; |
| bcmgenet_ext_writel(priv, reg, EXT_EXT_PWR_MGMT); |
| } |
| |
| static void bcmgenet_moca_phy_setup(struct bcmgenet_priv *priv) |
| { |
| u32 reg; |
| |
| /* Speed settings are set in bcmgenet_mii_setup() */ |
| reg = bcmgenet_sys_readl(priv, SYS_PORT_CTRL); |
| reg |= LED_ACT_SOURCE_MAC; |
| bcmgenet_sys_writel(priv, reg, SYS_PORT_CTRL); |
| } |
| |
| int bcmgenet_mii_config(struct net_device *dev, bool init) |
| { |
| struct bcmgenet_priv *priv = netdev_priv(dev); |
| const char *phy_name = NULL; |
| u32 id_mode_dis = 0; |
| u32 port_ctrl; |
| u32 reg; |
| |
| priv->ext_phy = (priv->phy_type != BRCM_PHY_TYPE_INT) && |
| (priv->phy_type != BRCM_PHY_TYPE_MOCA); |
| |
| switch (priv->phy_interface) { |
| case PHY_INTERFACE_MODE_NA: |
| case PHY_INTERFACE_MODE_MOCA: |
| phy_name = "internal PHY"; |
| /* Irrespective of the actually configured PHY speed (100 or |
| * 1000) GENETv4 only has an internal GPHY so we will just end |
| * up masking the Gigabit features from what we support, not |
| * switching to the EPHY |
| */ |
| if (GENET_IS_V4(priv)) { |
| priv->phy_supported = PHY_GBIT_FEATURES; |
| port_ctrl = PORT_MODE_INT_GPHY; |
| } else { |
| priv->phy_supported = PHY_BASIC_FEATURES; |
| port_ctrl = PORT_MODE_INT_EPHY; |
| } |
| |
| bcmgenet_sys_writel(priv, port_ctrl, SYS_PORT_CTRL); |
| |
| if (priv->phy_type == BRCM_PHY_TYPE_INT) { |
| phy_name = "internal PHY"; |
| bcmgenet_internal_phy_setup(dev); |
| } else if (priv->phy_type == BRCM_PHY_TYPE_MOCA) { |
| phy_name = "MoCA"; |
| bcmgenet_moca_phy_setup(priv); |
| } |
| break; |
| |
| case PHY_INTERFACE_MODE_MII: |
| phy_name = "external MII"; |
| priv->phy_supported = PHY_BASIC_FEATURES; |
| bcmgenet_sys_writel(priv, |
| PORT_MODE_EXT_EPHY, SYS_PORT_CTRL); |
| break; |
| |
| case PHY_INTERFACE_MODE_REVMII: |
| phy_name = "external RvMII"; |
| if (priv->phy_speed == SPEED_100) { |
| priv->phy_supported = PHY_BASIC_FEATURES; |
| port_ctrl = PORT_MODE_EXT_RVMII_25; |
| } else { |
| priv->phy_supported = PHY_GBIT_FEATURES; |
| port_ctrl = PORT_MODE_EXT_RVMII_50; |
| } |
| bcmgenet_sys_writel(priv, port_ctrl, SYS_PORT_CTRL); |
| break; |
| |
| case PHY_INTERFACE_MODE_RGMII: |
| /* |
| * RGMII_NO_ID: TXC transitions at the same time as TXD |
| * (requires PCB or receiver-side delay) |
| * RGMII: Add 2ns delay on TXC (90 degree shift) |
| * |
| * ID is implicitly disabled for 100Mbps (RG)MII operation. |
| */ |
| id_mode_dis = BIT(16); |
| /* fall through */ |
| case PHY_INTERFACE_MODE_RGMII_TXID: |
| if (id_mode_dis) |
| phy_name = "external RGMII (no delay)"; |
| else |
| phy_name = "external RGMII (TX delay)"; |
| bcmgenet_sys_writel(priv, |
| PORT_MODE_EXT_GPHY, SYS_PORT_CTRL); |
| priv->phy_supported = PHY_GBIT_FEATURES; |
| /* |
| * setup mii based on configure speed and RGMII txclk is set in |
| * umac->cmd, mii_setup() after link established. |
| */ |
| break; |
| default: |
| dev_err(&priv->pdev->dev, "unknown phy_interface: %d\n", |
| priv->phy_interface); |
| return -EINVAL; |
| } |
| |
| /* This is an external PHY, aka xMII, so we need to enable the RGMII |
| * block for the interface to work |
| */ |
| if (priv->ext_phy) { |
| reg = bcmgenet_ext_readl(priv, EXT_RGMII_OOB_CTRL); |
| reg |= RGMII_MODE_EN | id_mode_dis; |
| bcmgenet_ext_writel(priv, reg, EXT_RGMII_OOB_CTRL); |
| } |
| |
| if (init) |
| dev_info(&priv->pdev->dev, "configuring instance for %s\n", |
| phy_name); |
| |
| return 0; |
| } |
| |
| static int bcmgenet_mii_new_dt_init(struct bcmgenet_priv *priv) |
| { |
| struct device_node *dn = priv->pdev->dev.of_node; |
| struct device *kdev = &priv->pdev->dev; |
| struct device_node *mdio_dn; |
| const char *phy_mode_str = NULL; |
| u32 propval; |
| int phy_mode; |
| int ret; |
| |
| mdio_dn = of_get_next_child(dn, NULL); |
| if (!mdio_dn) { |
| dev_err(kdev, "unable to find MDIO bus node\n"); |
| return -ENODEV; |
| } |
| |
| ret = of_mdiobus_register(priv->mii_bus, mdio_dn); |
| if (ret) { |
| dev_err(kdev, "failed to register MDIO bus\n"); |
| return ret; |
| } |
| |
| /* Check if we have an internal or external PHY */ |
| priv->phy_dn = of_parse_phandle(dn, "phy-handle", 0); |
| if (priv->phy_dn) { |
| if (!of_property_read_u32(priv->phy_dn, "max-speed", &propval)) |
| priv->phy_speed = propval; |
| } else { |
| if (of_phy_is_fixed_link(dn)) { |
| ret = of_phy_register_fixed_link(dn); |
| if (ret) |
| return ret; |
| } |
| |
| priv->phy_dn = of_node_get(dn); |
| } |
| |
| /* Get the link mode */ |
| phy_mode = of_get_phy_mode(dn); |
| priv->phy_interface = phy_mode; |
| |
| /* This is not a standard "phy-mode" do an explicit look */ |
| if (phy_mode < 0) { |
| ret = of_property_read_string(dn, "phy-mode", &phy_mode_str); |
| if (ret < 0) { |
| dev_err(kdev, "invalid PHY mode property\n"); |
| goto out; |
| } |
| |
| priv->phy_interface = PHY_INTERFACE_MODE_NA; |
| if (!strcasecmp(phy_mode_str, "internal")) |
| priv->phy_type = BRCM_PHY_TYPE_INT; |
| else if (!strcasecmp(phy_mode_str, "moca")) |
| priv->phy_type = BRCM_PHY_TYPE_MOCA; |
| else { |
| dev_err(kdev, "invalid PHY mode: %s\n", phy_mode_str); |
| ret = -EINVAL; |
| goto out; |
| } |
| } |
| |
| return 0; |
| out: |
| mdiobus_unregister(priv->mii_bus); |
| return ret; |
| } |
| |
| static int bcmgenet_mii_old_dt_init(struct bcmgenet_priv *priv) |
| { |
| int ret; |
| |
| ret = mdiobus_register(priv->mii_bus); |
| if (ret) { |
| kfree(priv->mii_bus->irq); |
| return ret; |
| } |
| |
| /* Ensure we set a correct phy_type to phy_interface |
| * translation |
| */ |
| switch (priv->phy_type) { |
| case BRCM_PHY_TYPE_MOCA: |
| priv->phy_addr = 0; |
| /* fall-through */ |
| case BRCM_PHY_TYPE_INT: |
| default: |
| priv->phy_interface = PHY_INTERFACE_MODE_NA; |
| break; |
| |
| case BRCM_PHY_TYPE_EXT_MII: |
| priv->phy_interface = PHY_INTERFACE_MODE_MII; |
| break; |
| |
| case BRCM_PHY_TYPE_EXT_RVMII: |
| priv->phy_interface = PHY_INTERFACE_MODE_REVMII; |
| break; |
| |
| case BRCM_PHY_TYPE_EXT_RGMII_NO_ID: |
| priv->phy_interface = PHY_INTERFACE_MODE_RGMII; |
| break; |
| |
| case BRCM_PHY_TYPE_EXT_RGMII: |
| case BRCM_PHY_TYPE_EXT_RGMII_IBS: |
| priv->phy_interface = PHY_INTERFACE_MODE_RGMII_TXID; |
| break; |
| } |
| |
| return 0; |
| } |
| |
| int bcmgenet_mii_init(struct net_device *dev) |
| { |
| struct bcmgenet_priv *priv = netdev_priv(dev); |
| struct device *kdev = &priv->pdev->dev; |
| int ret; |
| |
| ret = bcmgenet_mii_alloc(priv); |
| if (ret) |
| return ret; |
| |
| if (priv->old_dt_binding) { |
| dev_warn(kdev, "using old DT binding, update BOLT!\n"); |
| ret = bcmgenet_mii_old_dt_init(priv); |
| } else |
| ret = bcmgenet_mii_new_dt_init(priv); |
| |
| if (ret) |
| goto out; |
| |
| ret = bcmgenet_mii_config(dev, true); |
| if (ret) |
| goto out; |
| |
| ret = bcmgenet_mii_probe(dev); |
| if (ret) |
| goto out; |
| |
| return 0; |
| out: |
| bcmgenet_mii_free(priv); |
| return ret; |
| } |
| |
| void bcmgenet_mii_exit(struct net_device *dev) |
| { |
| bcmgenet_mii_free(netdev_priv(dev)); |
| } |