//==========================================================================
//
//      lm3s_common.c
//
//      Common routines for Ethernet driver for Luminary LM3S family on-chip EMAC.
//
//==========================================================================
// ####ECOSGPLCOPYRIGHTBEGIN####                                            
// -------------------------------------------                              
// This file is part of eCos, the Embedded Configurable Operating System.   
// Copyright (C) 2007, 2008, 2009 eCosCentric Limited
//
// eCos 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 2 or (at your option) any later      
// version.                                                                 
//
// eCos 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 eCos; if not, write to the Free Software Foundation, Inc.,    
// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.            
//
// As a special exception, if other files instantiate templates or use      
// macros or inline functions from this file, or you compile this file      
// and link it with other works to produce a work based on this file,       
// this file does not by itself cause the resulting work to be covered by   
// the GNU General Public License. However the source code for this file    
// must still be made available in accordance with section (3) of the GNU   
// General Public License v2.                                               
//
// This exception does not invalidate any other reasons why a work based    
// on this file might be covered by the GNU General Public License.         
// -------------------------------------------                              
// ####ECOSGPLCOPYRIGHTEND####                                              
//==========================================================================
//#####DESCRIPTIONBEGIN####
//
// Author(s):    nickg
// Contributors:
// Date:         2009-08-03
//
//####DESCRIPTIONEND####
//==========================================================================

//======================================================================
// Common data structure between all driver types.

typedef struct lm3s_eth_common
{
    cyg_uint32          base;           // EMAC base address
    cyg_uint32          vector;         // interrupt vector

    // Transmit and receive staging buffers. The FIFO can only be read
    // or written in word-sized chunks, but we really want to handle
    // bytes. These buffers handle the transition.
    union
    {
        cyg_uint32      ui32;
        cyg_uint8       ui8[4];
    } txbuf, rxbuf;
    int                 txbuf_index;
    int                 rxbuf_index;
    
} lm3s_eth_common;

//======================================================================

static bool lm3s_read_phy(int reg_addr, int phy_addr, unsigned short *data);

//======================================================================

