//==========================================================================
//
//      spi_nxpssp.c
//
//      Atmel NXPSSP (ARM) SPI driver 
//
//==========================================================================
// ####ECOSGPLCOPYRIGHTBEGIN####                                            
// -------------------------------------------                              
// This file is part of eCos, the Embedded Configurable Operating System.   
// Copyright (C) 2005, 2006, 2007, 2008 Free Software Foundation, Inc.      
// Copyright (C) 2005, 2006, 2007, 2008 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
// Date:          2008-02-27
//
//####DESCRIPTIONEND####
//
//==========================================================================

#include <pkgconf/hal.h>
#include <pkgconf/io_spi.h>
#include <pkgconf/devs_spi_arm_nxpssp.h>

#include <cyg/infra/cyg_type.h>
#include <cyg/infra/cyg_ass.h>
#include <cyg/infra/diag.h>
#include <cyg/hal/hal_io.h>
#include <cyg/hal/hal_if.h>
#include <cyg/hal/hal_intr.h>
#include <cyg/hal/drv_api.h>
#include <cyg/io/spi.h>
#include <cyg/io/spi_nxpssp.h>
#include <cyg/error/codes.h>

// -------------------------------------------------------------------------

//#define SPI_NXPSSP_DIAG

#ifdef SPI_NXPSSP_DIAG
#define spi_diag( __fmt, ... ) diag_printf("SPI: %30s[%d]: " __fmt, __FUNCTION__, __LINE__, ## __VA_ARGS__ );
#define spi_dump_buf(__addr, __size ) diag_dump_buf( __addr, __size )
#else
#define spi_diag( __fmt, ... )
#define spi_dump_buf(__addr, __size )
#endif

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

// -------------------------------------------------------------------------

static void spi_nxpssp_init_bus(cyg_spi_nxpssp_bus_t * spi_bus);

static cyg_uint32 spi_nxpssp_ISR(cyg_vector_t vector, cyg_addrword_t data);

static void spi_nxpssp_DSR(cyg_vector_t   vector, 
                           cyg_ucount32   count, 
                           cyg_addrword_t data);

static void spi_nxpssp_transaction_begin(cyg_spi_device *dev);

static void spi_nxpssp_transaction_transfer(cyg_spi_device  *dev,
                                            cyg_bool         polled,
                                            cyg_uint32       count,
                                            const cyg_uint8 *tx_data,
                                            cyg_uint8       *rx_data,
                                            cyg_bool         drop_cs);

static void spi_nxpssp_transaction_tick(cyg_spi_device *dev,
                                        cyg_bool        polled,
                                        cyg_uint32      count);

static void spi_nxpssp_transaction_end(cyg_spi_device* dev);

static int spi_nxpssp_get_config(cyg_spi_device *dev, 
                                 cyg_uint32      key, 
                                 void           *buf,
                                 cyg_uint32     *len);

static int spi_nxpssp_set_config(cyg_spi_device *dev, 
                                 cyg_uint32      key, 
                                 const void     *buf, 
                                 cyg_uint32     *len);

// -------------------------------------------------------------------------
// NXPSSP SPI BUS


#if defined(CYGNUM_HAL_INTERRUPT_SPI0)          && \
    defined(CYGARC_HAL_LPC2XXX_REG_SSP0_BASE)   && \
    defined(CYGPKG_HAL_ARM_LPC2XXX_SPI0)

static cyg_uint32 cyg_spi_nxpssp_bus0_cs_pins[] = { CYGDAT_HAL_ARM_LPC2XXX_SPI0_CS_PINS };

