#include "phy_common.h" #include #include #include "../mac_drv.h" #include // ------------------------- static uint32_t phyAddr = EPHY_MDIO_BAD_ADDR; // PHY MDIO address static ETH_TypeDef *phyEth = NULL; // corresponding Ethernet peripheral static struct { uint32_t OUI; // Organizationally Unique Identifier uint8_t model; // Vendor Model Number uint8_t revision; // Model Revision Number } phyId = {0}; static const char *phyName = NULL; // printable PHY name typedef void (*phyIntSetupFn)(); // typedef for PHY interrupt setup function typedef int (*phyIntHandlerFn)(); // typedef for PHY interrupt handling typedef void (*phyFetchLinkStatus)(PHY_LinkStatus *ls); // typedef for link status fetching static phyIntSetupFn phyIntSetupCb = NULL; // PHY interrupt setup callback static phyIntHandlerFn phyIntHandlerCb = NULL; // PHY interrupt handler callback static phyFetchLinkStatus phyFetchLinkStatusCb = NULL; // fetch link status callback static uint32_t macModeInit = MODEINIT_FULL_DUPLEX | MODEINIT_SPEED_100MBPS; // default mode: 100Mbps FD // ------------------------- static PHY_LinkStatus linkStatus = {0}; // ------------------------- static uint32_t WRITE(uint16_t r, uint32_t v) { return ETHHW_WritePHYRegister(phyEth, phyAddr, (r), (v)); } static uint32_t __READ(uint16_t r, uint32_t *v) { return ETHHW_ReadPHYRegister(phyEth, phyAddr, (r), v); } #define READ(r, v) __READ((r), &(v)) #define READ_MODIFY_WRITE(r, v) \ { \ uint32_t regVal; \ READ(r, regVal); \ regVal |= (v); \ WRITE(r, regVal); \ } // ------------------------- /** * Sweep and poll MDIO addresses, return with the first one with non-zero * response. * @param addr0 starting address * @return address of connected PHY or EPHY_MDIO_BAD_ADDR */ static int phy_sweep_addresses(uint32_t addr0) { for (uint32_t addr = addr0; addr <= EPHY_MDIO_MAX_ADDR; addr++) { uint32_t regVal = 0; ETHHW_ReadPHYRegister( phyEth, addr, EPHY_BCR, ®Val); // read the BCR, since it certainly will contain zero elements if (regVal != 0xFFFF) { return addr; } } return EPHY_MDIO_BAD_ADDR; } // -------------------------- static void phy_setup_int_DP83848() { // clear possible interrupts uint32_t regVal; READ(EPHY_DP83848_MISR, regVal); // enable relevant interrupt sources regVal = EPHY_DP83848_MISR_LINK_INT_EN | EPHY_DP83848_MISR_ANC_INT_EN; WRITE(EPHY_DP83848_MISR, regVal); // global enable interrupts regVal = EPHY_DP83848_MICR_INT_OE | EPHY_DP83848_MICR_INTEN; READ(EPHY_DP83848_MICR, regVal); } static void phy_fetch_link_status_DP83848(PHY_LinkStatus *ls) { // read and clear interrupt status uint32_t regVal; READ(EPHY_DP83848_PHYSTS, regVal); // fetch link status flags ls->speed = (regVal & EPHY_DP83848_SPEED_STATUS) ? PHY_LS_10Mbps : PHY_LS_100Mbps; ls->type = (regVal & EPHY_DP83848_DUPLEX_STATUS) ? PHY_LT_FULL_DUPLEX : PHY_LT_HALF_DUPLEX; // fetch link state READ(EPHY_BSR, regVal); ls->up = (regVal & EPHY_BSR_LINK_STATUS); } static int phy_int_handler_DP83848() { // read and clear interrupt status uint32_t regVal; ETHHW_ReadPHYRegister(phyEth, phyAddr, EPHY_DP83848_MISR, ®Val); // decide which event has been fired if (regVal & EPHY_DP83848_MISR_ANC_INT) { linkStatus.up = true; phy_fetch_link_status_DP83848(&linkStatus); return PHYEVENT_AUTONEGOTIATION_DONE; } else if (regVal & EPHY_DP83848_MISR_LINK_INT) { linkStatus.up = false; return PHYEVENT_LINK_DOWN; } return PHYEVENT_NO_EVENT; } static void phy_setup_int_LAN8720A() { // force 10Mbps full duplex mode /* uint32_t regVal = 0x2100; ETHHW_WritePHYRegister(phyEth, phyAddr, EPHY_BCR, regVal); */ // enable relevant interrupt sources uint32_t regVal = EPHY_LAN8720A_IMR_ANC_INT_EN | EPHY_LAN8720A_IMR_ENERGYON_INT_EN; WRITE(EPHY_LAN8720A_IMR, regVal); } static void phy_fetch_link_status_LAN8720A(PHY_LinkStatus *ls) { // read register containing link state uint32_t regVal; READ(EPHY_LAN8720A_SCSR, regVal); // fetch link status flags ls->speed = (regVal & EPHY_LAN8720A_SCSR_100MBPS) ? PHY_LS_100Mbps : PHY_LS_10Mbps; ls->type = (regVal & EPHY_LAN8720A_SCSR_FULL_DUPLEX) ? PHY_LT_FULL_DUPLEX : PHY_LT_HALF_DUPLEX; // fetch link state READ(EPHY_BSR, regVal); ls->up = (regVal & EPHY_BSR_LINK_STATUS); } static int phy_int_handler_LAN8720A() { // read and clear possible interrupts uint32_t regVal; ETHHW_ReadPHYRegister(phyEth, phyAddr, EPHY_LAN8720A_ISFR, ®Val); // decide which event has been fired if (regVal & EPHY_LAN8720A_ISFR_ANC_INT) { linkStatus.up = true; phy_fetch_link_status_LAN8720A(&linkStatus); return PHYEVENT_AUTONEGOTIATION_DONE; } else if (regVal & EPHY_LAN8720A_ISFR_ENERGYON_INT) { ETHHW_ReadPHYRegister(phyEth, phyAddr, EPHY_BSR, ®Val); if (!(regVal & EPHY_BSR_LINK_STATUS)) { linkStatus.up = false; return PHYEVENT_LINK_DOWN; } else { return PHYEVENT_NO_EVENT; } } return PHYEVENT_NO_EVENT; } static void phy_setup_int_RTL8201F() { // switch to Page 7 WRITE(EPHY_RTL8201F_PSR, 0x07); // configure RMII mode and configure TXC as clock input instead of the default // output mode WRITE(EPHY_RTL8201F_RMSR, 0x7FFB); // magic constant from the datasheet // enable Link Status Change interrupt uint32_t regVal; READ(EPHY_RTL8201F_INT_WOL_LED_REG, regVal); regVal |= EPHY_RTL8201F_INT_WOL_LED_REG_LINKCHG; WRITE(EPHY_RTL8201F_INT_WOL_LED_REG, regVal); // switch to Page 0 WRITE(EPHY_RTL8201F_PSR, 0x00); return; } static void phy_fetch_link_status_RTL8201F(PHY_LinkStatus *ls) { // read register containing link state uint32_t regVal; READ(EPHY_BCR, regVal); // set link status flags ls->speed = (regVal & EPHY_BCR_SPEED_SELECTION) ? PHY_LS_100Mbps : PHY_LS_10Mbps; ls->type = (regVal & EPHY_BCR_DUPLEX_MODE) ? PHY_LT_FULL_DUPLEX : PHY_LT_HALF_DUPLEX; // fetch link state READ(EPHY_BSR, regVal); ls->up = (regVal & EPHY_BSR_LINK_STATUS); return; } static int phy_int_handler_RTL8201F() { // read and clear possible interrupts uint32_t regVal; READ(EPHY_RTL8201F_IIR_SNR, regVal); if (!(regVal & EPHY_RTL8201F_IIR_SNR_LINKSTATUSCHG)) { return PHYEVENT_NO_EVENT; } // read link status twice (read datasheet why) READ(EPHY_BSR, regVal); READ(EPHY_BSR, regVal); // decide which event has been fired if (regVal & EPHY_BSR_LINK_STATUS) { linkStatus.up = true; phy_fetch_link_status_RTL8201F(&linkStatus); return PHYEVENT_AUTONEGOTIATION_DONE; } else { linkStatus.up = false; return PHYEVENT_LINK_DOWN; } } static void phy_setup_int_DP83TC813() { // read and clear possible interrupts uint32_t regVal; // ETHHW_ReadPHYRegister(phyEth, phyAddr, EPHY_DP83TC813_MISR1, ®Val); // enable link status interrupt regVal = EPHY_DP83TC813_MISR1_LINK_INT_EN; WRITE(EPHY_DP83TC813_MISR1, regVal); // turn on master mode // WRITE(EPHY_DP83TC813_REGCR, 0x01); // WRITE(EPHY_DP83TC813_ADDAR, EPHY_DP83TC813_MMD1_PMA_CTRL_2); // WRITE(EPHY_DP83TC813_REGCR, 0x4001); // WRITE(EPHY_DP83TC813_ADDAR, EPHY_DP83TC813_MMD1_PMA_CTRL_2_MS_CFG); return; } static void phy_fetch_link_status_DP83TC813(PHY_LinkStatus *ls) { // read and clear interrupt status uint32_t regVal; READ(EPHY_DP83TC813_MISR1, regVal); // read basic control register READ(EPHY_BCR, regVal); // set link status flags ls->speed = PHY_LS_100Mbps; // this PHY is only capable of 100Mbps ls->type = (regVal & EPHY_BCR_DUPLEX_MODE) ? PHY_LT_FULL_DUPLEX : PHY_LT_HALF_DUPLEX; // fetch link state READ(EPHY_BSR, regVal); ls->up = (regVal & EPHY_BSR_LINK_STATUS); return; } static int phy_int_handler_DP83TC813() { // read and clear interrupt status uint32_t regVal; ETHHW_ReadPHYRegister(phyEth, phyAddr, EPHY_DP83TC813_MISR1, ®Val); // if link has changed, then if (regVal & EPHY_DP83TC813_MISR1_LINK_INT) { // get actual link state ETHHW_ReadPHYRegister(phyEth, phyAddr, EPHY_BSR, ®Val); if (regVal & EPHY_BSR_LINK_STATUS) { linkStatus.up = true; phy_fetch_link_status_DP83TC813(&linkStatus); return PHYEVENT_AUTONEGOTIATION_DONE; } else { linkStatus.up = false; return PHYEVENT_LINK_DOWN; } } return PHYEVENT_NO_EVENT; } static void phy_setup_int_LAN8670() { // PHY cannot signal link change return; } static void phy_fetch_link_status_LAN8670(PHY_LinkStatus *ls) { // link is always 10Mbps HALF duplex ls->up = true; ls->speed = PHY_LS_10Mbps; ls->type = PHY_LT_HALF_DUPLEX; return; } static int phy_int_handler_LAN8670() { // since the PHY cannot signal link change, we should consider the link is up linkStatus.up = true; phy_fetch_link_status_LAN8670(&linkStatus); return PHYEVENT_AUTONEGOTIATION_DONE; } static void phy_setup_int_DP83TD510E() { // configure LEDs to operate in active high mode WRITE(EPHY_DP83TD510E_REGCR, 0x1F); WRITE(EPHY_DP83TD510E_ADDAR, EPHY_DP83TD510E_LEDS_CFG2); WRITE(EPHY_DP83TD510E_REGCR, 0x4001); WRITE(EPHY_DP83TD510E_ADDAR, EPHY_DP83TD510E_LEDS_CFG2_LED_1_POLARITY | EPHY_DP83TD510E_LEDS_CFG2_LED_0_POLARITY); // enable Link Change Interrupt WRITE(EPHY_DP83TD510E_INTERRUPT_REG_1, EPHY_DP83TD510E_INTERRUPT_REG_1_LINK_INT_EN); // select INT functionality on PWDN pin and globally enable interrupts READ_MODIFY_WRITE(EPHY_DP83TD510E_GEN_CFG, EPHY_DP83TD510E_GEN_CFG_INT_EN | EPHY_DP83TD510E_GEN_CFG_INT_OE); } static void phy_fetch_link_status_DP83TD510E(PHY_LinkStatus *ls) { // read basic control register uint32_t regVal; ETHHW_ReadPHYRegister(phyEth, phyAddr, EPHY_BCR, ®Val); // link is always 10Mbps HALF duplex ls->speed = PHY_LS_10Mbps; ls->type = (regVal & EPHY_BCR_DUPLEX_MODE) ? PHY_LT_FULL_DUPLEX : PHY_LT_HALF_DUPLEX; // fetch link state READ(EPHY_BSR, regVal); ls->up = (regVal & EPHY_BSR_LINK_STATUS); return; } static int phy_int_handler_DP83TD510E() { // read interrupt status uint32_t regVal; READ(EPHY_DP83TD510E_INTERRUPT_REG_1, regVal); // clear interrupt WRITE(EPHY_DP83TD510E_PHYSTS, 0x0000); // link has changed if (regVal & EPHY_DP83TD510E_INTERRUPT_REG_1_LINK_INT) { READ(EPHY_DP83TD510E_PHYSTS, regVal); if (regVal & EPHY_DP83TD510E_PHYSTS_LINK_STATUS) { linkStatus.up = true; phy_fetch_link_status_DP83TD510E(&linkStatus); return PHYEVENT_AUTONEGOTIATION_DONE; } else { linkStatus.up = false; return PHYEVENT_LINK_DOWN; } } return PHYEVENT_NO_EVENT; } // -------------------------- static bool phy_populate_handlers() { switch (phyId.OUI) { case EPHY_OUI_TEXAS_INSTRUMENTS: // Texas Instruments PHYs case EPHY_OUI_TEXAS_INSTRUMENTS_2: case EPHY_OUI_TEXAS_INSTRUMENTS_3: switch (phyId.model) { case EPHY_MODEL_DP83848: phyName = "Texas Instruments DP83848"; phyFetchLinkStatusCb = phy_fetch_link_status_DP83848; phyIntSetupCb = phy_setup_int_DP83848; phyIntHandlerCb = phy_int_handler_DP83848; break; case EPHY_MODEL_DP83TC813: phyName = "Texas Instruments DP83TC813"; phyFetchLinkStatusCb = phy_fetch_link_status_DP83TC813; phyIntSetupCb = phy_setup_int_DP83TC813; phyIntHandlerCb = phy_int_handler_DP83TC813; break; case EPHY_MODEL_DP83TD510E: phyName = "Texas Instruments DP83TD510E"; phyFetchLinkStatusCb = phy_fetch_link_status_DP83TD510E; phyIntSetupCb = phy_setup_int_DP83TD510E; phyIntHandlerCb = phy_int_handler_DP83TD510E; macModeInit = MODEINIT_HALF_DUPLEX | MODEINIT_SPEED_10MBPS; default: break; } break; case EPHY_OUI_SMSC: // SMSC PHYs switch (phyId.model) { case EPHY_MODEL_LAN8720A: case EPHY_MODEL_LAN8742A: phyName = (phyId.model == EPHY_MODEL_LAN8720A) ? "SMSC LAN8720A" : "SMSC LAN8742A"; phyFetchLinkStatusCb = phy_fetch_link_status_LAN8720A; phyIntSetupCb = phy_setup_int_LAN8720A; phyIntHandlerCb = phy_int_handler_LAN8720A; break; case EPHY_MODEL_LAN8670: phyName = "Microchip LAN8670"; phyFetchLinkStatusCb = phy_fetch_link_status_LAN8670; phyIntSetupCb = phy_setup_int_LAN8670; phyIntHandlerCb = phy_int_handler_LAN8670; macModeInit = MODEINIT_HALF_DUPLEX | MODEINIT_SPEED_10MBPS; break; default: break; } break; case EPHY_OUI_REALTEK: switch (phyId.model) { case EPHY_MODEL_RTL8201F: phyName = "Realtek RTL8201F"; phyFetchLinkStatusCb = phy_fetch_link_status_RTL8201F; phyIntSetupCb = phy_setup_int_RTL8201F; phyIntHandlerCb = phy_int_handler_RTL8201F; break; default: break; } break; default: phyName = "\0"; break; } return phyName[0] != '\0'; } // ---------------- static void phy_soft_reset() { WRITE(EPHY_BCR, EPHY_BCR_RESET); uint32_t r; do { osDelay(5); READ(EPHY_BCR, r); } while (r & EPHY_BCR_RESET); } uint32_t ETHHW_setupPHY(ETH_TypeDef *eth) { phyEth = eth; // store pointer to ETH hardware phyAddr = phy_sweep_addresses(0); if (phyAddr == EPHY_MDIO_BAD_ADDR) { // no PHY detected MSG(ANSI_COLOR_BRED "No Ethernet PHY detected!\n" ANSI_COLOR_RESET); return MODEINIT_FULL_DUPLEX | MODEINIT_SPEED_100MBPS; } // issue a software reset phy_soft_reset(); // read the PHYID registers uint32_t id1 = 0x00, id2 = 0x00; READ(EPHY_ID1, id1); READ(EPHY_ID2, id2); // WRITE(0x006C, ((uint16_t)0b100) << 12); // construct IDs phyId.OUI = (id2 >> 10) | (id1 << 6); phyId.model = (id2 >> 4) & (0x3F); phyId.revision = id2 & 0x0F; // populate management functions based on PHYID if (phy_populate_handlers()) { phyIntSetupCb(); // invoke management function enabling PHY interrupts } // print PHY name phy_print_full_name(); // return with mode initialization settings return macModeInit; } // ------------------- PHY_Event phy_get_event() { if (phyIntHandlerCb != NULL) { return phyIntHandlerCb(); } else { return PHYEVENT_NO_EVENT; } } void phy_print_full_name() { if (phyName[0] != '\0') { MSG("PHY: " ANSI_COLOR_BYELLOW "%s" ANSI_COLOR_RESET " (rev. %d) @" ANSI_COLOR_CYAN "%d\n" ANSI_COLOR_RESET, phyName, phyId.revision, phyAddr); } else { MSG("PHY: " ANSI_COLOR_BYELLOW "UNKOWN, OUI: %X, MODEL: %u" ANSI_COLOR_RESET " (rev. %d) @" ANSI_COLOR_CYAN "%d\n" ANSI_COLOR_RESET, phyId.OUI, phyId.model, phyId.revision, phyAddr); } } int phy_get_link_state() { uint32_t regVal; READ(EPHY_BSR, regVal); return (regVal & EPHY_BSR_AUTONEGOTIATION_COMPLETE) && (regVal & EPHY_BSR_LINK_STATUS); } const PHY_LinkStatus *phy_get_link_status() { phyFetchLinkStatusCb(&linkStatus); return &linkStatus; } void phy_refresh_link_status() { if (phyIntHandlerCb != NULL) { phyIntHandlerCb(); } } static void phy_read_range(uint32_t firstAddr, uint16_t len) { for (uint32_t a = firstAddr; a < (firstAddr + len); a += 2) { uint32_t regVal; READ(a, regVal); if (regVal != 0) { MSG(ANSI_COLOR_BYELLOW "%04X" ANSI_COLOR_RESET ": " ANSI_COLOR_CYAN "%04X\n" ANSI_COLOR_RESET, a, regVal); } } } void phy_read_all_regs() { phy_read_range(0, EPHY_MDIO_MAX_ADDR); } bool phy_read_reg(uint32_t addr, uint32_t *dst) { return READ(addr, *dst); } bool phy_write_reg(uint32_t addr, uint32_t data) { return WRITE(addr, data); }