Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cpu/sam0_eth: interrupt based link detection/auto-negotiation #19703

Merged
merged 5 commits into from
Jun 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cpu/sam0_common/Makefile.dep
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ ifneq (,$(filter sam0_eth,$(USEMODULE)))
USEMODULE += netdev_legacy_api
USEMODULE += netopt
FEATURES_REQUIRED += periph_eth
FEATURES_REQUIRED += periph_gpio_irq
endif
include $(RIOTCPU)/cortexm_common/Makefile.dep
62 changes: 36 additions & 26 deletions cpu/sam0_common/periph/eth.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
*/

#include "iolist.h"
#include "mii.h"
#include "net/eui48.h"
#include "net/ethernet.h"
#include "net/netdev/eth.h"
Expand Down Expand Up @@ -120,6 +121,36 @@ static void _disable_clock(void)
MCLK->APBCMASK.reg &= ~MCLK_APBCMASK_GMAC;
}

unsigned sam0_read_phy(uint8_t phy, uint8_t addr)
{
if (_is_sleeping) {
return 0;
}

GMAC->MAN.reg = GMAC_MAN_REGA(addr) | GMAC_MAN_PHYA(phy)
| GMAC_MAN_CLTTO | GMAC_MAN_WTN(0x2)
| GMAC_MAN_OP(PHY_READ_OP);

/* Wait for operation completion */
while (!GMAC->NSR.bit.IDLE) {}
/* return content of shift register */
return (GMAC->MAN.reg & GMAC_MAN_DATA_Msk);
}

void sam0_write_phy(uint8_t phy, uint8_t addr, uint16_t data)
{
if (_is_sleeping) {
return;
}

GMAC->MAN.reg = GMAC_MAN_REGA(addr) | GMAC_MAN_PHYA(phy)
| GMAC_MAN_WTN(0x2) | GMAC_MAN_OP(PHY_WRITE_OP)
| GMAC_MAN_CLTTO | GMAC_MAN_DATA(data);

/* Wait for operation completion */
while (!GMAC->NSR.bit.IDLE) {}
}

void sam0_eth_poweron(void)
{
_enable_clock();
Expand All @@ -128,6 +159,8 @@ void sam0_eth_poweron(void)
/* enable PHY */
gpio_set(sam_gmac_config[0].rst_pin);
_is_sleeping = false;

while (MII_BMCR_RESET & sam0_read_phy(0, MII_BMCR)) {}
}

void sam0_eth_poweroff(void)
Expand Down Expand Up @@ -165,28 +198,6 @@ static void _init_desc_buf(void)
GMAC->TBQB.reg = (uint32_t) tx_desc;
}

unsigned sam0_read_phy(uint8_t phy, uint8_t addr)
{
GMAC->MAN.reg = GMAC_MAN_REGA(addr) | GMAC_MAN_PHYA(phy)
| GMAC_MAN_CLTTO | GMAC_MAN_WTN(0x2)
| GMAC_MAN_OP(PHY_READ_OP);

/* Wait for operation completion */
while (!GMAC->NSR.bit.IDLE) {}
/* return content of shift register */
return (GMAC->MAN.reg & GMAC_MAN_DATA_Msk);
}

void sam0_write_phy(uint8_t phy, uint8_t addr, uint16_t data)
{
GMAC->MAN.reg = GMAC_MAN_REGA(addr) | GMAC_MAN_PHYA(phy)
| GMAC_MAN_WTN(0x2) | GMAC_MAN_OP(PHY_WRITE_OP)
| GMAC_MAN_CLTTO | GMAC_MAN_DATA(data);

/* Wait for operation completion */
while (!GMAC->NSR.bit.IDLE) {}
}