cyg_spi_nxpssp_bus_t cyg_spi_nxpssp_bus0 = {
    .spi_bus.spi_transaction_begin    = spi_nxpssp_transaction_begin,
    .spi_bus.spi_transaction_transfer = spi_nxpssp_transaction_transfer,
    .spi_bus.spi_transaction_tick     = spi_nxpssp_transaction_tick,
    .spi_bus.spi_transaction_end      = spi_nxpssp_transaction_end,
    .spi_bus.spi_get_config           = spi_nxpssp_get_config,
    .spi_bus.spi_set_config           = spi_nxpssp_set_config,
    .interrupt_number                 = CYGNUM_HAL_INTERRUPT_SPI0,
    .base                             = CYGARC_HAL_LPC2XXX_REG_SSP0_BASE,
    .bus_num                          = 0,
    .sclk_pin                         = CYGDAT_HAL_ARM_LPC2XXX_SPI0_SCLK,
    .miso_pin                         = CYGDAT_HAL_ARM_LPC2XXX_SPI0_MISO,
    .mosi_pin                         = CYGDAT_HAL_ARM_LPC2XXX_SPI0_MOSI,
    .cs_pin                           = &cyg_spi_nxpssp_bus0_cs_pins[0],
    .cs_count                         = sizeof(cyg_spi_nxpssp_bus0_cs_pins)/sizeof(cyg_uint32),
};

CYG_SPI_DEFINE_BUS_TABLE(cyg_spi_nxpssp_device_t, 0);
#endif

#if defined(CYGNUM_HAL_INTERRUPT_SPI1)          && \
    defined(CYGARC_HAL_LPC2XXX_REG_SSP1_BASE)   && \
    defined(CYGPKG_HAL_ARM_LPC2XXX_SPI1)

static cyg_uint32 cyg_spi_nxpssp_bus1_cs_pins[] = { CYGDAT_HAL_ARM_LPC2XXX_SPI1_CS_PINS };

cyg_spi_nxpssp_bus_t cyg_spi_nxpssp_bus1 = {
    .spi_bus.spi_transaction_begin    = spi_nxpssp_transaction_begin,
    .spi_bus.spi_transaction_transfer = spi_nxpssp_transaction_transfer,
    .spi_bus.spi_transaction_tick     = spi_nxpssp_transaction_tick,
    .spi_bus.spi_transaction_end      = spi_nxpssp_transaction_end,
    .spi_bus.spi_get_config           = spi_nxpssp_get_config,
    .spi_bus.spi_set_config           = spi_nxpssp_set_config,
    .interrupt_number                 = CYGNUM_HAL_INTERRUPT_SPI1,
    .base                             = CYGARC_HAL_LPC2XXX_REG_SSP1_BASE,
    .bus_num                          = 1,
    .sclk_pin                         = CYGDAT_HAL_ARM_LPC2XXX_SPI1_SCLK,
    .miso_pin                         = CYGDAT_HAL_ARM_LPC2XXX_SPI1_MISO,
    .mosi_pin                         = CYGDAT_HAL_ARM_LPC2XXX_SPI1_MOSI,
    .cs_pin                           = &cyg_spi_nxpssp_bus1_cs_pins[0],
    .cs_count                         = sizeof(cyg_spi_nxpssp_bus1_cs_pins)/sizeof(cyg_uint32),    
};

CYG_SPI_DEFINE_BUS_TABLE(cyg_spi_nxpssp_device_t, 1);
#endif

// -------------------------------------------------------------------------

// Helper object to assist initialisation at system startup time.
// This isn't strictly the right type, but that doesn't matter.
__externC void *cyg_spi_nxpssp_bus_init_helper_obj[];

// -------------------------------------------------------------------------

void
cyg_spi_nxpssp_bus_init(void)
{
    spi_diag("\n==================================================================\n");
#if defined(CYGNUM_HAL_INTERRUPT_SPI0)          && \
    defined(CYGARC_HAL_LPC2XXX_REG_SSP0_BASE)   && \
    defined(CYGPKG_HAL_ARM_LPC2XXX_SPI0)
   spi_nxpssp_init_bus(&cyg_spi_nxpssp_bus0);
#endif
#if defined(CYGNUM_HAL_INTERRUPT_SPI1)          && \
    defined(CYGARC_HAL_LPC2XXX_REG_SSP1_BASE)   && \
    defined(CYGPKG_HAL_ARM_LPC2XXX_SPI1)
   spi_nxpssp_init_bus(&cyg_spi_nxpssp_bus1);
#endif
}

