//==========================================================================
//
//      spi_at91.c
//
//      Atmel AT91 (ARM) SPI driver 
//
//==========================================================================
// ####ECOSGPLCOPYRIGHTBEGIN####                                            
// -------------------------------------------                              
// This file is part of eCos, the Embedded Configurable Operating System.   
// Copyright (C) 1998, 1999, 2000, 2001, 2002, 2009 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):     Savin Zlobec <savin@elatec.si> 
// Date:          2004-08-25
//
//####DESCRIPTIONEND####
//
//==========================================================================

#include <pkgconf/hal.h>
#include <pkgconf/io_spi.h>
#include <pkgconf/devs_spi_arm_at91.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/hal_cache.h>
#include <cyg/hal/drv_api.h>
#include <cyg/io/spi.h>
#include <cyg/io/spi_at91.h>
#include <cyg/error/codes.h>

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

//#define SPI_AT91_DIAG

#ifdef SPI_AT91_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_AT91_DELAY_US(us) CYGACC_CALL_IF_DELAY_US(us)
#else
# include <cyg/hal/hal_diag.h>
# define SPI_AT91_DELAY_US(us) HAL_DELAY_US(us)
#endif

// -------------------------------------------------------------------------
static void spi_at91_init_bus(cyg_spi_at91_bus_t * bus);

static cyg_uint32 spi_at91_ISR(cyg_vector_t vector, cyg_addrword_t data);

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

static void spi_at91_transaction_begin(cyg_spi_device *dev);

static void spi_at91_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_at91_transaction_tick(cyg_spi_device *dev,
                                      cyg_bool        polled,
                                      cyg_uint32      count);

static void spi_at91_transaction_end(cyg_spi_device* dev);

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

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

// -------------------------------------------------------------------------
// AT91 SPI BUS


#if defined(CYGNUM_HAL_INTERRUPT_SPI) && defined(AT91_SPI)
cyg_spi_at91_bus_t cyg_spi_at91_bus0 = {
    .spi_bus.spi_transaction_begin    = spi_at91_transaction_begin,
    .spi_bus.spi_transaction_transfer = spi_at91_transaction_transfer,
    .spi_bus.spi_transaction_tick     = spi_at91_transaction_tick,
    .spi_bus.spi_transaction_end      = spi_at91_transaction_end,
    .spi_bus.spi_get_config           = spi_at91_get_config,
    .spi_bus.spi_set_config           = spi_at91_set_config,
    .interrupt_number                 = CYGNUM_HAL_INTERRUPT_SPI,
    .base                             = AT91_SPI,
    .bus_num                          = 0,
};

CYG_SPI_DEFINE_BUS_TABLE(cyg_spi_at91_device_t, 0);
#endif

#if defined(CYGNUM_HAL_INTERRUPT_SPI1) && defined(AT91_SPI1)
cyg_spi_at91_bus_t cyg_spi_at91_bus1 = {
    .spi_bus.spi_transaction_begin    = spi_at91_transaction_begin,
    .spi_bus.spi_transaction_transfer = spi_at91_transaction_transfer,
    .spi_bus.spi_transaction_tick     = spi_at91_transaction_tick,
    .spi_bus.spi_transaction_end      = spi_at91_transaction_end,
    .spi_bus.spi_get_config           = spi_at91_get_config,
    .spi_bus.spi_set_config           = spi_at91_set_config,
    .interrupt_number                 = CYGNUM_HAL_INTERRUPT_SPI1,
    .base                             = AT91_SPI1,
    .bus_num                          = 1,
};

CYG_SPI_DEFINE_BUS_TABLE(cyg_spi_at91_device_t, 1);
#endif

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

// If C constructor with init priority functionality is not in compiler,
// rely on spi_at91_init.cxx to init us.
#ifndef CYGBLD_ATTRIB_C_INIT_PRI
# define CYGBLD_ATTRIB_C_INIT_PRI(x)
#endif

void CYGBLD_ATTRIB_C_INIT_PRI(CYG_INIT_BUS_SPI)
cyg_spi_at91_bus_init(void)
{
    spi_diag("\n==================================================================\n");
#if defined(CYGNUM_HAL_INTERRUPT_SPI) && defined(AT91_SPI)
   spi_at91_init_bus(&cyg_spi_at91_bus0);
#endif
#if defined(CYGNUM_HAL_INTERRUPT_SPI1) && defined(AT91_SPI1)
   spi_at91_init_bus(&cyg_spi_at91_bus1);
#endif
}

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

