| /* |
| * 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 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 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_T |
| | ETHERNET_LINK_100BASE_T |
| #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, |
| .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 void mdio_smsc_bus_disable(const struct device *dev) |
| { |
| ARG_UNUSED(dev); |
| } |
| |
| static void mdio_smsc_bus_enable(const struct device *dev) |
| { |
| ARG_UNUSED(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 const struct mdio_driver_api mdio_smsc_api = { |
| .bus_disable = mdio_smsc_bus_disable, |
| .bus_enable = mdio_smsc_bus_enable, |
| .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); |