336 lines
8.7 KiB
C
336 lines
8.7 KiB
C
/* KSDK Ethernet Driver
|
|
*
|
|
* Copyright (c) 2016 ARM Ltd
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
/* The driver performs one shot PHY setup. There is no support for
|
|
* PHY disconnect, reconnect or configuration change. The PHY setup,
|
|
* implemented via KSDK contains polled code that can block the
|
|
* initialization thread for a few seconds.
|
|
*
|
|
* There is no statistics collection for either normal operation or
|
|
* error behaviour.
|
|
*/
|
|
|
|
#include <board.h>
|
|
#include <device.h>
|
|
#include <misc/util.h>
|
|
#include <nanokernel.h>
|
|
#include <net/ip/net_driver_ethernet.h>
|
|
|
|
#include "fsl_enet.h"
|
|
#include "fsl_phy.h"
|
|
#include "fsl_port.h"
|
|
|
|
#define SYS_LOG_DOMAIN "ETH KSDK"
|
|
#define SYS_LOG_LEVEL CONFIG_SYS_LOG_ETHERNET_LEVEL
|
|
#include <misc/sys_log.h>
|
|
|
|
struct eth_context {
|
|
enet_handle_t enet_handle;
|
|
struct nano_sem tx_buf_sem;
|
|
uint8_t mac_addr[6];
|
|
};
|
|
|
|
static int eth_net_tx(struct net_buf *);
|
|
static void eth_0_config_func(void);
|
|
|
|
static enet_rx_bd_struct_t __aligned(ENET_BUFF_ALIGNMENT)
|
|
rx_buffer_desc[CONFIG_ETH_KSDK_TX_BUFFERS];
|
|
|
|
static enet_tx_bd_struct_t __aligned(ENET_BUFF_ALIGNMENT)
|
|
tx_buffer_desc[CONFIG_ETH_KSDK_TX_BUFFERS];
|
|
|
|
/* Use ENET_FRAME_MAX_VALNFRAMELEN for VLAN frame size
|
|
* Use ENET_FRAME_MAX_FRAMELEN for ethernet frame size
|
|
*/
|
|
#define ETH_KSDK_BUFFER_SIZE \
|
|
ROUND_UP(ENET_FRAME_MAX_VALNFRAMELEN, ENET_BUFF_ALIGNMENT)
|
|
|
|
static uint8_t __aligned(ENET_BUFF_ALIGNMENT)
|
|
rx_buffer[CONFIG_ETH_KSDK_RX_BUFFERS][ETH_KSDK_BUFFER_SIZE];
|
|
|
|
static uint8_t __aligned(ENET_BUFF_ALIGNMENT)
|
|
tx_buffer[CONFIG_ETH_KSDK_TX_BUFFERS][ETH_KSDK_BUFFER_SIZE];
|
|
|
|
static int eth_tx(struct device *iface, struct net_buf *buf)
|
|
{
|
|
struct eth_context *context = iface->driver_data;
|
|
status_t status;
|
|
|
|
nano_sem_take(&context->tx_buf_sem, TICKS_UNLIMITED);
|
|
|
|
status = ENET_SendFrame(ENET, &context->enet_handle, uip_buf(buf),
|
|
uip_len(buf));
|
|
if (status) {
|
|
SYS_LOG_ERR("ENET_SendFrame error: %d\n", status);
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static void eth_rx(struct device *iface)
|
|
{
|
|
struct eth_context *context = iface->driver_data;
|
|
struct net_buf *buf;
|
|
uint32_t frame_length = 0;
|
|
status_t status;
|
|
|
|
status = ENET_GetRxFrameSize(&context->enet_handle, &frame_length);
|
|
if (status) {
|
|
enet_data_error_stats_t error_stats;
|
|
|
|
SYS_LOG_ERR("ENET_GetRxFrameSize return: %d", status);
|
|
|
|
ENET_GetRxErrBeforeReadFrame(&context->enet_handle,
|
|
&error_stats);
|
|
/* Flush the current read buffer. This operation can
|
|
* only report failure if there is no frame to flush,
|
|
* which cannot happen in this context.
|
|
*/
|
|
status = ENET_ReadFrame(ENET, &context->enet_handle, NULL, 0);
|
|
assert(status == kStatus_Success);
|
|
return;
|
|
}
|
|
|
|
buf = ip_buf_get_reserve_rx(0);
|
|
if (buf == NULL) {
|
|
/* We failed to get a receive buffer. We don't add
|
|
* any further logging here because the allocator
|
|
* issued a diagnostic when it failed to allocate.
|
|
*
|
|
* Flush the current read buffer. This operation can
|
|
* only report failure if there is no frame to flush,
|
|
* which cannot happen in this context.
|
|
*/
|
|
status = ENET_ReadFrame(ENET, &context->enet_handle, NULL, 0);
|
|
assert(status == kStatus_Success);
|
|
return;
|
|
}
|
|
|
|
if (net_buf_tailroom(buf) < frame_length) {
|
|
SYS_LOG_ERR("frame too large\n");
|
|
net_buf_unref(buf);
|
|
status = ENET_ReadFrame(ENET, &context->enet_handle, NULL, 0);
|
|
assert(status == kStatus_Success);
|
|
return;
|
|
}
|
|
|
|
status = ENET_ReadFrame(ENET, &context->enet_handle,
|
|
net_buf_add(buf, frame_length), frame_length);
|
|
if (status) {
|
|
SYS_LOG_ERR("ENET_ReadFrame failed: %d\n", status);
|
|
net_buf_unref(buf);
|
|
return;
|
|
}
|
|
|
|
uip_len(buf) = frame_length;
|
|
net_driver_ethernet_recv(buf);
|
|
}
|
|
|
|
void eth_callback(ENET_Type *base, enet_handle_t *handle, enet_event_t event,
|
|
void *param)
|
|
{
|
|
struct device *iface = param;
|
|
struct eth_context *context = iface->driver_data;
|
|
|
|
switch (event) {
|
|
case kENET_RxEvent:
|
|
eth_rx(iface);
|
|
break;
|
|
case kENET_TxEvent:
|
|
/* Free the TX buffer. */
|
|
nano_sem_give(&context->tx_buf_sem);
|
|
break;
|
|
case kENET_ErrEvent:
|
|
/* Error event: BABR/BABT/EBERR/LC/RL/UN/PLR. */
|
|
break;
|
|
case kENET_WakeUpEvent:
|
|
/* Wake up from sleep mode event. */
|
|
break;
|
|
#ifdef ENET_ENHANCEDBUFFERDESCRIPTOR_MODE
|
|
case kENET_TimeStampEvent:
|
|
/* Time stamp event. */
|
|
break;
|
|
case kENET_TimeStampAvailEvent:
|
|
/* Time stamp available event. */
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#if defined(CONFIG_ETH_KSDK_0_RANDOM_MAC)
|
|
static void generate_mac(uint8_t *mac_addr)
|
|
{
|
|
uint32_t entropy;
|
|
|
|
entropy = sys_rand32_get();
|
|
|
|
/* Locally administered, unicast */
|
|
mac_addr[0] = ((entropy >> 0) & 0xfc) | 0x02;
|
|
|
|
mac_addr[1] = entropy >> 8;
|
|
mac_addr[2] = entropy >> 16;
|
|
mac_addr[3] = entropy >> 24;
|
|
|
|
entropy = sys_rand32_get();
|
|
|
|
mac_addr[4] = entropy >> 0;
|
|
mac_addr[5] = entropy >> 8;
|
|
}
|
|
#endif
|
|
|
|
static int eth_0_init(struct device *dev)
|
|
{
|
|
struct eth_context *context = dev->driver_data;
|
|
enet_config_t enet_config;
|
|
uint32_t sys_clock;
|
|
const uint32_t phy_addr = 0x0;
|
|
bool link;
|
|
status_t status;
|
|
int result;
|
|
enet_buffer_config_t buffer_config = {
|
|
.rxBdNumber = CONFIG_ETH_KSDK_RX_BUFFERS,
|
|
.txBdNumber = CONFIG_ETH_KSDK_TX_BUFFERS,
|
|
.rxBuffSizeAlign = ETH_KSDK_BUFFER_SIZE,
|
|
.txBuffSizeAlign = ETH_KSDK_BUFFER_SIZE,
|
|
.rxBdStartAddrAlign = rx_buffer_desc,
|
|
.txBdStartAddrAlign = tx_buffer_desc,
|
|
.rxBufferAlign = rx_buffer[0],
|
|
.txBufferAlign = tx_buffer[0],
|
|
};
|
|
|
|
nano_sem_init(&context->tx_buf_sem);
|
|
for (int i = 0; i < CONFIG_ETH_KSDK_TX_BUFFERS; i++) {
|
|
nano_sem_give(&context->tx_buf_sem);
|
|
}
|
|
|
|
sys_clock = CLOCK_GetFreq(kCLOCK_CoreSysClk);
|
|
|
|
ENET_GetDefaultConfig(&enet_config);
|
|
enet_config.interrupt |= kENET_RxFrameInterrupt;
|
|
enet_config.interrupt |= kENET_TxFrameInterrupt;
|
|
|
|
status = PHY_Init(ENET, phy_addr, sys_clock);
|
|
if (status) {
|
|
SYS_LOG_ERR("PHY_Init() failed: %d", status);
|
|
return 1;
|
|
}
|
|
|
|
PHY_GetLinkStatus(ENET, phy_addr, &link);
|
|
if (link) {
|
|
phy_speed_t phy_speed;
|
|
phy_duplex_t phy_duplex;
|
|
|
|
PHY_GetLinkSpeedDuplex(ENET, phy_addr, &phy_speed, &phy_duplex);
|
|
enet_config.miiSpeed = (enet_mii_speed_t) phy_speed;
|
|
enet_config.miiDuplex = (enet_mii_duplex_t) phy_duplex;
|
|
|
|
SYS_LOG_INF("Enabled %dM %s-duplex mode.",
|
|
(phy_speed ? 100 : 10),
|
|
(phy_duplex ? "full" : "half"));
|
|
} else {
|
|
SYS_LOG_INF("Link down.");
|
|
}
|
|
|
|
#if defined(CONFIG_ETH_KSDK_0_RANDOM_MAC)
|
|
generate_mac(context->mac_addr);
|
|
#endif
|
|
|
|
ENET_Init(ENET,
|
|
&context->enet_handle,
|
|
&enet_config,
|
|
&buffer_config,
|
|
context->mac_addr,
|
|
sys_clock);
|
|
|
|
SYS_LOG_DBG("MAC %2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x",
|
|
context->mac_addr[0], context->mac_addr[1],
|
|
context->mac_addr[2], context->mac_addr[3],
|
|
context->mac_addr[4], context->mac_addr[5]);
|
|
|
|
result = net_set_mac(context->mac_addr, sizeof(context->mac_addr));
|
|
if (result) {
|
|
return 1;
|
|
}
|
|
|
|
ENET_SetCallback(&context->enet_handle, eth_callback, dev);
|
|
net_driver_ethernet_register_tx(eth_net_tx);
|
|
eth_0_config_func();
|
|
ENET_ActiveRead(ENET);
|
|
return 0;
|
|
}
|
|
|
|
static void eth_ksdk_rx_isr(void *p)
|
|
{
|
|
struct device *dev = p;
|
|
struct eth_context *context = dev->driver_data;
|
|
|
|
ENET_ReceiveIRQHandler(ENET, &context->enet_handle);
|
|
}
|
|
|
|
static void eth_ksdk_tx_isr(void *p)
|
|
{
|
|
struct device *dev = p;
|
|
struct eth_context *context = dev->driver_data;
|
|
|
|
ENET_TransmitIRQHandler(ENET, &context->enet_handle);
|
|
}
|
|
|
|
static void eth_ksdk_error_isr(void *p)
|
|
{
|
|
struct device *dev = p;
|
|
struct eth_context *context = dev->driver_data;
|
|
|
|
ENET_ErrorIRQHandler(ENET, &context->enet_handle);
|
|
}
|
|
|
|
static struct eth_context eth_0_context = {
|
|
#if !defined(CONFIG_ETH_KSDK_0_RANDOM_MAC)
|
|
.mac_addr = {
|
|
CONFIG_ETH_KSDK_0_MAC0,
|
|
CONFIG_ETH_KSDK_0_MAC1,
|
|
CONFIG_ETH_KSDK_0_MAC2,
|
|
CONFIG_ETH_KSDK_0_MAC3,
|
|
CONFIG_ETH_KSDK_0_MAC4,
|
|
CONFIG_ETH_KSDK_0_MAC5
|
|
}
|
|
#endif
|
|
};
|
|
|
|
DEVICE_INIT(eth_ksdk_0, CONFIG_ETH_KSDK_0_NAME, eth_0_init,
|
|
ð_0_context, NULL,
|
|
NANOKERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE);
|
|
|
|
static int eth_net_tx(struct net_buf *buf)
|
|
{
|
|
return eth_tx(DEVICE_GET(eth_ksdk_0), buf);
|
|
}
|
|
|
|
static void eth_0_config_func(void)
|
|
{
|
|
IRQ_CONNECT(IRQ_ETH_RX, CONFIG_ETH_KSDK_0_IRQ_PRI,
|
|
eth_ksdk_rx_isr, DEVICE_GET(eth_ksdk_0), 0);
|
|
irq_enable(IRQ_ETH_RX);
|
|
|
|
IRQ_CONNECT(IRQ_ETH_TX, CONFIG_ETH_KSDK_0_IRQ_PRI,
|
|
eth_ksdk_tx_isr, DEVICE_GET(eth_ksdk_0), 0);
|
|
irq_enable(IRQ_ETH_TX);
|
|
|
|
IRQ_CONNECT(IRQ_ETH_ERR_MISC, CONFIG_ETH_KSDK_0_IRQ_PRI,
|
|
eth_ksdk_error_isr, DEVICE_GET(eth_ksdk_0), 0);
|
|
irq_enable(IRQ_ETH_ERR_MISC);
|
|
}
|