moccos mizuki / EthernetXpresso

Dependents:   XNetServicesMin

LPC1769Emac.cpp

Committer:
moccos
Date:
2012-05-06
Revision:
0:b4bf563e9741
Child:
1:95a4c234aaf6

File content as of revision 0:b4bf563e9741:

#include "LPC1769Emac.h"
#include <LPC1768/ARM/cmsis.h>
#include <mbed.h>
#include "LAN8710AReg.h"
#include "LPC1769Reg.h"
#include "Frame.h"

#define BUFFER_SECTION __attribute((section("AHBSRAM1"),aligned(8)))

#define SET_EMAC_BIT(NAME, PATTERN) (LPC_EMAC->NAME = PATTERN}
#define ENABLE_EMAC_BIT(NAME, MASK) (LPC_EMAC->NAME |= MASK}
#define DISABLE_EMAC_BIT(NAME, MASK)(LPC_EMAC->NAME = LPC_EMAC->NAME & ~MASK}

using namespace LPC1769Reg;

uint8_t LPC1769Emac::mac_[6];

Descriptor LPC1769Emac::rx_desc_[LPC1769Emac::N_RX_BUF] BUFFER_SECTION;
Descriptor LPC1769Emac::tx_desc_[LPC1769Emac::N_TX_BUF] BUFFER_SECTION;
StatusRx LPC1769Emac::rx_status_[LPC1769Emac::N_RX_BUF] BUFFER_SECTION;
StatusTx LPC1769Emac::tx_status_[LPC1769Emac::N_TX_BUF] BUFFER_SECTION;
Frame LPC1769Emac::rx_frame_[LPC1769Emac::N_RX_BUF] BUFFER_SECTION;
Frame LPC1769Emac::tx_frame_[LPC1769Emac::N_TX_BUF] BUFFER_SECTION;

// === inline debug function
#ifdef __MY_DEBUG__
#include "global.h"
static inline void print_buffer(const char* title, uint8_t *buf, uint16_t size) {
    serial.printf("%s (%d bytes) dst:%02x %02x %02x %02x %02x %02x ",
    title, size, buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]);
    buf += 6;
    serial.printf("src:%02x %02x %02x %02x %02x %02x ",
    buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]);
    buf += 6;
    serial.printf("type:%02x %02x\r\n", buf[0], buf[1]);
}
#else
static inline void print_buffer(const char* title, uint8_t *buf, uint16_t size){}
#endif

// === public functions ===
LPC1769Emac::LPC1769Emac() {
    // set address
    // enable ethernet block
    LPC_SC->PCONP |= 0x40000000;

    // enable alternate functions for ethernet.
    // old values are not saved because those pins have only two functions.
    LPC_PINCON->PINSEL2 = 0x50150105;
    LPC_PINCON->PINSEL3 = (LPC_PINCON->PINSEL3 & ~0x0000000F) | 0x00000005;
}

LPC1769Emac::~LPC1769Emac() {
    // TODO: disable PHY before disable ethernet block?

    // disable alternate functions for ethernet
    LPC_PINCON->PINSEL2 = 0x00000000;
    LPC_PINCON->PINSEL3 = LPC_PINCON->PINSEL3 & ~0x0000000F;

    // disable ethernet block
    LPC_SC->PCONP = LPC_SC->PCONP & ~0x40000000;
}

bool
LPC1769Emac::PhyWrite(uint8_t reg, uint16_t value) {
    LPC_EMAC->MADR = PHY_ADDR | reg;
    LPC_EMAC->MWTD = value;

    uint32_t i;
    for (i = 0; i < 0x10000; i++) {
        if ((LPC_EMAC->MIND & MIND_BUSY) == 0) {
            return true;
        }
    }
    return false;
}

uint16_t
LPC1769Emac::PhyRead(uint16_t reg) {
    LPC_EMAC->MADR = PHY_ADDR | reg;
    LPC_EMAC->MCMD = MCMD_READ;

    uint32_t i;
    for (i = 0; i < 0x10000; i++) {
        if ((LPC_EMAC->MIND & MIND_BUSY) == 0) {
            break;
        }
    }
    LPC_EMAC->MCMD = 0x0000;

    return LPC_EMAC->MRDD;
}

void
LPC1769Emac::SetAddress(uint8_t a5, uint8_t a4, uint8_t a3, uint8_t a2, uint8_t a1, uint8_t a0) {
    mac_[5] = a0;
    mac_[4] = a1;
    mac_[3] = a2;
    mac_[2] = a3;
    mac_[1] = a4;
    mac_[0] = a5;
    //WriteAddress_();
}

void
LPC1769Emac::StartRx() {
    LPC_EMAC->Command |= CMD_RX_EN;
    LPC_EMAC->MAC1 |= MAC1_RX_EN;
    //LPC_EMAC->IntEnable |= INT_RX_DONE;
}

void
LPC1769Emac::StartTx() {
    LPC_EMAC->Command |= CMD_TX_EN;
    //LPC_EMAC->IntEnable |= INT_TX_DONE;
}

