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.
883 lines
19 KiB
883 lines
19 KiB
/* |
|
* Copyright (c) 2023 Arm Limited (or its affiliates). All rights reserved. |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#include <zephyr/net/ethernet.h> |
|
#include <zephyr/net/phy.h> |
|
#include <zephyr/arch/cpu.h> |
|
#include <zephyr/sys_clock.h> |
|
#include <zephyr/drivers/mdio.h> |
|
#include <zephyr/logging/log.h> |
|
#include "eth_smsc91x_priv.h" |
|
|
|
#define DT_DRV_COMPAT smsc_lan91c111 |
|
|
|
LOG_MODULE_REGISTER(eth_smsc91x, CONFIG_ETHERNET_LOG_LEVEL); |
|
|
|
#define SMSC_LOCK(sc) k_mutex_lock(&(sc)->lock, K_FOREVER) |
|
#define SMSC_UNLOCK(sc) k_mutex_unlock(&(sc)->lock) |
|
#define HW_CYCLE_PER_US (sys_clock_hw_cycles_per_sec() / 1000000UL) |
|
#define TX_ALLOC_WAIT_TIME 100 |
|
#define MAX_IRQ_LOOPS 8 |
|
|
|
/* |
|
* MII |
|
*/ |
|
#define MDO MGMT_MDO |
|
#define MDI MGMT_MDI |
|
#define MDC MGMT_MCLK |
|
#define MDIRPHY MGMT_MDOE |
|
#define MDIRHOST 0 |
|
|
|
#define MII_IDLE_DETECT_CYCLES 32 |
|
|
|
#define MII_COMMAND_START 0x01 |
|
#define MII_COMMAND_READ 0x02 |
|
#define MII_COMMAND_WRITE 0x01 |
|
#define MII_COMMAND_ACK 0x02 |
|
|
|
static const char *smsc_chip_ids[16] = { |
|
NULL, |
|
NULL, |
|
NULL, |
|
NULL, |
|
NULL, |
|
NULL, |
|
NULL, |
|
NULL, |
|
NULL, |
|
/* 9 */ "SMSC LAN91C11", |
|
NULL, |
|
NULL, |
|
NULL, |
|
NULL, |
|
NULL, |
|
NULL, |
|
}; |
|
|
|
struct smsc_data { |
|
mm_reg_t smsc_reg; |
|
unsigned int irq; |
|
unsigned int smsc_chip; |
|
unsigned int smsc_rev; |
|
unsigned int smsc_mask; |
|
uint8_t mac[6]; |
|
struct k_mutex lock; |
|
struct k_work isr_work; |
|
}; |
|
|
|
struct eth_config { |
|
DEVICE_MMIO_ROM; |
|
const struct device *phy_dev; |
|
}; |
|
|
|
struct eth_context { |
|
DEVICE_MMIO_RAM; |
|
struct net_if *iface; |
|
struct smsc_data sc; |
|
}; |
|
|
|
static uint8_t tx_buffer[NET_ETH_MAX_FRAME_SIZE]; |
|
static uint8_t rx_buffer[NET_ETH_MAX_FRAME_SIZE]; |
|
|
|
static ALWAYS_INLINE void delay(int us) |
|
{ |
|
k_busy_wait(us); |
|
} |
|
|
|
static ALWAYS_INLINE void smsc_select_bank(struct smsc_data *sc, uint16_t bank) |
|
{ |
|
sys_write16(bank & BSR_BANK_MASK, sc->smsc_reg + BSR); |
|
} |
|
|
|
static ALWAYS_INLINE unsigned int smsc_current_bank(struct smsc_data *sc) |
|
{ |
|
return FIELD_GET(BSR_BANK_MASK, sys_read16(sc->smsc_reg + BSR)); |
|
} |
|
|
|
static void smsc_mmu_wait(struct smsc_data *sc) |
|
{ |
|
__ASSERT((smsc_current_bank(sc) == 2), "%s called when not in bank 2", __func__); |
|
while (sys_read16(sc->smsc_reg + MMUCR) & MMUCR_BUSY) { |
|
; |
|
} |
|
} |
|
|
|
static ALWAYS_INLINE uint8_t smsc_read_1(struct smsc_data *sc, int offset) |
|
{ |
|
return sys_read8(sc->smsc_reg + offset); |
|
} |
|
|
|
static ALWAYS_INLINE uint16_t smsc_read_2(struct smsc_data *sc, int offset) |
|
{ |
|
return sys_read16(sc->smsc_reg + offset); |
|
} |
|
|
|
static ALWAYS_INLINE void smsc_read_multi_2(struct smsc_data *sc, int offset, uint16_t *datap, |
|
uint16_t count) |
|
{ |
|
while (count--) { |
|
*datap++ = sys_read16(sc->smsc_reg + offset); |
|
} |
|
} |
|
|
|
static ALWAYS_INLINE void smsc_write_1(struct smsc_data *sc, int offset, uint8_t val) |
|
{ |
|
sys_write8(val, sc->smsc_reg + offset); |
|
} |
|
|
|
static ALWAYS_INLINE void smsc_write_2(struct smsc_data *sc, int offset, uint16_t val) |
|
{ |
|
sys_write16(val, sc->smsc_reg + offset); |
|
} |
|
|
|
static ALWAYS_INLINE void smsc_write_multi_2(struct smsc_data *sc, int offset, uint16_t *datap, |
|
uint16_t count) |
|
{ |
|
while (count--) { |
|
sys_write16(*datap++, sc->smsc_reg + offset); |
|
} |
|
} |
|
|
|
static uint32_t smsc_mii_bitbang_read(struct smsc_data *sc) |
|
{ |
|
uint16_t val; |
|
|
|
__ASSERT(FIELD_GET(BSR_BANK_MASK, smsc_read_2(sc, BSR)) == 3, |
|
"%s called with bank %d (!=3)", __func__, |
|
FIELD_GET(BSR_BANK_MASK, smsc_read_2(sc, BSR))); |
|
|
|
val = smsc_read_2(sc, MGMT); |
|
delay(1); /* Simulate a timing sequence */ |
|
|
|
return val; |
|
} |
|
|
|
static void smsc_mii_bitbang_write(struct smsc_data *sc, uint16_t val) |
|
{ |
|
__ASSERT(FIELD_GET(BSR_BANK_MASK, smsc_read_2(sc, BSR)) == 3, |
|
"%s called with bank %d (!=3)", __func__, |
|
FIELD_GET(BSR_BANK_MASK, smsc_read_2(sc, BSR))); |
|
|
|
smsc_write_2(sc, MGMT, val); |
|
delay(1); /* Simulate a timing sequence */ |
|
} |
|
|
|
static void smsc_miibus_sync(struct smsc_data *sc) |
|
{ |
|
int i; |
|
uint32_t v; |
|
|
|
v = MDIRPHY | MDO; |
|
|
|
smsc_mii_bitbang_write(sc, v); |
|
for (i = 0; i < MII_IDLE_DETECT_CYCLES; i++) { |
|
smsc_mii_bitbang_write(sc, v | MDC); |
|
smsc_mii_bitbang_write(sc, v); |
|
} |
|
} |
|
|
|
static void smsc_miibus_sendbits(struct smsc_data *sc, uint32_t data, int nbits) |
|
{ |
|
int i; |
|
uint32_t v; |
|
|
|
v = MDIRPHY; |
|
smsc_mii_bitbang_write(sc, v); |
|
|
|
for (i = 1 << (nbits - 1); i != 0; i >>= 1) { |
|
if (data & i) { |
|
v |= MDO; |
|
} else { |
|
v &= ~MDO; |
|
} |
|
|
|
smsc_mii_bitbang_write(sc, v); |
|
smsc_mii_bitbang_write(sc, v | MDC); |
|
smsc_mii_bitbang_write(sc, v); |
|
} |
|
} |
|
|
|
static int smsc_miibus_readreg(struct smsc_data *sc, int phy, int reg) |
|
{ |
|
int i, err, val; |
|
|
|
irq_disable(sc->irq); |
|
SMSC_LOCK(sc); |
|
|
|
smsc_select_bank(sc, 3); |
|
|
|
smsc_miibus_sync(sc); |
|
|
|
smsc_miibus_sendbits(sc, MII_COMMAND_START, 2); |
|
smsc_miibus_sendbits(sc, MII_COMMAND_READ, 2); |
|
smsc_miibus_sendbits(sc, phy, 5); |
|
smsc_miibus_sendbits(sc, reg, 5); |
|
|
|
/* Switch direction to PHY -> host */ |
|
smsc_mii_bitbang_write(sc, MDIRHOST); |
|
smsc_mii_bitbang_write(sc, MDIRHOST | MDC); |
|
smsc_mii_bitbang_write(sc, MDIRHOST); |
|
|
|
/* Check for error. */ |
|
err = smsc_mii_bitbang_read(sc) & MDI; |
|
|
|
/* Idle clock. */ |
|
smsc_mii_bitbang_write(sc, MDIRHOST | MDC); |
|
smsc_mii_bitbang_write(sc, MDIRHOST); |
|
|
|
val = 0; |
|
for (i = 0; i < 16; i++) { |
|
val <<= 1; |
|
/* Read data prior to clock low-high transition. */ |
|
if (err == 0 && (smsc_mii_bitbang_read(sc) & MDI) != 0) { |
|
val |= 1; |
|
} |
|
|
|
smsc_mii_bitbang_write(sc, MDIRHOST | MDC); |
|
smsc_mii_bitbang_write(sc, MDIRHOST); |
|
} |
|
|
|
/* Set direction to host -> PHY, without a clock transition. */ |
|
smsc_mii_bitbang_write(sc, MDIRPHY); |
|
|
|
SMSC_UNLOCK(sc); |
|
irq_enable(sc->irq); |
|
|
|
return (err == 0 ? val : 0); |
|
} |
|
|
|
static void smsc_miibus_writereg(struct smsc_data *sc, int phy, int reg, uint16_t val) |
|
{ |
|
irq_disable(sc->irq); |
|
SMSC_LOCK(sc); |
|
|
|
smsc_select_bank(sc, 3); |
|
|
|
smsc_miibus_sync(sc); |
|
|
|
smsc_miibus_sendbits(sc, MII_COMMAND_START, 2); |
|
smsc_miibus_sendbits(sc, MII_COMMAND_WRITE, 2); |
|
smsc_miibus_sendbits(sc, phy, 5); |
|
smsc_miibus_sendbits(sc, reg, 5); |
|
smsc_miibus_sendbits(sc, MII_COMMAND_ACK, 2); |
|
smsc_miibus_sendbits(sc, val, 16); |
|
|
|
smsc_mii_bitbang_write(sc, MDIRPHY); |
|
|
|
SMSC_UNLOCK(sc); |
|
irq_enable(sc->irq); |
|
} |
|
|
|
static void smsc_reset(struct smsc_data *sc) |
|
{ |
|
uint16_t ctr; |
|
|
|
/* |
|
* Mask all interrupts |
|
*/ |
|
smsc_select_bank(sc, 2); |
|
smsc_write_1(sc, MSK, 0); |
|
|
|
/* |
|
* Tell the device to reset |
|
*/ |
|
smsc_select_bank(sc, 0); |
|
smsc_write_2(sc, RCR, RCR_SOFT_RST); |
|
|
|
/* |
|
* Set up the configuration register |
|
*/ |
|
smsc_select_bank(sc, 1); |
|
smsc_write_2(sc, CR, CR_EPH_POWER_EN); |
|
delay(1); |
|
|
|
/* |
|
* Turn off transmit and receive. |
|
*/ |
|
smsc_select_bank(sc, 0); |
|
smsc_write_2(sc, TCR, 0); |
|
smsc_write_2(sc, RCR, 0); |
|
|
|
/* |
|
* Set up the control register |
|
*/ |
|
smsc_select_bank(sc, 1); |
|
ctr = smsc_read_2(sc, CTR); |
|
ctr |= CTR_LE_ENABLE | CTR_AUTO_RELEASE; |
|
smsc_write_2(sc, CTR, ctr); |
|
|
|
/* |
|
* Reset the MMU |
|
*/ |
|
smsc_select_bank(sc, 2); |
|
smsc_mmu_wait(sc); |
|
smsc_write_2(sc, MMUCR, FIELD_PREP(MMUCR_CMD_MASK, MMUCR_CMD_MMU_RESET)); |
|
smsc_mmu_wait(sc); |
|
} |
|
|
|
static void smsc_enable(struct smsc_data *sc) |
|
{ |
|
/* |
|
* Set up the receive/PHY control register. |
|
*/ |
|
smsc_select_bank(sc, 0); |
|
smsc_write_2(sc, RPCR, |
|
RPCR_ANEG | RPCR_DPLX | RPCR_SPEED | |
|
FIELD_PREP(RPCR_LSA_MASK, RPCR_LED_LINK_ANY) | |
|
FIELD_PREP(RPCR_LSB_MASK, RPCR_LED_ACT_ANY)); |
|
|
|
/* |
|
* Set up the transmit and receive control registers. |
|
*/ |
|
smsc_write_2(sc, TCR, TCR_TXENA | TCR_PAD_EN); |
|
smsc_write_2(sc, RCR, RCR_RXEN | RCR_STRIP_CRC); |
|
|
|
/* |
|
* Clear all interrupt status |
|
*/ |
|
smsc_select_bank(sc, 2); |
|
smsc_write_1(sc, ACK, 0); |
|
|
|
/* |
|
* Set up the interrupt mask |
|
*/ |
|
smsc_select_bank(sc, 2); |
|
sc->smsc_mask = RCV_INT; |
|
smsc_write_1(sc, MSK, sc->smsc_mask); |
|
} |
|
|
|
static int smsc_check(struct smsc_data *sc) |
|
{ |
|
uint16_t val; |
|
|
|
val = smsc_read_2(sc, BSR); |
|
if (FIELD_GET(BSR_IDENTIFY_MASK, val) != BSR_IDENTIFY) { |
|
LOG_ERR("Identification value not in BSR"); |
|
return -ENODEV; |
|
} |
|
|
|
smsc_write_2(sc, BSR, 0); |
|
val = smsc_read_2(sc, BSR); |
|
if (FIELD_GET(BSR_IDENTIFY_MASK, val) != BSR_IDENTIFY) { |
|
LOG_ERR("Identification value not in BSR after write"); |
|
return -ENODEV; |
|
} |
|
|
|
smsc_select_bank(sc, 3); |
|
val = smsc_read_2(sc, REV); |
|
val = FIELD_GET(REV_CHIP_MASK, val); |
|
if (smsc_chip_ids[val] == NULL) { |
|
LOG_ERR("Unknown chip revision: %d", val); |
|
return -ENODEV; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static void smsc_recv_pkt(struct eth_context *data) |
|
{ |
|
struct net_pkt *pkt; |
|
unsigned int packet, status, len; |
|
struct smsc_data *sc = &data->sc; |
|
uint16_t val16; |
|
int ret; |
|
|
|
smsc_select_bank(sc, 2); |
|
packet = smsc_read_1(sc, FIFO_RX); |
|
while ((packet & FIFO_EMPTY) == 0) { |
|
/* |
|
* Point to the start of the packet. |
|
*/ |
|
smsc_select_bank(sc, 2); |
|
smsc_write_1(sc, PNR, packet); |
|
smsc_write_2(sc, PTR, PTR_READ | PTR_RCV | PTR_AUTO_INCR); |
|
|
|
/* |
|
* Grab status and packet length. |
|
*/ |
|
status = smsc_read_2(sc, DATA0); |
|
val16 = smsc_read_2(sc, DATA0); |
|
len = FIELD_GET(RX_LEN_MASK, val16); |
|
if (len < PKT_CTRL_DATA_LEN) { |
|
LOG_WRN("rxlen(%d) too short", len); |
|
} else { |
|
len -= PKT_CTRL_DATA_LEN; |
|
if (status & RX_ODDFRM) { |
|
len += 1; |
|
} |
|
|
|
if (len > NET_ETH_MAX_FRAME_SIZE) { |
|
LOG_WRN("rxlen(%d) too large", len); |
|
goto _mmu_release; |
|
} |
|
|
|
/* |
|
* Check for errors. |
|
*/ |
|
if (status & (RX_TOOSHORT | RX_TOOLNG | RX_BADCRC | RX_ALIGNERR)) { |
|
LOG_WRN("status word (0x%04x) indicate some error", status); |
|
goto _mmu_release; |
|
} |
|
|
|
/* |
|
* Pull the packet out of the device. |
|
*/ |
|
smsc_select_bank(sc, 2); |
|
smsc_write_1(sc, PNR, packet); |
|
|
|
/* |
|
* Pointer start from 4 because we have already read status and len from |
|
* RX_FIFO |
|
*/ |
|
smsc_write_2(sc, PTR, 4 | PTR_READ | PTR_RCV | PTR_AUTO_INCR); |
|
smsc_read_multi_2(sc, DATA0, (uint16_t *)rx_buffer, len / 2); |
|
if (len & 1) { |
|
rx_buffer[len - 1] = smsc_read_1(sc, DATA0); |
|
} |
|
|
|
pkt = net_pkt_rx_alloc_with_buffer(data->iface, len, AF_UNSPEC, 0, |
|
K_NO_WAIT); |
|
if (!pkt) { |
|
LOG_ERR("Failed to obtain RX buffer"); |
|
goto _mmu_release; |
|
} |
|
|
|
ret = net_pkt_write(pkt, rx_buffer, len); |
|
if (ret) { |
|
net_pkt_unref(pkt); |
|
LOG_WRN("net_pkt_write return %d", ret); |
|
goto _mmu_release; |
|
} |
|
|
|
ret = net_recv_data(data->iface, pkt); |
|
if (ret) { |
|
LOG_WRN("net_recv_data return %d", ret); |
|
net_pkt_unref(pkt); |
|
} |
|
} |
|
|
|
_mmu_release: |
|
/* |
|
* Tell the device we're done |
|
*/ |
|
smsc_mmu_wait(sc); |
|
smsc_write_2(sc, MMUCR, FIELD_PREP(MMUCR_CMD_MASK, MMUCR_CMD_RELEASE)); |
|
smsc_mmu_wait(sc); |
|
|
|
packet = smsc_read_1(sc, FIFO_RX); |
|
} |
|
|
|
sc->smsc_mask |= RCV_INT; |
|
smsc_write_1(sc, MSK, sc->smsc_mask); |
|
} |
|
|
|
static int smsc_send_pkt(struct smsc_data *sc, uint8_t *buf, uint16_t len) |
|
{ |
|
unsigned int polling_count; |
|
uint8_t packet; |
|
|
|
SMSC_LOCK(sc); |
|
/* |
|
* Request memory |
|
*/ |
|
smsc_select_bank(sc, 2); |
|
smsc_mmu_wait(sc); |
|
smsc_write_2(sc, MMUCR, FIELD_PREP(MMUCR_CMD_MASK, MMUCR_CMD_TX_ALLOC)); |
|
|
|
/* |
|
* Polling if the allocation succeeds. |
|
*/ |
|
for (polling_count = TX_ALLOC_WAIT_TIME; polling_count > 0; polling_count--) { |
|
if (smsc_read_1(sc, IST) & ALLOC_INT) { |
|
break; |
|
} |
|
|
|
delay(1); |
|
} |
|
|
|
if (polling_count == 0) { |
|
SMSC_UNLOCK(sc); |
|
LOG_WRN("Alloc TX mem timeout"); |
|
return -1; |
|
} |
|
|
|
packet = smsc_read_1(sc, ARR); |
|
if (packet & ARR_FAILED) { |
|
SMSC_UNLOCK(sc); |
|
LOG_WRN("Alloc TX mem failed"); |
|
return -1; |
|
} |
|
|
|
/* |
|
* Tell the device to write to our packet number. |
|
*/ |
|
smsc_write_1(sc, PNR, packet); |
|
smsc_write_2(sc, PTR, PTR_AUTO_INCR); |
|
|
|
/* |
|
* Tell the device how long the packet is (include control data). |
|
*/ |
|
smsc_write_2(sc, DATA0, 0); |
|
smsc_write_2(sc, DATA0, len + PKT_CTRL_DATA_LEN); |
|
smsc_write_multi_2(sc, DATA0, (uint16_t *)buf, len / 2); |
|
|
|
/* Push out the control byte and the odd byte if needed. */ |
|
if (len & 1) { |
|
smsc_write_2(sc, DATA0, (CTRL_ODD << 8) | buf[len - 1]); |
|
} else { |
|
smsc_write_2(sc, DATA0, 0); |
|
} |
|
|
|
/* |
|
* Enqueue the packet. |
|
*/ |
|
smsc_mmu_wait(sc); |
|
smsc_write_2(sc, MMUCR, FIELD_PREP(MMUCR_CMD_MASK, MMUCR_CMD_ENQUEUE)); |
|
|
|
/* |
|
* Unmask the TX empty interrupt |
|
*/ |
|
sc->smsc_mask |= (TX_EMPTY_INT | TX_INT); |
|
smsc_write_1(sc, MSK, sc->smsc_mask); |
|
|
|
SMSC_UNLOCK(sc); |
|
|
|
/* |
|
* Finish up |
|
*/ |
|
return 0; |
|
} |
|
|
|
static void smsc_isr_task(struct k_work *item) |
|
{ |
|
struct smsc_data *sc = CONTAINER_OF(item, struct smsc_data, isr_work); |
|
struct eth_context *data = CONTAINER_OF(sc, struct eth_context, sc); |
|
uint8_t status; |
|
unsigned int mem_info, ephsr, packet, tcr; |
|
|
|
SMSC_LOCK(sc); |
|
|
|
for (int loop_count = 0; loop_count < MAX_IRQ_LOOPS; loop_count++) { |
|
smsc_select_bank(sc, 0); |
|
mem_info = smsc_read_2(sc, MIR); |
|
|
|
smsc_select_bank(sc, 2); |
|
status = smsc_read_1(sc, IST); |
|
LOG_DBG("INT 0x%02x MASK 0x%02x MEM 0x%04x FIFO 0x%04x", status, |
|
smsc_read_1(sc, MSK), mem_info, smsc_read_2(sc, FIFO)); |
|
|
|
status &= sc->smsc_mask; |
|
if (!status) { |
|
break; |
|
} |
|
|
|
/* |
|
* Transmit error |
|
*/ |
|
if (status & TX_INT) { |
|
/* |
|
* Kill off the packet if there is one. |
|
*/ |
|
packet = smsc_read_1(sc, FIFO_TX); |
|
if ((packet & FIFO_EMPTY) == 0) { |
|
smsc_select_bank(sc, 2); |
|
smsc_write_1(sc, PNR, packet); |
|
smsc_write_2(sc, PTR, PTR_READ | PTR_AUTO_INCR); |
|
|
|
smsc_select_bank(sc, 0); |
|
ephsr = smsc_read_2(sc, EPHSR); |
|
|
|
if ((ephsr & EPHSR_TX_SUC) == 0) { |
|
LOG_WRN("bad packet, EPHSR: 0x%04x", ephsr); |
|
} |
|
|
|
smsc_select_bank(sc, 2); |
|
smsc_mmu_wait(sc); |
|
smsc_write_2(sc, MMUCR, |
|
FIELD_PREP(MMUCR_CMD_MASK, MMUCR_CMD_RELEASE_PKT)); |
|
|
|
smsc_select_bank(sc, 0); |
|
tcr = smsc_read_2(sc, TCR); |
|
tcr |= TCR_TXENA | TCR_PAD_EN; |
|
smsc_write_2(sc, TCR, tcr); |
|
} |
|
|
|
/* |
|
* Ack the interrupt |
|
*/ |
|
smsc_select_bank(sc, 2); |
|
smsc_write_1(sc, ACK, TX_INT); |
|
} |
|
|
|
/* |
|
* Receive |
|
*/ |
|
if (status & RCV_INT) { |
|
smsc_write_1(sc, ACK, RCV_INT); |
|
smsc_recv_pkt(data); |
|
} |
|
|
|
/* |
|
* Transmit empty |
|
*/ |
|
if (status & TX_EMPTY_INT) { |
|
smsc_write_1(sc, ACK, TX_EMPTY_INT); |
|
sc->smsc_mask &= ~TX_EMPTY_INT; |
|
} |
|
} |
|
|
|
smsc_select_bank(sc, 2); |
|
smsc_write_1(sc, MSK, sc->smsc_mask); |
|
|
|
SMSC_UNLOCK(sc); |
|
} |
|
|
|
static int smsc_init(struct smsc_data *sc) |
|
{ |
|
int ret; |
|
unsigned int val; |
|
|
|
ret = smsc_check(sc); |
|
if (ret) { |
|
return ret; |
|
} |
|
|
|
SMSC_LOCK(sc); |
|
smsc_reset(sc); |
|
SMSC_UNLOCK(sc); |
|
|
|
smsc_select_bank(sc, 3); |
|
val = smsc_read_2(sc, REV); |
|
sc->smsc_chip = FIELD_GET(REV_CHIP_MASK, val); |
|
sc->smsc_rev = FIELD_GET(REV_REV_MASK, val); |
|
|
|
smsc_select_bank(sc, 1); |
|
sc->mac[0] = smsc_read_1(sc, IAR0); |
|
sc->mac[1] = smsc_read_1(sc, IAR1); |
|
sc->mac[2] = smsc_read_1(sc, IAR2); |
|
sc->mac[3] = smsc_read_1(sc, IAR3); |
|
sc->mac[4] = smsc_read_1(sc, IAR4); |
|
sc->mac[5] = smsc_read_1(sc, IAR5); |
|
|
|
return 0; |
|
} |
|
|
|
static const struct device *eth_get_phy(const struct device *dev) |
|
{ |
|
const struct eth_config *cfg = dev->config; |
|
|
|
return cfg->phy_dev; |
|
} |
|
|
|
static void phy_link_state_changed(const struct device *phy_dev, struct phy_link_state *state, |
|
void *user_data) |
|
{ |
|
const struct device *dev = user_data; |
|
struct eth_context *data = dev->data; |
|
|
|
if (state->is_up) { |
|
net_eth_carrier_on(data->iface); |
|
} else { |
|
net_eth_carrier_off(data->iface); |
|
} |
|
} |
|
|
|
static enum ethernet_hw_caps eth_smsc_get_caps(const struct device *dev) |
|
{ |
|
ARG_UNUSED(dev); |
|
|
|
return (ETHERNET_LINK_10BASE |
|
| ETHERNET_LINK_100BASE |
|
#if defined(CONFIG_NET_PROMISCUOUS_MODE) |
|
| ETHERNET_PROMISC_MODE |
|
#endif |
|
); |
|
} |
|
|
|
static int eth_tx(const struct device *dev, struct net_pkt *pkt) |
|
{ |
|
struct eth_context *data = dev->data; |
|
struct smsc_data *sc = &data->sc; |
|
uint16_t len; |
|
|
|
len = net_pkt_get_len(pkt); |
|
if (net_pkt_read(pkt, tx_buffer, len)) { |
|
LOG_WRN("read pkt failed"); |
|
return -1; |
|
} |
|
|
|
return smsc_send_pkt(sc, tx_buffer, len); |
|
} |
|
|
|
static int eth_smsc_set_config(const struct device *dev, |
|
enum ethernet_config_type type, |
|
const struct ethernet_config *config) |
|
{ |
|
int ret = 0; |
|
|
|
switch (type) { |
|
#if defined(CONFIG_NET_PROMISCUOUS_MODE) |
|
case ETHERNET_CONFIG_TYPE_PROMISC_MODE: |
|
struct eth_context *data = dev->data; |
|
struct smsc_data *sc = &data->sc; |
|
uint8_t reg_val; |
|
|
|
SMSC_LOCK(sc); |
|
smsc_select_bank(sc, 0); |
|
reg_val = smsc_read_1(sc, RCR); |
|
if (config->promisc_mode && !(reg_val & RCR_PRMS)) { |
|
smsc_write_1(sc, RCR, reg_val | RCR_PRMS); |
|
} else if (!config->promisc_mode && (reg_val & RCR_PRMS)) { |
|
smsc_write_1(sc, RCR, reg_val & ~RCR_PRMS); |
|
} else { |
|
ret = -EALREADY; |
|
} |
|
SMSC_UNLOCK(sc); |
|
break; |
|
#endif |
|
|
|
default: |
|
ret = -ENOTSUP; |
|
break; |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
static void eth_initialize(struct net_if *iface) |
|
{ |
|
const struct device *dev = net_if_get_device(iface); |
|
struct eth_context *data = dev->data; |
|
const struct eth_config *cfg = dev->config; |
|
const struct device *phy_dev = cfg->phy_dev; |
|
struct smsc_data *sc = &data->sc; |
|
|
|
ethernet_init(iface); |
|
|
|
net_if_carrier_off(iface); |
|
|
|
smsc_reset(sc); |
|
smsc_enable(sc); |
|
|
|
LOG_INF("MAC %02x:%02x:%02x:%02x:%02x:%02x", sc->mac[0], sc->mac[1], sc->mac[2], sc->mac[3], |
|
sc->mac[4], sc->mac[5]); |
|
|
|
net_if_set_link_addr(iface, sc->mac, sizeof(sc->mac), NET_LINK_ETHERNET); |
|
data->iface = iface; |
|
|
|
if (device_is_ready(phy_dev)) { |
|
phy_link_callback_set(phy_dev, phy_link_state_changed, (void *)dev); |
|
} else { |
|
LOG_ERR("PHY device not ready"); |
|
} |
|
} |
|
|
|
static const struct ethernet_api api_funcs = { |
|
.iface_api.init = eth_initialize, |
|
.get_capabilities = eth_smsc_get_caps, |
|
.get_phy = eth_get_phy, |
|
.set_config = eth_smsc_set_config, |
|
.send = eth_tx, |
|
}; |
|
|
|
static void eth_smsc_isr(const struct device *dev) |
|
{ |
|
struct eth_context *data = dev->data; |
|
struct smsc_data *sc = &data->sc; |
|
uint32_t curbank; |
|
|
|
curbank = smsc_current_bank(sc); |
|
|
|
/* |
|
* Block interrupts in order to let smsc91x_isr_task to kick in |
|
*/ |
|
smsc_select_bank(sc, 2); |
|
smsc_write_1(sc, MSK, 0); |
|
|
|
smsc_select_bank(sc, curbank); |
|
k_work_submit(&(sc->isr_work)); |
|
} |
|
|
|
int eth_init(const struct device *dev) |
|
{ |
|
struct eth_context *data = (struct eth_context *)dev->data; |
|
struct smsc_data *sc = &data->sc; |
|
int ret; |
|
|
|
ret = k_mutex_init(&sc->lock); |
|
if (ret) { |
|
return ret; |
|
} |
|
|
|
k_work_init(&sc->isr_work, smsc_isr_task); |
|
|
|
IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), eth_smsc_isr, DEVICE_DT_INST_GET(0), |
|
0); |
|
|
|
DEVICE_MMIO_MAP(dev, K_MEM_CACHE_NONE); |
|
sc->smsc_reg = DEVICE_MMIO_GET(dev); |
|
sc->irq = DT_INST_IRQN(0); |
|
|
|
smsc_init(sc); |
|
|
|
irq_enable(DT_INST_IRQN(0)); |
|
|
|
return 0; |
|
} |
|
|
|
static struct eth_context eth_0_context; |
|
|
|
static struct eth_config eth_0_config = { |
|
DEVICE_MMIO_ROM_INIT(DT_PARENT(DT_DRV_INST(0))), |
|
.phy_dev = DEVICE_DT_GET(DT_INST_PHANDLE(0, phy_handle)), |
|
}; |
|
|
|
ETH_NET_DEVICE_DT_INST_DEFINE(0, |
|
eth_init, NULL, ð_0_context, |
|
ð_0_config, CONFIG_ETH_INIT_PRIORITY, |
|
&api_funcs, NET_ETH_MTU); |
|
|
|
#undef DT_DRV_COMPAT |
|
#define DT_DRV_COMPAT smsc_lan91c111_mdio |
|
|
|
struct mdio_smsc_config { |
|
const struct device *eth_dev; |
|
}; |
|
|
|
static int mdio_smsc_read(const struct device *dev, uint8_t prtad, uint8_t devad, uint16_t *data) |
|
{ |
|
const struct mdio_smsc_config *cfg = dev->config; |
|
const struct device *eth_dev = cfg->eth_dev; |
|
struct eth_context *eth_data = eth_dev->data; |
|
struct smsc_data *sc = ð_data->sc; |
|
|
|
*data = smsc_miibus_readreg(sc, prtad, devad); |
|
|
|
return 0; |
|
} |
|
|
|
static int mdio_smsc_write(const struct device *dev, uint8_t prtad, uint8_t devad, uint16_t data) |
|
{ |
|
const struct mdio_smsc_config *cfg = dev->config; |
|
const struct device *eth_dev = cfg->eth_dev; |
|
struct eth_context *eth_data = eth_dev->data; |
|
struct smsc_data *sc = ð_data->sc; |
|
|
|
smsc_miibus_writereg(sc, prtad, devad, data); |
|
|
|
return 0; |
|
} |
|
|
|
static DEVICE_API(mdio, mdio_smsc_api) = { |
|
.read = mdio_smsc_read, |
|
.write = mdio_smsc_write, |
|
}; |
|
|
|
const struct mdio_smsc_config mdio_smsc_config_0 = { |
|
.eth_dev = DEVICE_DT_GET(DT_CHILD(DT_INST_PARENT(0), ethernet)), |
|
}; |
|
|
|
DEVICE_DT_INST_DEFINE(0, NULL, NULL, NULL, &mdio_smsc_config_0, POST_KERNEL, |
|
CONFIG_MDIO_INIT_PRIORITY, &mdio_smsc_api);
|
|
|