/* * Copyright (C) Cvitek Co., Ltd. 2019-2020. All rights reserved. */ #include #include "cvi_eth_phy.h" #define CSI_ETH_AUTONEG_DISABLE (0) ///< Disable auto-negotiation #define CSI_ETH_AUTONEG_ENABLE (1) ///< Enable auto-negotiation #define CONFIG_ETH_PHY_NUM 2 eth_phy_priv_t phy_priv_list[CONFIG_ETH_PHY_NUM]; extern eth_phy_dev_t cv181x_device; /* registered phy devices */ static eth_phy_dev_t *const eth_phy_devices[] = { &cv181x_device, NULL /* Must be the last item */ }; int32_t eth_phy_read(eth_phy_priv_t *priv, uint8_t phy_addr, uint8_t reg_addr, uint16_t *data) { RT_ASSERT(priv); RT_ASSERT(priv->phy_read); return priv->phy_read(phy_addr, reg_addr, data); } int32_t eth_phy_write(eth_phy_priv_t *priv, uint8_t phy_addr, uint8_t reg_addr, uint16_t data) { RT_ASSERT(priv); RT_ASSERT(priv->phy_write); return priv->phy_write(phy_addr, reg_addr, data); } static eth_phy_dev_t *eth_get_phy_device(eth_phy_priv_t *priv, uint8_t phy_addr, uint32_t phy_id) { eth_phy_dev_t *p = eth_phy_devices[0]; int32_t i = 0; while (p != NULL) { if ((p->phy_id & p->mask) == (phy_id & p->mask)) { p->phy_addr = phy_addr; p->advertising = p->supported = p->features; return p; } i ++; p = eth_phy_devices[i]; } return NULL; } static int32_t eth_read_phy_id(eth_phy_priv_t *priv, uint8_t phy_addr, uint32_t *phy_id) { int32_t ret; uint16_t data; uint32_t id; ret = eth_phy_read(priv, phy_addr, CVI_MII_PHYSID1, &data); if (ret != 0) { return ret; } id = data; id = (id & 0xffff) << 16; ret = eth_phy_read(priv, phy_addr, CVI_MII_PHYSID2, &data); if (ret != 0) { return ret; } id |= (data & 0xffff); if (phy_id != NULL) { *phy_id = id; } return 0; } static eth_phy_dev_t * eth_get_phy_by_mask(eth_phy_priv_t *priv, uint32_t phy_mask, phy_if_mode_t interface) { uint32_t phy_id = 0xffffffff; while (phy_mask) { int32_t addr = ffs(phy_mask) - 1; int32_t r = eth_read_phy_id(priv, addr, &phy_id); /* If the PHY ID is mostly f's, we didn't find anything */ if (r == 0 && (phy_id & 0x1fffffff) != 0x1fffffff) return eth_get_phy_device(priv, addr, phy_id); phy_mask &= ~(1 << addr); } return NULL; } static void eth_config(void) { unsigned int val; val = mmio_read_32(ETH_PHY_BASE) & ETH_PHY_INIT_MASK; mmio_write_32(ETH_PHY_BASE, (val | ETH_PHY_SHUTDOWN) & ETH_PHY_RESET); rt_thread_mdelay(1); mmio_write_32(ETH_PHY_BASE, val & ETH_PHY_POWERUP & ETH_PHY_RESET); rt_thread_mdelay(20); mmio_write_32(ETH_PHY_BASE, (val & ETH_PHY_POWERUP) | ETH_PHY_RESET_N); rt_thread_mdelay(1); } static eth_phy_dev_t *eth_connect_phy(eth_phy_priv_t *priv, uint32_t phy_mask, phy_if_mode_t interface) { int32_t i; eth_phy_dev_t *phydev = NULL; /* config eth internal phy on ASIC board */ eth_config(); #ifdef CONFIG_PHY_ADDR phy_mask = 1 << CONFIG_PHY_ADDR; #endif for (i = 0; i < 5; i++) { phydev = eth_get_phy_by_mask(priv, phy_mask, interface); if (phydev) return phydev; } rt_kprintf("\n PHY: "); while (phy_mask) { int32_t addr = ffs(phy_mask) - 1; rt_kprintf("%d ", addr); phy_mask &= ~(1 << addr); } rt_kprintf("not found\n"); return NULL; } int32_t eth_phy_reset(eth_phy_handle_t handle) { RT_ASSERT(handle); eth_phy_dev_t *dev = (eth_phy_dev_t *)handle; RT_ASSERT(dev->priv); uint16_t data; int32_t ret; int32_t timeout = 600; /* in ms */ eth_phy_priv_t *priv = dev->priv; uint32_t phy_addr = dev->phy_addr; /* Soft reset */ ret = eth_phy_read(priv, phy_addr, CVI_MII_BMCR, &data); if (ret != 0) { rt_kprintf("eth phy read failed\n"); return ret; } ret = eth_phy_write(priv, phy_addr, CVI_MII_BMCR, data | CVI_BMCR_RESET); if (ret != 0) { rt_kprintf("eth phy write failed\n"); return ret; } #ifdef CONFIG_PHY_RESET_DELAY rt_hw_us_delay(CONFIG_PHY_RESET_DELAY); /* Intel LXT971A needs this */ #endif /* * Wait up to 0.6s for the reset sequence to finish. According to * IEEE 802.3, Section 2, Subsection 22.2.4.1.1 a PHY reset may take * up to 0.5 s. */ ret = eth_phy_read(priv, phy_addr, CVI_MII_BMCR, &data); while ((data & CVI_BMCR_RESET) && timeout--) { ret = eth_phy_read(priv, phy_addr, CVI_MII_BMCR, &data); if (ret != 0) { return ret; } rt_thread_mdelay(1); } if (data & CVI_BMCR_RESET) { rt_kprintf("eth phy reset timed out\n"); return -1; } return 0; } int32_t eth_phy_config(eth_phy_handle_t handle) { RT_ASSERT(handle); eth_phy_dev_t *dev = (eth_phy_dev_t *)handle; if (dev->config) { return dev->config(handle); } return 0; } int32_t eth_phy_start(eth_phy_handle_t handle) { RT_ASSERT(handle); eth_phy_dev_t *dev = (eth_phy_dev_t *)handle; if (dev->start) { return dev->start(handle); } return 0; } int32_t eth_phy_stop(eth_phy_handle_t handle) { RT_ASSERT(handle); eth_phy_dev_t *dev = (eth_phy_dev_t *)handle; if (dev->start) { return dev->stop(handle); } return 0; } int32_t cvi_eth_phy_power_control(eth_phy_handle_t handle, eth_power_state_t state) { if (state == CSI_ETH_POWER_FULL) { return eth_phy_start(handle); } else if (state == CSI_ETH_POWER_OFF) { return eth_phy_stop(handle); } return 0; } int32_t genphy_update_link(eth_phy_dev_t *phy_dev) { uint8_t phy_addr = phy_dev->phy_addr; uint16_t mii_reg; int32_t ret; /* * Wait if the link is up, and autonegotiation is in progress * (ie - we're capable and it's not done) */ ret = eth_phy_read(phy_dev->priv, phy_addr, CVI_MII_BMSR, &mii_reg); if (ret != 0) { return ret; } /* * If we already saw the link up, and it hasn't gone down, then * we don't need to wait for autoneg again */ if (phy_dev->link_state && mii_reg & CVI_BMSR_LSTATUS) return 0; if ((phy_dev->priv->link_info.autoneg == CSI_ETH_AUTONEG_ENABLE) && !(mii_reg & CVI_BMSR_ANEGCOMPLETE)) { int i = 0; rt_kprintf("%s waiting for PHY auto negotiation to complete...\n", phy_dev->name); while (!(mii_reg & CVI_BMSR_ANEGCOMPLETE)) { /* * Timeout reached ? */ if (i > CVI_PHY_ANEG_TIMEOUT) { rt_kprintf("TIMEOUT!\n"); phy_dev->link_state = ETH_LINK_DOWN; return -1; } // if ((i++ % 1000) == 0) // rt_kprintf("."); i ++; rt_hw_us_delay(1000); /* 1 ms */ ret = eth_phy_read(phy_dev->priv, phy_addr, CVI_MII_BMSR, &mii_reg); if (ret != 0) { return ret; } } rt_kprintf("auto negotiation Done!\n"); phy_dev->link_state = ETH_LINK_UP; } else { /* Read the link a second time to clear the latched state */ ret = eth_phy_read(phy_dev->priv, phy_addr, CVI_MII_BMSR, &mii_reg); if (ret != 0) { return ret; } if (mii_reg & CVI_BMSR_LSTATUS) phy_dev->link_state = ETH_LINK_UP; else phy_dev->link_state = ETH_LINK_DOWN; } return 0; } int32_t eth_phy_update_link(eth_phy_handle_t handle) { RT_ASSERT(handle); eth_phy_dev_t *dev = (eth_phy_dev_t *)handle; if (dev->update_link) { return dev->update_link(handle); } else { return genphy_update_link(dev); } } static int32_t genphy_config_advert(eth_phy_dev_t *phy_dev) { RT_ASSERT(phy_dev->priv); eth_phy_priv_t *priv = phy_dev->priv; uint8_t phy_addr = phy_dev->phy_addr; uint32_t advertise; uint16_t oldadv, adv, bmsr; int32_t changed = 0; int32_t ret; /* Only allow advertising what this PHY supports */ phy_dev->advertising &= phy_dev->supported; advertise = phy_dev->advertising; /* Setup standard advertisement */ ret = eth_phy_read(priv, phy_addr, CVI_MII_ADVERTISE, &adv); if (ret != 0) { return ret; } oldadv = adv; if (adv < 0) return adv; adv &= ~(CVI_ADVERTISE_ALL | CVI_ADVERTISE_100BASE4 | CVI_ADVERTISE_PAUSE_CAP | CVI_ADVERTISE_PAUSE_ASYM); if (advertise & CVI_ADVERTISED_10baseT_Half) adv |= CVI_ADVERTISE_10HALF; if (advertise & CVI_ADVERTISED_10baseT_Full) adv |= CVI_ADVERTISE_10FULL; if (advertise & CVI_ADVERTISED_100baseT_Half) adv |= CVI_ADVERTISE_100HALF; if (advertise & CVI_ADVERTISED_100baseT_Full) adv |= CVI_ADVERTISE_100FULL; if (advertise & CVI_ADVERTISED_Pause) adv |= CVI_ADVERTISE_PAUSE_CAP; if (advertise & CVI_ADVERTISED_Asym_Pause) adv |= CVI_ADVERTISE_PAUSE_ASYM; if (advertise & CVI_ADVERTISED_1000baseX_Half) adv |= CVI_ADVERTISE_1000XHALF; if (advertise & CVI_ADVERTISED_1000baseX_Full) adv |= CVI_ADVERTISE_1000XFULL; if (adv != oldadv) { ret = eth_phy_write(priv, phy_addr, CVI_MII_ADVERTISE, adv); if (ret != 0) { return ret; } changed = 1; } ret = eth_phy_read(priv, phy_addr, CVI_MII_BMSR, &bmsr); if (ret != 0 || bmsr < 0) { return ret; } /* Per 802.3-2008, Section 22.2.4.2.16 Extended status all * 1000Mbits/sec capable PHYs shall have the CVI_BMSR_ESTATEN bit set to a * logical 1. */ if (!(bmsr & CVI_BMSR_ESTATEN)) return changed; /* Configure gigabit if it's supported */ ret = eth_phy_read(priv, phy_addr, CVI_MII_CTRL1000, &adv); if (ret != 0 || adv < 0) { return ret; } oldadv = adv; adv &= ~(CVI_ADVERTISE_1000FULL | CVI_ADVERTISE_1000HALF); if (phy_dev->supported & (CVI_SUPPORTED_1000baseT_Half | CVI_SUPPORTED_1000baseT_Full)) { if (advertise & CVI_SUPPORTED_1000baseT_Half) adv |= CVI_ADVERTISE_1000HALF; if (advertise & CVI_SUPPORTED_1000baseT_Full) adv |= CVI_ADVERTISE_1000FULL; } if (adv != oldadv) changed = 1; ret = eth_phy_write(priv, phy_addr, CVI_MII_CTRL1000, adv); if (ret != 0) { return ret; } return changed; } static int32_t genphy_setup_forced(eth_phy_dev_t *phy_dev) { RT_ASSERT(phy_dev->priv); eth_phy_priv_t *priv = phy_dev->priv; uint8_t phy_addr = phy_dev->phy_addr; int32_t ctl = CVI_BMCR_ANRESTART; int32_t ret; if (CSI_ETH_SPEED_1G == priv->link_info.speed) ctl |= CVI_BMCR_SPEED1000; else if (CSI_ETH_SPEED_100M == priv->link_info.speed) ctl |= CVI_BMCR_SPEED100; else//CSI_ETH_SPEED_10M == priv->link_info.speed ctl |= CVI_BMCR_SPEED100; if (CSI_ETH_DUPLEX_FULL == priv->link_info.duplex) ctl |= CVI_BMCR_FULLDPLX; ret = eth_phy_write(priv, phy_addr, CVI_MII_BMCR, ctl); return ret; } int genphy_restart_aneg(eth_phy_dev_t *phy_dev) { int32_t ret; uint16_t ctl; ret = eth_phy_read(phy_dev->priv, phy_dev->phy_addr, CVI_MII_BMCR, &ctl); if (ret != 0 || ctl < 0) return ret; ctl |= (CVI_BMCR_ANENABLE | CVI_BMCR_ANRESTART); /* Don't isolate the PHY if we're negotiating */ ctl &= ~(CVI_BMCR_ISOLATE); ret = eth_phy_write(phy_dev->priv, phy_dev->phy_addr, CVI_MII_BMCR, ctl); return ret; } int32_t genphy_config_aneg(eth_phy_dev_t *phy_dev) { RT_ASSERT(phy_dev->priv); eth_phy_priv_t *priv = phy_dev->priv; uint8_t phy_addr = phy_dev->phy_addr; int32_t result; uint16_t ctl; int32_t ret; if (CSI_ETH_AUTONEG_ENABLE != priv->link_info.autoneg) return genphy_setup_forced(phy_dev); result = genphy_config_advert(phy_dev); if (result < 0) /* error */ return result; if (result == 0) { /* Advertisment hasn't changed, but maybe aneg was never on to * begin with? Or maybe phy was isolated? */ ret = eth_phy_read(priv, phy_addr, CVI_MII_BMCR, &ctl); if (ret != 0 || ctl < 0) return ret; if (!(ctl & CVI_BMCR_ANENABLE) || (ctl & CVI_BMCR_ISOLATE)) result = 1; /* do restart aneg */ } /* Only restart aneg if we are advertising something different * than we were before. */ if (result > 0) result = genphy_restart_aneg(phy_dev); return result; } int32_t genphy_config(eth_phy_dev_t *phy_dev) { RT_ASSERT(phy_dev->priv); eth_phy_priv_t *priv = phy_dev->priv; uint8_t phy_addr = phy_dev->phy_addr; int32_t ret; uint16_t val; uint32_t features; features = (CVI_SUPPORTED_TP | CVI_SUPPORTED_MII | CVI_SUPPORTED_AUI | CVI_SUPPORTED_FIBRE | CVI_SUPPORTED_BNC); /* Do we support autonegotiation? */ ret = eth_phy_read(priv, phy_addr, CVI_MII_BMSR, &val); if (ret != 0 || val < 0) return ret; if (val & CVI_BMSR_ANEGCAPABLE) features |= CVI_SUPPORTED_Autoneg; if (val & CVI_BMSR_100FULL) features |= CVI_SUPPORTED_100baseT_Full; if (val & CVI_BMSR_100HALF) features |= CVI_SUPPORTED_100baseT_Half; if (val & CVI_BMSR_10FULL) features |= CVI_SUPPORTED_10baseT_Full; if (val & CVI_BMSR_10HALF) features |= CVI_SUPPORTED_10baseT_Half; if (val & CVI_BMSR_ESTATEN) { ret = eth_phy_read(priv, phy_addr, CVI_MII_ESTATUS, &val); if (ret != 0 || val < 0) return val; if (val & CVI_ESTATUS_1000_TFULL) features |= CVI_SUPPORTED_1000baseT_Full; if (val & CVI_ESTATUS_1000_THALF) features |= CVI_SUPPORTED_1000baseT_Half; if (val & CVI_ESTATUS_1000_XFULL) features |= CVI_SUPPORTED_1000baseX_Full; if (val & CVI_ESTATUS_1000_XHALF) features |= CVI_SUPPORTED_1000baseX_Half; } phy_dev->supported &= features; phy_dev->advertising &= features; genphy_config_aneg(phy_dev); return 0; } eth_phy_handle_t cvi_eth_phy_init(csi_eth_phy_read_t fn_read, csi_eth_phy_write_t fn_write) { eth_phy_dev_t *phy_dev; eth_phy_priv_t *priv; uint32_t phy_mask = 0xffffffff; phy_if_mode_t interface = 0; RT_ASSERT(fn_read != RT_NULL); RT_ASSERT(fn_write != RT_NULL); priv = &phy_priv_list[0]; priv->phy_read = fn_read; priv->phy_write = fn_write; priv->link_info.autoneg = CSI_ETH_AUTONEG_ENABLE; phy_dev = eth_connect_phy(priv, phy_mask, interface); if (phy_dev == NULL) { rt_kprintf("No phy device found!\n"); return; } rt_kprintf("connect phy id: 0x%X\n", phy_dev->phy_id); phy_dev->priv = priv; /* Reset PHY */ eth_phy_reset(phy_dev); /* Config PHY */ eth_phy_config(phy_dev); return phy_dev; }