void
LPC1769Emac::StopRx() {
    LPC_EMAC->Command = LPC_EMAC->Command & ~CMD_RX_EN;
    LPC_EMAC->MAC1 &= ~MAC1_RX_EN;
    //LPC_EMAC->IntEnable &= ~INT_RX_DONE;
}

void
LPC1769Emac::StopTx() {
    LPC_EMAC->Command = LPC_EMAC->Command & ~CMD_TX_EN;
    //LPC_EMAC->IntEnable &= ~INT_TX_DONE;
}

bool
LPC1769Emac::Link() {
    return ((PhyRead(SMIReg::BasicStatus) & SMIReg::BS_LINK) > 0);
}

uint16_t
LPC1769Emac::Recv(void *buf, uint16_t max_size) {
    uint32_t index = LPC_EMAC->RxConsumeIndex;
    if (index == LPC_EMAC->RxProduceIndex || max_size == 0) return 0;

    uint16_t packet_size = (rx_status_[index].info & Descriptor::SIZE_MASK) + 1;
    uint16_t size = packet_size - read_size_;
    if (size > Frame::MAX_FRAME_LEN) size = Frame::MAX_FRAME_LEN;
    if (size > max_size) size = max_size;
    memcpy(buf, read_next_, size);

    read_size_ += size;
    if (read_size_ >= packet_size) {
        // goto next buffer
        index = (index >= LPC_EMAC->RxDescriptorNumber) ? 0 : ++index;
        read_size_ = 0;
        read_next_ = rx_frame_[index].buffer;
        LPC_EMAC->RxConsumeIndex = index;
    } else {
        read_next_ += size;
    }

    return size;
}

uint16_t
LPC1769Emac::ReadyToReceive() {
    uint32_t index = LPC_EMAC->RxConsumeIndex;
    if (index == LPC_EMAC->RxProduceIndex) return 0;
    else return (rx_status_[index].info & Descriptor::SIZE_MASK) + 1;
}

uint16_t
LPC1769Emac::Write(void *buf, uint16_t size) {
    if (size + write_size_ > Frame::MAX_FRAME_LEN) size = Frame::MAX_FRAME_LEN - write_size_;
    if (size == 0) return 0;
    uint32_t index = LPC_EMAC->TxProduceIndex;

    memcpy(write_next_, buf, size);
    write_next_ += size;
    write_size_ += size;
    tx_desc_[index].control = (tx_desc_[index].control & (~Descriptor::SIZE_MASK)) | (size - 1);
    
    return size;
}

bool
LPC1769Emac::Send() {
    if (write_size_ == 0) return true;
    uint32_t index = LPC_EMAC->TxProduceIndex;
    uint32_t index_next = (index == LPC_EMAC->TxDescriptorNumber) ? 0 : index + 1;
    if (index_next == LPC_EMAC->TxConsumeIndex) return false;

    tx_desc_[index].control = (tx_desc_[index].control & (~Descriptor::SIZE_MASK)) | (write_size_ - 1);
    LPC_EMAC->TxProduceIndex = index_next;
    write_next_ = tx_frame_[index_next].buffer;
    write_size_ = 0;

    return true;
}

bool
LPC1769Emac::Send(void *buf, uint16_t size) {
    write_next_ -= write_size_;
    write_size_ = 0;
    if (!Write(buf, size)) return false;
    return Send();
}

bool
LPC1769Emac::Reset(LinkMode mode) {
    bool link_100m = false;
    bool duplex_full = false;
    switch (mode) {
        case HalfDuplex10:
            break;
        case FullDuplex10:
            duplex_full = true;
            break;
        case HalfDuplex100:
            link_100m = true;
            break;
        case FullDuplex100:
            duplex_full = true;
            link_100m = true;
            break;
        case AutoNegotiate:
            duplex_full = true;
            link_100m = true;
            break;
        default:
            return false;
    }

    // reset EMAC
    LPC_EMAC->MAC1 = MAC1_RESET_MASK;
    LPC_EMAC->Command = CMD_RESET_MASK;
    wait_us(100);

    // emac settings
    LPC_EMAC->MCFG = MCFG_RESET_MII | MCFG_CS_DIV52;  // reset MII Management
    wait_us(200);
    LPC_EMAC->MAC1 = 0x0000;
    LPC_EMAC->CLRT = CLRT_DEFAULT;
    LPC_EMAC->MAXF = Frame::MAX_FRAME_LEN;
    LPC_EMAC->IPGR = IPGR_DEFAULT;
    LPC_EMAC->MCFG = MCFG_CS_DIV52;
    if (duplex_full) {
        LPC_EMAC->MAC2 = MAC2_AUTO_PAD | MAC2_FULL_DUPLEX;
        LPC_EMAC->IPGT = IPGT_FULL_DUPLEX;
        LPC_EMAC->Command = CMD_RMII | CMD_FULL_DUPLEX;
    } else {
        LPC_EMAC->MAC2 = MAC2_AUTO_PAD;
        LPC_EMAC->IPGT = IPGT_HALF_DUPLEX;
        LPC_EMAC->Command = CMD_RMII;
    }
    LPC_EMAC->SUPP = link_100m ? SUPP_100M_MODE : 0x0000;

    // reset PHY
    // "the reset process will be completed within 0.5s from the setting of this bit" (8710a datasheet)
    PhyWrite(SMIReg::BasicControl, SMIReg::BC_RESET);
    uint32_t i;
    bool success_flag = false;
    for (i = 0; i < 500; i++) {
        if ((PhyRead(SMIReg::BasicControl) & SMIReg::BC_RESET) == 0) {  // reset bit is self-clearing
            success_flag = true;
            break;
        }
        wait_ms(1);
    }
    if (!success_flag) return false;  // failed to reset

    // why 100M settings fail??
    switch (mode) {
        case HalfDuplex10:
            PhyWrite(SMIReg::BasicControl, 0x0000);
            break;
        case FullDuplex10:
            PhyWrite(SMIReg::BasicControl, SMIReg::BC_FULL_DUPLEX);
            break;
        case HalfDuplex100:
            PhyWrite(SMIReg::BasicControl, SMIReg::BC_100M);
            break;
        case FullDuplex100:
            PhyWrite(SMIReg::BasicControl, SMIReg::BC_FULL_DUPLEX | SMIReg::BC_100M);
            break;
        case AutoNegotiate:
            PhyWrite(SMIReg::BasicControl, SMIReg::BC_AUTONEG);
            // CheckAutoNeg_();
            break;
        default:
            return false;
    }
    
    InitRxBuffer_();
    InitTxBuffer_();
    WriteAddress_();


    LPC_EMAC->IntClear  = INT_ALL_MASK;
    LPC_EMAC->RxFilterCtrl = RXFC_BCAST | RXFC_PERFECT;

    return true;
}

