drivers: Add Ethernet PHY API

This commit adds support for Ethernet PHY drivers via a PHY API.

It also includes a driver for a generic MII compliant PHY
which supports most PHYs on the market.

Separating PHY driver from the SoC specific Ethernet driver
simplifies the Ethernet driver code and enables code re-use.
Drivers for specific PHYs with more advanced features, such as
RGMII delay in PHY can be developed independent of the Ethernet
MAC driver.

Signed-off-by: Arvin Farahmand <arvinf@ip-logix.com>
This commit is contained in:
Arvin Farahmand 2021-05-02 21:06:30 -04:00 committed by Anas Nashif
parent 2bec7587e9
commit f845cddcf7
9 changed files with 749 additions and 0 deletions

View file

@ -222,6 +222,7 @@
/drivers/ethernet/*stm32* @Nukersson @lochej /drivers/ethernet/*stm32* @Nukersson @lochej
/drivers/ethernet/*w5500* @parthitce /drivers/ethernet/*w5500* @parthitce
/drivers/ethernet/*xlnx_gem* @ibirnbaum /drivers/ethernet/*xlnx_gem* @ibirnbaum
/drivers/ethernet/phy/ @rlubos @tbursztyka @arvinf
/drivers/mdio/ @rlubos @tbursztyka @arvinf /drivers/mdio/ @rlubos @tbursztyka @arvinf
/drivers/flash/ @nashif @nvlsianpu /drivers/flash/ @nashif @nvlsianpu
/drivers/flash/*b91* @yurvyn /drivers/flash/*b91* @yurvyn

View file

@ -40,3 +40,5 @@ if(CONFIG_ETH_NATIVE_POSIX)
eth_native_posix_adapt.c eth_native_posix_adapt.c
) )
endif() endif()
add_subdirectory(phy)

View file

@ -59,4 +59,6 @@ source "drivers/ethernet/Kconfig.w5500"
source "drivers/ethernet/Kconfig.dsa" source "drivers/ethernet/Kconfig.dsa"
source "drivers/ethernet/Kconfig.xlnx_gem" source "drivers/ethernet/Kconfig.xlnx_gem"
source "drivers/ethernet/phy/Kconfig"
endmenu # "Ethernet Drivers" endmenu # "Ethernet Drivers"

View file

@ -0,0 +1,5 @@
# SPDX-License-Identifier: Apache-2.0
zephyr_library()
zephyr_library_sources_ifdef(CONFIG_PHY_GENERIC_MII phy_mii.c)

View file

@ -0,0 +1,47 @@
# Ethernet PHY drivers configuration options
# Copyright (c) 2021 IP-Logix Inc.
# SPDX-License-Identifier: Apache-2.0
menu "Ethernet PHY Drivers"
depends on NET_L2_ETHERNET
module = PHY
module-dep = LOG
module-str = Log level for Ethernet PHY driver
module-help = Sets log level for Ethernet PHY Device Drivers.
source "subsys/net/Kconfig.template.log_config.net"
config PHY_INIT_PRIORITY
int "Ethernet PHY driver init priority"
default 70
help
Ethernet PHY device driver initialization priority.
Do not mess with it unless you know what you are doing.
Note that the priority needs to be lower than the net stack
so that it can start before the networking sub-system.
config PHY_GENERIC_MII
bool "Generic MII PHY Driver"
default y
depends on MDIO
help
This is a generic MII PHY interface that communicates with the
PHY using the MDIO bus.
config PHY_AUTONEG_TIMEOUT_MS
int "Auto-negotiation timeout value in milliseconds"
default 4000
help
Maximum duration of auto-negotiation sequence in milliseconds
before the process fails
config PHY_MONITOR_PERIOD
int "Monitor task execution period"
default 500
help
Monitor task execution period in milliseconds. The monitor task is
periodically executed to detect and report any changes in the
PHY link status to the operating system.
endmenu # "Ethernet PHY Drivers"

View file

@ -0,0 +1,422 @@
/*
* Copyright (c) 2021 IP-Logix Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT ethernet_phy
#include <errno.h>
#include <device.h>
#include <init.h>
#include <soc.h>
#include <drivers/mdio.h>
#include <net/phy.h>
#include <net/mii.h>
#include <logging/log.h>
LOG_MODULE_REGISTER(phy_mii, CONFIG_PHY_LOG_LEVEL);
struct phy_mii_dev_config {
uint8_t phy_addr;
bool no_reset;
bool fixed;
int fixed_speed;
const struct device * const mdio;
};
struct phy_mii_dev_data {
const struct device *dev;
phy_callback_t cb;
void *cb_data;
struct k_work_delayable monitor_work;
struct phy_link_state state;
struct k_sem sem;
};
#define DEV_NAME(dev) ((dev)->name)
#define DEV_DATA(dev) ((struct phy_mii_dev_data *const)(dev)->data)
#define DEV_CFG(dev) \
((const struct phy_mii_dev_config *const)(dev)->config)
static int phy_mii_get_link_state(const struct device *dev,
struct phy_link_state *state);
static inline int reg_read(const struct device *dev, uint16_t reg_addr,
uint16_t *value)
{
const struct phy_mii_dev_config *const cfg = DEV_CFG(dev);
return mdio_read(cfg->mdio, cfg->phy_addr, reg_addr, value);
}
static inline int reg_write(const struct device *dev, uint16_t reg_addr,
uint16_t value)
{
const struct phy_mii_dev_config *const cfg = DEV_CFG(dev);
return mdio_write(cfg->mdio, cfg->phy_addr, reg_addr, value);
}
static int reset(const struct device *dev)
{
uint32_t timeout = 12U;
uint16_t value;
/* Issue a soft reset */
if (reg_write(dev, MII_BMCR, MII_BMCR_RESET) < 0) {
return -EIO;
}
/* 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 (timeout-- == 0U) {
return -ETIMEDOUT;
}
k_sleep(K_MSEC(50));
if (reg_read(dev, MII_BMCR, &value) < 0) {
return -EIO;
}
} while (value & MII_BMCR_RESET);
return 0;
}
static int get_id(const struct device *dev, uint32_t *phy_id)
{
uint16_t value;
if (reg_read(dev, MII_PHYID1R, &value) < 0) {
return -EIO;
}
*phy_id = (value & 0xFFFF) << 16;
if (reg_read(dev, MII_PHYID2R, &value) < 0) {
return -EIO;
}
*phy_id |= (value & 0xFFFF);
return 0;
}
static int update_link_state(const struct device *dev)
{
const struct phy_mii_dev_config *const cfg = DEV_CFG(dev);
struct phy_mii_dev_data *const data = DEV_DATA(dev);
bool link_up;
uint16_t anar_reg = 0;
uint16_t bmcr_reg = 0;
uint16_t bmsr_reg = 0;
uint16_t anlpar_reg = 0;
uint32_t timeout = CONFIG_PHY_AUTONEG_TIMEOUT_MS / 100;
if (reg_read(dev, MII_BMSR, &bmsr_reg) < 0) {
return -EIO;
}
link_up = bmsr_reg & MII_BMSR_LINK_STATUS;
/* If there is no change in link state don't proceed. */
if (link_up == data->state.is_up) {
return -EAGAIN;
}
data->state.is_up = link_up;
/* If link is down, there is nothing more to be done */
if (data->state.is_up == false) {
return 0;
}
/**
* Perform auto-negotiation sequence.
*/
LOG_DBG("PHY (%d) Starting MII PHY auto-negotiate sequence",
cfg->phy_addr);
/* Read PHY default advertising parameters */
if (reg_read(dev, MII_ANAR, &anar_reg) < 0) {
return -EIO;
}
/* Configure and start auto-negotiation process */
if (reg_read(dev, MII_BMCR, &bmcr_reg) < 0) {
return -EIO;
}
bmcr_reg |= MII_BMCR_AUTONEG_ENABLE | MII_BMCR_AUTONEG_RESTART;
bmcr_reg &= ~MII_BMCR_ISOLATE; /* Don't isolate the PHY */
if (reg_write(dev, MII_BMCR, bmcr_reg) < 0) {
return -EIO;
}
/* Wait for the auto-negotiation process to complete */
do {
if (timeout-- == 0U) {
LOG_DBG("PHY (%d) auto-negotiate timedout",
cfg->phy_addr);
return -ETIMEDOUT;
}
k_sleep(K_MSEC(100));
if (reg_read(dev, MII_BMSR, &bmsr_reg) < 0) {
return -EIO;
}
} while (!(bmsr_reg & MII_BMSR_AUTONEG_COMPLETE));
LOG_DBG("PHY (%d) auto-negotiate sequence completed",
cfg->phy_addr);
/** Read peer device capability */
if (reg_read(dev, MII_ANLPAR, &anlpar_reg) < 0) {
return -EIO;
}
/* Determine best link speed */
if ((anar_reg & anlpar_reg) & MII_ADVERTISE_100_FULL) {
data->state.speed = LINK_FULL_100BASE_T;
} else if ((anar_reg & anlpar_reg) & MII_ADVERTISE_100_HALF) {
data->state.speed = LINK_HALF_100BASE_T;
} else if ((anar_reg & anlpar_reg) & MII_ADVERTISE_10_FULL) {
data->state.speed = LINK_FULL_10BASE_T;
} else {
data->state.speed = LINK_HALF_10BASE_T;
}
LOG_INF("PHY (%d) Link speed %s Mb, %s duplex",
cfg->phy_addr,
PHY_LINK_IS_SPEED_100M(data->state.speed) ? "100" : "10",
PHY_LINK_IS_FULL_DUPLEX(data->state.speed) ? "full" : "half");
return 0;
}
static void invoke_link_cb(const struct device *dev)
{
struct phy_mii_dev_data *const data = DEV_DATA(dev);
struct phy_link_state state;
if (data->cb == NULL) {
return;
}
phy_mii_get_link_state(dev, &state);
data->cb(data->dev, &state, data->cb_data);
}
static void monitor_work_handler(struct k_work *work)
{
struct phy_mii_dev_data *const data =
CONTAINER_OF(work, struct phy_mii_dev_data, monitor_work);
const struct device *dev = data->dev;
int rc;
k_sem_take(&data->sem, K_FOREVER);
rc = update_link_state(dev);
k_sem_give(&data->sem);
/* If link state has changed and a callback is set, invoke callback */
if (rc == 0) {
invoke_link_cb(dev);
}
/* Submit delayed work */
k_work_reschedule(&data->monitor_work,
K_MSEC(CONFIG_PHY_MONITOR_PERIOD));
}
static int phy_mii_read(const struct device *dev, uint16_t reg_addr,
uint32_t *data)
{
return reg_read(dev, reg_addr, (uint16_t *)data);
}
static int phy_mii_write(const struct device *dev, uint16_t reg_addr,
uint32_t data)
{
return reg_write(dev, reg_addr, (uint16_t)data);
}
static int phy_mii_cfg_link(const struct device *dev,
enum phy_link_speed adv_speeds)
{
uint16_t anar_reg;
uint16_t bmcr_reg;
if (reg_read(dev, MII_ANAR, &anar_reg) < 0) {
return -EIO;
}
if (reg_read(dev, MII_BMCR, &bmcr_reg) < 0) {
return -EIO;
}
if (adv_speeds & LINK_FULL_10BASE_T)
anar_reg |= MII_ADVERTISE_10_FULL;
else
anar_reg &= ~MII_ADVERTISE_10_FULL;
if (adv_speeds & LINK_HALF_10BASE_T)
anar_reg |= MII_ADVERTISE_10_HALF;
else
anar_reg &= ~MII_ADVERTISE_10_HALF;
if (adv_speeds & LINK_FULL_100BASE_T)
anar_reg |= MII_ADVERTISE_100_FULL;
else
anar_reg &= ~MII_ADVERTISE_100_FULL;
if (adv_speeds & LINK_HALF_100BASE_T)
anar_reg |= MII_ADVERTISE_100_HALF;
else
anar_reg &= ~MII_ADVERTISE_100_HALF;
bmcr_reg |= MII_BMCR_AUTONEG_ENABLE;
if (reg_write(dev, MII_ANAR, anar_reg) < 0) {
return -EIO;
}
if (reg_write(dev, MII_BMCR, bmcr_reg) < 0) {
return -EIO;
}
return 0;
}
static int phy_mii_get_link_state(const struct device *dev,
struct phy_link_state *state)
{
struct phy_mii_dev_data *const data = DEV_DATA(dev);
k_sem_take(&data->sem, K_FOREVER);
memcpy(state, &data->state, sizeof(struct phy_link_state));
k_sem_give(&data->sem);
return 0;
}
static int phy_mii_link_cb_set(const struct device *dev, phy_callback_t cb,
void *user_data)
{
struct phy_mii_dev_data *const data = DEV_DATA(dev);
data->cb = cb;
data->cb_data = user_data;
/**
* Immediately invoke the callback to notify the caller of the
* current link status.
*/
invoke_link_cb(dev);
return 0;
}
static int phy_mii_initialize(const struct device *dev)
{
const struct phy_mii_dev_config *const cfg = DEV_CFG(dev);
struct phy_mii_dev_data *const data = DEV_DATA(dev);
uint32_t phy_id;
k_sem_init(&data->sem, 1, 1);
data->dev = dev;
data->cb = NULL;
/**
* If this is a *fixed* link then we don't need to communicate
* with a PHY. We set the link parameters as configured
* and set link state to up.
*/
if (cfg->fixed) {
const static int speed_to_phy_link_speed[] = {
LINK_HALF_10BASE_T,
LINK_FULL_10BASE_T,
LINK_HALF_100BASE_T,
LINK_FULL_100BASE_T,
};
data->state.speed = speed_to_phy_link_speed[cfg->fixed_speed];
data->state.is_up = true;
} else {
data->state.is_up = false;
mdio_bus_enable(cfg->mdio);
if (cfg->no_reset == false) {
reset(dev);
}
if (get_id(dev, &phy_id) == 0) {
if (phy_id == 0xFFFFFF) {
LOG_ERR("No PHY found at address %d",
cfg->phy_addr);
return -EINVAL;
}
LOG_INF("PHY (%d) ID %X", cfg->phy_addr, phy_id);
}
/* Advertise all speeds */
phy_mii_cfg_link(dev, LINK_HALF_10BASE_T |
LINK_FULL_10BASE_T |
LINK_HALF_100BASE_T |
LINK_FULL_100BASE_T);
k_work_init_delayable(&data->monitor_work,
monitor_work_handler);
monitor_work_handler(&data->monitor_work.work);
}
return 0;
}
#define IS_FIXED_LINK(n) DT_NODE_HAS_PROP(DT_DRV_INST(n), fixed_link)
static const struct ethphy_driver_api phy_mii_driver_api = {
.get_link = phy_mii_get_link_state,
.cfg_link = phy_mii_cfg_link,
.link_cb_set = phy_mii_link_cb_set,
.read = phy_mii_read,
.write = phy_mii_write,
};
#define PHY_MII_CONFIG(n) \
static const struct phy_mii_dev_config phy_mii_dev_config_##n = { \
.phy_addr = DT_PROP(DT_DRV_INST(n), address), \
.fixed = IS_FIXED_LINK(n), \
.fixed_speed = DT_ENUM_IDX_OR(DT_DRV_INST(n), fixed_link, 0), \
.mdio = UTIL_AND(UTIL_NOT(IS_FIXED_LINK(n)), \
DEVICE_DT_GET(DT_PHANDLE(DT_DRV_INST(n), mdio)))\
};
#define PHY_MII_DEVICE(n) \
PHY_MII_CONFIG(n); \
static struct phy_mii_dev_data phy_mii_dev_data_##n; \
DEVICE_DT_INST_DEFINE(n, \
&phy_mii_initialize, \
NULL, \
&phy_mii_dev_data_##n, \
&phy_mii_dev_config_##n, POST_KERNEL, \
CONFIG_PHY_INIT_PRIORITY, \
&phy_mii_driver_api);
DT_INST_FOREACH_STATUS_OKAY(PHY_MII_DEVICE)

