//==========================================================================
//
//      if_lm3s-lwip.c
//
//      lwIP-specific 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
// Description:  Hardware driver for LM3S ethernet devices specifically
//               and solely for lwIP TCP/IP stack.
//
//####DESCRIPTIONEND####
//==========================================================================

#include <pkgconf/system.h>
#include <pkgconf/hal.h>
#include <pkgconf/devs_eth_cortexm_lm3s.h>
#include <pkgconf/io_eth_drivers.h>

#include <cyg/infra/cyg_type.h>
#include <cyg/infra/cyg_ass.h>
#include <cyg/hal/hal_arch.h>
#include <cyg/hal/hal_intr.h>
#include <cyg/hal/hal_if.h>
#include <cyg/infra/diag.h>
#include <cyg/hal/drv_api.h>
#include <cyg/io/eth/netdev.h>
#include <cyg/io/eth/eth_drv.h>
#include <string.h>
//#include <cyg/io/eth_phy.h>

#include <pkgconf/net_lwip.h>
#include "lwip/opt.h"
#include "lwip/err.h"
#include "lwip/netif.h"
#include "lwip/pbuf.h"

#include <cyg/kernel/ktypes.h>

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

#ifdef CYGACC_CALL_IF_DELAY_US
# define LM3S_ETH_DELAY_US(us) CYGACC_CALL_IF_DELAY_US(us)
#else
# include <cyg/hal/hal_diag.h>
# define LM3S_ETH_DELAY_US(us) HAL_DELAY_US(us)
#endif

//==========================================================================
// Debug support

#if 0
#define eth_diag( __fmt, ... ) diag_printf("LM3S_ETH: %30s[%4d]: " __fmt "\n", __FUNCTION__, __LINE__, ## __VA_ARGS__ )
#define eth_dump_buf( __buf, __len ) diag_dump_buf( __buf, __len )
#else
#define eth_diag( __fmt, ... ) 
#define eth_dump_buf( __buf, __len ) 
#endif

//#define eth_diag_printf diag_printf
#define eth_diag_printf( __fmt, ... )

//======================================================================
// Include common stuff after debug macro defs. This includes PHY
// access code and other routines that are common between all driver
// types.

#include "lm3s_common.h"

//======================================================================
// Private device structure

typedef struct lm3s_eth
{
    lm3s_eth_common     common;         // Common data
    
    // LWIP interfacing

    struct netif        netif;          // LwIP structure
    
    // Transmit handling

    volatile cyg_uint8  tx_can_send;    // Transmitter free
    
    // Shared data

    cyg_uint8           mac[6];                 // MAC address
    cyg_uint8           started;                // driver started?
    
    cyg_handle_t        interrupt_handle;
    cyg_interrupt       interrupt_data;
    
} lm3s_eth;

//==========================================================================
// Instantiate the current single MAC device.

static lm3s_eth lm3s_eth_info =
{
    .common.base        = CYGHWR_HAL_LM3S_ETH,
    .common.vector      = CYGNUM_HAL_INTERRUPT_ETH,
};

//==========================================================================
// Define a type for pool used for RX packet data pbufs.

struct lm3s_pbuf_pool_and_payload {
    struct pbuf pbuf;
    char payload[CYGNUM_LWIP_PBUF_POOL_BUFSIZE];
};

struct lm3s_pbuf_pool_and_payload lm3s_pbuf_pool[CYGNUM_LWIP_PBUF_POOL_SIZE];

//==========================================================================
// Forward definitions

static cyg_uint32 lm3s_eth_isr(cyg_vector_t vector, cyg_addrword_t data);
static void       lm3s_eth_dsr(cyg_vector_t vector, cyg_ucount32 count, cyg_addrword_t data);
static void       lm3s_eth_deliver_tx(lm3s_eth *eth, cyg_bool check_sr);

//==========================================================================
/* lwIP PBUF_POOL creation override */

