zephyr/drivers/ethernet/phy_gecko.c
Luuk Bosma 63b22d7015 drivers/ethernet/eth_gecko: auto-negotiate after link up
Move auto-negotiate sequence from driver initialization to link up event
Previously when booting without ethernet cable connected the
initialization would fail and never recover.
Now we can connect the ethernet cable any time and multiple times.

This also drastically reduces boot time to main.

Logging Link up and Link down events.
Logging speed and duplex from eth_gecko logger instead of eth_gecko_phy.

Signed-off-by: Luuk Bosma <l.bosma@interay.com>
2020-03-10 14:08:51 +02:00

285 lines
6.1 KiB
C

/*
* Copyright (c) 2019 Interay Solutions B.V.
* Copyright (c) 2019 Oane Kingma
*
* SPDX-License-Identifier: Apache-2.0
*/
/* SiLabs Giant Gecko GG11 Ethernet PHY driver. */
#include <errno.h>
#include <kernel.h>
#include <net/mii.h>
#include "phy_gecko.h"
#include <logging/log.h>
LOG_MODULE_REGISTER(eth_gecko_phy, CONFIG_ETHERNET_LOG_LEVEL);
/* 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(ETH_TypeDef *eth)
{
eth->NETWORKCTRL |= ETH_NETWORKCTRL_MANPORTEN;
}
/* Enable MDIO serial bus between MAC and PHY. */
static void mdio_bus_disable(ETH_TypeDef *eth)
{
eth->NETWORKCTRL &= ~ETH_NETWORKCTRL_MANPORTEN;
}
/* Wait PHY operation complete. */
static int mdio_bus_wait(ETH_TypeDef *eth)
{
u32_t retries = 100U; /* will wait up to 1 s */
while (!(eth->NETWORKSTATUS & ETH_NETWORKSTATUS_MANDONE)) {
if (retries-- == 0U) {
LOG_ERR("timeout");
return -ETIMEDOUT;
}
k_sleep(10);
}
return 0;
}
/* Send command to PHY over MDIO serial bus */
static int mdio_bus_send(ETH_TypeDef *eth, u8_t phy_addr, u8_t reg_addr,
u8_t rw, u16_t data)
{
int retval;
/* Write PHY management register */
eth->PHYMNGMNT = ETH_PHYMNGMNT_WRITE0_DEFAULT
| ETH_PHYMNGMNT_WRITE1
| ((rw ? 0x02 : 0x01) << _ETH_PHYMNGMNT_OPERATION_SHIFT)
| ((phy_addr << _ETH_PHYMNGMNT_PHYADDR_SHIFT)
& _ETH_PHYMNGMNT_PHYADDR_MASK)
| ((reg_addr << _ETH_PHYMNGMNT_REGADDR_SHIFT)
& _ETH_PHYMNGMNT_REGADDR_MASK)
| (0x2 << _ETH_PHYMNGMNT_WRITE10_SHIFT)
| (data & _ETH_PHYMNGMNT_PHYRWDATA_MASK);
/* Wait until PHY is ready */
retval = mdio_bus_wait(eth);
if (retval < 0) {
return retval;
}
return 0;
}
/* Read PHY register. */
static int phy_read(const struct phy_gecko_dev *phy, u8_t reg_addr,
u32_t *value)
{
ETH_TypeDef *const eth = phy->regs;
u8_t phy_addr = phy->address;
int retval;
retval = mdio_bus_send(eth, phy_addr, reg_addr, 1, 0);
if (retval < 0) {
return retval;
}
/* Read data */
*value = eth->PHYMNGMNT & _ETH_PHYMNGMNT_PHYRWDATA_MASK;
return 0;
}
/* Write PHY register. */
static int phy_write(const struct phy_gecko_dev *phy, u8_t reg_addr,
u32_t value)
{
ETH_TypeDef *const eth = phy->regs;
u8_t phy_addr = phy->address;
return mdio_bus_send(eth, phy_addr, reg_addr, 0, value);
}
/* Issue a PHY soft reset. */
static int phy_soft_reset(const struct phy_gecko_dev *phy)
{
u32_t phy_reg;
u32_t retries = 12U;
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-- == 0U) {
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_gecko_init(const struct phy_gecko_dev *phy)
{
ETH_TypeDef *const eth = phy->regs;
int phy_id;
mdio_bus_enable(eth);
LOG_INF("Soft Reset of ETH PHY");
phy_soft_reset(phy);
/* Verify that the PHY device is responding */
phy_id = phy_gecko_id_get(phy);
if (phy_id == 0xFFFFFFFF) {
LOG_ERR("Unable to detect a valid PHY");
return -1;
}
LOG_INF("PHYID: 0x%X at addr: %d", phy_id, phy->address);
mdio_bus_disable(eth);
return 0;
}
u32_t phy_gecko_id_get(const struct phy_gecko_dev *phy)
{
ETH_TypeDef *const eth = phy->regs;
u32_t phy_reg;
u32_t phy_id;
mdio_bus_enable(eth);
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(eth);
return phy_id;
}
int phy_gecko_auto_negotiate(const struct phy_gecko_dev *phy,
u32_t *status)
{
ETH_TypeDef *const eth = 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(eth);
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-- == 0U) {
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));
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 = ETH_NETWORKCFG_FULLDUPLEX | ETH_NETWORKCFG_SPEED;
} else if ((ability_adv & ability_rcvd) & MII_ADVERTISE_100_HALF) {
*status = ETH_NETWORKCFG_SPEED;
} else if ((ability_adv & ability_rcvd) & MII_ADVERTISE_10_FULL) {
*status = ETH_NETWORKCFG_FULLDUPLEX;
} else {
*status = 0;
}
LOG_DBG("common abilities: speed %s Mb, %s duplex",
*status & ETH_NETWORKCFG_SPEED ? "100" : "10",
*status & ETH_NETWORKCFG_FULLDUPLEX ? "full" : "half");
auto_negotiate_exit:
mdio_bus_disable(eth);
return retval;
}
bool phy_gecko_is_linked(const struct phy_gecko_dev *phy)
{
ETH_TypeDef *const eth = phy->regs;
u32_t phy_reg;
bool phy_linked = false;
mdio_bus_enable(eth);
if (phy_read(phy, MII_BMSR, &phy_reg) < 0) {
return phy_linked;
}
phy_linked = (phy_reg & MII_BMSR_LINK_STATUS);
mdio_bus_disable(eth);
return phy_linked;
}