static void spi_at91_init_bus(cyg_spi_at91_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_at91_ISR,
                             spi_at91_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;
    
    // Soft reset the SPI controller
    HAL_WRITE_UINT32(spi_bus->base+AT91_SPI_CR, AT91_SPI_CR_SWRST);

    // We could configure the SPI pins here, but may as well wait until
    // we're really going to talk SPI, i.e. at transaction_begin.

    AT91_PDC_RX_DISABLE( spi_bus->base );
    AT91_PDC_TX_DISABLE( spi_bus->base );
    
    // Call upper layer bus init
    CYG_SPI_BUS_COMMON_INIT(&spi_bus->spi_bus);
}

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

static cyg_uint32 
spi_at91_ISR(cyg_vector_t vector, cyg_addrword_t data)
{
    cyg_uint32 stat;
    cyg_spi_at91_bus_t * spi_bus = (cyg_spi_at91_bus_t *)data;
    // Read the status register and disable
    // the SPI int events that have occurred
    
    HAL_READ_UINT32(spi_bus->base+AT91_SPI_SR,   stat);
    HAL_WRITE_UINT32(spi_bus->base+AT91_SPI_IDR, stat);

    spi_diag("stat %08x\n", stat );
    
    cyg_drv_interrupt_mask(vector);
    cyg_drv_interrupt_acknowledge(vector);

    return CYG_ISR_CALL_DSR;
}

static void 
spi_at91_DSR(cyg_vector_t vector, cyg_ucount32 count, cyg_addrword_t data)
{
    cyg_spi_at91_bus_t *spi_bus = (cyg_spi_at91_bus_t *) data;
    cyg_uint32 stat;
    
    // Read the status register and 
    // check for transfer completion
    
    HAL_READ_UINT32(spi_bus->base+AT91_SPI_SR, stat);

    spi_diag("stat %08x\n", stat );
    
    if((stat & AT91_SPI_SR_ENDRX) && (stat & AT91_SPI_SR_ENDTX))
    {
        // Transfer ended  
        spi_bus->transfer_end = true;
        cyg_drv_cond_signal(&spi_bus->transfer_cond);
    }
    else
    {
        // Transfer still in progress - unmask the SPI 
        // int so we can get more SPI int events
        cyg_drv_interrupt_unmask(vector);
    }
}

static cyg_bool 
spi_at91_calc_scbr(cyg_spi_at91_device_t *dev)
{
    cyg_uint32 scbr;
    cyg_bool   res = true;
    
    // Calculate SCBR from baud rate, rounding up
    
    scbr = (CYGHWR_AT91_SPI_CLOCK_SPEED + dev->cl_brate - 1) / dev->cl_brate;

    spi_diag("dev %d MCK %d cd_brate %d scbr %d\n", dev->dev_num, CYGHWR_AT91_SPI_CLOCK_SPEED, dev->cl_brate, scbr );
    
    if (scbr < 2)
    {
        dev->cl_scbr  = 2;
        dev->cl_div32 = 0;
        res = false;
    }
    else if (scbr > 255)
    {
        dev->cl_div32 = 1;
        
        scbr = (CYGHWR_AT91_SPI_CLOCK_SPEED + (32*dev->cl_brate) - 1) / (32*dev->cl_brate);

        if (scbr < 2) 
        {
            dev->cl_scbr = 2;
            res = false;
        }
        else if (scbr > 255) 
        {
            dev->cl_scbr = 255;
            res = false;
        }
        else
            dev->cl_scbr = (cyg_uint8)scbr;
    }
    else
    {
        dev->cl_scbr  = (cyg_uint8)scbr;
        dev->cl_div32 = 0;
    }

#ifdef CYGHWR_AT91_SPI_SCBR_ADJUST
    // Some hardware needs the SCBR to be adjusted to work around
    // strange quirks.
    CYGHWR_AT91_SPI_SCBR_ADJUST( dev->cl_scbr, dev->cl_div32 );
#endif
        
    spi_diag("cl_scbr %d cl_div32 %d res %d\n", dev->cl_scbr, dev->cl_div32, res );
    
    return res;
}