__externC struct pbuf *
cyg_lm3s_eth_lwip_pool_init(void)
{
    struct pbuf *p;
    cyg_ucount32 i;

    for (i=0; i<CYGNUM_LWIP_PBUF_POOL_SIZE; i++)
    {
        p = &lm3s_pbuf_pool[i].pbuf;
        p->payload = lm3s_pbuf_pool[i].payload;
        p->next = &lm3s_pbuf_pool[i+1].pbuf;
        p->len = p->tot_len = CYGNUM_LWIP_PBUF_POOL_BUFSIZE;
        p->flags = PBUF_FLAG_POOL;
    }

    // Last pbuf's next field in fact gets set to NULL to denote end.
    p->next = NULL;

    return &lm3s_pbuf_pool[0].pbuf;
}

#if 0
void show_pool( void )
{
    int i;
    for (i=0; i<CYGNUM_LWIP_PBUF_POOL_SIZE; i++)
    {
        struct lm3s_pbuf_pool_and_payload *p = &lm3s_pbuf_pool[i];
        eth_diag("%p %p %d %d", p, p->pbuf.next, p->pbuf.tot_len, p->pbuf.len );
    }
}
#endif

//==========================================================================
/* Hook when freeing a pool pbuf. */
/* This is called inside a SYS_ARCH_PROTECT, so we should be m-t safe */

__externC void
cyg_lm3s_eth_lwip_pool_free_hook( struct pbuf *p )
{
    CYG_UNUSED_PARAM(struct pbuf *, p);

    eth_diag( "Freeing pbuf @ 0x%08x, with payload 0x%08x", (unsigned)p, (unsigned)p->payload);
}

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

__externC void cyg_lwip_eth_ecos_init(void)
{
    lm3s_eth           *eth = &lm3s_eth_info;
    err_t               err;
    char                do_dhcp;

    eth_diag("base %08x vector %d", eth->common.base, eth->common.vector);
    
    lm3s_eth_cfg( &eth->common );

    lm3s_eth_init( &eth->common );

    // Create and attach interrupt
    cyg_drv_interrupt_create(eth->common.vector,
                             CYGNUM_DEVS_ETH_CORTEXM_LM3S_INTRPRI,
                             (CYG_ADDRWORD) eth,
                             lm3s_eth_isr,
                             lm3s_eth_dsr,
                             &eth->interrupt_handle,
                             &eth->interrupt_data);
    cyg_drv_interrupt_attach(eth->interrupt_handle);
    cyg_drv_interrupt_unmask(eth->common.vector);


    // Fetch and set MAC address
    
    lm3s_eth_get_config_mac_address(eth->mac);

    lm3s_eth_set_mac( &eth->common, eth->mac );

    // Set up PHY

    if( !lm3s_eth_phy_init( &eth->common ) )
    {
        eth_diag("Phy initialization failed");
        return;
    }

    eth->tx_can_send = 1;

    // Initialize upper level driver

    err = cyg_lwip_eth_drv_init_netif((void *)eth, eth->mac, &eth->netif, &do_dhcp);

    if (ERR_OK == err)
    {
        // Enable rx/tx operation.
        lm3s_eth_enable(&eth->common);
        eth->started = 1;

#ifdef CYGFUN_LWIP_DHCP
        //
        // we call this after the driver was started successfully
        //
        if (do_dhcp)
            cyg_lwip_dhcp_init(&eth->netif);
#endif
    }

    eth_diag("Done");
    
    return;
}

//==========================================================================
// The RX interrupt triggers whenever a whole frame has been received (or
// when an error has occurred). 
//
// The TX interrupt triggers when a whole frame has been sent. Since at present
// we only send one frame at a time, the TX hardware will be idle.

static cyg_uint32
lm3s_eth_isr(cyg_vector_t vector, cyg_addrword_t data)
{
    lm3s_eth *eth = (lm3s_eth *)data;
    cyg_uint32 ris;

    HAL_READ_UINT32( eth->common.base+CYGHWR_HAL_LM3S_ETH_RIS, ris );
        
//    eth_diag("ris %08x", ris);

    cyg_drv_interrupt_mask(eth->common.vector);

    // Ack now, to prevent issues with other interrupts.
    cyg_drv_interrupt_acknowledge(eth->common.vector);

    return (CYG_ISR_HANDLED|CYG_ISR_CALL_DSR);  // Run the DSR
}