View file

@ -0,0 +1,33 @@
# Copyright (c) 2021 IP-Logix Inc.
# SPDX-License-Identifier: Apache-2.0
# Common fields for MIIPHY devices
description: Generic MII PHY
compatible: "ethernet-phy"
include: phy.yaml
properties:
address:
type: int
required: true
description: PHY address
mdio:
type: phandle
required: true
description: MDIO driver node
no-reset:
type: boolean
required: false
description: Do not reset the PHY during initialization
fixed-link:
type: string
required: false
description: This link is fixed and does not require PHY configuration
enum:
- "10BASE-T Half-Duplex"
- "10BASE-T Full-Duplex"
- "100BASE-T Half-Duplex"
- "100BASE-T Full-Duplex"

View file

@ -0,0 +1,6 @@
# Copyright (c) 2021 IP-Logix Inc.
# SPDX-License-Identifier: Apache-2.0
# Common fields for PHY devices
include: base.yaml

231
include/net/phy.h Normal file
View file

@ -0,0 +1,231 @@
/**
* @file
*
* @brief Public APIs for Ethernet PHY drivers.
*/
/*
* Copyright (c) 2021 IP-Logix Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_DRIVERS_PHY_H_
#define ZEPHYR_INCLUDE_DRIVERS_PHY_H_
/**
* @brief Ethernet PHY Interface
* @defgroup ethernet_phy Ethernet PHY Interface
* @ingroup networking
* @{
*/
#include <zephyr/types.h>
#include <device.h>
#ifdef __cplusplus
extern "C" {
#endif
/** @brief Ethernet link speeds. */
enum phy_link_speed {
/** 10Base-T Half-Duplex */
LINK_HALF_10BASE_T = BIT(0),
/** 10Base-T Full-Duplex */
LINK_FULL_10BASE_T = BIT(1),
/** 100Base-T Half-Duplex */
LINK_HALF_100BASE_T = BIT(2),
/** 100Base-T Full-Duplex */
LINK_FULL_100BASE_T = BIT(3),
};
#define PHY_LINK_IS_FULL_DUPLEX(x) (x & (BIT(1) | BIT(3)))
#define PHY_LINK_IS_SPEED_100M(x) (x & (BIT(2) | BIT(3)))
/** @brief Link state */
struct phy_link_state {
/** Link speed */
enum phy_link_speed speed;
/** When true the link is active and connected */
bool is_up;
};
/**
* @typedef phy_callback_t
* @brief Define the callback function signature for
* `phy_link_callback_set()` function.
*
* @param dev PHY device structure
* @param state Pointer to link_state structure.
* @param user_data Pointer to data specified by user
*/
typedef void (*phy_callback_t)(const struct device *dev,
struct phy_link_state *state,
void *user_data);
/**
* @cond INTERNAL_HIDDEN
*
* These are for internal use only, so skip these in
* public documentation.
*/
__subsystem struct ethphy_driver_api {
/** Get link state */
int (*get_link)(const struct device *dev,
struct phy_link_state *state);
/** Configure link */
int (*cfg_link)(const struct device *dev,
enum phy_link_speed adv_speeds);
/** Set callback to be invoked when link state changes. */
int (*link_cb_set)(const struct device *dev, phy_callback_t cb,
void *user_data);
/** Read PHY register */
int (*read)(const struct device *dev, uint16_t reg_addr,
uint32_t *data);
/** Write PHY register */
int (*write)(const struct device *dev, uint16_t reg_addr,
uint32_t data);
};
/**
* @endcond
*/
/**
* @brief Configure PHY link
*
* This route configures the advertised link speeds.
*
* @param[in] dev PHY device structure
* @param speeds OR'd link speeds to be advertised by the PHY
*
* @retval 0 If successful.
* @retval -EIO If communication with PHY failed.
* @retval -ENOTSUP If not supported.
*/
__syscall int phy_configure_link(const struct device *dev,
enum phy_link_speed speeds);
static inline int z_impl_phy_configure_link(const struct device *dev,
enum phy_link_speed speeds)
{
const struct ethphy_driver_api *api =
(const struct ethphy_driver_api *)dev->api;
return api->cfg_link(dev, speeds);
}
/**
* @brief Get PHY link state
*
* Returns the current state of the PHY link. This can be used by
* to determine when a link is up and the negotiated link speed.
*
*
* @param[in] dev PHY device structure
* @param state Pointer to receive PHY state
*
* @retval 0 If successful.
* @retval -EIO If communication with PHY failed.
*/
__syscall int phy_get_link_state(const struct device *dev,
struct phy_link_state *state);
static inline int z_impl_phy_get_link_state(const struct device *dev,
struct phy_link_state *state)
{
const struct ethphy_driver_api *api =
(const struct ethphy_driver_api *)dev->api;
return api->get_link(dev, state);
}
/**
* @brief Set link state change callback
*
* Sets a callback that is invoked when link state changes. This is the
* preferred method for ethernet drivers to be notified of the PHY link
* state change.
*
* @param[in] dev PHY device structure
* @param callback Callback handler
* @param user_data Pointer to data specified by user.
*
* @retval 0 If successful.
* @retval -ENOTSUP If not supported.
*/
__syscall int phy_link_callback_set(const struct device *dev,
phy_callback_t callback,
void *user_data);
static inline int z_impl_phy_link_callback_set(const struct device *dev,
phy_callback_t callback,
void *user_data)
{
const struct ethphy_driver_api *api =
(const struct ethphy_driver_api *)dev->api;
return api->link_cb_set(dev, callback, user_data);
}
/**
* @brief Read PHY registers
*
* This routine provides a generic interface to read from a PHY register.
*
* @param[in] dev PHY device structure
* @param[in] reg_addr Register address
* @param value Pointer to receive read value
*
* @retval 0 If successful.
* @retval -EIO If communication with PHY failed.
*/
__syscall int phy_read(const struct device *dev, uint16_t reg_addr,
uint32_t *value);
static inline int z_impl_phy_read(const struct device *dev, uint16_t reg_addr,
uint32_t *value)
{
const struct ethphy_driver_api *api =
(const struct ethphy_driver_api *)dev->api;
return api->read(dev, reg_addr, value);
}
/**
* @brief Write PHY register
*
* This routine provides a generic interface to write to a PHY register.
*
* @param[in] dev PHY device structure
* @param[in] reg_addr Register address
* @param[in] value Value to write
*
* @retval 0 If successful.
* @retval -EIO If communication with PHY failed.
*/
__syscall int phy_write(const struct device *dev, uint16_t reg_addr,
uint32_t value);
static inline int z_impl_phy_write(const struct device *dev, uint16_t reg_addr,
uint32_t value)
{
const struct ethphy_driver_api *api =
(const struct ethphy_driver_api *)dev->api;
return api->write(dev, reg_addr, value);
}
#ifdef __cplusplus
}
#endif
/**
* @}
*/
#include <syscalls/phy.h>
#endif /* ZEPHYR_INCLUDE_DRIVERS_PHY_H_ */