// === private functions ===
void
LPC1769Emac::WriteAddress_() {
    LPC_EMAC->SA0 = ((uint16_t)mac_[5] << 8) | mac_[4];
    LPC_EMAC->SA1 = ((uint16_t)mac_[3] << 8) | mac_[2];
    LPC_EMAC->SA2 = ((uint16_t)mac_[1] << 8) | mac_[0];
}

// currently not used
bool
LPC1769Emac::CheckAutoNeg_() {
    bool success_flag = false;
    uint32_t i, r;
    for (i = 0; i < 0x10000; i++) {
        r = PhyRead(SMIReg::BasicStatus);
        if (r & SMIReg::BS_AUTONEG_COMPLETE) {
            success_flag = true;
            break;
        }
    }
    if (!success_flag) return false;

    r = PhyRead(SMIReg::PHYSpecialControlStatus);

    // update regs
    bool link_100m = ((r & SMIReg::SP_100M) > 0);
    bool duplex_full = ((r & SMIReg::SP_FULL_DUPLEX) > 0);
    
    if (duplex_full) {
        LPC_EMAC->MAC2 = MAC2_AUTO_PAD | MAC2_FULL_DUPLEX;
        LPC_EMAC->IPGT = IPGT_FULL_DUPLEX;
        LPC_EMAC->Command = CMD_RMII | CMD_FULL_DUPLEX;
    } else {
        LPC_EMAC->MAC2 = MAC2_AUTO_PAD;
        LPC_EMAC->IPGT = IPGT_HALF_DUPLEX;
        LPC_EMAC->Command = CMD_RMII;
    }
    LPC_EMAC->SUPP = link_100m ? SUPP_100M_MODE : 0x0000;

    return true;
}

void
LPC1769Emac::InitRxBuffer_() {
    uint16_t i;

    // set initial descriptor
    for (i = 0; i < N_RX_BUF; i++) {
        rx_desc_[i].packet = (uint32_t)&rx_frame_[i];
        rx_desc_[i].control = 0x80000000 | (Frame::MAX_FRAME_LEN - 1);
    }

    // emac registers
    LPC_EMAC->RxDescriptor = (uint32_t)rx_desc_;
    LPC_EMAC->RxStatus = (uint32_t)rx_status_;
    LPC_EMAC->RxDescriptorNumber = N_RX_BUF - 1;  // this register is minus one encoded
    LPC_EMAC->RxConsumeIndex  = 0;
    read_next_ = rx_frame_[0].buffer;
    read_size_ = 0;
}

void
LPC1769Emac::InitTxBuffer_() {
    uint16_t i;

    // set initial descriptor
    for (i = 0; i < N_TX_BUF; i++) {
        tx_desc_[i].packet = (uint32_t)&tx_frame_[i];
        tx_desc_[i].control = 0xf4000000 | Frame::MAX_FRAME_LEN;
        tx_status_[i].info = 0;
    }

    // emac registers
    LPC_EMAC->TxDescriptor = (uint32_t)tx_desc_;
    LPC_EMAC->TxStatus = (uint32_t)tx_status_;
    LPC_EMAC->TxDescriptorNumber = N_TX_BUF - 1;  // this register is minus one encoded
    LPC_EMAC->TxProduceIndex  = 0;
    write_next_ = tx_frame_[0].buffer;
    write_size_ = 0;
}