static void
lm3s_eth_dsr(cyg_vector_t vector, cyg_ucount32 count, cyg_addrword_t data)
{
    // There is no need to worry about cleaning up the buffer descriptors.
    // The hardware will have set the ownership bit, which is the only one
    // which needs handling.
//    eth_diag("vector %d", vector);    

    cyg_lwip_eth_dsr();
}

//==========================================================================
// The driver only supports one transmit at a time, and a global is used
// to keep track of whether or not a transmit is in progress.

static __inline__ int
lm3s_eth_can_send(struct lm3s_eth* eth)
{
    return eth->tx_can_send;
}

//==========================================================================
//
// cyg_lwip_eth_low_level_output():
//
// This is the function called by higher layers which should do the actual
// transmission of the packet. The packet is contained in the pbuf that is
// passed to the function. This pbuf might be chained.


__externC err_t
cyg_lwip_eth_low_level_output(struct netif *netif, struct pbuf *p)
{
    lm3s_eth *eth = (lm3s_eth *)netif->state;
    struct pbuf *q;

    eth_diag("pbuf %p", p );
    
    // Sanity check p. Don't waste code returning on an error like this.
    // Just don't screw up in the first place!
    if (!p)
    {
        eth_diag("passed NULL pbuf");
        CYG_FAIL("cyg_lwip_eth_low_level_output passed NULL pbuf");
    }

    if (!p->len || !p->tot_len)
    {
        eth_diag("passed empty pbuf");
        CYG_FAIL("cyg_lwip_eth_low_level_output passed empty pbuf");
    }

    if (!eth->started)
        return ERR_IF;

    // Can we send?
    if (lm3s_eth_can_send(eth) <= 0)
    {
        eth_diag("could not send");

        // If we can't send, we need to call the delivery function to make
        // sure that the reason we can't send isn't just because we are
        // either higher priority than the normal thread doing delivery,
        // and thus preventing it delivering; or that we _are_ the
        // delivery thread, in which case we would never see it complete
        // even if it had, because clearly this thread has been "doing
        // stuff" if it has stuff to send.
        lm3s_eth_deliver_tx(eth, true);

#ifdef CYGIMP_IO_ETH_DRIVERS_LWIP_TX_FULL_WAIT
        while (lm3s_eth_can_send(eth) <= 0)
        {
            // As above, call the delivery function each time. We may
            // be the delivery thread anyway, so if we didn't, looping here
            // and not calling it would otherwise cause everything to freeze.
            lm3s_eth_deliver_tx(eth, true);

# if (CYGNUM_IO_ETH_DRIVERS_LWIP_TX_FULL_WAIT_DELAY > 0)
            // Give others a chance to run
            LM3S_ETH_DELAY_US(CYGNUM_IO_ETH_DRIVERS_LWIP_TX_FULL_WAIT_DELAY);
# endif
        }
#else // !CYGIMP_IO_ETH_DRIVERS_LWIP_TX_FULL_WAIT
        /* Otherwise if we still can't send, just drop the packet completely. 
         * We claim success because there isn't necessarily really an error as such.
         * We may just be generating data too fast.
         */
        if (lm3s_eth_can_send(eth) <= 0) {
            eth_diag("Couldn't send packet. Dropping");
            return ERR_OK;
        }
#endif
        eth_diag("Can now send");
    }

    eth->tx_can_send    = 0;

    // Send frame size
    cyg_uint16 frame_len = p->tot_len - 14;
    lm3s_eth_txbyte( &eth->common, frame_len&0xFF );
    lm3s_eth_txbyte( &eth->common, (frame_len>>8)&0xFF );
    
    for (q=p; q; q=q->next)
    {
        // Don't waste effort on empty chain entries
        if (q->len)
        {
            int i;
            cyg_uint8 *payload = (cyg_uint8 *)q->payload;
            eth_diag("pbuf %08x payload 0x%08x len %d", (cyg_uint32)q, (cyg_uint32)q->payload, q->len );
//            eth_dump_buf( q->payload, q->len );

            for( i = 0; i < q->len; i++ )
                lm3s_eth_txbyte( &eth->common, payload[i] );
        }
    }

    lm3s_eth_txflush ( &eth->common );
    lm3s_eth_tx_start( &eth->common );
    
    return ERR_OK;
}