// -------------------------------------------------------------------------

static void spi_nxpssp_init_bus(cyg_spi_nxpssp_bus_t * spi_bus)
{
    spi_diag("bus base %08x\n",spi_bus->base);
    
    // Create and attach SPI interrupt object
    cyg_drv_interrupt_create(spi_bus->interrupt_number,
                             4,                   
                             (cyg_addrword_t)spi_bus,   
                             spi_nxpssp_ISR,
                             spi_nxpssp_DSR,
                             &spi_bus->spi_interrupt_handle,
                             &spi_bus->spi_interrupt);

    cyg_drv_interrupt_attach(spi_bus->spi_interrupt_handle);

    // Init transfer mutex and condition
    cyg_drv_mutex_init(&spi_bus->transfer_mx);
    cyg_drv_cond_init(&spi_bus->transfer_cond, 
                      &spi_bus->transfer_mx);
   
    // Init flags
    spi_bus->transfer_end = true;
    spi_bus->cs_up        = false;

    // Configure pins
    __LPC2XXX_PINSEL_FUNCTION( spi_bus->sclk_pin );
    __LPC2XXX_PINSEL_FUNCTION( spi_bus->miso_pin );
    __LPC2XXX_PINSEL_FUNCTION( spi_bus->mosi_pin );

    // Remove pull up/down
    __LPC2XXX_PINMODE( spi_bus->sclk_pin, __LPC2XXX_PINMODE_NONE );
    __LPC2XXX_PINMODE( spi_bus->miso_pin, __LPC2XXX_PINMODE_NONE );
    __LPC2XXX_PINMODE( spi_bus->mosi_pin, __LPC2XXX_PINMODE_NONE );
    
    // Leave the CS pins to be enabled when we do an actual
    // transfer. That way we don't steal them from other uses if we
    // don't use them.
    
    // Disable SSP
    HAL_WRITE_UINT32( spi_bus->base+CYGARC_HAL_LPC2XXX_REG_SSP_CR1, 0 );
    
    // Configure SSP for SPI mode
    HAL_WRITE_UINT32( spi_bus->base+CYGARC_HAL_LPC2XXX_REG_SSP_CR0,
                      CYGARC_HAL_LPC2XXX_REG_SSP_CR0_DSS(8)|
                      CYGARC_HAL_LPC2XXX_REG_SSP_CR0_FRF_SPI );

    // Set master mode, SSP disabled
    HAL_WRITE_UINT32( spi_bus->base+CYGARC_HAL_LPC2XXX_REG_SSP_CR1,
                      CYGARC_HAL_LPC2XXX_REG_SSP_CR1_MASTER );

    // Disable all interrupts
    HAL_WRITE_UINT32( spi_bus->base+CYGARC_HAL_LPC2XXX_REG_SSP_IMSC, 0 );
        
    // Call upper layer bus init
    CYG_SPI_BUS_COMMON_INIT(&spi_bus->spi_bus);
}

// -------------------------------------------------------------------------