void sam0_eth_set_mac(const eui48_t *mac)
{
GMAC->Sa[0].SAT.reg = ((mac->uint8[5] << 8) | mac->uint8[4]);
Expand Down Expand Up @@ -374,11 +385,10 @@ int sam0_eth_init(void)
/* Enable needed interrupts */
GMAC->IER.reg = GMAC_IER_RCOMP;

/* Set TxBase-100-FD by default */
/* TODO: implement auto negotiation */
GMAC->NCFGR.reg = GMAC_NCFGR_SPD | GMAC_NCFGR_FD | GMAC_NCFGR_MTIHEN
GMAC->NCFGR.reg = GMAC_NCFGR_MTIHEN
| GMAC_NCFGR_RXCOEN | GMAC_NCFGR_MAXFS | GMAC_NCFGR_CAF
| GMAC_NCFGR_LFERD | GMAC_NCFGR_RFCS | GMAC_NCFGR_CLK(3);
| GMAC_NCFGR_LFERD | GMAC_NCFGR_RFCS | GMAC_NCFGR_CLK(3)
| GMAC_NCFGR_DBW(1);

/* Enable all multicast addresses */
GMAC->HRB.reg = 0xffffffff;
Expand Down
1 change: 1 addition & 0 deletions cpu/sam0_common/sam0_eth/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ config MODULE_SAM0_ETH
depends on TEST_KCONFIG
depends on CPU_COMMON_SAM0
depends on HAS_PERIPH_ETH
select MODULE_PERIPH_GPIO_IRQ
select MODULE_NETDEV_ETH
select MODULE_NETDEV_LEGACY_API
select MODULE_NETOPT
Expand Down
128 changes: 126 additions & 2 deletions cpu/sam0_common/sam0_eth/eth-netdev.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,29 @@
#include <string.h>

#include "iolist.h"
#include "mii.h"
#include "net/gnrc/netif/ethernet.h"
#include "net/gnrc.h"
#include "net/ethernet.h"
#include "net/netdev/eth.h"
#include "net/eui_provider.h"

#include "periph/gpio.h"
#include "ztimer.h"

#include "sam0_eth_netdev.h"

#define ENABLE_DEBUG 0
#include "debug.h"
#include "log.h"

/**
* @brief Link auto-negotiation timeout
*/
#ifndef CONFIG_SAM0_ETH_LINK_TIMEOUT_MS
#define CONFIG_SAM0_ETH_LINK_TIMEOUT_MS (5 * MS_PER_SEC)
#endif

/* Internal helpers */
extern int sam0_eth_init(void);
extern void sam0_eth_poweron(void);
Expand All @@ -44,26 +53,130 @@ extern bool sam0_eth_has_queued_pkt(void);
extern void sam0_eth_set_mac(const eui48_t *mac);
extern void sam0_eth_get_mac(char *out);
extern void sam0_clear_rx_buffers(void);
extern unsigned sam0_read_phy(uint8_t phy, uint8_t addr);
extern void sam0_write_phy(uint8_t phy, uint8_t addr, uint16_t data);
static void _restart_an(void *ctx);

/* SAM0 CPUs only have one GMAC IP, so it is safe to
statically defines one in this file */
static sam0_eth_netdev_t _sam0_eth_dev;

/* auto-negotiation timeout timer */
static ztimer_t _phy_tim = { .callback = _restart_an };
/* PHY interrupt status register */
static uint16_t _phy_irq;

static inline bool _get_link_status(void)
{
return sam0_read_phy(0, MII_BMSR) & MII_BMSR_LINK;
dylad marked this conversation as resolved.
Show resolved Hide resolved
}

static void _restart_an(void *ctx)
{
(void)ctx;
sam0_write_phy(0, MII_IRQ, MII_IRQ_EN_LPA_ACK);
sam0_write_phy(0, MII_BMCR, MII_BMCR_AN_RESTART | MII_BMCR_AN_ENABLE |
MII_BMCR_SPEED_100 | MII_BMCR_FULL_DPLX);
}

static void _phy_isr(void *ctx)
{
(void)ctx;

_phy_irq = sam0_read_phy(0, MII_IRQ);

netdev_trigger_event_isr(_sam0_eth_dev.netdev);
}

static void _handle_phy_irq(uint16_t irq)
{
netdev_t *netdev = _sam0_eth_dev.netdev;

if (irq & MII_IRQ_LINK_DOWN) {
DEBUG_PUTS("[sam0_eth]: link down");

if (IS_USED(MODULE_ZTIMER_MSEC)) {
ztimer_remove(ZTIMER_MSEC, &_phy_tim);
}
/* only listen for link partner ACK events now */
sam0_write_phy(0, MII_IRQ, MII_IRQ_EN_LPA_ACK);

netdev->event_callback(netdev, NETDEV_EVENT_LINK_DOWN);
return;
}
if (irq & MII_IRQ_LINK_UP) {
DEBUG_PUTS("[sam0_eth]: link up");

uint16_t adv = sam0_read_phy(0, MII_ADVERTISE);
uint16_t lpa = sam0_read_phy(0, MII_LPA);

if (IS_USED(MODULE_ZTIMER_MSEC)) {
ztimer_remove(ZTIMER_MSEC, &_phy_tim);
}

uint32_t ncfgr = GMAC->NCFGR.reg & ~(GMAC_NCFGR_FD | GMAC_NCFGR_MTIHEN);

if ((adv & MII_ADVERTISE_100) && (lpa & MII_LPA_100)) {
/* 100 Mbps */
ncfgr |= GMAC_NCFGR_SPD;
}
if ((adv & MII_ADVERTISE_10_F) && (lpa & MII_LPA_10_F)) {
/* full duplex */
ncfgr |= GMAC_NCFGR_FD;
}

GMAC->NCFGR.reg = ncfgr;
netdev->event_callback(netdev, NETDEV_EVENT_LINK_UP);
return;
}

if (irq & MII_IRQ_LPA_ACK) {
DEBUG_PUTS("[sam0_eth]: link partner present");

/* if we don't succeed, restart auto-negotiation in 5s */
if (IS_USED(MODULE_ZTIMER_MSEC)) {
ztimer_set(ZTIMER_MSEC, &_phy_tim, CONFIG_SAM0_ETH_LINK_TIMEOUT_MS);
}

/* we only care about link up / down events now */
sam0_write_phy(0, MII_IRQ, MII_IRQ_EN_LINK_UP | MII_IRQ_EN_LINK_DOWN);
return;
}

DEBUG("[sam0_eth]: unexpected PHY IRQ: %x\n", irq);
}

static inline void _setup_phy_irq(gpio_cb_t cb, void *arg)
{
gpio_init_int(sam_gmac_config[0].int_pin, GPIO_IN, GPIO_FALLING, cb, arg);
}

static int _sam0_eth_init(netdev_t *netdev)
{
sam0_eth_init();
eui48_t hwaddr;
netdev_eui48_get(netdev, &hwaddr);
sam0_eth_set_mac(&hwaddr);

/* signal link UP */
netdev->event_callback(netdev, NETDEV_EVENT_LINK_UP);
/* wait for PHY to be ready */
while (MII_BMCR_RESET & sam0_read_phy(0, MII_BMCR)) {}

_setup_phy_irq(_phy_isr, NULL);
_restart_an(NULL);

return 0;
}

static void _sam0_eth_isr(netdev_t *netdev)
{
if (_phy_irq) {
uint16_t tmp = _phy_irq;
_phy_irq = 0;

_handle_phy_irq(tmp);
return;
}

netdev->event_callback(netdev, NETDEV_EVENT_RX_COMPLETE);
return;
}
Expand Down Expand Up @@ -106,6 +219,11 @@ static int _sam0_eth_get(netdev_t *netdev, netopt_t opt, void *val, size_t max_l
sam0_eth_get_mac((char *)val);
res = ETHERNET_ADDR_LEN;
break;
case NETOPT_LINK:
assert(max_len == sizeof(netopt_enable_t));
*(netopt_enable_t *)val = _get_link_status();
res = sizeof(netopt_enable_t);
break;
default:
res = netdev_eth_get(netdev, opt, val, max_len);
break;
Expand All @@ -118,10 +236,16 @@ static int _set_state(netopt_state_t state)
{
switch (state) {
case NETOPT_STATE_SLEEP:
if (IS_USED(MODULE_ZTIMER_MSEC)) {
ztimer_remove(ZTIMER_MSEC, &_phy_tim);
}
sam0_eth_poweroff();
_sam0_eth_dev.netdev->event_callback(_sam0_eth_dev.netdev,
NETDEV_EVENT_LINK_DOWN);
break;
case NETOPT_STATE_IDLE:
sam0_eth_poweron();
_restart_an(NULL);
break;
default:
return -ENOTSUP;
Expand Down
30 changes: 30 additions & 0 deletions drivers/include/mii.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ extern "C" {
#define MII_LPA (0x05U) /**< Link Parter Abilities */
#define MII_EXPANSION (0x06U) /**< Auto-Negotiation Expansion */
#define MII_ESTATUS (0x0fU) /**< Extended Status Register */
#define MII_IRQ (0x1bU) /**< Interrupt Control/Status */
/** @} */

/**
Expand Down Expand Up @@ -119,6 +120,35 @@ extern "C" {
#define MII_LPA_10 (BIT5 | BIT6) /**< Partner can 10BASE-T */
/** @} */

/**
* @name Bits in the MII Auto-Negotiation Expansion Register
* @{
*/
#define MII_LPA_HAS_AN BIT0 /**< Partner can auto-negotiate */
/** @} */

/**
* @name Bits in the MII Interrupt Control/Status Register
* @{
*/
#define MII_IRQ_LINK_UP BIT0 /**< Link-up occurred */
#define MII_IRQ_RMT_FAULT BIT1 /**< Remote fault occurred */
#define MII_IRQ_LINK_DOWN BIT2 /**< Link-down occurred */
#define MII_IRQ_LPA_ACK BIT3 /**< Link partner acknowledge occurred */
#define MII_IRQ_PD_FAULT BIT4 /**< Parallel detect fault occurred */
#define MII_IRQ_PAGE_RX BIT5 /**< Page receive occurred */
#define MII_IRQ_RX_ERROR BIT6 /**< Receive error occurred */
#define MII_IRQ_JABBER BIT7 /**< Jabber occurred */
#define MII_IRQ_EN_LINK_UP BIT8 /**< Enable Link-up occurred IRQ */
#define MII_IRQ_EN_RMT_FAULT BIT9 /**< Enable Remote fault occurred IRQ */
#define MII_IRQ_EN_LINK_DOWN BIT10 /**< Enable Link-down occurred IRQ */
#define MII_IRQ_EN_LPA_ACK BIT11 /**< Enable Link partner acknowledge occurred IRQ */
#define MII_IRQ_EN_PD_FAULT BIT12 /**< Enable Parallel detect fault occurred IRQ */
#define MII_IRQ_EN_PAGE_RX BIT13 /**< Enable Page receive occurred IRQ */
#define MII_IRQ_EN_RX_ERROR BIT14 /**< Enable Receive error occurred IRQ */
#define MII_IRQ_EN_JABBER BIT15 /**< Enable Jabber occurred IRQ */
/** @} */

/**
* @brief Check if an Ethernet PHY supports 100 Mbps at full duplex
*
Expand Down