#define lm3s_eth_show_reg( __reg )                              \
{                                                               \
    cyg_uint32 r;                                               \
    HAL_READ_UINT32( base+CYGHWR_HAL_LM3S_ETH_##__reg, r );     \
    eth_diag("\t%11s = %08x", #__reg, r);                       \
}

static void
lm3s_eth_show_mac( lm3s_eth_common *eth )
{
    cyg_uint32 base = eth->base;
    
    eth_diag("MAC:");
    lm3s_eth_show_reg( RIS );
    lm3s_eth_show_reg( IACK );
    lm3s_eth_show_reg( IM );
    lm3s_eth_show_reg( RCTRL );
    lm3s_eth_show_reg( TCTRL );
    lm3s_eth_show_reg( DATA );
    lm3s_eth_show_reg( IA0 );
    lm3s_eth_show_reg( IA1 );
    lm3s_eth_show_reg( THR );
    lm3s_eth_show_reg( MCTL );
    lm3s_eth_show_reg( MDV );
    lm3s_eth_show_reg( MTXD );
    lm3s_eth_show_reg( MRXD );
    lm3s_eth_show_reg( NP );
    lm3s_eth_show_reg( TR );
    lm3s_eth_show_reg( TS );

}

//======================================================================
// The MAC address. Comes from RedBoot's config info, if CDL indicates we should
// try. Otherwise this will be supplied by the platform HAL, but some
// platforms may not have appropriate hardware. Worst case, we use the CDL-defined
// configurable default value.

static cyg_uint8    lm3s_eth_default_mac[6]  = { CYGDAT_DEVS_ETH_CORTEXM_LM3S_MACADDR } ;

static void
lm3s_eth_get_config_mac_address(cyg_uint8* mac)
{
    int mac_ok  = 0;
    // Do we have virtual vectors?
#if defined(CYGPKG_DEVS_ETH_CORTEXM_LM3S_REDBOOT_HOLDS_ESA) // implies VV support
    // There has been an incompatible change to hal_if.h. Cope with both new and old names
# ifdef CYGNUM_FLASH_CFG_TYPE_CONFIG_ESA    
    mac_ok  = CYGACC_CALL_IF_FLASH_CFG_OP(CYGNUM_CALL_IF_FLASH_CFG_GET, "eth0_esa_data", mac, CYGNUM_FLASH_CFG_TYPE_CONFIG_ESA);
# else    
    mac_ok  = CYGACC_CALL_IF_FLASH_CFG_OP(CYGNUM_CALL_IF_FLASH_CFG_GET, "eth0_esa_data", mac, CYGNUM_FLASH_CFG_OP_CONFIG_ESA);
# endif
#endif
#ifdef CYGHWR_DEVS_ETH_CORTEXM_LM3S_GET_ESA
    if (!mac_ok) {
        CYGHWR_DEVS_ETH_CORTEXM_LM3S_GET_ESA(mac, mac_ok);
    }
#endif
    if (!mac_ok) {
        memcpy(mac, lm3s_eth_default_mac, 6);
    }
}

//======================================================================
// Configure pins and clocks

static void lm3s_eth_cfg( lm3s_eth_common *eth )
{
    eth_diag("");

    // Enable clocks for MAC and PHY
    CYGHWR_HAL_LM3S_CLOCK_ENABLE( CYGHWR_HAL_LM3S_ETH_MAC0_CLOCK ); 
    CYGHWR_HAL_LM3S_CLOCK_ENABLE( CYGHWR_HAL_LM3S_ETH_PHY0_CLOCK ); 

    // LED GPIO pins
    CYGHWR_HAL_LM3S_GPIO_SET( CYGHWR_HAL_LM3S_ETH_ETH0_LED0 );
    CYGHWR_HAL_LM3S_GPIO_SET( CYGHWR_HAL_LM3S_ETH_ETH0_LED1 );
}

//======================================================================
// Set MAC address

static void lm3s_eth_set_mac( lm3s_eth_common *eth, cyg_uint8 *mac )
{
    cyg_uint32 hi, lo;

    eth_diag("mac: %02x:%02x:%02x:%02x:%02x:%02x", mac[0],mac[1],mac[2],mac[3],mac[4],mac[5]);
    
    lo = ( mac[0] <<  0 ) |
         ( mac[1] <<  8 ) |
         ( mac[2] << 16 ) |
         ( mac[3] << 24 ) ;

    hi = ( mac[4] <<  0 ) |
         ( mac[5] <<  8 ) ;

    HAL_WRITE_UINT32( eth->base+CYGHWR_HAL_LM3S_ETH_IA1, hi );
    HAL_WRITE_UINT32( eth->base+CYGHWR_HAL_LM3S_ETH_IA0, lo );
}

//======================================================================
// Initialize MAC

__externC cyg_uint32 hal_lm3s_sysclk;

static cyg_bool lm3s_eth_init( lm3s_eth_common *eth )
{
    cyg_uint32 base = eth->base;
    cyg_uint32 reg;

    eth_diag("build time: %s %s", __DATE__, __TIME__);

    // Set MII clock divider. This needs to be set to give a MII clock
    // speed less than 2.5MHz.
    for( reg = 1; hal_lm3s_sysclk/reg > 2500000; reg++ )
        continue;
    HAL_WRITE_UINT32( base+CYGHWR_HAL_LM3S_ETH_MDV, reg );

    // Reset Rx FIFO and disable RX engine
    HAL_WRITE_UINT32( base+CYGHWR_HAL_LM3S_ETH_RCTRL,
                      CYGHWR_HAL_LM3S_ETH_RCTRL_RSTFIFO );

    // Set TCTL to auto CRC, padding and full duplex
    HAL_WRITE_UINT32( base+CYGHWR_HAL_LM3S_ETH_TCTRL,
                      CYGHWR_HAL_LM3S_ETH_TCTRL_CRC     |
                      CYGHWR_HAL_LM3S_ETH_TCTRL_PADEN   );

    // Set RCTRL to reject frames with bad FCS
    HAL_WRITE_UINT32( base+CYGHWR_HAL_LM3S_ETH_RCTRL,
                      CYGHWR_HAL_LM3S_ETH_RCTRL_BADCRC  );

    // Disable all interrupts.
    HAL_WRITE_UINT32( base+CYGHWR_HAL_LM3S_ETH_IM,  0 );    
    
    // Ack any pending interrupts.
    HAL_WRITE_UINT32( base+CYGHWR_HAL_LM3S_ETH_IACK,  0xff );

    // Init buffers
    eth->txbuf_index = 0;
    eth->rxbuf_index = 4;
    
    eth_diag("done");
    return true;
}

//======================================================================
// Enable MAC

static cyg_bool lm3s_eth_enable( lm3s_eth_common *eth )
{
    cyg_uint32 base = eth->base;
    cyg_uint32 reg;

    eth_diag("enter");

    // Enable Rx engine. Tx will be enabled later.
    HAL_READ_UINT32( base+CYGHWR_HAL_LM3S_ETH_RCTRL, reg );
    reg |= CYGHWR_HAL_LM3S_ETH_RCTRL_RXEN;
    HAL_WRITE_UINT32( base+CYGHWR_HAL_LM3S_ETH_RCTRL, reg );

    // Enable interrupts
    reg = 0;
    reg |= CYGHWR_HAL_LM3S_ETH_INT_RXINT;
    reg |= CYGHWR_HAL_LM3S_ETH_INT_RXER;
    reg |= CYGHWR_HAL_LM3S_ETH_INT_TXEMP;
    reg |= CYGHWR_HAL_LM3S_ETH_INT_TXER;
    reg |= CYGHWR_HAL_LM3S_ETH_INT_FOV;
    HAL_WRITE_UINT32( base+CYGHWR_HAL_LM3S_ETH_IM, reg );    

    eth_diag("done");

    lm3s_eth_show_mac( eth );
        
    return true;
}

//======================================================================

static void lm3s_eth_tx_start( lm3s_eth_common *eth )
{
    cyg_uint32 base = eth->base;
    cyg_uint32 reg;

    HAL_READ_UINT32( base+CYGHWR_HAL_LM3S_ETH_TCTRL, reg );
    reg |= CYGHWR_HAL_LM3S_ETH_TCTRL_TXEN;
    HAL_WRITE_UINT32( base+CYGHWR_HAL_LM3S_ETH_TCTRL, reg );

    HAL_WRITE_UINT32( base+CYGHWR_HAL_LM3S_ETH_TR, CYGHWR_HAL_LM3S_ETH_TR_NEWTX );
}

//======================================================================

static void lm3s_eth_tx_stop( lm3s_eth_common *eth )
{
    cyg_uint32 base = eth->base;    
    cyg_uint32 reg;

    HAL_READ_UINT32( base+CYGHWR_HAL_LM3S_ETH_TCTRL, reg );
    reg &= ~CYGHWR_HAL_LM3S_ETH_TCTRL_TXEN;
    HAL_WRITE_UINT32( base+CYGHWR_HAL_LM3S_ETH_TCTRL, reg );
}

//======================================================================
// Transmit and receive buffer handling.

static void lm3s_eth_txbyte( lm3s_eth_common *eth, cyg_uint8 byte )
{
    eth->txbuf.ui8[eth->txbuf_index++] = byte;

    if( eth->txbuf_index == 4 )
    {
        HAL_WRITE_UINT32( eth->base+CYGHWR_HAL_LM3S_ETH_DATA, eth->txbuf.ui32 );
        eth->txbuf_index = 0;
        eth->txbuf.ui32 = 0;
    }
}

static void lm3s_eth_txflush( lm3s_eth_common *eth )
{
    if( eth->txbuf_index != 0 )
    {
        HAL_WRITE_UINT32( eth->base+CYGHWR_HAL_LM3S_ETH_DATA, eth->txbuf.ui32 );
        eth->txbuf_index = 0;
        eth->txbuf.ui32 = 0;
    }
}


static cyg_uint8 lm3s_eth_rxbyte( lm3s_eth_common *eth )
{
    if( eth->rxbuf_index == 4 )
    {
        HAL_READ_UINT32( eth->base+CYGHWR_HAL_LM3S_ETH_DATA, eth->rxbuf.ui32 );
        eth->rxbuf_index = 0;
    }

    return eth->rxbuf.ui8[eth->rxbuf_index++];
}

static void lm3s_eth_rxflush( lm3s_eth_common *eth )
{
    eth->rxbuf_index = 4;
    eth->rxbuf.ui32 = 0;
}

//======================================================================
// Get the PHY started, read the state from it and set the MAC up
// to match.

static cyg_bool lm3s_eth_phy_init( lm3s_eth_common *eth )
{
    cyg_uint16 mr0;
    cyg_uint16 mr1;

    lm3s_read_phy(0, 0, &mr0 );
    lm3s_read_phy(1, 0, &mr1 );

    eth_diag("mr0 %04x mr1 %04x", mr0, mr1 );

    if( (mr1 & 0x0004) == 0 )
    {
        diag_printf("ETH_LM3S: No Link\n");
        return false;
    }
    
    if( mr0 & 0x0100 )
    {
        cyg_uint32 reg;
        eth_diag("Set DUPLEX");
        HAL_READ_UINT32( eth->base+CYGHWR_HAL_LM3S_ETH_TCTRL, reg );
        reg |= CYGHWR_HAL_LM3S_ETH_TCTRL_DUPLEX;
        HAL_WRITE_UINT32( eth->base+CYGHWR_HAL_LM3S_ETH_TCTRL, reg );
    }
    
    return true;
}

//======================================================================
// PHY support

// ---------------------------------------------------------------------
// Write one of the PHY registers via the MII bus

static void
lm3s_write_phy(int reg_addr, int phy_addr, unsigned short data)
{
    cyg_uint32 base = CYGHWR_HAL_LM3S_ETH;
    cyg_uint32 val;

    eth_diag("phy %d reg %d data %04x", phy_addr, reg_addr, (unsigned)data);

    HAL_WRITE_UINT32( base+CYGHWR_HAL_LM3S_ETH_MTXD, data );

    val = CYGHWR_HAL_LM3S_ETH_MCTL_REGADDR( reg_addr )  |
          CYGHWR_HAL_LM3S_ETH_MCTL_WRITE                |
          CYGHWR_HAL_LM3S_ETH_MCTL_START                ;

    HAL_WRITE_UINT32( base+CYGHWR_HAL_LM3S_ETH_MCTL, val );

    do {
        HAL_READ_UINT32( base+CYGHWR_HAL_LM3S_ETH_MCTL, val );        
    } while( val & CYGHWR_HAL_LM3S_ETH_MCTL_START );
}

// ---------------------------------------------------------------------
// Read one of the PHY registers via the MII bus

static bool
lm3s_read_phy(int reg_addr, int phy_addr, unsigned short *data)
{
    cyg_uint32 base = CYGHWR_HAL_LM3S_ETH;
    cyg_uint32 val;

    val = CYGHWR_HAL_LM3S_ETH_MCTL_REGADDR( reg_addr )  |
          CYGHWR_HAL_LM3S_ETH_MCTL_START                ;

    HAL_WRITE_UINT32( base+CYGHWR_HAL_LM3S_ETH_MCTL, val );

    do {
        HAL_READ_UINT32( base+CYGHWR_HAL_LM3S_ETH_MCTL, val );        
    } while( val & CYGHWR_HAL_LM3S_ETH_MCTL_START );
    
    HAL_READ_UINT32( base+CYGHWR_HAL_LM3S_ETH_MRXD, val );
    *data = (unsigned short)val;
    
    eth_diag("phy %d reg %d data %04x", phy_addr, reg_addr, (unsigned)*data);   

    return true;
}


#if 0

// ---------------------------------------------------------------------
// PHY driver support. Not currently needed, but left here, disabled,
// just in case.


static void 
lm3s_init_phy(void)
{
    return;
}

// ---------------------------------------------------------------------
// Instantiate the PHY device access driver

ETH_PHY_REG_LEVEL_ACCESS_FUNS(lm3s_phy, 
                              lm3s_init_phy,
                              NULL,
                              lm3s_write_phy,
                              lm3s_read_phy);

#endif

//======================================================================
// end of lm3s_common.h
