diff options
Diffstat (limited to 'c/src')
-rw-r--r-- | c/src/lib/libbsp/arm/shared/lpc/network/lpc-ethernet.c | 264 |
1 files changed, 211 insertions, 53 deletions
diff --git a/c/src/lib/libbsp/arm/shared/lpc/network/lpc-ethernet.c b/c/src/lib/libbsp/arm/shared/lpc/network/lpc-ethernet.c index 824d2a4fba..b545800137 100644 --- a/c/src/lib/libbsp/arm/shared/lpc/network/lpc-ethernet.c +++ b/c/src/lib/libbsp/arm/shared/lpc/network/lpc-ethernet.c @@ -333,6 +333,7 @@ typedef struct { unsigned transmit_no_descriptor_errors; unsigned transmit_overflow_errors; unsigned transmit_fatal_errors; + uint32_t phy_id; rtems_vector_number interrupt_number; rtems_id control_task; } lpc_eth_driver_entry; @@ -1163,6 +1164,148 @@ static int lpc_eth_mdio_write( return eno; } +static int lpc_eth_phy_get_id(uint32_t *id) +{ + uint32_t id1 = 0; + int eno = lpc_eth_mdio_read(DEFAULT_PHY, NULL, MII_PHYIDR1, &id1); + + if (eno == 0) { + uint32_t id2 = 0; + + eno = lpc_eth_mdio_read(DEFAULT_PHY, NULL, MII_PHYIDR2, &id2); + if (eno == 0) { + *id = (id1 << 16) | (id2 & 0xfff0); + } + } + + return eno; +} + +#define PHY_KSZ80X1RNL 0x221550 + +typedef struct { + unsigned reg; + uint32_t set; + uint32_t clear; +} lpc_eth_phy_action; + +static int lpc_eth_phy_set_and_clear( + const lpc_eth_phy_action *actions, + size_t n +) +{ + int eno = 0; + size_t i; + + for (i = 0; eno == 0 && i < n; ++i) { + const lpc_eth_phy_action *action = &actions [i]; + uint32_t val; + + eno = lpc_eth_mdio_read(DEFAULT_PHY, NULL, action->reg, &val); + if (eno == 0) { + val |= action->set; + val &= ~action->clear; + eno = lpc_eth_mdio_write(DEFAULT_PHY, NULL, action->reg, val); + } + } + + return eno; +} + +static const lpc_eth_phy_action lpc_eth_phy_up_action_default [] = { + { MII_BMCR, 0, BMCR_PDOWN }, + { MII_BMCR, BMCR_RESET, 0 }, + { MII_BMCR, BMCR_AUTOEN, 0 } +}; + +static const lpc_eth_phy_action lpc_eth_phy_up_pre_action_KSZ80X1RNL [] = { + /* Disable slow oscillator mode */ + { 0x11, 0, 0x10 } +}; + +static const lpc_eth_phy_action lpc_eth_phy_up_post_action_KSZ80X1RNL [] = { + /* Enable energy detect power down (EDPD) mode */ + { 0x18, 0x0800, 0 }, + /* Turn PLL of automatically in EDPD mode */ + { 0x10, 0x10, 0 } +}; + +static int lpc_eth_phy_up(lpc_eth_driver_entry *e) +{ + int eno = lpc_eth_phy_get_id(&e->phy_id); + + if (eno == 0) { + switch (e->phy_id) { + case PHY_KSZ80X1RNL: + eno = lpc_eth_phy_set_and_clear( + &lpc_eth_phy_up_pre_action_KSZ80X1RNL [0], + RTEMS_ARRAY_SIZE(lpc_eth_phy_up_pre_action_KSZ80X1RNL) + ); + break; + case 0: + case 0xfffffff0: + eno = EIO; + break; + default: + break; + } + + if (eno == 0) { + eno = lpc_eth_phy_set_and_clear( + &lpc_eth_phy_up_action_default [0], + RTEMS_ARRAY_SIZE(lpc_eth_phy_up_action_default) + ); + } + + if (eno == 0) { + switch (e->phy_id) { + case PHY_KSZ80X1RNL: + eno = lpc_eth_phy_set_and_clear( + &lpc_eth_phy_up_post_action_KSZ80X1RNL [0], + RTEMS_ARRAY_SIZE(lpc_eth_phy_up_post_action_KSZ80X1RNL) + ); + break; + default: + break; + } + } + } else { + e->phy_id = 0; + } + + return eno; +} + +static const lpc_eth_phy_action lpc_eth_phy_down_action_default [] = { + { MII_BMCR, BMCR_PDOWN, 0 } +}; + +static const lpc_eth_phy_action lpc_eth_phy_down_post_action_KSZ80X1RNL [] = { + /* Enable slow oscillator mode */ + { 0x11, 0x10, 0 } +}; + +static void lpc_eth_phy_down(lpc_eth_driver_entry *e) +{ + int eno = lpc_eth_phy_set_and_clear( + &lpc_eth_phy_down_action_default [0], + RTEMS_ARRAY_SIZE(lpc_eth_phy_down_action_default) + ); + + if (eno == 0) { + switch (e->phy_id) { + case PHY_KSZ80X1RNL: + eno = lpc_eth_phy_set_and_clear( + &lpc_eth_phy_down_post_action_KSZ80X1RNL [0], + RTEMS_ARRAY_SIZE(lpc_eth_phy_down_post_action_KSZ80X1RNL) + ); + break; + default: + break; + } + } +} + static void lpc_eth_soft_reset(void) { lpc_eth->command = 0x38; @@ -1170,69 +1313,81 @@ static void lpc_eth_soft_reset(void) lpc_eth->mac1 = 0x0; } -static void lpc_eth_up_or_down(lpc_eth_driver_entry *e, bool up) +static int lpc_eth_up_or_down(lpc_eth_driver_entry *e, bool up) { + int eno = 0; rtems_status_code sc = RTEMS_SUCCESSFUL; struct ifnet *ifp = &e->arpcom.ac_if; if (up && e->state == LPC_ETH_STATE_DOWN) { - lpc_eth_config_module_enable(); - lpc_eth_soft_reset(); + lpc_eth_config_module_enable(); /* Initialize PHY */ lpc_eth->mcfg = ETH_MCFG_CLOCK_SELECT(0x7); - /* TODO */ - - /* Reinitialize registers */ - lpc_eth->mac2 = 0x31; - lpc_eth->ipgt = 0x15; - lpc_eth->ipgr = 0x12; - lpc_eth->clrt = 0x370f; - lpc_eth->maxf = 0x0600; - lpc_eth->supp = ETH_SUPP_SPEED; - lpc_eth->test = 0; - #ifdef LPC_ETH_CONFIG_RMII - lpc_eth->command = 0x0600; - #else - lpc_eth->command = 0x0400; - #endif - lpc_eth->intenable = ETH_INT_RX_OVERRUN | ETH_INT_TX_UNDERRUN; - lpc_eth->intclear = 0x30ff; - lpc_eth->powerdown = 0; - - /* MAC address */ - lpc_eth->sa0 = ((uint32_t) e->arpcom.ac_enaddr [5] << 8) - | (uint32_t) e->arpcom.ac_enaddr [4]; - lpc_eth->sa1 = ((uint32_t) e->arpcom.ac_enaddr [3] << 8) - | (uint32_t) e->arpcom.ac_enaddr [2]; - lpc_eth->sa2 = ((uint32_t) e->arpcom.ac_enaddr [1] << 8) - | (uint32_t) e->arpcom.ac_enaddr [0]; - - lpc_eth_enable_promiscous_mode((ifp->if_flags & IFF_PROMISC) != 0); - - /* Enable receiver */ - lpc_eth->mac1 = 0x03; - - /* Initialize tasks */ - lpc_eth_control_request(e, e->receive_task, LPC_ETH_EVENT_INITIALIZE); - lpc_eth_control_request(e, e->transmit_task, LPC_ETH_EVENT_INITIALIZE); - - /* Install interrupt handler */ - sc = rtems_interrupt_handler_install( - e->interrupt_number, - "Ethernet", - RTEMS_INTERRUPT_UNIQUE, - lpc_eth_interrupt_handler, - e - ); - assert(sc == RTEMS_SUCCESSFUL); + eno = lpc_eth_phy_up(e); + + if (eno == 0) { + /* + * We must have a valid external clock from the PHY at this point, + * otherwise the system bus hangs and only a watchdog reset helps. + */ + lpc_eth_soft_reset(); + + /* Reinitialize registers */ + lpc_eth->mac2 = 0x31; + lpc_eth->ipgt = 0x15; + lpc_eth->ipgr = 0x12; + lpc_eth->clrt = 0x370f; + lpc_eth->maxf = 0x0600; + lpc_eth->supp = ETH_SUPP_SPEED; + lpc_eth->test = 0; + #ifdef LPC_ETH_CONFIG_RMII + lpc_eth->command = 0x0600; + #else + lpc_eth->command = 0x0400; + #endif + lpc_eth->intenable = ETH_INT_RX_OVERRUN | ETH_INT_TX_UNDERRUN; + lpc_eth->intclear = 0x30ff; + lpc_eth->powerdown = 0; - /* Start watchdog timer */ - ifp->if_timer = 1; + /* MAC address */ + lpc_eth->sa0 = ((uint32_t) e->arpcom.ac_enaddr [5] << 8) + | (uint32_t) e->arpcom.ac_enaddr [4]; + lpc_eth->sa1 = ((uint32_t) e->arpcom.ac_enaddr [3] << 8) + | (uint32_t) e->arpcom.ac_enaddr [2]; + lpc_eth->sa2 = ((uint32_t) e->arpcom.ac_enaddr [1] << 8) + | (uint32_t) e->arpcom.ac_enaddr [0]; - /* Change state */ - e->state = LPC_ETH_STATE_UP; + lpc_eth_enable_promiscous_mode((ifp->if_flags & IFF_PROMISC) != 0); + + /* Enable receiver */ + lpc_eth->mac1 = 0x03; + + /* Initialize tasks */ + lpc_eth_control_request(e, e->receive_task, LPC_ETH_EVENT_INITIALIZE); + lpc_eth_control_request(e, e->transmit_task, LPC_ETH_EVENT_INITIALIZE); + + /* Install interrupt handler */ + sc = rtems_interrupt_handler_install( + e->interrupt_number, + "Ethernet", + RTEMS_INTERRUPT_UNIQUE, + lpc_eth_interrupt_handler, + e + ); + assert(sc == RTEMS_SUCCESSFUL); + + /* Start watchdog timer */ + ifp->if_timer = 1; + + /* Change state */ + e->state = LPC_ETH_STATE_UP; + } + + if (eno != 0) { + ifp->if_flags &= ~IFF_UP; + } } else if (!up && e->state == LPC_ETH_STATE_UP) { /* Remove interrupt handler */ sc = rtems_interrupt_handler_remove( @@ -1247,6 +1402,7 @@ static void lpc_eth_up_or_down(lpc_eth_driver_entry *e, bool up) lpc_eth_control_request(e, e->transmit_task, LPC_ETH_EVENT_STOP); lpc_eth_soft_reset(); + lpc_eth_phy_down(e); lpc_eth_config_module_disable(); /* Stop watchdog timer */ @@ -1255,6 +1411,8 @@ static void lpc_eth_up_or_down(lpc_eth_driver_entry *e, bool up) /* Change state */ e->state = LPC_ETH_STATE_DOWN; } + + return eno; } static void lpc_eth_interface_init(void *arg) @@ -1374,7 +1532,7 @@ static int lpc_eth_interface_ioctl( ether_ioctl(ifp, cmd, data); break; case SIOCSIFFLAGS: - lpc_eth_up_or_down(e, (ifp->if_flags & IFF_UP) != 0); + eno = lpc_eth_up_or_down(e, (ifp->if_flags & IFF_UP) != 0); break; case SIOCADDMULTI: case SIOCDELMULTI: |