You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
347 lines
9.4 KiB
347 lines
9.4 KiB
/*
|
|
* Copyright (c) 2021, WangHuachen
|
|
*
|
|
* SPDX-License-Identifier: MIT
|
|
*
|
|
* Change Logs:
|
|
* Date Author Notes
|
|
* 2021-5-10 WangHuachen the first version
|
|
*/
|
|
#include "board.h"
|
|
#include <netif/ethernetif.h>
|
|
#include "lwipopts.h"
|
|
#include "lwip/opt.h"
|
|
#include "drv_eth.h"
|
|
#include "lwip/netif.h"
|
|
#include "netif/xadapter.h"
|
|
#include "netif/xemacpsif.h"
|
|
#include "xparameters.h"
|
|
#include "xemacps.h"
|
|
|
|
#define DBG_TAG "drv.emac"
|
|
#define DBG_LEVEL DBG_INFO
|
|
#include <rtdbg.h>
|
|
|
|
#define MAC_BASE_ADDR XPAR_PSU_ETHERNET_3_BASEADDR
|
|
#define MAX_ADDR_LEN 6
|
|
|
|
struct rt_zynqmp_eth
|
|
{
|
|
/* inherit from ethernet device */
|
|
struct eth_device parent;
|
|
|
|
/* interface address info, hw address */
|
|
rt_uint8_t dev_addr[MAX_ADDR_LEN];
|
|
|
|
struct xemac_s *xemac;
|
|
};
|
|
|
|
static struct rt_zynqmp_eth zynqmp_eth_device;
|
|
extern XEmacPs_Config *mac_config;
|
|
extern struct netif *NetIf;
|
|
|
|
static void rt_hw_eth_isr(int irqno, void *param)
|
|
{
|
|
struct rt_zynqmp_eth *eth_dev = (struct rt_zynqmp_eth *)param;
|
|
xemacpsif_s *xemacpsif = (xemacpsif_s *)eth_dev->xemac->state;
|
|
XEmacPs_IntrHandler(&xemacpsif->emacps);
|
|
}
|
|
|
|
extern enum ethernet_link_status eth_link_status;
|
|
extern u32_t phy_link_detect(XEmacPs *xemacp, u32_t phy_addr);
|
|
extern u32_t phy_autoneg_status(XEmacPs *xemacp, u32_t phy_addr);
|
|
extern u32_t phyaddrforemac;
|
|
|
|
void rt_zynqmp_eth_link_detect(struct rt_zynqmp_eth *eth_dev)
|
|
{
|
|
u32_t link_speed, phy_link_status;
|
|
struct xemac_s *xemac = eth_dev->xemac;
|
|
xemacpsif_s *xemacs = (xemacpsif_s *)(xemac->state);
|
|
XEmacPs *xemacp = &xemacs->emacps;
|
|
|
|
if ((xemacp->IsReady != (u32)XIL_COMPONENT_IS_READY) ||
|
|
(eth_link_status == ETH_LINK_UNDEFINED))
|
|
return;
|
|
|
|
phy_link_status = phy_link_detect(xemacp, phyaddrforemac);
|
|
|
|
if ((eth_link_status == ETH_LINK_UP) && (!phy_link_status))
|
|
eth_link_status = ETH_LINK_DOWN;
|
|
|
|
switch (eth_link_status) {
|
|
case ETH_LINK_UNDEFINED:
|
|
case ETH_LINK_UP:
|
|
return;
|
|
case ETH_LINK_DOWN:
|
|
eth_device_linkchange(&zynqmp_eth_device.parent, RT_FALSE);
|
|
eth_link_status = ETH_LINK_NEGOTIATING;
|
|
LOG_D("Ethernet Link down");
|
|
break;
|
|
case ETH_LINK_NEGOTIATING:
|
|
if (phy_link_status &&
|
|
phy_autoneg_status(xemacp, phyaddrforemac)) {
|
|
|
|
/* Initiate Phy setup to get link speed */
|
|
link_speed = phy_setup_emacps(xemacp,
|
|
phyaddrforemac);
|
|
XEmacPs_SetOperatingSpeed(xemacp, link_speed);
|
|
|
|
eth_device_linkchange(&zynqmp_eth_device.parent, RT_TRUE);
|
|
eth_link_status = ETH_LINK_UP;
|
|
LOG_D("Ethernet Link up");
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void phy_monitor_thread(void *parameter)
|
|
{
|
|
struct rt_zynqmp_eth *eth_dev = (struct rt_zynqmp_eth *)parameter;
|
|
|
|
while (1)
|
|
{
|
|
rt_zynqmp_eth_link_detect(eth_dev);
|
|
rt_thread_delay(RT_TICK_PER_SECOND);
|
|
}
|
|
}
|
|
|
|
static rt_err_t rt_zynqmp_eth_init(rt_device_t dev)
|
|
{
|
|
struct rt_zynqmp_eth *eth_dev = (struct rt_zynqmp_eth *)dev->user_data;
|
|
struct netif *netif = eth_dev->parent.netif;
|
|
struct xemac_s *xemac;
|
|
xemacpsif_s *xemacpsif;
|
|
u32 dmacrreg;
|
|
s32_t status = XST_SUCCESS;
|
|
struct xtopology_t *xtopologyp;
|
|
|
|
if (eth_dev->xemac != RT_NULL)
|
|
{
|
|
LOG_W("rt_zynqmp_eth_init: device has been initialized");
|
|
return -RT_ERROR;
|
|
}
|
|
|
|
NetIf = netif;
|
|
|
|
xemacpsif = rt_malloc(sizeof *xemacpsif);
|
|
if (xemacpsif == NULL)
|
|
{
|
|
LOG_E("rt_zynqmp_eth_init: out of memory");
|
|
return -RT_ENOMEM;
|
|
}
|
|
|
|
xemac = rt_malloc(sizeof *xemac);
|
|
if (xemac == NULL)
|
|
{
|
|
LOG_E("rt_zynqmp_eth_init: out of memory");
|
|
return -RT_ENOMEM;
|
|
}
|
|
|
|
xemac->state = (void *)xemacpsif;
|
|
xemac->topology_index = xtopology_find_index(MAC_BASE_ADDR);
|
|
xemac->type = xemac_type_emacps;
|
|
xemac->rt_eth_device = ð_dev->parent;
|
|
|
|
xemacpsif->send_q = NULL;
|
|
xemacpsif->recv_q = pq_create_queue();
|
|
if (!xemacpsif->recv_q)
|
|
return -RT_ENOMEM;
|
|
|
|
eth_dev->xemac = xemac;
|
|
|
|
/* obtain config of this emac */
|
|
mac_config = (XEmacPs_Config *)xemacps_lookup_config(MAC_BASE_ADDR);
|
|
|
|
status = XEmacPs_CfgInitialize(&xemacpsif->emacps, mac_config,
|
|
mac_config->BaseAddress);
|
|
if (status != XST_SUCCESS)
|
|
{
|
|
LOG_W("In %s:EmacPs Configuration Failed....", __func__);
|
|
return -RT_ERROR;
|
|
}
|
|
|
|
/* initialize the mac */
|
|
init_emacps(xemacpsif, netif);
|
|
|
|
dmacrreg = XEmacPs_ReadReg(xemacpsif->emacps.Config.BaseAddress,
|
|
XEMACPS_DMACR_OFFSET);
|
|
dmacrreg = dmacrreg | (0x00000010);
|
|
XEmacPs_WriteReg(xemacpsif->emacps.Config.BaseAddress,
|
|
XEMACPS_DMACR_OFFSET, dmacrreg);
|
|
|
|
setup_isr(xemac);
|
|
init_dma(xemac);
|
|
|
|
xtopologyp = &xtopology[xemac->topology_index];
|
|
/*
|
|
* Connect the device driver handler that will be called when an
|
|
* interrupt for the device occurs, the handler defined above performs
|
|
* the specific interrupt processing for the device.
|
|
*/
|
|
rt_hw_interrupt_install(xtopologyp->scugic_emac_intr, rt_hw_eth_isr, (void *)eth_dev, "eth");
|
|
/*
|
|
* Enable the interrupt for emacps.
|
|
*/
|
|
rt_hw_interrupt_umask(xtopologyp->scugic_emac_intr);
|
|
|
|
start_emacps(xemacpsif);
|
|
|
|
if (eth_link_status == ETH_LINK_UP)
|
|
eth_device_linkchange(ð_dev->parent, RT_TRUE);
|
|
|
|
rt_thread_t tid;
|
|
tid = rt_thread_create("phylnk",
|
|
phy_monitor_thread,
|
|
eth_dev,
|
|
1024,
|
|
RT_THREAD_PRIORITY_MAX - 2,
|
|
2);
|
|
if (tid != RT_NULL)
|
|
rt_thread_startup(tid);
|
|
else
|
|
return -RT_ERROR;
|
|
|
|
return RT_EOK;
|
|
}
|
|
|
|
static rt_err_t rt_zynqmp_eth_open(rt_device_t dev, rt_uint16_t oflag)
|
|
{
|
|
LOG_D("emac open");
|
|
return RT_EOK;
|
|
}
|
|
|
|
static rt_err_t rt_zynqmp_eth_close(rt_device_t dev)
|
|
{
|
|
LOG_D("emac close");
|
|
return RT_EOK;
|
|
}
|
|
|
|
static rt_ssize_t rt_zynqmp_eth_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size)
|
|
{
|
|
LOG_D("emac read");
|
|
rt_set_errno(-RT_ENOSYS);
|
|
return 0;
|
|
}
|
|
|
|
static rt_ssize_t rt_zynqmp_eth_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size)
|
|
{
|
|
LOG_D("emac write");
|
|
rt_set_errno(-RT_ENOSYS);
|
|
return 0;
|
|
}
|
|
|
|
static rt_err_t rt_zynqmp_eth_control(rt_device_t dev, int cmd, void *args)
|
|
{
|
|
struct rt_zynqmp_eth *eth_dev = (struct rt_zynqmp_eth *)dev->user_data;
|
|
switch (cmd)
|
|
{
|
|
case NIOCTL_GADDR:
|
|
/* get mac address */
|
|
if (args) rt_memcpy(args, eth_dev->dev_addr, 6);
|
|
else return -RT_ERROR;
|
|
break;
|
|
|
|
default :
|
|
break;
|
|
}
|
|
|
|
return RT_EOK;
|
|
}
|
|
|
|
extern err_t _unbuffered_low_level_output(xemacpsif_s *xemacpsif, struct pbuf *p);
|
|
rt_err_t rt_zynqmp_eth_tx(rt_device_t dev, struct pbuf *p)
|
|
{
|
|
rt_base_t lev;
|
|
rt_err_t err;
|
|
XEmacPs_BdRing *txring;
|
|
|
|
struct rt_zynqmp_eth *eth_dev = (struct rt_zynqmp_eth *)dev->user_data;
|
|
struct xemac_s *xemac = eth_dev->xemac;
|
|
xemacpsif_s *xemacpsif = (xemacpsif_s *)(xemac->state);
|
|
|
|
lev = rt_hw_interrupt_disable();
|
|
|
|
txring = &(XEmacPs_GetTxRing(&xemacpsif->emacps));
|
|
process_sent_bds(xemacpsif, txring);
|
|
|
|
if (is_tx_space_available(xemacpsif))
|
|
{
|
|
_unbuffered_low_level_output(xemacpsif, p);
|
|
err = RT_EOK;
|
|
}
|
|
else
|
|
{
|
|
#if LINK_STATS
|
|
lwip_stats.link.drop++;
|
|
#endif
|
|
LOG_D("pack dropped, no space");
|
|
err = -RT_ENOMEM;
|
|
}
|
|
|
|
rt_hw_interrupt_enable(lev);
|
|
|
|
return err;
|
|
}
|
|
|
|
struct pbuf *rt_zynqmp_eth_rx(rt_device_t dev)
|
|
{
|
|
rt_base_t lev;
|
|
struct rt_zynqmp_eth *eth_dev = (struct rt_zynqmp_eth *)dev->user_data;
|
|
struct xemac_s *xemac = eth_dev->xemac;
|
|
xemacpsif_s *xemacpsif = (xemacpsif_s *)(xemac->state);
|
|
struct pbuf *p;
|
|
|
|
lev = rt_hw_interrupt_disable();
|
|
|
|
/* see if there is data to process */
|
|
if (pq_qlength(xemacpsif->recv_q) == 0)
|
|
return NULL;
|
|
|
|
/* return one packet from receive q */
|
|
p = (struct pbuf *)pq_dequeue(xemacpsif->recv_q);
|
|
|
|
rt_hw_interrupt_enable(lev);
|
|
|
|
return p;
|
|
}
|
|
|
|
static int rt_hw_zynqmp_eth_init(void)
|
|
{
|
|
rt_err_t state = RT_EOK;
|
|
|
|
zynqmp_eth_device.xemac = RT_NULL;
|
|
|
|
zynqmp_eth_device.dev_addr[0] = 0x00;
|
|
zynqmp_eth_device.dev_addr[1] = 0x0A;
|
|
zynqmp_eth_device.dev_addr[2] = 0x35;
|
|
zynqmp_eth_device.dev_addr[3] = 0x00;
|
|
zynqmp_eth_device.dev_addr[4] = 0x01;
|
|
zynqmp_eth_device.dev_addr[5] = 0x02;
|
|
|
|
zynqmp_eth_device.parent.parent.init = rt_zynqmp_eth_init;
|
|
zynqmp_eth_device.parent.parent.open = rt_zynqmp_eth_open;
|
|
zynqmp_eth_device.parent.parent.close = rt_zynqmp_eth_close;
|
|
zynqmp_eth_device.parent.parent.read = rt_zynqmp_eth_read;
|
|
zynqmp_eth_device.parent.parent.write = rt_zynqmp_eth_write;
|
|
zynqmp_eth_device.parent.parent.control = rt_zynqmp_eth_control;
|
|
zynqmp_eth_device.parent.parent.user_data = &zynqmp_eth_device;
|
|
|
|
zynqmp_eth_device.parent.eth_rx = rt_zynqmp_eth_rx;
|
|
zynqmp_eth_device.parent.eth_tx = rt_zynqmp_eth_tx;
|
|
|
|
/* register eth device */
|
|
state = eth_device_init(&(zynqmp_eth_device.parent), "e0");
|
|
if (RT_EOK == state)
|
|
{
|
|
LOG_D("emac device init success");
|
|
}
|
|
else
|
|
{
|
|
LOG_E("emac device init faild: %d", state);
|
|
state = -RT_ERROR;
|
|
return state;
|
|
}
|
|
|
|
return state;
|
|
}
|
|
INIT_DEVICE_EXPORT(rt_hw_zynqmp_eth_init);
|
|
|