static cyg_uint32 
spi_nxpssp_ISR(cyg_vector_t vector, cyg_addrword_t data)
{
    cyg_spi_nxpssp_bus_t *spi_bus = (cyg_spi_nxpssp_bus_t *) data;    
    cyg_uint32 ret = CYG_ISR_HANDLED;
    cyg_uint32 sr;
    cyg_uint8 val;

#ifdef SPI_NXPSSP_DIAG
    cyg_uint32 tx_count0 = spi_bus->tx_count;
    cyg_uint32 rx_count0 = spi_bus->rx_count;
#endif
    
    HAL_READ_UINT32(spi_bus->base+CYGARC_HAL_LPC2XXX_REG_SSP_SR, sr );

    // Loop while there are bytes to send and there is space in the Tx
    // FIFO, or bytes still to receive and some available in the Rx
    // FIFO.
    while( (spi_bus->tx_count > 0 && (sr & CYGARC_HAL_LPC2XXX_REG_SSP_SR_TNF)) ||
           (spi_bus->rx_count > 0 && (sr & CYGARC_HAL_LPC2XXX_REG_SSP_SR_RNE))  )
    {
        // We need a short delay here. It is not entirely clear why,
        // but remove this at your peril.
        { int i; for(i = 0; i< 10; i++ ) continue; }
        
        if(spi_bus->tx_count > 0 && (sr & CYGARC_HAL_LPC2XXX_REG_SSP_SR_TNF))
        {
            // Send a byte to the TX FIFO
            if( NULL != spi_bus->tx_data )
                val = *spi_bus->tx_data++;
            else
                val = 0xFF;
            HAL_WRITE_UINT8( spi_bus->base+CYGARC_HAL_LPC2XXX_REG_SSP_DR, val );
            spi_bus->tx_count--;
        }

        if(spi_bus->rx_count > 0 && (sr & CYGARC_HAL_LPC2XXX_REG_SSP_SR_RNE))
        {
            // Fetch a byte from the RX FIFO
            HAL_READ_UINT8( spi_bus->base+CYGARC_HAL_LPC2XXX_REG_SSP_DR, val );
            if( NULL != spi_bus->rx_data )
                *spi_bus->rx_data++ = val;
            spi_bus->rx_count--;
        }

        // We need a small delay here. The SR register takes a while
        // to update and if we jump on it too soon we may see a false
        // value. A loop is probably overkill, since only a few
        // instructions will be needed, but this is the easiest way to
        // do it.
        { int i; for(i = 0; i< 5; i++ ) continue; }
        
        // Reread SR and loop
        HAL_READ_UINT32(spi_bus->base+CYGARC_HAL_LPC2XXX_REG_SSP_SR, sr );
        
    }
    
    cyg_drv_interrupt_acknowledge(vector);
    
    // If all the data has been transferred, call the DSR
    if( spi_bus->tx_count == 0 && spi_bus->rx_count == 0 )
    {
        cyg_drv_interrupt_mask(vector);

        ret |= CYG_ISR_CALL_DSR;
    }

    return ret;
}

// -------------------------------------------------------------------------

static void 
spi_nxpssp_DSR(cyg_vector_t vector, cyg_ucount32 count, cyg_addrword_t data)
{
    cyg_spi_nxpssp_bus_t *spi_bus = (cyg_spi_nxpssp_bus_t *) data;

    spi_diag("\n");
    
    // Transfer ended  
    spi_bus->transfer_end = true;
    cyg_drv_cond_signal(&spi_bus->transfer_cond);
}

// -------------------------------------------------------------------------

static cyg_bool 
spi_nxpssp_calc_cpsr(cyg_spi_nxpssp_device_t *dev)
{
    cyg_uint32 cpsr = 2;
    cyg_uint32 scr;

    // Get a first estimate at a scr value
    scr = CYGNUM_HAL_ARM_LPC2XXX_PCLK_SPEED/(dev->cl_brate*cpsr);

    spi_diag("cpsr %d scr %d\n", cpsr, scr );    
    // Integer division is inaccuarate, so if the calculated scr
    // gives a higher bit rate that requested, increase it to reduce
    // the rate. It is better to provide a bit rate slightly lower
    // that requested than one higher, which may be out of device
    // tolerance.
    while( (CYGNUM_HAL_ARM_LPC2XXX_PCLK_SPEED/(cpsr*scr)) > dev->cl_brate )
        scr++;

    spi_diag("cpsr %d scr %d\n", cpsr, scr );

    while( scr > 255 )
        scr >>= 1, cpsr <<= 1;

    spi_diag("cpsr %d scr %d\n", cpsr, scr );    
    
    dev->cl_psr = cpsr;
    dev->cl_scr = scr-1;
    
    spi_diag("cl_brate %d cl_psr %d cl_scr %d actual br %d\n", dev->cl_brate, dev->cl_psr, dev->cl_scr,
             CYGNUM_HAL_ARM_LPC2XXX_PCLK_SPEED/(cpsr*scr));

    return true;
}

