diff options
Diffstat (limited to 'usrp2/firmware/lib/ethernet.c')
-rw-r--r-- | usrp2/firmware/lib/ethernet.c | 281 |
1 files changed, 281 insertions, 0 deletions
diff --git a/usrp2/firmware/lib/ethernet.c b/usrp2/firmware/lib/ethernet.c new file mode 100644 index 000000000..0e2f46167 --- /dev/null +++ b/usrp2/firmware/lib/ethernet.c @@ -0,0 +1,281 @@ +/* + * Copyright 2007 Free Software Foundation, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "ethernet.h" +#include "memory_map.h" +#include "eth_phy.h" +#include "eth_mac.h" +#include "eth_mac_regs.h" +#include "pic.h" +#include "hal_io.h" +#include "nonstdio.h" +#include "bool.h" +#include "i2c.h" +#include "usrp2_i2c_addr.h" + + +#define VERBOSE 0 + +static ethernet_t ed_state; +static ethernet_link_changed_callback_t ed_callback = 0; + +void +ethernet_register_link_changed_callback(ethernet_link_changed_callback_t new_callback) +{ + ed_callback = new_callback; +} + + +static void +ed_set_mac_speed(int speed) +{ + switch(speed){ + case 10: + eth_mac->speed = 1; + break; + case 100: + eth_mac->speed = 2; + break; + case 1000: + eth_mac->speed = 4; + break; + default: + break; + } +} + +static void +ed_link_up(int speed) +{ + // putstr("ed_link_up: "); puthex16_nl(speed); + + ed_set_mac_speed(speed); + + if (ed_callback) // fire link changed callback + (*ed_callback)(speed); +} + +static void +ed_link_down(void) +{ + // putstr("ed_link_down\n"); + + if (ed_callback) // fire link changed callback + (*ed_callback)(0); +} + + +static void +ed_link_speed_change(int speed) +{ + ed_link_down(); + ed_link_up(speed); +} + +/* + * Read the PHY state register to determine link state and speed + */ +static void +ed_check_phy_state(void) +{ + int lansr = eth_mac_miim_read(PHY_LINK_AN); + eth_link_state_t new_state = LS_UNKNOWN; + int new_speed = S_UNKNOWN; + + if (VERBOSE){ + putstr("LANSR: "); + puthex16_nl(lansr); + } + + if (lansr & LANSR_LINK_GOOD){ // link's up + if (VERBOSE) + puts(" LINK_GOOD"); + + new_state = LS_UP; + switch (lansr & LANSR_SPEED_MASK){ + case LANSR_SPEED_10: + new_speed = 10; + break; + + case LANSR_SPEED_100: + new_speed = 100; + break; + + case LANSR_SPEED_1000: + new_speed = 1000; + break; + + default: + new_speed = S_UNKNOWN; + break; + } + } + else { // link's down + if (VERBOSE) + puts(" NOT LINK_GOOD"); + + new_state = LS_DOWN; + new_speed = S_UNKNOWN; + } + + if (new_state != ed_state.link_state){ + ed_state.link_state = new_state; // remember new state + if (new_state == LS_UP) + ed_link_up(new_speed); + else if (new_state == LS_DOWN) + ed_link_down(); + } + else if (new_state == LS_UP && new_speed != ed_state.link_speed){ + ed_state.link_speed = new_speed; // remember new speed + ed_link_speed_change(new_speed); + } +} + +/* + * This is fired when the ethernet PHY state changes + */ +static void +eth_phy_irq_handler(unsigned irq) +{ + ed_check_phy_state(); + eth_mac_miim_write(PHY_INT_CLEAR, ~0); // clear all ints +} + +void +ethernet_init(void) +{ + eth_mac_init(ethernet_mac_addr()); + + ed_state.link_state = LS_UNKNOWN; + ed_state.link_speed = S_UNKNOWN; + + // initialize MAC registers + eth_mac->tx_hwmark = 0x1e; + eth_mac->tx_lwmark = 0x19; + + eth_mac->crc_chk_en = 1; + eth_mac->rx_max_length = 2048; + + // configure PAUSE frame stuff + eth_mac->tx_pause_en = 1; // pay attn to pause frames sent to us + + eth_mac->pause_quanta_set = 38; // a bit more than 1 max frame 16kb/512 + fudge + eth_mac->pause_frame_send_en = 1; // enable sending pause frames + + + // setup PHY to interrupt on changes + + unsigned mask = + (PHY_INT_AN_CMPL // auto-neg completed + | PHY_INT_NO_LINK // no link after auto-neg + | PHY_INT_NO_HCD // no highest common denominator + | PHY_INT_MAS_SLA_ERR // couldn't resolve master/slave + | PHY_INT_PRL_DET_FLT // parallel detection fault + | PHY_INT_LNK_CNG // link established or broken + | PHY_INT_SPD_CNG // speed changed + ); + + eth_mac_miim_write(PHY_INT_CLEAR, ~0); // clear all pending interrupts + eth_mac_miim_write(PHY_INT_MASK, mask); // enable the ones we want + + pic_register_handler(IRQ_PHY, eth_phy_irq_handler); + + // Advertise that we handle PAUSE frames and asymmetric pause direction. + int t = eth_mac_miim_read(PHY_AUTONEG_ADV); + eth_mac_miim_write(PHY_AUTONEG_ADV, t | NWAY_AR_PAUSE | NWAY_AR_ASM_DIR); + + // Restart autonegotation. + // We want to ensure that we're advertising our PAUSE capabilities. + t = eth_mac_miim_read(PHY_CTRL); + eth_mac_miim_write(PHY_CTRL, t | MII_CR_RESTART_AUTO_NEG); +} + +static bool +unprogrammed(const u2_mac_addr_t *t) +{ + int i; + bool all_zeros = true; + bool all_ones = true; + for (i = 0; i < 6; i++){ + all_zeros &= t->addr[i] == 0x00; + all_ones &= t->addr[i] == 0xff; + } + return all_ones | all_zeros; +} + +static int8_t src_addr_initialized = false; +static u2_mac_addr_t src_addr = {{ + 0x00, 0x50, 0xC2, 0x85, 0x3f, 0xff + }}; + +const u2_mac_addr_t * +ethernet_mac_addr(void) +{ + if (!src_addr_initialized){ // fetch from eeprom + src_addr_initialized = true; + + // if we're simulating, don't read the EEPROM model, it's REALLY slow + if (hwconfig_simulation_p()) + return &src_addr; + + u2_mac_addr_t tmp; + bool ok = eeprom_read(I2C_ADDR_MBOARD, MBOARD_MAC_ADDR, &tmp.addr[0], 6); + if (!ok || unprogrammed(&tmp)){ + // use the default + } + else + src_addr = tmp; + } + + return &src_addr; +} + +bool +ethernet_set_mac_addr(const u2_mac_addr_t *t) +{ + bool ok = eeprom_write(I2C_ADDR_MBOARD, MBOARD_MAC_ADDR, &t->addr[0], 6); + if (ok){ + src_addr = *t; + src_addr_initialized = true; + eth_mac_set_addr(t); + } + + return ok; +} + +int +ethernet_check_errors(void) +{ + // these registers are reset when read + + int r = 0; + if (eth_mac_read_rmon(0x05) != 0) + r |= RME_RX_CRC; + if (eth_mac_read_rmon(0x06) != 0) + r |= RME_RX_FIFO_FULL; + if (eth_mac_read_rmon(0x07) != 0) + r |= RME_RX_2SHORT_2LONG; + + if (eth_mac_read_rmon(0x25) != 0) + r |= RME_TX_JAM_DROP; + if (eth_mac_read_rmon(0x26) != 0) + r |= RME_TX_FIFO_UNDER; + if (eth_mac_read_rmon(0x27) != 0) + r |= RME_TX_FIFO_OVER; + + return r; +} |