|  | /* | 
|  | * Copyright (c) 2016 Piotr Mienkowski | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | /** @file | 
|  | * @brief Atmel SAM MCU family Ethernet PHY (GMAC) driver. | 
|  | */ | 
|  |  | 
|  | #include <errno.h> | 
|  | #include <kernel.h> | 
|  | #include <net/mii.h> | 
|  | #include "phy_sam_gmac.h" | 
|  |  | 
|  | #define SYS_LOG_DOMAIN "soc/soc_phy" | 
|  | #define SYS_LOG_LEVEL CONFIG_SYS_LOG_ETHERNET_LEVEL | 
|  | #include <logging/sys_log.h> | 
|  |  | 
|  | /* Maximum time to establish a link through auto-negotiation for | 
|  | * 10BASE-T, 100BASE-TX is 3.7s, to add an extra margin the timeout | 
|  | * is set at 4s. | 
|  | */ | 
|  | #define PHY_AUTONEG_TIMEOUT_MS   4000 | 
|  |  | 
|  | /* Enable MDIO serial bus between MAC and PHY. */ | 
|  | static void mdio_bus_enable(Gmac *gmac) | 
|  | { | 
|  | gmac->GMAC_NCR |= GMAC_NCR_MPE; | 
|  | } | 
|  |  | 
|  | /* Disable MDIO serial bus between MAC and PHY. */ | 
|  | static void mdio_bus_disable(Gmac *gmac) | 
|  | { | 
|  | gmac->GMAC_NCR &= ~GMAC_NCR_MPE; | 
|  | } | 
|  |  | 
|  | /* Wait PHY operation complete. */ | 
|  | static int mdio_bus_wait(Gmac *gmac) | 
|  | { | 
|  | u32_t retries = 100;  /* will wait up to 1 s */ | 
|  |  | 
|  | while (!(gmac->GMAC_NSR & GMAC_NSR_IDLE))   { | 
|  | if (retries-- == 0) { | 
|  | SYS_LOG_ERR("timeout"); | 
|  | return -ETIMEDOUT; | 
|  | } | 
|  |  | 
|  | k_sleep(10); | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* Send command to PHY over MDIO serial bus */ | 
|  | static int mdio_bus_send(Gmac *gmac, u8_t phy_addr, u8_t reg_addr, | 
|  | u8_t rw, u16_t data) | 
|  | { | 
|  | int retval; | 
|  |  | 
|  | /* Write GMAC PHY maintenance register */ | 
|  | gmac->GMAC_MAN =   GMAC_MAN_CLTTO | 
|  | | (GMAC_MAN_OP(rw ? 0x2 : 0x1)) | 
|  | | GMAC_MAN_WTN(0x02) | 
|  | | GMAC_MAN_PHYA(phy_addr) | 
|  | | GMAC_MAN_REGA(reg_addr) | 
|  | | GMAC_MAN_DATA(data); | 
|  |  | 
|  | /* Wait until PHY is ready */ | 
|  | retval = mdio_bus_wait(gmac); | 
|  | if (retval < 0) { | 
|  | return retval; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* Read PHY register. */ | 
|  | static int phy_read(const struct phy_sam_gmac_dev *phy, u8_t reg_addr, | 
|  | u32_t *value) | 
|  | { | 
|  | Gmac *const gmac = phy->regs; | 
|  | u8_t phy_addr = phy->address; | 
|  | int retval; | 
|  |  | 
|  | retval = mdio_bus_send(gmac, phy_addr, reg_addr, 1, 0); | 
|  | if (retval < 0) { | 
|  | return retval; | 
|  | } | 
|  |  | 
|  | /* Read data */ | 
|  | *value = gmac->GMAC_MAN & GMAC_MAN_DATA_Msk; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* Write PHY register. */ | 
|  | static int phy_write(const struct phy_sam_gmac_dev *phy, u8_t reg_addr, | 
|  | u32_t value) | 
|  | { | 
|  | Gmac *const gmac = phy->regs; | 
|  | u8_t phy_addr = phy->address; | 
|  |  | 
|  | return mdio_bus_send(gmac, phy_addr, reg_addr, 0, value); | 
|  | } | 
|  |  | 
|  | /* Issue a PHY soft reset. */ | 
|  | static int phy_soft_reset(const struct phy_sam_gmac_dev *phy) | 
|  | { | 
|  | u32_t phy_reg; | 
|  | u32_t retries = 12; | 
|  | int retval; | 
|  |  | 
|  | /* Issue a soft reset */ | 
|  | retval = phy_write(phy, MII_BMCR, MII_BMCR_RESET); | 
|  | if (retval < 0) { | 
|  | return retval; | 
|  | } | 
|  |  | 
|  | /* 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. | 
|  | */ | 
|  | do { | 
|  | if (retries-- == 0) { | 
|  | return -ETIMEDOUT; | 
|  | } | 
|  |  | 
|  | k_sleep(50); | 
|  |  | 
|  | retval = phy_read(phy, MII_BMCR, &phy_reg); | 
|  | if (retval < 0) { | 
|  | return retval; | 
|  | } | 
|  | } while (phy_reg & MII_BMCR_RESET); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int phy_sam_gmac_init(const struct phy_sam_gmac_dev *phy) | 
|  | { | 
|  | Gmac *const gmac = phy->regs; | 
|  | int phy_id; | 
|  |  | 
|  | mdio_bus_enable(gmac); | 
|  |  | 
|  | SYS_LOG_INF("Soft Reset of ETH PHY"); | 
|  | phy_soft_reset(phy); | 
|  |  | 
|  | /* Verify that the PHY device is responding */ | 
|  | phy_id = phy_sam_gmac_id_get(phy); | 
|  | if (phy_id == 0xFFFFFFFF) { | 
|  | SYS_LOG_ERR("Unable to detect a valid PHY"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | SYS_LOG_INF("PHYID: 0x%X at addr: %d", phy_id, phy->address); | 
|  |  | 
|  | mdio_bus_disable(gmac); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | u32_t phy_sam_gmac_id_get(const struct phy_sam_gmac_dev *phy) | 
|  | { | 
|  | Gmac *const gmac = phy->regs; | 
|  | u32_t phy_reg; | 
|  | u32_t phy_id; | 
|  |  | 
|  | mdio_bus_enable(gmac); | 
|  |  | 
|  | if (phy_read(phy, MII_PHYID1R, &phy_reg) < 0) { | 
|  | return 0xFFFFFFFF; | 
|  | } | 
|  |  | 
|  | phy_id = (phy_reg & 0xFFFF) << 16; | 
|  |  | 
|  | if (phy_read(phy, MII_PHYID2R, &phy_reg) < 0) { | 
|  | return 0xFFFFFFFF; | 
|  | } | 
|  |  | 
|  | phy_id |= (phy_reg & 0xFFFF); | 
|  |  | 
|  | mdio_bus_disable(gmac); | 
|  |  | 
|  | return phy_id; | 
|  | } | 
|  |  | 
|  | int phy_sam_gmac_auto_negotiate(const struct phy_sam_gmac_dev *phy, | 
|  | u32_t *status) | 
|  | { | 
|  | Gmac *const gmac = phy->regs; | 
|  | u32_t val; | 
|  | u32_t ability_adv; | 
|  | u32_t ability_rcvd; | 
|  | u32_t retries = PHY_AUTONEG_TIMEOUT_MS / 100; | 
|  | int retval; | 
|  |  | 
|  | mdio_bus_enable(gmac); | 
|  |  | 
|  | SYS_LOG_DBG("Starting ETH PHY auto-negotiate sequence"); | 
|  |  | 
|  | /* Read PHY default advertising parameters */ | 
|  | retval = phy_read(phy, MII_ANAR, &ability_adv); | 
|  | if (retval < 0) { | 
|  | goto auto_negotiate_exit; | 
|  | } | 
|  |  | 
|  | /* Configure and start auto-negotiation process */ | 
|  | retval = phy_read(phy, MII_BMCR, &val); | 
|  | if (retval < 0) { | 
|  | goto auto_negotiate_exit; | 
|  | } | 
|  |  | 
|  | val |= MII_BMCR_AUTONEG_ENABLE | MII_BMCR_AUTONEG_RESTART; | 
|  | val &= ~MII_BMCR_ISOLATE;  /* Don't isolate the PHY */ | 
|  |  | 
|  | retval = phy_write(phy, MII_BMCR, val); | 
|  | if (retval < 0) { | 
|  | goto auto_negotiate_exit; | 
|  | } | 
|  |  | 
|  | /* Wait for the auto-negotiation process to complete */ | 
|  | do { | 
|  | if (retries-- == 0) { | 
|  | retval = -ETIMEDOUT; | 
|  | goto auto_negotiate_exit; | 
|  | } | 
|  |  | 
|  | k_sleep(100); | 
|  |  | 
|  | retval = phy_read(phy, MII_BMSR, &val); | 
|  | if (retval < 0) { | 
|  | goto auto_negotiate_exit; | 
|  | } | 
|  | } while (!(val & MII_BMSR_AUTONEG_COMPLETE)); | 
|  |  | 
|  | SYS_LOG_DBG("PHY auto-negotiate sequence completed"); | 
|  |  | 
|  | /* Read abilities of the remote device */ | 
|  | retval = phy_read(phy, MII_ANLPAR, &ability_rcvd); | 
|  | if (retval < 0) { | 
|  | goto auto_negotiate_exit; | 
|  | } | 
|  |  | 
|  | /* Determine the best possible mode of operation */ | 
|  | if ((ability_adv & ability_rcvd) & MII_ADVERTISE_100_FULL) { | 
|  | *status = PHY_DUPLEX_FULL | PHY_SPEED_100M; | 
|  | } else if ((ability_adv & ability_rcvd) & MII_ADVERTISE_100_HALF) { | 
|  | *status = PHY_DUPLEX_HALF | PHY_SPEED_100M; | 
|  | } else if ((ability_adv & ability_rcvd) & MII_ADVERTISE_10_FULL) { | 
|  | *status = PHY_DUPLEX_FULL | PHY_SPEED_10M; | 
|  | } else { | 
|  | *status = PHY_DUPLEX_HALF | PHY_SPEED_10M; | 
|  | } | 
|  |  | 
|  | SYS_LOG_INF("common abilities: speed %s Mb, %s duplex", | 
|  | *status & PHY_SPEED_100M ? "100" : "10", | 
|  | *status & PHY_DUPLEX_FULL ? "full" : "half"); | 
|  |  | 
|  | auto_negotiate_exit: | 
|  | mdio_bus_disable(gmac); | 
|  | return retval; | 
|  | } |