// -------------------------------------------------------------------------

static void
spi_nxpssp_start_transfer(cyg_spi_nxpssp_device_t *dev)
{
    cyg_spi_nxpssp_bus_t *spi_bus = (cyg_spi_nxpssp_bus_t *)dev->spi_device.spi_bus;
    cyg_uint32 cs_pio;
    
    if (spi_bus->cs_up)
        return;

    spi_diag("dev %d\n", dev->dev_num );
    
    // Force minimal delay between two transfers - in case two transfers
    // follow each other w/o delay, then we have to wait here in order for
    // the peripheral device to detect cs transition from inactive to active. 

    SPI_NXPSSP_DELAY_US(dev->tr_bt_udly);
    
    // Raise CS

    if( dev->dev_num < spi_bus->cs_count )
    {
        cs_pio = spi_bus->cs_pin[dev->dev_num];

        // CS is active low
        __LPC2XXX_GPIO_CLR( cs_pio );
    }
    
    SPI_NXPSSP_DELAY_US(dev->cs_up_udly);
   
    spi_bus->cs_up = true;
}

// -------------------------------------------------------------------------

static void
spi_nxpssp_drop_cs(cyg_spi_nxpssp_device_t *dev)
{
    cyg_spi_nxpssp_bus_t *spi_bus = (cyg_spi_nxpssp_bus_t *)dev->spi_device.spi_bus;
    cyg_uint32 cs_pio;
    
    if (!spi_bus->cs_up)
       return;

    spi_diag("dev %d\n", dev->dev_num );    
    
    // Drop CS

    SPI_NXPSSP_DELAY_US(dev->cs_dw_udly);

    if( dev->dev_num < spi_bus->cs_count )
    {
        cs_pio = spi_bus->cs_pin[dev->dev_num];

        // CS is active low
        __LPC2XXX_GPIO_SET( cs_pio );
    }
    
    spi_bus->cs_up = false;
}

// -------------------------------------------------------------------------

static void
spi_nxpssp_transfer(cyg_spi_nxpssp_device_t *dev,
                  cyg_uint32             count, 
                  const cyg_uint8       *tx_data,
                  cyg_uint8             *rx_data)
{
    cyg_spi_nxpssp_bus_t *spi_bus = (cyg_spi_nxpssp_bus_t *)dev->spi_device.spi_bus;

    spi_diag("dev %d count %d txd %p rxd %p\n", dev->dev_num, count, tx_data, rx_data );
    if( NULL != tx_data )
    {
        spi_diag("tx_data:\n");        
        spi_dump_buf( tx_data, count );
    }

    // Claim the lock
    cyg_drv_mutex_lock(&spi_bus->transfer_mx);
    {
        // Set up transfer parameters
        spi_bus->transfer_end = false;
        spi_bus->tx_data = tx_data;
        spi_bus->rx_data = rx_data;
        spi_bus->tx_count = count;
        spi_bus->rx_count = count;

        // Enable device interrupts
        HAL_WRITE_UINT32( spi_bus->base+CYGARC_HAL_LPC2XXX_REG_SSP_IMSC,
                          CYGARC_HAL_LPC2XXX_REG_SSP_INT_TX |
                          CYGARC_HAL_LPC2XXX_REG_SSP_INT_RX );
        
        // Unmask the SPI int
        cyg_drv_interrupt_unmask(spi_bus->interrupt_number);

        // Wait for its completion
        while (!spi_bus->transfer_end)
            cyg_drv_cond_wait(&spi_bus->transfer_cond);
    }    
    cyg_drv_mutex_unlock(&spi_bus->transfer_mx);
    
    if( rx_data != NULL )
    {
        spi_diag("rx_data:\n");
        spi_dump_buf( rx_data, count );
    }
}

