rfc: ksdk: Add KSDK ENET driver.
Provide a network driver wrapped around the KSDK ENET and PHY drivers. This driver is functional, just. It is not in a fit state to be merged to master yet. Origin: Original Change-Id: Id29e756f33c6c0263926139188c49f9a9c3d5e09 Signed-off-by: Marcus Shawcroft <marcus.shawcroft@arm.com>
This commit is contained in:
parent
ec60dcc4c1
commit
581e15ced2
4 changed files with 477 additions and 0 deletions
|
@ -93,3 +93,4 @@ config ETH_DW_0_IRQ_PRI
|
|||
endif # ETH_DW
|
||||
|
||||
source "drivers/ethernet/Kconfig.enc28j60"
|
||||
source "drivers/ethernet/Kconfig.ksdk"
|
||||
|
|
89
drivers/ethernet/Kconfig.ksdk
Normal file
89
drivers/ethernet/Kconfig.ksdk
Normal file
|
@ -0,0 +1,89 @@
|
|||
# Kconfig - ETH_KSDK Ethernet driver configuration options
|
||||
|
||||
# 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.
|
||||
|
||||
menuconfig ETH_KSDK
|
||||
bool
|
||||
prompt "KSDK Ethernet driver"
|
||||
depends on ETHERNET && HAS_KSDK
|
||||
default n
|
||||
help
|
||||
Enable KSDK Ethernet driver.
|
||||
|
||||
if ETH_KSDK
|
||||
config ETH_KSDK_0
|
||||
bool "KSDK Ethernet port 0"
|
||||
default n
|
||||
help
|
||||
Include port 0 driver
|
||||
|
||||
config ETH_KSDK_0_NAME
|
||||
string "Driver name"
|
||||
depends on ETH_KSDK_0
|
||||
default "ETH_0"
|
||||
|
||||
config ETH_KSDK_0_IRQ_PRI
|
||||
int "Controller interrupt priority"
|
||||
depends on ETH_KSDK_0
|
||||
default 0
|
||||
help
|
||||
IRQ priority
|
||||
|
||||
config ETH_KSDK_0_RANDOM_MAC
|
||||
bool "Random MAC address"
|
||||
depends on ETH_KSDK_0 && RANDOM_GENERATOR
|
||||
default y
|
||||
help
|
||||
Generate a random MAC address dynamically.
|
||||
|
||||
if ETH_KSDK_0 && ! ETH_KSDK_0_RANDOM_MAC
|
||||
|
||||
config ETH_KSDK_0_MAC0
|
||||
hex "MAC Address Byte 0"
|
||||
default 0
|
||||
help
|
||||
This is byte 0 of the MAC address.
|
||||
|
||||
config ETH_KSDK_0_MAC1
|
||||
hex "MAC Address Byte 1"
|
||||
default 0
|
||||
help
|
||||
This is the byte 1 of the MAC address.
|
||||
|
||||
config ETH_KSDK_0_MAC2
|
||||
hex "MAC Address Byte 2"
|
||||
default 0
|
||||
help
|
||||
This is the byte 2 of the MAC address.
|
||||
|
||||
config ETH_KSDK_0_MAC3
|
||||
hex "MAC Address Byte 3"
|
||||
default 0
|
||||
help
|
||||
This is the byte 3 of the MAC address.
|
||||
|
||||
config ETH_KSDK_0_MAC4
|
||||
hex "MAC Address Byte 4"
|
||||
default 0
|
||||
help
|
||||
This is the byte 4 of the MAC address.
|
||||
|
||||
config ETH_KSDK_0_MAC5
|
||||
hex "MAC Address Byte 5"
|
||||
default 0
|
||||
help
|
||||
This is the byte 5 of the MAC address.
|
||||
endif
|
||||
endif
|
|
@ -6,3 +6,4 @@ ccflags-y += -I${srctree}
|
|||
|
||||
obj-$(CONFIG_ETH_DW) += eth_dw.o
|
||||
obj-$(CONFIG_ETH_ENC28J60) += eth_enc28j60.o
|
||||
obj-$(CONFIG_ETH_KSDK) += eth_ksdk.o
|
||||
|
|
386
drivers/ethernet/eth_ksdk.c
Normal file
386
drivers/ethernet/eth_ksdk.c
Normal file
|
@ -0,0 +1,386 @@
|
|||
#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>
|
||||
|
||||
|
||||
/* todo: Why does KSDK not export prototypes for these, perhaps this is not
|
||||
* an appropriate interface?
|
||||
*/
|
||||
extern void ENET_Error_IRQHandler(void);
|
||||
extern void ENET_Receive_IRQHandler(void);
|
||||
extern void ENET_Transmit_IRQHandler(void);
|
||||
|
||||
/* todo: Can we unify the driver buffers with the uip rx and tx buffers ? */
|
||||
#define ENET_RX_RING_LEN 2
|
||||
#define ENET_TX_RING_LEN 2
|
||||
|
||||
#if CONFIG_ETH_KSDK_0
|
||||
|
||||
typedef void (*eth_config_irq_t)(struct device *port);
|
||||
|
||||
struct eth_config {
|
||||
enet_handle_t enet_handle;
|
||||
uint32_t base_addr;
|
||||
eth_config_irq_t config_func;
|
||||
};
|
||||
|
||||
struct eth_runtime {
|
||||
struct nano_sem tx_buf_sem;
|
||||
};
|
||||
|
||||
static int eth_net_tx(struct net_buf *buf);
|
||||
|
||||
static int eth_tx(struct device *port, struct net_buf *buf)
|
||||
{
|
||||
struct eth_runtime *context = port->driver_data;
|
||||
struct eth_config *config = port->config->config_info;
|
||||
status_t status;
|
||||
|
||||
nano_sem_take(&context->tx_buf_sem, TICKS_UNLIMITED);
|
||||
|
||||
if (uip_len(buf) > UIP_BUFSIZE) {
|
||||
SYS_LOG_ERR("Frame too large to TX: %u\n", uip_len(buf));
|
||||
return -1;
|
||||
}
|
||||
|
||||
status = ENET_SendFrame(ENET, &config->enet_handle, uip_buf(buf),
|
||||
uip_len(buf));
|
||||
if (status) {
|
||||
SYS_LOG_ERR("ENET_SendFrame error: %d\n", status);
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* todo: This is device specific setup, it doesn't belong here, it
|
||||
* needs to move out to somewhere device specific, but where ?
|
||||
*
|
||||
* todo: This setup code needs scrutiny by someone familiar with K64F.
|
||||
*/
|
||||
|
||||
static void
|
||||
k64f_init_eth_hardware(void)
|
||||
{
|
||||
port_pin_config_t config;
|
||||
|
||||
memset(&config, 0, sizeof(config));
|
||||
|
||||
PORT_SetPinMux(PORTA, 5u, kPORT_MuxAlt4); /* RMII0_RXER/MII0_RXER */
|
||||
PORT_SetPinMux(PORTA, 12u, kPORT_MuxAlt4); /* RMII0_RXD1/MII0_RXD1 */
|
||||
PORT_SetPinMux(PORTA, 13u, kPORT_MuxAlt4); /* RMII0_RXD0/MII0_RXD0 */
|
||||
PORT_SetPinMux(PORTA, 14u, kPORT_MuxAlt4); /* RMII0_CRS_DV/MII0_RXDV */
|
||||
PORT_SetPinMux(PORTA, 15u, kPORT_MuxAlt4); /* RMII0_TXEN/MII0_TXEN */
|
||||
PORT_SetPinMux(PORTA, 16u, kPORT_MuxAlt4); /* RMII0_TXD0/MII0_TXD0 */
|
||||
PORT_SetPinMux(PORTA, 17u, kPORT_MuxAlt4); /* RMII0_TXD1/MII0_TXD1 */
|
||||
PORT_SetPinMux(PORTA, 28u, kPORT_MuxAlt4); /* MII0_TXER */
|
||||
CLOCK_EnableClock(kCLOCK_PortA);
|
||||
|
||||
config.openDrainEnable = kPORT_OpenDrainEnable;
|
||||
config.mux = kPORT_MuxAlt4;
|
||||
config.pullSelect = kPORT_PullUp;
|
||||
PORT_SetPinConfig(PORTB, 0u, &config); /* RMII0_MDIO/MII0_MDIO */
|
||||
PORT_SetPinMux(PORTB, 1u, kPORT_MuxAlt4); /* RMII0_MDC/MII0_MDC */
|
||||
CLOCK_EnableClock(kCLOCK_PortB); /* do we need this? */
|
||||
|
||||
PORT_SetPinMux(PORTC, 16u, kPORT_MuxAlt4); /* ENET0_1588_TMR0 */
|
||||
PORT_SetPinMux(PORTC, 17u, kPORT_MuxAlt4); /* ENET0_1588_TMR1 */
|
||||
PORT_SetPinMux(PORTC, 18u, kPORT_MuxAlt4); /* ENET0_1588_TMR2 */
|
||||
PORT_SetPinMux(PORTC, 19u, kPORT_MuxAlt4); /* ENET0_1588_TMR3 */
|
||||
CLOCK_EnableClock(kCLOCK_PortC); /* do we need this? */
|
||||
|
||||
CLOCK_SetEnetTime0Clock(0x2);
|
||||
}
|
||||
|
||||
static enet_rx_bd_struct_t __aligned(ENET_BUFF_ALIGNMENT)
|
||||
rx_buffer_desc[ENET_RX_RING_LEN];
|
||||
|
||||
static enet_tx_bd_struct_t __aligned(ENET_BUFF_ALIGNMENT)
|
||||
tx_buffer_desc[ENET_TX_RING_LEN];
|
||||
|
||||
static uint8_t __aligned(ENET_BUFF_ALIGNMENT) rx_buffer[ENET_RX_RING_LEN][2048];
|
||||
static uint8_t __aligned(ENET_BUFF_ALIGNMENT) tx_buffer[ENET_TX_RING_LEN][2048];
|
||||
|
||||
|
||||
static void eth_rx(struct device *port)
|
||||
{
|
||||
struct eth_config *config = port->config->config_info;
|
||||
struct net_buf *buf;
|
||||
uint32_t frame_length = 0;
|
||||
status_t status;
|
||||
|
||||
status = ENET_GetRxFrameSize(&config->enet_handle, &frame_length);
|
||||
if (status) {
|
||||
enet_data_error_stats_t error_stats;
|
||||
|
||||
SYS_LOG_ERR("ENET_GetRxFrameSize return: %d", status);
|
||||
|
||||
ENET_GetRxErrBeforeReadFrame(&config->enet_handle,
|
||||
&error_stats);
|
||||
/* todo: error_stats need reporting somehere? */
|
||||
|
||||
/* Flush the current read buffer. */
|
||||
status = ENET_ReadFrame(ENET, &config->enet_handle, NULL, 0);
|
||||
if (status) {
|
||||
SYS_LOG_ERR("ENET_GetRxFrameSize return: %d\n", status);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* todo: We should be able to reduced this to an __ASSERT
|
||||
* provided the size of the buffers we allocate are hardwired
|
||||
* to an appropriate UIP constant.
|
||||
*/
|
||||
if (frame_length > UIP_BUFSIZE) {
|
||||
SYS_LOG_ERR("frame too large: %u.", frame_length);
|
||||
/* Flush the current read buffer. */
|
||||
ENET_ReadFrame(ENET, &config->enet_handle, NULL, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
buf = ip_buf_get_reserve_rx(0);
|
||||
if (buf == NULL) {
|
||||
/* We failed to get a receive buffer. Flush the
|
||||
* current read buffer. We don't add any further
|
||||
* logging here because the allocator issued a
|
||||
* diagnostic when it failed to allocate.
|
||||
*/
|
||||
ENET_ReadFrame(ENET, &config->enet_handle, NULL, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
status = ENET_ReadFrame(ENET, &config->enet_handle,
|
||||
net_buf_add(buf, frame_length), frame_length);
|
||||
if (status) {
|
||||
SYS_LOG_ERR("ENET_ReadFrame failed: %d\n", status);
|
||||
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 *port = param;
|
||||
struct eth_runtime *context = port->driver_data;
|
||||
|
||||
switch (event) {
|
||||
case kENET_RxEvent:
|
||||
eth_rx(port);
|
||||
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. */
|
||||
|
||||
/* todo: What should we do on this event? */
|
||||
|
||||
break;
|
||||
case kENET_WakeUpEvent:
|
||||
/* Wake up from sleep mode event. */
|
||||
|
||||
/* todo: What should we do on this event? */
|
||||
|
||||
break;
|
||||
#ifdef ENET_ENHANCEDBUFFERDESCRIPTOR_MODE
|
||||
case kENET_TimeStampEvent:
|
||||
/* Time stamp event. */
|
||||
|
||||
/* todo: What should we do on this event? */
|
||||
|
||||
break;
|
||||
case kENET_TimeStampAvailEvent:
|
||||
/* Time stamp available event. */
|
||||
|
||||
/* todo: What should we do on this 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();
|
||||
|
||||
mac_addr[0] = 0x02; /* Locally administered, unicast. */
|
||||
mac_addr[0] |= ((entropy >> 0) & 0xfe);
|
||||
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_initialize(struct device *port)
|
||||
{
|
||||
struct eth_runtime *context = port->driver_data;
|
||||
struct eth_config *config = port->config->config_info;
|
||||
enet_config_t enet_config;
|
||||
uint32_t sys_clock;
|
||||
const uint32_t phy_addr = 0;
|
||||
bool link;
|
||||
status_t status;
|
||||
uint8_t mac_addr[6];
|
||||
int result;
|
||||
int i;
|
||||
enet_buffer_config_t buffer_config = {
|
||||
ENET_RX_RING_LEN,
|
||||
ENET_TX_RING_LEN,
|
||||
/* todo: Hardwired buffer sizes need cleaning up ? */
|
||||
ROUND_UP(1522, ENET_BUFF_ALIGNMENT),
|
||||
ROUND_UP(1522, ENET_BUFF_ALIGNMENT),
|
||||
rx_buffer_desc,
|
||||
tx_buffer_desc,
|
||||
rx_buffer[0],
|
||||
tx_buffer[0],
|
||||
};
|
||||
|
||||
nano_sem_init(&context->tx_buf_sem);
|
||||
for (i = 0; i < ENET_TX_RING_LEN; i++) {
|
||||
nano_sem_give(&context->tx_buf_sem);
|
||||
}
|
||||
|
||||
k64f_init_eth_hardware();
|
||||
sys_clock = CLOCK_GetFreq(kCLOCK_CoreSysClk);
|
||||
|
||||
ENET_GetDefaultConfig(&enet_config);
|
||||
enet_config.interrupt |= kENET_RxFrameInterrupt;
|
||||
enet_config.interrupt |= kENET_TxFrameInterrupt;
|
||||
|
||||
status = PHY_Init(ENET, 0, sys_clock);
|
||||
if (status) {
|
||||
SYS_LOG_ERR("PHY_Init() failed: %d", status);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* todo: This PHY interaction is polled and *slow*, it blocks
|
||||
* the init thread for an unpleasant amount of time. Maybe
|
||||
* this should sit in its own thread, or perhaps there is an
|
||||
* async mechanism to drive the PHY?
|
||||
*
|
||||
* todo: We should also handle arbitrary link up / down dynamically etc ?
|
||||
* todo: Investigate if there is plumbing to allow dhcp clients etc to detect link up/down.
|
||||
*/
|
||||
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"));
|
||||
}
|
||||
|
||||
#if defined(CONFIG_ETH_KSDK_0_RANDOM_MAC)
|
||||
/* ? convenient (for the author), but should be abstracted
|
||||
* across drivers, or maybe random MAC generation is not
|
||||
* appropriate at all ?
|
||||
*/
|
||||
generate_mac(mac_addr);
|
||||
#else
|
||||
mac_addr[0] = CONFIG_ETH_KSDK_0_MAC0;
|
||||
mac_addr[1] = CONFIG_ETH_KSDK_0_MAC1;
|
||||
mac_addr[2] = CONFIG_ETH_KSDK_0_MAC2;
|
||||
mac_addr[3] = CONFIG_ETH_KSDK_0_MAC3;
|
||||
mac_addr[4] = CONFIG_ETH_KSDK_0_MAC4;
|
||||
mac_addr[5] = CONFIG_ETH_KSDK_0_MAC5;
|
||||
#endif
|
||||
|
||||
ENET_Init(ENET,
|
||||
&config->enet_handle,
|
||||
&enet_config,
|
||||
&buffer_config,
|
||||
mac_addr,
|
||||
sys_clock);
|
||||
|
||||
SYS_LOG_DBG("MAC %2x:%2x:%2x:%2x:%2x:%2x",
|
||||
mac_addr[0], mac_addr[1],
|
||||
mac_addr[2], mac_addr[3],
|
||||
mac_addr[4], mac_addr[5]);
|
||||
|
||||
|
||||
result = net_set_mac(mac_addr, sizeof(mac_addr));
|
||||
if (result) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
ENET_SetCallback(&config->enet_handle, eth_callback, port);
|
||||
net_driver_ethernet_register_tx(eth_net_tx);
|
||||
config->config_func(port);
|
||||
ENET_ActiveRead(ENET);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void eth_ksdk_rx_isr(void *port)
|
||||
{
|
||||
ENET_Receive_IRQHandler();
|
||||
}
|
||||
|
||||
static void eth_ksdk_tx_isr(void *port)
|
||||
{
|
||||
ENET_Transmit_IRQHandler();
|
||||
}
|
||||
|
||||
static void eth_ksdk_error_isr(void *port)
|
||||
{
|
||||
ENET_Error_IRQHandler();
|
||||
}
|
||||
|
||||
static void eth_config_0_irq(struct device *);
|
||||
|
||||
static struct eth_config eth_config_0 = {
|
||||
.config_func = eth_config_0_irq,
|
||||
};
|
||||
|
||||
static struct eth_runtime eth_0_runtime;
|
||||
|
||||
DEVICE_INIT(eth_ksdk_0, CONFIG_ETH_KSDK_0_NAME, eth_initialize,
|
||||
ð_0_runtime, ð_config_0,
|
||||
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_config_0_irq(struct device *port)
|
||||
{
|
||||
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);
|
||||
}
|
||||
#endif
|
Loading…
Add table
Add a link
Reference in a new issue