//==========================================================================
// Perform deliveries on the transmit side. Called in delivery thread
// context.

static void
lm3s_eth_deliver_tx(lm3s_eth *eth, cyg_bool check_sr)
{
    lm3s_eth_tx_stop( &eth->common );        
    eth->tx_can_send = 1;
}

//==========================================================================
// Perform deliveries on the RX side. Called in delivery thread context.


static void
lm3s_eth_deliver_rx( lm3s_eth *eth )
{
    cyg_uint32 base = eth->common.base;
    cyg_uint32 np;
            
    HAL_READ_UINT32( base+CYGHWR_HAL_LM3S_ETH_NP, np );

    while( np )
    {
        struct pbuf *first_pbuf = NULL;
        struct pbuf *p, *newp;
        cyg_uint32 pkt_size;

        pkt_size = lm3s_eth_rxbyte( &eth->common );
        pkt_size |= lm3s_eth_rxbyte( &eth->common )<<8;
        pkt_size -= 6;
        
        eth_diag("np %d pkt_size %d", np, pkt_size );

        while( pkt_size > 0 )
        {
            newp = pbuf_alloc(PBUF_RAW, CYGNUM_LWIP_PBUF_POOL_BUFSIZE, PBUF_POOL);

            eth_diag("pkt_size %d newp %p", pkt_size, newp );
            
            if( newp != NULL )
            {
                cyg_int32 len = pkt_size;
                cyg_uint8 *payload;
                
                if( len > CYGNUM_LWIP_PBUF_POOL_BUFSIZE )
                    len = CYGNUM_LWIP_PBUF_POOL_BUFSIZE;

                eth_diag("len %d pkt_size %d", len, pkt_size );
                
                if( first_pbuf == NULL )
                    first_pbuf = newp;
                else
                    p->next = newp;

                newp->next = NULL;
                p = newp;
                p->len = len;
                p->tot_len = pkt_size;
                pkt_size -= len;

                payload = (cyg_uint8 *)p->payload;

                while( len-- > 0 )
                    *payload++ = lm3s_eth_rxbyte( &eth->common );

                eth_diag("pbuf %p tl %d len %d", p, p->tot_len, p->len );
//                eth_dump_buf( p->payload, p->len );
            }
            else
            {
                eth_diag_printf("LM3S_ETH: out of pbufs!!\n");
                break; // No pbufs available
            }
        }

        if( pkt_size != 0 )
        {
            // Something went wrong, dispose of existing pbufs and
            // pull in rest of packet.

            if( first_pbuf )
                pbuf_free(first_pbuf);
            first_pbuf = NULL;
            
            while( pkt_size-- > 0 )
                lm3s_eth_rxbyte( &eth->common );
        }

        // Absorb the FCS and throw it away
        lm3s_eth_rxbyte( &eth->common );
        lm3s_eth_rxbyte( &eth->common );
        lm3s_eth_rxbyte( &eth->common );
        lm3s_eth_rxbyte( &eth->common );

        // Dispose of any remaining bytes in the buffer
        lm3s_eth_rxflush( &eth->common );

        // Now pass packet up to stack, if we have one.
        if( first_pbuf )
            cyg_lwip_eth_drv_ecosif_input( &eth->netif, first_pbuf );
        
        HAL_READ_UINT32( base+CYGHWR_HAL_LM3S_ETH_NP, np );
    }
    
} /* lm3s_eth_deliver_rx() */

//==========================================================================
// The delivery function is called from thread context, after the DSR has woken
// up the packet handling thread. It needs to report completed transmits so
// that higher-level code can release the tx pbuf if needed, and report all
// received frames.