// -------------------------------------------------------------------------

static void
spi_nxpssp_transfer_polled(cyg_spi_nxpssp_device_t *dev, 
                         cyg_uint32             count,
                         const cyg_uint8       *tx_data,
                         cyg_uint8             *rx_data)
{
    cyg_spi_nxpssp_bus_t *spi_bus = (cyg_spi_nxpssp_bus_t *)dev->spi_device.spi_bus;
    cyg_uint32 tx_count = count;
    cyg_uint32 rx_count = count;
    cyg_uint32 sr;
    cyg_uint8 val;
#ifdef SPI_NXPSSP_DIAG
    cyg_uint32 count0 = count;
    cyg_uint8 *rx_data0 = rx_data;
#endif

    spi_diag("dev %d count %d txd %p rxd %p\n", dev->dev_num, count, tx_data, rx_data );
    spi_dump_buf( tx_data, count );


    // Transmit and receive byte by byte
    while (tx_count > 0 || rx_count > 0)
    {
        
        HAL_READ_UINT32(spi_bus->base+CYGARC_HAL_LPC2XXX_REG_SSP_SR, sr );
        
        if(tx_count > 0 && (sr & CYGARC_HAL_LPC2XXX_REG_SSP_SR_TNF))
        {
            // Send a byte to the TX FIFO
            if( NULL != tx_data )            
                val = *tx_data++;
            else
                val = 0xFF;
            HAL_WRITE_UINT8( spi_bus->base+CYGARC_HAL_LPC2XXX_REG_SSP_DR, val );
            tx_count--;
        }

        if(rx_count > 0 && (sr & CYGARC_HAL_LPC2XXX_REG_SSP_SR_RNE))
        {
            // Fetch a byte from the RX FIFO
            HAL_READ_UINT8( spi_bus->base+CYGARC_HAL_LPC2XXX_REG_SSP_DR, val );
            if( NULL != rx_data )            
                *rx_data++ = val;
            rx_count--;
        }
    }
    
    if( rx_data != NULL )
    {
        spi_diag("rx_data:\n");
        spi_dump_buf( rx_data0, count0 );
    }    
}

// -------------------------------------------------------------------------

