rt-thread-official/bsp/imx/imx6ull-smart/drivers/drv_eth.c

569 lines
17 KiB
C

/*
* Copyright (c) 2006-2023, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2021-06-16 songchao support emac driver
* 2021-06-29 songchao add phy link detect
* 2021-08-13 songchao support dual mac and reduse copy
*/
#include "drv_eth.h"
#define DBG_TAG "drv.enet"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#define BSP_USING_IMX6ULL_ART_PI
#if (defined(RT_USING_ENET1)) || (defined(RT_USING_ENET2))
#ifdef BSP_USING_IMX6ULL_ART_PI
static struct imx6ull_iomuxc mdio_gpio[2] =
{
{IOMUXC_GPIO1_IO06_ENET1_MDIO,0U,0xB029},
{IOMUXC_GPIO1_IO07_ENET1_MDC,0U,0xB0E9}
};
#else
static struct imx6ull_iomuxc mdio_gpio[2] =
{
{IOMUXC_GPIO1_IO06_ENET2_MDIO,0U,0xB029},
{IOMUXC_GPIO1_IO07_ENET2_MDC,0U,0xB0E9},
};
#endif
enum
{
#ifdef RT_USING_ENET1
DEV_ENET1,
#endif
#ifdef RT_USING_ENET2
DEV_ENET2,
#endif
DEV_ENET_MAX,
};
static struct rt_imx6ul_ethps _imx6ul_eth_device[DEV_ENET_MAX] =
{
#ifdef RT_USING_ENET1
{
.dev_addr = {0xa8,0x5e,0x45,0x91,0x92,0x93},
.mac_name = "e1",
.irq_name = "emac1_intr",
.enet_phy_base_addr = ENET1,
.irq_num = IMX_INT_ENET1,
.phy_num = ENET_PHY1,
.mac_num = 1,
.phy_base_addr = GPIO5,
.phy_gpio_pin = 9,
.phy_id = 7,
.buffConfig =
{
ENET_RXBD_NUM,
ENET_TXBD_NUM,
ENET_RXBUFF_ALIGN_SIZE,
ENET_TXBUFF_ALIGN_SIZE,
RT_NULL,
RT_NULL,
RT_NULL,
RT_NULL,
RT_NULL,
RT_NULL,
RT_NULL,
RT_NULL,
ENET_RXBUFF_TOTAL_SIZE,
ENET_TXBUFF_TOTAL_SIZE
},
.gpio =
{
{IOMUXC_SNVS_SNVS_TAMPER9_GPIO5_IO09,0U,0x110B0},
{IOMUXC_ENET1_RX_DATA0_ENET1_RDATA00,0U,0xB0E9},
{IOMUXC_ENET1_RX_DATA1_ENET1_RDATA01,0U,0xB0E9},
{IOMUXC_ENET1_RX_EN_ENET1_RX_EN,0U,0xB0E9},
{IOMUXC_ENET1_RX_ER_ENET1_RX_ER,0U,0xB0E9},
{IOMUXC_ENET1_TX_CLK_ENET1_REF_CLK1,1U,0x00F0},
{IOMUXC_ENET1_TX_DATA0_ENET1_TDATA00,0U,0xB0E9},
{IOMUXC_ENET1_TX_DATA1_ENET1_TDATA01,0U,0xB0E9},
{IOMUXC_ENET1_TX_EN_ENET1_TX_EN,0U,0xB0E9}
}
},
#endif
#ifdef RT_USING_ENET2
{
.dev_addr = {0xa8,0x5e,0x45,0x01,0x02,0x03},
.mac_name = "e2",
.irq_name = "emac2_intr",
.enet_phy_base_addr = ENET2,
.irq_num = IMX_INT_ENET2,
.phy_num = ENET_PHY2,
.mac_num = 2,
.phy_base_addr = GPIO5,
.phy_gpio_pin = 6,
.phy_id = 7,
.buffConfig =
{
ENET_RXBD_NUM,
ENET_TXBD_NUM,
ENET_RXBUFF_ALIGN_SIZE,
ENET_TXBUFF_ALIGN_SIZE,
RT_NULL,
RT_NULL,
RT_NULL,
RT_NULL,
RT_NULL,
RT_NULL,
RT_NULL,
RT_NULL,
ENET_RXBUFF_TOTAL_SIZE,
ENET_TXBUFF_TOTAL_SIZE
},
.gpio =
{
{IOMUXC_SNVS_SNVS_TAMPER6_GPIO5_IO06,0U,0x110B0},
{IOMUXC_ENET2_RX_DATA0_ENET2_RDATA00,0U,0xB0E9},
{IOMUXC_ENET2_RX_DATA1_ENET2_RDATA01,0U,0xB0E9},
{IOMUXC_ENET2_RX_EN_ENET2_RX_EN,0U,0xB0E9},
{IOMUXC_ENET2_RX_ER_ENET2_RX_ER,0U,0xB0E9},
{IOMUXC_ENET2_TX_CLK_ENET2_REF_CLK2,1U,0x00F0},
{IOMUXC_ENET2_TX_DATA0_ENET2_TDATA00,0U,0xB0E9},
{IOMUXC_ENET2_TX_DATA1_ENET2_TDATA01,0U,0xB0E9},
{IOMUXC_ENET2_TX_EN_ENET2_TX_EN,0U,0xB0E9}
}
},
#endif
};
void imx6ul_eth_link_change(struct rt_imx6ul_ethps *imx6ul_device,rt_bool_t up)
{
if(up)
{
LOG_D("enet%d link up",imx6ul_device->mac_num);
eth_device_linkchange(&imx6ul_device->parent, RT_TRUE);
imx6ul_device->phy_link_status = RT_TRUE;
}
else
{
LOG_D("enet%d link down",imx6ul_device->mac_num);
eth_device_linkchange(&imx6ul_device->parent, RT_FALSE);
imx6ul_device->phy_link_status = RT_FALSE;
}
}
void ENET_InitModuleClock(void)
{
const clock_enet_pll_config_t config = {true, true, false, 1, 1};
CLOCK_InitEnetPll(&config);
}
rt_err_t enet_buffer_init(enet_buffer_config_t *buffConfig)
{
void *tx_buff_addr = RT_NULL;
void *rx_buff_addr = RT_NULL;
void *tx_bd_addr = RT_NULL;
void *rx_bd_addr = RT_NULL;
if(((SYS_PAGE_SIZE<<RX_BUFFER_INDEX_NUM)<buffConfig->rxBufferTotalSize)||
((SYS_PAGE_SIZE<<TX_BUFFER_INDEX_NUM)<buffConfig->txBufferTotalSize))
{
LOG_E("ERROR: alloc mem not enough for enet driver");
return RT_ERROR;
}
rx_buff_addr = rt_pages_alloc(RX_BUFFER_INDEX_NUM);
if(!rx_buff_addr)
{
LOG_E("ERROR: rx buff page alloc failed");
return RT_ERROR;
}
buffConfig->rxBufferAlign = (void *)rt_ioremap_nocache(virtual_to_physical(rx_buff_addr), (SYS_PAGE_SIZE<<RX_BUFFER_INDEX_NUM));
buffConfig->rxPhyBufferAlign = (void *)virtual_to_physical(rx_buff_addr);
tx_buff_addr = rt_pages_alloc(TX_BUFFER_INDEX_NUM);
if(!tx_buff_addr)
{
LOG_E("ERROR: tx buff page alloc failed");
return RT_ERROR;
}
buffConfig->txBufferAlign = (void *)rt_ioremap_nocache(virtual_to_physical(tx_buff_addr), (SYS_PAGE_SIZE<<TX_BUFFER_INDEX_NUM));
buffConfig->txPhyBufferAlign = (void *)virtual_to_physical(tx_buff_addr);
rx_bd_addr = rt_pages_alloc(RX_BD_INDEX_NUM);
if(!rx_bd_addr)
{
LOG_E("ERROR: rx bd page alloc failed");
return RT_ERROR;
}
buffConfig->rxBdStartAddrAlign = (void *)rt_ioremap_nocache(virtual_to_physical(rx_bd_addr), (SYS_PAGE_SIZE<<RX_BD_INDEX_NUM));
buffConfig->rxPhyBdStartAddrAlign = virtual_to_physical(rx_bd_addr);
tx_bd_addr = rt_pages_alloc(TX_BD_INDEX_NUM);
if(!tx_bd_addr)
{
LOG_E("ERROR: tx bd page alloc failed");
return RT_ERROR;
}
buffConfig->txBdStartAddrAlign = (void *)rt_ioremap_nocache(virtual_to_physical(tx_bd_addr), (SYS_PAGE_SIZE<<TX_BD_INDEX_NUM));
buffConfig->txPhyBdStartAddrAlign = virtual_to_physical(tx_bd_addr);
return RT_EOK;
}
/* EMAC initialization function */
static rt_err_t rt_imx6ul_eth_init(rt_device_t dev)
{
rt_err_t state;
struct rt_imx6ul_ethps *imx6ul_device = (struct rt_imx6ul_ethps *)dev;
ENET_Type *base_addr = RT_NULL;
enet_config_t *config;
enet_handle_t *handle;
enet_buffer_config_t *buffConfig;
rt_uint32_t reg_value;
imx6ul_device->enet_virtual_base_addr = (ENET_Type *)rt_ioremap((void *)imx6ul_device->enet_phy_base_addr,SYS_PAGE_SIZE);
base_addr = imx6ul_device->enet_virtual_base_addr;
config = &imx6ul_device->config;
handle = &imx6ul_device->handle;
buffConfig = &imx6ul_device->buffConfig;
for (int i=0; i<GET_ARRAY_NUM(imx6ul_device->gpio); i++)
{
imx6ull_gpio_init(&imx6ul_device->gpio[i]);
}
IOMUXC_GPR_Type *GPR1 = (IOMUXC_GPR_Type *)rt_ioremap((void *)IOMUXC_GPR,0x1000);
if(imx6ul_device->mac_num == 1)
{
reg_value = GPR1->GPR1;
reg_value &= ~(IOMUXC_GPR_GPR1_ENET1_CLK_SEL_MASK
| IOMUXC_GPR_GPR1_ENET1_CLK_SEL_MASK);
reg_value |= IOMUXC_GPR_GPR1_ENET1_TX_CLK_DIR(1);
reg_value |= IOMUXC_GPR_GPR1_ENET1_CLK_SEL(0);
GPR1->GPR1 = reg_value;
}
else if(imx6ul_device->mac_num == 2)
{
reg_value = GPR1->GPR1;
reg_value &= ~(IOMUXC_GPR_GPR1_ENET2_CLK_SEL_MASK
| IOMUXC_GPR_GPR1_ENET2_CLK_SEL_MASK);
reg_value |= IOMUXC_GPR_GPR1_ENET2_TX_CLK_DIR(1);
reg_value |= IOMUXC_GPR_GPR1_ENET2_CLK_SEL(0);
GPR1->GPR1 = reg_value;
}
ENET_InitModuleClock();
ENET_GetDefaultConfig(config);
config->interrupt |= (ENET_RX_INTERRUPT);
state = enet_buffer_init(buffConfig);
if(state != RT_EOK)
{
return state;
}
ENET_Init(base_addr, handle, config, buffConfig, &imx6ul_device->dev_addr[0], SYS_CLOCK_HZ);
ENET_ActiveRead(base_addr);
rt_hw_interrupt_install(imx6ul_device->irq_num, (rt_isr_handler_t)ENET_DriverIRQHandler, (void *)base_addr,imx6ul_device->irq_name);
rt_hw_interrupt_umask(imx6ul_device->irq_num);
return RT_EOK;
}
static rt_err_t rt_imx6ul_eth_control(rt_device_t dev, int cmd, void *args)
{
struct rt_imx6ul_ethps *imx6ul_device = (struct rt_imx6ul_ethps *)dev;
switch (cmd)
{
case NIOCTL_GADDR:
/* get MAC address */
if (args)
{
OCOTP_Type *ocotp_base;
rt_uint32_t uid[2];
rt_uint32_t uid_crc = 0;
ocotp_base = (OCOTP_Type *)rt_ioremap((void*)OCOTP_BASE, 0x1000);
uid[0] = ocotp_base->CFG0;
uid[1] = ocotp_base->CFG1;
rt_iounmap(ocotp_base);
LOG_D("UNIQUE_ID is %x%x",uid[0], uid[1]);
uid_crc = uid[0] - uid[1];
LOG_D("UNIQUE_ID change to 32 bits %x", uid_crc);
if (imx6ul_device->enet_phy_base_addr == ENET1)
{
imx6ul_device->dev_addr[0] = 0xa8;
imx6ul_device->dev_addr[1] = 0x5e;
imx6ul_device->dev_addr[2] = 0x45;
imx6ul_device->dev_addr[3] = (uid_crc>>16) & 0x7f;
imx6ul_device->dev_addr[4] = (uid_crc>>8) & 0xff;
imx6ul_device->dev_addr[5] = uid_crc & 0xff;
}
else /*if (imx6ul_device->enet_phy_base_addr == ENET2)*/
{
imx6ul_device->dev_addr[0] = 0xa8;
imx6ul_device->dev_addr[1] = 0x5e;
imx6ul_device->dev_addr[2] = 0x46;
imx6ul_device->dev_addr[3] = (uid_crc >> 16) & 0x7f;
imx6ul_device->dev_addr[4] = (uid_crc >> 8) & 0xff;
imx6ul_device->dev_addr[5] = uid_crc & 0xff;
}
rt_memcpy(args, imx6ul_device->dev_addr, MAX_ADDR_LEN);
}
else
{
return -RT_ERROR;
}
break;
default :
break;
}
return RT_EOK;
}
static status_t read_data_from_eth(rt_device_t dev,void *read_data,uint16_t *read_length)
{
status_t status = 0;
uint16_t length = 0;
ENET_Type *base_addr = RT_NULL;
enet_config_t *config;
enet_handle_t *handle;
enet_buffer_config_t *buffConfig;
struct rt_imx6ul_ethps *imx6ul_device = (struct rt_imx6ul_ethps *)dev;
base_addr = imx6ul_device->enet_virtual_base_addr;
config = &imx6ul_device->config;
handle = &imx6ul_device->handle;
buffConfig = &imx6ul_device->buffConfig;
/* Get the Frame size */
status = ENET_ReadFrame(base_addr,handle,config,read_data,&length);
if((status == kStatus_ENET_RxFrameEmpty)||(status == kStatus_ENET_RxFrameError))
{
ENET_EnableInterrupts(base_addr,ENET_RX_INTERRUPT);
if(status == kStatus_ENET_RxFrameError)
{
/*recv error happend reinitialize mac*/
ENET_Init(base_addr, handle, config, buffConfig, &imx6ul_device->dev_addr[0], SYS_CLOCK_HZ);
ENET_ActiveRead(base_addr);
return kStatus_ENET_RxFrameError;
}
else if(status == kStatus_ENET_RxFrameEmpty)
{
return kStatus_ENET_RxFrameEmpty;
}
}
*read_length = length;
return status;
}
/* transmit data*/
rt_err_t rt_imx6ul_eth_tx(rt_device_t dev, struct pbuf *p)
{
rt_err_t ret = RT_ERROR;
struct pbuf *q = RT_NULL;
uint16_t offset = 0;
uint32_t last_flag = 0;
status_t status;
ENET_Type *base_addr = RT_NULL;
enet_handle_t *handle;
struct rt_imx6ul_ethps *imx6ul_device = (struct rt_imx6ul_ethps *)dev;
base_addr = imx6ul_device->enet_virtual_base_addr;
handle = &imx6ul_device->handle;
RT_ASSERT(p);
for(q = p;q != RT_NULL;q=q->next)
{
if(q->next == RT_NULL)
{
last_flag = 1;
}
else
{
last_flag = 0;
}
status = ENET_SendFrame(base_addr, handle, q->payload, q->len,last_flag);
offset = offset + q->len;
if(status == kStatus_Success)
{
}
else
{
return RT_ERROR;
}
}
if(offset > ENET_FRAME_MAX_FRAMELEN)
{
LOG_E("net error send length %d exceed max length",offset);
}
return ret;
}
struct pbuf *rt_imx6ul_eth_rx(rt_device_t dev)
{
static struct pbuf *p_s = RT_NULL;
struct pbuf *p = RT_NULL;
status_t status;
uint16_t length = 0;
if(p_s == RT_NULL)
{
p_s = pbuf_alloc(PBUF_RAW, ENET_FRAME_MAX_FRAMELEN, PBUF_POOL);
if(p_s == RT_NULL)
{
return RT_NULL;
}
}
p = p_s;
status = read_data_from_eth(dev,p->payload,&length);
if(status == kStatus_ENET_RxFrameEmpty)
{
return RT_NULL;
}
else if(status == kStatus_ENET_RxFrameError)
{
return RT_NULL;
}
if(length > ENET_FRAME_MAX_FRAMELEN)
{
LOG_E("net error recv length %d exceed max length",length);
return RT_NULL;
}
pbuf_realloc(p, length);
p_s = RT_NULL;
return p;
}
int32_t get_instance_by_base(void *base)
{
int32_t i = 0;
int32_t instance = 0;
for(i = 0; i < DEV_ENET_MAX; i ++)
{
if((void *)_imx6ul_eth_device[i].enet_virtual_base_addr == base)
{
break;
}
}
if(i == DEV_ENET_MAX)
{
return -1;
}
return instance;
}
void rx_enet_callback(void *base)
{
int32_t instance = 0;
instance = get_instance_by_base(base);
if(instance == -1)
{
LOG_E("interrput match base addr error");
return;
}
eth_device_ready(&(_imx6ul_eth_device[instance].parent));
ENET_DisableInterrupts(base,ENET_RX_INTERRUPT);
}
void tx_enet_callback(void *base)
{
ENET_DisableInterrupts(base,ENET_TX_INTERRUPT);
}
/*phy link detect thread*/
static void phy_detect_thread_entry(void *param)
{
bool link = false;
phy_speed_t speed;
phy_duplex_t duplex;
ENET_Type *base_addr = RT_NULL;
struct rt_imx6ul_ethps *imx6ul_device = (struct rt_imx6ul_ethps *)param;
base_addr = imx6ul_device->enet_virtual_base_addr;
phy_reset(imx6ul_device->phy_base_addr,imx6ul_device->phy_gpio_pin);
PHY_Init(base_addr, imx6ul_device->phy_num, SYS_CLOCK_HZ,imx6ul_device->phy_id);
PHY_GetLinkStatus(base_addr, imx6ul_device->phy_num, &link);
if (link)
{
/* Get the actual PHY link speed. */
PHY_GetLinkSpeedDuplex(base_addr, imx6ul_device->phy_num, &speed, &duplex);
/* Change the MII speed and duplex for actual link status. */
imx6ul_device->config.miiSpeed = (enet_mii_speed_t)speed;
imx6ul_device->config.miiDuplex = (enet_mii_duplex_t)duplex;
}
else
{
LOG_W("PHY Link down, please check the cable connection and link partner setting.");
}
while(1)
{
PHY_GetLinkStatus(base_addr, imx6ul_device->phy_num, &link);
if(link != imx6ul_device->phy_link_status)
{
if(link == true)
{
PHY_StartNegotiation(base_addr,imx6ul_device->phy_num);
}
imx6ul_eth_link_change(imx6ul_device,link);
}
rt_thread_delay(DETECT_DELAY_ONE_SECOND);
}
}
_internal_ro struct rt_device_ops _k_enet_ops =
{
.init = rt_imx6ul_eth_init,
.control = rt_imx6ul_eth_control,
};
static int imx6ul_eth_init(void)
{
rt_err_t state = RT_EOK;
char link_detect[10];
imx6ull_gpio_init(&mdio_gpio[0]);
imx6ull_gpio_init(&mdio_gpio[1]);
for (int idx=0; idx<GET_ARRAY_NUM(_imx6ul_eth_device); idx++)
{
_imx6ul_eth_device[idx].parent.parent.ops = &_k_enet_ops;
_imx6ul_eth_device[idx].parent.eth_rx = rt_imx6ul_eth_rx;
_imx6ul_eth_device[idx].parent.eth_tx = rt_imx6ul_eth_tx;
_imx6ul_eth_device[idx].phy_link_status = RT_FALSE;
/* register eth device */
state = eth_device_init(&(_imx6ul_eth_device[idx].parent), _imx6ul_eth_device[idx].mac_name);
if (RT_EOK == state)
{
LOG_I("emac device init success");
}
else
{
LOG_E("emac device init faild: %d", state);
state = -RT_ERROR;
}
rt_sprintf(link_detect,"link_d%d",_imx6ul_eth_device[idx].mac_num);
/* start phy link detect */
rt_thread_t phy_link_tid;
phy_link_tid = rt_thread_create(link_detect,
phy_detect_thread_entry,
&_imx6ul_eth_device[idx],
4096,
RT_THREAD_PRIORITY_MAX - 2,
2);
if (phy_link_tid != RT_NULL)
{
rt_thread_startup(phy_link_tid);
}
memset(link_detect,0,sizeof(link_detect));
}
return state;
}
INIT_DEVICE_EXPORT(imx6ul_eth_init);
#endif