/**
******************************************************************************
* @file sdk/lib/ethernet_phy/eth_phy.c
* @author HUGE-IC Application Team
* @version V1.0.0
* @date 10-10-2018
* @brief Framework for configuring and reading PHY devices
******************************************************************************
* @attention
*
*
© COPYRIGHT 2018 HUGE-IC
*
*
*
******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include "typesdef.h"
#include "errno.h"
#include "list.h"
#include "dev.h"
#include "osal/task.h"
#include "osal/work.h"
#include "osal/sleep.h"
#include "osal/timer.h"
#include "hal/netdev.h"
#include "lib/common/sysevt.h"
#include "lib/net/ethphy/eth_phy.h"
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
#define MAX_NUM_SETTINGS ARRAY_SIZE(settings)
/* Private variables ---------------------------------------------------------*/
/**
* @brief A structure for mapping a particular speed and duplex combination
* to a particular SUPPORTED and ADVERTISED value
*/
struct __phy_setting {
int16 speed;
int16 duplex;
uint32 setting;
};
/**
* @brief A mapping of all SUPPORTED settings to speed/duplex
*/
static const struct __phy_setting settings[] = {
{
.speed = SPEED_1000,
.duplex = DUPLEX_FULL,
.setting = SUPPORTED_1000baseT_Full,
},
{
.speed = SPEED_1000,
.duplex = DUPLEX_HALF,
.setting = SUPPORTED_1000baseT_Half,
},
{
.speed = SPEED_100,
.duplex = DUPLEX_FULL,
.setting = SUPPORTED_100baseT_Full,
},
{
.speed = SPEED_100,
.duplex = DUPLEX_HALF,
.setting = SUPPORTED_100baseT_Half,
},
{
.speed = SPEED_10,
.duplex = DUPLEX_FULL,
.setting = SUPPORTED_10baseT_Full,
},
{
.speed = SPEED_10,
.duplex = DUPLEX_HALF,
.setting = SUPPORTED_10baseT_Half,
},
};
struct _eth_phy_wkq {
struct os_work os_work_phy;
struct ethernet_phy_device *eth_phy_dev;
};
static struct _eth_phy_wkq eth_phy_wkq;
/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/
/** @addtogroup NETWORK
* @{
*/
static void eth_phy_event(struct ethernet_phy_device *phydev, uint32 event, uint32 param1, uint32 param2)
{
const struct netdev_hal_ops *ops = (const struct netdev_hal_ops *)phydev->ndev->dev.ops;
if (ops->phy_event) {
ops->phy_event(phydev, event, param1, param2);
}
}
/** @defgroup NETWORK_PHY PHY Framework
* @brief Framework for finding and configuring PHYs.
* Also contains generic PHY driver
* @{
*/
/**
* @brief return auto-negotiation status
* @param phydev : target phy_device struct
* @retval auto-negotiation status
* @note Reads the status register and returns 0 either if
* auto-negotiation is incomplete, or if there was an error.
* Returns BMSR_ANEGCOMPLETE if auto-negotiation is done.
*/
static inline int eth_phy_aneg_done(struct ethernet_phy_device *phydev)
{
int retval;
retval = phy_read(phydev, MII_BMSR);
return (retval < 0) ? retval : (retval & BMSR_ANEGCOMPLETE);
}
/**
* @brief find a PHY settings array entry that matches speed & duplex
* @param advertising :
* @retval the index of that setting
* @note Searches the settings array for the setting which matches the
* desired speed and duplex, and returns the index of that setting.
* Returns the index of the last setting if none of the others
* match.
*/
static inline int eth_phy_find_setting(uint32 advertising)
{
int idx = 0;
while (idx < ARRAY_SIZE(settings) && (!(advertising & settings[idx].setting))) {
idx++;
}
return idx < MAX_NUM_SETTINGS ? idx : MAX_NUM_SETTINGS - 1;
}
/**
* @brief find a PHY setting that matches the requested features mask
* @param idx : The first index in settings[] to search
* @retval the index of the first valid setting
* @note Returns the index of the first valid setting less than or equal
* to the one pointed to by idx, as determined by the mask in
* features. Returns the index of the last setting if nothing else
* matches.
*/
static inline int eth_phy_find_valid(int idx, uint32 features)
{
while (idx < MAX_NUM_SETTINGS && !(settings[idx].setting & features)) {
idx++;
}
return idx < MAX_NUM_SETTINGS ? idx : MAX_NUM_SETTINGS - 1;
}
/**
* @brief make sure the PHY is set to supported speed and duplex
* @param phydev : the target phy_device struct
* @retval None
* @note Make sure the PHY is set to supported speeds and duplexes. Drop
* down by one in this order:
* 1000/FULL, 1000/HALF, 100/FULL, 100/HALF, 10/FULL, 10/HALF.
*/
static void eth_phy_sanitize_settings(struct ethernet_phy_device *phydev)
{
uint32 features = phydev->supported;
int idx;
/* Sanitize settings based on PHY capabilities */
if ((features & SUPPORTED_Autoneg) == 0) {
phydev->autoneg = AUTONEG_DISABLE;
}
idx = eth_phy_find_valid(eth_phy_find_setting(phydev->advertising), features);
phydev->speed = settings[idx].speed;
phydev->duplex = settings[idx].duplex;
}
/**
* @brief start auto-negotiation for this PHY device
* @param phydev : the phy_device struct
* @retval Returning 0 means that the PHY does not support
* auto-negotiation.
* @note Sanitizes the settings (if we're not autonegotiating them), and
* then calls the driver's config_aneg function. If the PHYCONTROL
* Layer is operating, we change the state to reflect the beginning
* of Auto-negotiation or forcing.
*/
uint8 eth_phy_start_aneg(struct ethernet_phy_device *phydev)
{
int err;
if (AUTONEG_DISABLE == phydev->autoneg) {
eth_phy_sanitize_settings(phydev);
}
err = phydev->drv->config_aneg(phydev);
if (err < 0) {
return 0;
}
if (phydev->state != ETH_PHY_HALTED) {
if (AUTONEG_ENABLE == phydev->autoneg) {
phydev->state = ETH_PHY_AN;
} else {
phydev->state = ETH_PHY_FORCING;
}
}
return 1;
}
/**
* @brief start PHY state machine tracking
* @param phydev : the phy_device struct
* @param handler : callback function for state change notifications
* @retval None
* @note The PHY infrastructure can run a state machine which tracks
* whether the PHY is starting up, negotiating, etc. This function
* starts the timer which tracks the state of the PHY. If you want
* to be notified when the state changes, pass in the callback
* @handler, otherwise, pass NULL. If you want to maintain your own
* state machine, do not call this function.
*/
void eth_phy_start_machine(struct ethernet_phy_device *phydev)
{
//phydev->adjust_state = handler;
}
/**
* @brief Handle the state machine
* @param phydev : target phy_device struct
* @retval None
*/
void eth_phy_state_machine(struct ethernet_phy_device *phydev)
{
int needs_aneg = 0;
int err = 0;
int16 link = phydev->link;
int16 speed = phydev->speed;
int16 duplex = phydev->duplex;
//if(phydev->adjust_state) {
// phydev->adjust_state(phydev);
//}
switch (phydev->state) {
case ETH_PHY_DOWN:
case ETH_PHY_STARTING:
case ETH_PHY_READY:
case ETH_PHY_PENDING:
break;
case ETH_PHY_UP:
needs_aneg = 1;
break;
case ETH_PHY_AN:
err = phydev->drv->read_status(phydev);
if (err < 0) {
break;
}
/* If the link is down, give up on
* negotiation for now */
if (!phydev->link) {
phydev->state = ETH_PHY_NOLINK;
eth_phy_event(phydev, NETDEV_PHY_EVENT_LINK_CHANGED, phydev->link, 0);
break;
}
/* Check if negotiation is done. Break
* if there's an error */
err = eth_phy_aneg_done(phydev);
if (err < 0) {
break;
}
/* If AN is done, we're running */
if (err > 0) {
phydev->state = ETH_PHY_RUNNING;
eth_phy_event(phydev, NETDEV_PHY_EVENT_LINK_CHANGED, phydev->link, 0);
} else if (0 == phydev->link_timeout--) {
needs_aneg = 1;
}
break;
case ETH_PHY_NOLINK:
err = phydev->drv->read_status(phydev);
if (err) {
break;
}
if (phydev->link) {
phydev->state = ETH_PHY_RUNNING;
eth_phy_event(phydev, NETDEV_PHY_EVENT_LINK_CHANGED, phydev->link, 0);
}
break;
case ETH_PHY_FORCING:
err = genphy_update_link(phydev);
if (err) {
break;
}
if (phydev->link) {
phydev->state = ETH_PHY_RUNNING;
} else {
if (0 == phydev->link_timeout--) {
needs_aneg = 1;
}
}
eth_phy_event(phydev, NETDEV_PHY_EVENT_LINK_CHANGED, phydev->link, 0);
break;
case ETH_PHY_RUNNING:
err = phydev->drv->read_status(phydev);
if (err) {
break;
}
if (phydev->link) {
phydev->state = ETH_PHY_RUNNING;
} else {
phydev->state = ETH_PHY_NOLINK;
}
eth_phy_event(phydev, NETDEV_PHY_EVENT_LINK_CHANGED, phydev->link, 0);
break;
case ETH_PHY_HALTED:
if (phydev->link) {
phydev->link = 0;
eth_phy_event(phydev, NETDEV_PHY_EVENT_LINK_CHANGED, phydev->link, 0);
}
break;
default:
break;
}
if(link != phydev->link || speed != phydev->speed || duplex != phydev->duplex){
SYSEVT_NEW_NETWORK_EVT(phydev->link ? SYSEVT_GMAC_LINK_UP : SYSEVT_GMAC_LINK_DOWN, phydev->speed);
}
if (needs_aneg) {
err = eth_phy_start_aneg(phydev);
}
}
/**
* @brief Handle the state machine thread
* @param phydev : target phy_device struct
* @retval None
*/
static int32 eth_phy_state_machine_thread(struct os_work *work)
{
struct _eth_phy_wkq *phydev = container_of(work, struct _eth_phy_wkq, os_work_phy);
eth_phy_state_machine(phydev->eth_phy_dev);
os_run_work_delay(work, 200);
return 0;
}
int32 eth_phy_open(struct ethernet_phy_device *phydev, struct ethernet_mdio_bus *mdio_bus, struct netdev *ndev)
{
eth_phy_wkq.eth_phy_dev = phydev;
uint32 feature_supproted = phydev->drv->features;
if (!phydev || !mdio_bus) {
return RET_ERR;
}
phydev->ndev = ndev;
ASSERT(ndev);
phydev->bus = mdio_bus;
ASSERT(mdio_bus);
if (eth_get_phy_device(phydev)) {
phydev->state = ETH_PHY_READY;
} else {
return RET_ERR;
}
/* attach the mac to the phy */
if (eth_phy_connect_direct(phydev) == RET_ERR) {
return RET_ERR;
}
/* Only support features that we can support */
phydev->advertising &= feature_supproted;
if (feature_supproted & SUPPORTED_Autoneg) {
phydev->autoneg = AUTONEG_ENABLE;
} else {
phydev->autoneg = AUTONEG_DISABLE;
}
/* Initial invalid value */
phydev->last_link = 0;
phydev->last_speed = 0;
phydev->last_duplex = -1;
/* start */
phydev->state = ETH_PHY_UP;
OS_WORK_INIT(ð_phy_wkq.os_work_phy, eth_phy_state_machine_thread, 0);
os_run_work_delay(ð_phy_wkq.os_work_phy, 1);
return RET_OK;
}
void eth_phy_close(struct ethernet_phy_device *phydev)
{
os_work_cancle(ð_phy_wkq.os_work_phy, 1);
}
__init void eth_phy_attach(uint32 dev_id, struct ethernet_phy_device *phydev)
{
dev_register(dev_id, (struct dev_obj *)phydev);
}
/**
* @}
*/
/**
* @}
*/
/*************************** (C) COPYRIGHT 2018 HUGE-IC ***** END OF FILE *****/