static void 
spi_nxpssp_transaction_begin(cyg_spi_device *dev)
{
    cyg_spi_nxpssp_device_t *nxpssp_spi_dev = (cyg_spi_nxpssp_device_t *) dev;
    cyg_spi_nxpssp_bus_t *spi_bus = (cyg_spi_nxpssp_bus_t *)dev->spi_bus;
    cyg_uint32 cr0, cr1;
    
    // Make sure we reference the helper object which will initialise the NXPSSP SPI support
    CYG_REFERENCE_OBJECT(cyg_spi_nxpssp_bus_init_helper_obj);

    // Configure CS pin for output, ensure it starts high and has no pull up/down.
    if( nxpssp_spi_dev->dev_num < spi_bus->cs_count )
    {
        cyg_uint32 cs_pio = spi_bus->cs_pin[nxpssp_spi_dev->dev_num];

        __LPC2XXX_PINMODE( cs_pio, __LPC2XXX_PINMODE_PULLUP );        
        __LPC2XXX_GPIO_SET( cs_pio );        
        __LPC2XXX_GPIO_CFG_DIRECTION( cs_pio, __LPC2XXX_GPIO_PIN_OUT );
    }
    
    if (!nxpssp_spi_dev->init)
    {
        nxpssp_spi_dev->init = true;
        spi_nxpssp_calc_cpsr(nxpssp_spi_dev);
    }

    spi_diag("dev %d\n", nxpssp_spi_dev->dev_num );

    // Configure phase and polarity

    cr0 = CYGARC_HAL_LPC2XXX_REG_SSP_CR0_DSS(8) | CYGARC_HAL_LPC2XXX_REG_SSP_CR0_FRF_SPI;

    if (1 == nxpssp_spi_dev->cl_pol)
        cr0 |= CYGARC_HAL_LPC2XXX_REG_SSP_CR0_SPO;

    if (1 == nxpssp_spi_dev->cl_pha)
        cr0 |= CYGARC_HAL_LPC2XXX_REG_SSP_CR0_SPH;

    cr0 |= CYGARC_HAL_LPC2XXX_REG_SSP_CR0_SCR(nxpssp_spi_dev->cl_scr);

    HAL_WRITE_UINT32( spi_bus->base+CYGARC_HAL_LPC2XXX_REG_SSP_CR0, cr0 );
    HAL_WRITE_UINT32( spi_bus->base+CYGARC_HAL_LPC2XXX_REG_SSP_CPSR, nxpssp_spi_dev->cl_psr );

    // Enable SSP in master mode
    cr1 = CYGARC_HAL_LPC2XXX_REG_SSP_CR1_MASTER;
    HAL_WRITE_UINT32( spi_bus->base+CYGARC_HAL_LPC2XXX_REG_SSP_CR1, cr1 );    
    cr1 |= CYGARC_HAL_LPC2XXX_REG_SSP_CR1_SSE;
    HAL_WRITE_UINT32( spi_bus->base+CYGARC_HAL_LPC2XXX_REG_SSP_CR1, cr1 );        
//    cr1 |= CYGARC_HAL_LPC2XXX_REG_SSP_CR1_SOD;
//    cr1 |= CYGARC_HAL_LPC2XXX_REG_SSP_CR1_LBM;    
//    HAL_WRITE_UINT32( spi_bus->base+CYGARC_HAL_LPC2XXX_REG_SSP_CR1, cr1 );    
}

// -------------------------------------------------------------------------

static void 
spi_nxpssp_transaction_transfer(cyg_spi_device  *dev, 
                              cyg_bool         polled,  
                              cyg_uint32       count, 
                              const cyg_uint8 *tx_data, 
                              cyg_uint8       *rx_data, 
                              cyg_bool         drop_cs) 
{
    cyg_spi_nxpssp_device_t *nxpssp_spi_dev = (cyg_spi_nxpssp_device_t *) dev;
    // Make sure we reference the helper object which will initialise the NXPSSP SPI support
    CYG_REFERENCE_OBJECT(cyg_spi_nxpssp_bus_init_helper_obj);

    spi_diag("dev %d count %d txd %p rxd %p%s%s\n", nxpssp_spi_dev->dev_num, count, tx_data, rx_data,
             polled?" polled":"", drop_cs?" drop_cs":"");

    // Select the device if not already selected
    spi_nxpssp_start_transfer(nxpssp_spi_dev);
 
    // Perform the transfer
    if (polled)
        spi_nxpssp_transfer_polled(nxpssp_spi_dev, count, tx_data, rx_data);
    else
        spi_nxpssp_transfer(nxpssp_spi_dev, count, tx_data, rx_data);

    // Deselect the device if requested
    if (drop_cs)
        spi_nxpssp_drop_cs(nxpssp_spi_dev);
}

// -------------------------------------------------------------------------