static void
spi_at91_start_transfer(cyg_spi_at91_device_t *dev)
{
    cyg_spi_at91_bus_t *spi_bus = (cyg_spi_at91_bus_t *)dev->spi_device.spi_bus;
  
    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_AT91_DELAY_US(dev->tr_bt_udly);
    
    // Raise CS

    CYGHWR_AT91_SPI_RAISE_CS( dev->dev_num, spi_bus->bus_num );
    
    SPI_AT91_DELAY_US(dev->cs_up_udly);
   
    spi_bus->cs_up = true;
}

static void
spi_at91_drop_cs(cyg_spi_at91_device_t *dev)
{
    cyg_spi_at91_bus_t *spi_bus = (cyg_spi_at91_bus_t *)dev->spi_device.spi_bus;
    
    if (!spi_bus->cs_up)
       return;

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

    SPI_AT91_DELAY_US(dev->cs_dw_udly);

    CYGHWR_AT91_SPI_DROP_CS( dev->dev_num, spi_bus->bus_num );
    
    spi_bus->cs_up = false;
}

static void
spi_at91_transfer(cyg_spi_at91_device_t *dev,
                  cyg_uint32             count, 
                  const cyg_uint8       *tx_data,
                  cyg_uint8             *rx_data)
{
    cyg_spi_at91_bus_t *spi_bus = (cyg_spi_at91_bus_t *)dev->spi_device.spi_bus;
    CYG_INTERRUPT_STATE intr_state;
#ifdef SPI_AT91_DIAG
    cyg_uint8 *rx_data_orig = rx_data;
    cyg_uint32 count_orig = count;
#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 );
    
    if (NULL != rx_data)
    {
        // We must delay here, in case the last operation was polled. Why?
        // Because empirically there seems to be a silicon bug with some AT91s
        // which prevents correct operation of the PDC at the point the RPR is
        // set below *if* the last operation was polled *and* certain ranges of
        // address are used *and* insufficient time has passed since the last
        // (polled) transfer.
        SPI_AT91_DELAY_US(1);
    }

    // Since PDC transfer buffer counters are 16 bit long, 
    // we have to split longer transfers into chunks. 
    while (count > 0)
    {
        cyg_uint16 tr_count = count > 0xFFFF ? 0xFFFF : count;

        cyg_drv_dsr_lock();

        // Set rx buf pointer and counter 
        if (NULL != rx_data)
        {
            HAL_WRITE_UINT32(spi_bus->base+AT91_SPI_RPR, CYGARC_PHYSICAL_ADDRESS(rx_data));
            HAL_WRITE_UINT32(spi_bus->base+AT91_SPI_RCR, (cyg_uint32)tr_count);
            AT91_PDC_RX_ENABLE( spi_bus->base );
        }

        // Set tx buf pointer and counter
        HAL_DCACHE_SYNC();
        HAL_WRITE_UINT32(spi_bus->base+AT91_SPI_TPR, CYGARC_PHYSICAL_ADDRESS(tx_data));
        HAL_WRITE_UINT32(spi_bus->base+AT91_SPI_TCR, (cyg_uint32)tr_count);
        AT91_PDC_TX_ENABLE( spi_bus->base );
            
        // Enable the SPI int events we are interested in
        HAL_WRITE_UINT32(spi_bus->base+AT91_SPI_IER, AT91_SPI_SR_ENDRX | 
                                                AT91_SPI_SR_ENDTX);

        cyg_drv_mutex_lock(&spi_bus->transfer_mx);
        {
            spi_bus->transfer_end = false;
    
            // 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 (NULL == rx_data)
        {
            cyg_uint32 val;
        
            // If rx buffer was NULL, then the PDC receiver data transfer
            // was not started and we didn't wait for ENDRX, but only for 
            // ENDTX. Meaning that right now the last byte is being serialized 
            // over the line and when finished input data will appear in 
            // rx data reg. We have to wait for this to happen here, if we
            // don't we'll get the last received byte as the first one in the
            // next transfer!
        
            // FIXME: is there any better way to do this? 
            // If not, then precalculate this value.
            val = 8000000/dev->cl_brate;
            SPI_AT91_DELAY_US(val > 1 ? val : 1);

            // Clear the rx data reg
            HAL_READ_UINT32(spi_bus->base+AT91_SPI_RDR, val);

        }
        else
        {
            AT91_PDC_RX_DISABLE( spi_bus->base );
        }

        AT91_PDC_TX_DISABLE( spi_bus->base );

        cyg_drv_dsr_unlock();

        
        // Adjust running variables
        
        if (NULL != rx_data)
            rx_data += tr_count;
        tx_data += tr_count;
        count   -= tr_count;
    }

    HAL_DISABLE_INTERRUPTS(intr_state);
    HAL_DCACHE_SYNC();
    HAL_DCACHE_INVALIDATE_ALL();
    HAL_RESTORE_INTERRUPTS(intr_state);

    if( rx_data != NULL )
    {
        spi_diag("rx_data:\n");
        spi_dump_buf( rx_data_orig, count_orig );
    }
}

static void
spi_at91_transfer_polled(cyg_spi_at91_device_t *dev, 
                         cyg_uint32             count,
                         const cyg_uint8       *tx_data,
                         cyg_uint8             *rx_data)
{
    cyg_spi_at91_bus_t *spi_bus = (cyg_spi_at91_bus_t *)dev->spi_device.spi_bus;
    cyg_uint32 val;
#ifdef SPI_AT91_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 );
    
    // Some targets (e.g. SAM7X) need the RDR and SR register to be
    // cleared by reading here. It should be benign even where not
    // necessary.
    HAL_READ_UINT32(spi_bus->base+AT91_SPI_RDR, val);
    HAL_READ_UINT32(spi_bus->base+AT91_SPI_SR, val);    
    
    // Transmit and receive byte by byte
    while (count-- > 0)
    {
        // Wait for transmit data register empty
        do
        {
            HAL_READ_UINT32(spi_bus->base+AT91_SPI_SR, val);
        } while ( !(val & AT91_SPI_SR_TDRE) );
        
        // Send next byte over the wire 
        val = *tx_data++;
//        spi_diag("sending %08x\n", val);
        HAL_WRITE_UINT32(spi_bus->base+AT91_SPI_TDR, val);

        // Wait for receive data register full 
        do
        {
            HAL_READ_UINT32(spi_bus->base+AT91_SPI_SR, val);
        } while ( !(val & AT91_SPI_SR_RDRF) );

        // Store received byte 
        HAL_READ_UINT32(spi_bus->base+AT91_SPI_RDR, val);
//        spi_diag("received %08x\n", val);
        if (NULL != rx_data) {
            *rx_data++ = val;
        }
    }

    if( rx_data != NULL )
    {
        spi_diag("rx_data:\n");
        spi_dump_buf( rx_data0, count0 );
    }    
}

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

static void 
spi_at91_transaction_begin(cyg_spi_device *dev)
{
    cyg_spi_at91_device_t *at91_spi_dev = (cyg_spi_at91_device_t *) dev;
    cyg_spi_at91_bus_t *spi_bus = (cyg_spi_at91_bus_t *)dev->spi_bus;
    cyg_uint32 val;
    
    // Configure SPI pins
    CYGHWR_AT91_SPI_CONFIGURE_PINS( spi_bus->base );

    if (!at91_spi_dev->init)
    {
        at91_spi_dev->init = true;
        spi_at91_calc_scbr(at91_spi_dev);
    }

    spi_diag("dev %d\n", at91_spi_dev->dev_num );
    
    // Configure SPI channel 0 - this is the only channel we 
    // use for all devices since we drive chip selects manually
    
    val = AT91_SPI_CSR_BITS8;

    if (1 == at91_spi_dev->cl_pol)
        val |= AT91_SPI_CSR_CPOL;

    if (1 == at91_spi_dev->cl_pha)
        val |= AT91_SPI_CSR_NCPHA;

    val |= AT91_SPI_CSR_SCBR(at91_spi_dev->cl_scbr);

    HAL_WRITE_UINT32(spi_bus->base+AT91_SPI_CSR0, val); 

    // Enable SPI clock
    CYGHWR_AT91_SPI_ENABLE_SPI_CLOCK(spi_bus->bus_num);
    
    // Enable the SPI controller
    HAL_WRITE_UINT32(spi_bus->base+AT91_SPI_CR, AT91_SPI_CR_SPIEN);
    
    /* As we are using this driver only in master mode with NPCS0 
       configured as GPIO instead of a peripheral pin, it is necessary 
       for the Mode Failure detection to be switched off as this will
       cause havoc with the driver */ 

    // Put SPI bus into master mode
    if (1 == at91_spi_dev->cl_div32) {
      val = AT91_SPI_MR_MSTR | AT91_SPI_MR_DIV32;
#ifdef AT91_SPI_MR_MODFDIS
      val |= AT91_SPI_MR_MODFDIS;
#endif
      HAL_WRITE_UINT32(spi_bus->base+AT91_SPI_MR, val);
    } else {
      val = AT91_SPI_MR_MSTR;
#ifdef AT91_SPI_MR_MODFDIS
      val |= AT91_SPI_MR_MODFDIS;
#endif
      HAL_WRITE_UINT32(spi_bus->base+AT91_SPI_MR, val);
    }
}

static void 
spi_at91_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_at91_device_t *at91_spi_dev = (cyg_spi_at91_device_t *) dev;

    spi_diag("dev %d count %d txd %p rxd %p%s%s\n", at91_spi_dev->dev_num, count, tx_data, rx_data,
             polled?" polled":"", drop_cs?" drop_cs":"");
    
    // Select the device if not already selected
    spi_at91_start_transfer(at91_spi_dev);
 
    // Perform the transfer
    if (polled)
        spi_at91_transfer_polled(at91_spi_dev, count, tx_data, rx_data);
    else
        spi_at91_transfer(at91_spi_dev, count, tx_data, rx_data);

    // Deselect the device if requested
    if (drop_cs)
        spi_at91_drop_cs(at91_spi_dev);
}

static void 
spi_at91_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_at91_device_t *at91_spi_dev = (cyg_spi_at91_device_t *) dev;

    spi_diag("dev %d count %d%s\n", at91_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_at91_transfer_polled(at91_spi_dev, tcnt, 
                                     (const cyg_uint8 *) FFs, NULL);
        else
            spi_at91_transfer(at91_spi_dev, tcnt, 
                              (const cyg_uint8 *) FFs, NULL);

        count -= tcnt;
    }
}

static void                    
spi_at91_transaction_end(cyg_spi_device* dev)
{
    cyg_spi_at91_bus_t *spi_bus = (cyg_spi_at91_bus_t *)dev->spi_bus;
    
    // Disable the SPI controller
    HAL_WRITE_UINT32(spi_bus->base+AT91_SPI_CR, AT91_SPI_CR_SPIDIS);

    spi_diag("dev %d\n", ((cyg_spi_at91_device_t *)dev)->dev_num );
    
    // Disable SPI clock
    CYGHWR_AT91_SPI_DISABLE_SPI_CLOCK(spi_bus->bus_num);
    
    spi_at91_drop_cs((cyg_spi_at91_device_t *) dev);
}

static int                     
spi_at91_get_config(cyg_spi_device *dev, 
                    cyg_uint32      key, 
                    void           *buf,
                    cyg_uint32     *len)
{
    cyg_spi_at91_device_t *at91_spi_dev = (cyg_spi_at91_device_t *) dev;
    
    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 = at91_spi_dev->cl_brate; 
            }
        }
        break;
        default:
            return -EINVAL;
    }
    return ENOERR;
}

static int                     
spi_at91_set_config(cyg_spi_device *dev, 
                    cyg_uint32      key, 
                    const void     *buf, 
                    cyg_uint32     *len)
{
    cyg_spi_at91_device_t *at91_spi_dev = (cyg_spi_at91_device_t *) dev;
   
    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 = at91_spi_dev->cl_brate;
           
                at91_spi_dev->cl_brate = cl_brate;
            
                if (!spi_at91_calc_scbr(at91_spi_dev))
                {
                    at91_spi_dev->cl_brate = old_cl_brate;
                    spi_at91_calc_scbr(at91_spi_dev);
                    return -EINVAL;
                }
            }
        }
        break;
        default:
            return -EINVAL;
    }
    return ENOERR;
}

// -------------------------------------------------------------------------
// EOF spi_at91.c
