summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSebastian Huber <sebastian.huber@embedded-brains.de>2012-12-17 15:41:53 +0100
committerSebastian Huber <sebastian.huber@embedded-brains.de>2012-12-18 11:22:19 +0100
commit3f58ac7add7adef88729c214d60ee359f4825f61 (patch)
tree2dbd9f55ccd1eab1a45622ca3a0816d600db6dd4
parentbsps/arm: Add PHY timeout detection (diff)
downloadrtems-3f58ac7add7adef88729c214d60ee359f4825f61.tar.bz2
bsps/arm: Add PHY up/down
All PHYs will be set to power up/down state with the BMCR[BMCR_PDOWN] setting. The KSZ80X1RNL will use the energy detect power down (EDPD) mode with PLL automatically turned in the up state. It will use the slow oscillator mode in the down state. To prevent system bus lock-ups the PHY is set to the up state before Ethernet module initialization. In case of a communication failure with the PHY or weird PHY identifiers the initialization will be aborted.
-rw-r--r--c/src/lib/libbsp/arm/shared/lpc/network/lpc-ethernet.c264
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: