[rtems commit] bsps/arm: Add PHY up/down

Sebastian Huber sebh at rtems.org
Tue Dec 18 10:18:05 UTC 2012


Module:    rtems
Branch:    master
Commit:    3f58ac7add7adef88729c214d60ee359f4825f61
Changeset: http://git.rtems.org/rtems/commit/?id=3f58ac7add7adef88729c214d60ee359f4825f61

Author:    Sebastian Huber <sebastian.huber at embedded-brains.de>
Date:      Mon Dec 17 15:41:53 2012 +0100

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.

---

 .../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 824d2a4..b545800 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:




More information about the vc mailing list