static void 
spi_nxpssp_transaction_tick(cyg_spi_device *dev, 
                          cyg_bool        polled,  
                          cyg_uint32      count)
{
    // We write FFs, and not zeroes. MMC cards at least require that.
    const cyg_uint32 FFs[10] = { 0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff,
                                 0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff };

    cyg_spi_nxpssp_device_t *nxpssp_spi_dev = (cyg_spi_nxpssp_device_t *) dev;
    // Make sure we reference the helper object which will initialise the NXPSSP SPI support
    CYG_REFERENCE_OBJECT(cyg_spi_nxpssp_bus_init_helper_obj);

    spi_diag("dev %d count %d%s\n", nxpssp_spi_dev->dev_num, count, polled?" polled":"");
    
    // Transfer count FFs to the device - we don't touch the
    // chip select, the device could be selected or deselected.
    // It is up to the device driver to decide in which state the
    // device will be ticked.
    
    while (count > 0)
    {
        int tcnt = count > sizeof(FFs) ? sizeof(FFs) : count;
        
        if (polled)
            spi_nxpssp_transfer_polled(nxpssp_spi_dev, tcnt, 
                                     (const cyg_uint8 *) FFs, NULL);
        else
            spi_nxpssp_transfer(nxpssp_spi_dev, tcnt, 
                              (const cyg_uint8 *) FFs, NULL);

        count -= tcnt;
    }
}

// -------------------------------------------------------------------------

static void                    
spi_nxpssp_transaction_end(cyg_spi_device* dev)
{
    cyg_spi_nxpssp_bus_t *spi_bus = (cyg_spi_nxpssp_bus_t *)dev->spi_bus;

    spi_diag("dev %d\n", ((cyg_spi_nxpssp_device_t *)dev)->dev_num );

    // Disable SSP
    HAL_WRITE_UINT32( spi_bus->base+CYGARC_HAL_LPC2XXX_REG_SSP_CR1, 0 );
    
    spi_nxpssp_drop_cs((cyg_spi_nxpssp_device_t *) dev);
}

// -------------------------------------------------------------------------

static int                     
spi_nxpssp_get_config(cyg_spi_device *dev, 
                    cyg_uint32      key, 
                    void           *buf,
                    cyg_uint32     *len)
{
    cyg_spi_nxpssp_device_t *nxpssp_spi_dev = (cyg_spi_nxpssp_device_t *) dev;
    // Make sure we reference the helper object which will initialise the NXPSSP SPI support
    CYG_REFERENCE_OBJECT(cyg_spi_nxpssp_bus_init_helper_obj);
    
    switch (key) 
    {
        case CYG_IO_GET_CONFIG_SPI_CLOCKRATE:
        {
            if (*len != sizeof(cyg_uint32))
                return -EINVAL;
            else
            {
                cyg_uint32 *cl_brate = (cyg_uint32 *)buf;
                *cl_brate = nxpssp_spi_dev->cl_brate; 
            }
        }
        break;
        default:
            return -EINVAL;
    }
    return ENOERR;
}

// -------------------------------------------------------------------------

static int                     
spi_nxpssp_set_config(cyg_spi_device *dev, 
                    cyg_uint32      key, 
                    const void     *buf, 
                    cyg_uint32     *len)
{
    cyg_spi_nxpssp_device_t *nxpssp_spi_dev = (cyg_spi_nxpssp_device_t *) dev;
    // Make sure we reference the helper object which will initialise the NXPSSP SPI support
    CYG_REFERENCE_OBJECT(cyg_spi_nxpssp_bus_init_helper_obj);
   
    switch (key) 
    {
        case CYG_IO_SET_CONFIG_SPI_CLOCKRATE:
        {
            if (*len != sizeof(cyg_uint32))
                return -EINVAL;
            else
            {
                cyg_uint32 cl_brate     = *((cyg_uint32 *)buf);
                cyg_uint32 old_cl_brate = nxpssp_spi_dev->cl_brate;
           
                nxpssp_spi_dev->cl_brate = cl_brate;
            
                if (!spi_nxpssp_calc_cpsr(nxpssp_spi_dev))
                {
                    nxpssp_spi_dev->cl_brate = old_cl_brate;
                    spi_nxpssp_calc_cpsr(nxpssp_spi_dev);
                    return -EINVAL;
                }
            }
        }
        break;
        default:
            return -EINVAL;
    }
    return ENOERR;
}

// -------------------------------------------------------------------------
// EOF spi_nxpssp.c