__externC void
cyg_lwip_eth_run_deliveries(void)
{
    lm3s_eth *eth = &lm3s_eth_info;
    cyg_uint32 base = eth->common.base;
    cyg_uint32 ris;
    cyg_uint32 im;
        
    HAL_READ_UINT32( base+CYGHWR_HAL_LM3S_ETH_RIS, ris );
    HAL_READ_UINT32( base+CYGHWR_HAL_LM3S_ETH_IM, im );

    ris &= im;
                
    {
        static cyg_uint32 ris0;
        if( ris != ris0 )
        {
//                eth_diag("ris %08x", ris);
            ris0 = ris;
        }
    }

    if( ris & CYGHWR_HAL_LM3S_ETH_INT_TXEMP )
    {
        // End of frame transmission
        HAL_WRITE_UINT32( base+CYGHWR_HAL_LM3S_ETH_IACK,
                          CYGHWR_HAL_LM3S_ETH_INT_TXEMP );

        lm3s_eth_deliver_tx( eth, false );
    }

    if( ris & CYGHWR_HAL_LM3S_ETH_INT_RXINT )
    {
        // End of frame reception
        HAL_WRITE_UINT32( base+CYGHWR_HAL_LM3S_ETH_IACK,
                          CYGHWR_HAL_LM3S_ETH_INT_RXINT);

        lm3s_eth_deliver_rx( eth );

    }

    if( ris & CYGHWR_HAL_LM3S_ETH_INT_RXER )
    {
        HAL_WRITE_UINT32( base+CYGHWR_HAL_LM3S_ETH_IACK,
                          CYGHWR_HAL_LM3S_ETH_INT_RXER);

        eth_diag_printf("LM3S_ETH: Rx error\n");
    }

    if( ris & CYGHWR_HAL_LM3S_ETH_INT_TXER )
    {
        HAL_WRITE_UINT32( base+CYGHWR_HAL_LM3S_ETH_IACK,
                          CYGHWR_HAL_LM3S_ETH_INT_TXER);

        eth_diag_printf("LM3S_ETH: Tx error\n");
    }

    if( ris & CYGHWR_HAL_LM3S_ETH_INT_FOV )
    {
        HAL_WRITE_UINT32( base+CYGHWR_HAL_LM3S_ETH_IACK,
                          CYGHWR_HAL_LM3S_ETH_INT_FOV);

        eth_diag_printf("LM3S_ETH: FIFO Overrun\n");
    }

    if( ris & CYGHWR_HAL_LM3S_ETH_INT_PHY )
    {
        HAL_WRITE_UINT32( base+CYGHWR_HAL_LM3S_ETH_IACK,
                          CYGHWR_HAL_LM3S_ETH_INT_PHY);

        eth_diag_printf("LM3S_ETH: PHY Interrupt\n");
    }
        
    if( ris & CYGHWR_HAL_LM3S_ETH_INT_MD )
    {
        HAL_WRITE_UINT32( base+CYGHWR_HAL_LM3S_ETH_IACK,
                          CYGHWR_HAL_LM3S_ETH_INT_MD);

        eth_diag_printf("LM3S_ETH: MDIO Interrupt\n");
    }
    
    // Allow more interrupts.
    cyg_drv_interrupt_unmask(eth->common.vector);
}

//==========================================================================
//
// SET_MAC_ADDRESS is straightforward, it just requires updating two registers.

__externC int
cyg_lwip_eth_ioctl(struct netif *netif, unsigned long key, void* data, int data_length)
{
    lm3s_eth*    eth = (lm3s_eth*) netif->state;

    eth_diag("key %lx", key );
    
    switch(key)
    {
    case ETH_DRV_SET_MAC_ADDRESS:
    {
        memcpy(eth->mac, data, 6);
        lm3s_eth_set_mac(&eth->common, eth->mac);
        return 0;
    }

    default:
        return 1;
    }
}

//==========================================================================
// End of if_lm3s.c
