/* ----------------------------------------------------------------------------
 *         ATMEL Microcontroller Software Support  -  ROUSSET  -
 * ----------------------------------------------------------------------------
 * Copyright (c) 2007, Atmel Corporation

 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the disclaiimer below.
 *
 * - Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the disclaimer below in the documentation and/or
 * other materials provided with the distribution.
 *
 * Atmel's name may not be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
 * DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ----------------------------------------------------------------------------
 */

#ifndef trace_LEVEL
    #define trace_LEVEL  1
#endif

//------------------------------------------------------------------------------
//         Headers
//------------------------------------------------------------------------------

#include "twid.h"
#include <twi/twi.h>
#include <utility/assert.h>
#include <utility/trace.h>

//------------------------------------------------------------------------------
//         Local types
//------------------------------------------------------------------------------

/// TWI driver callback function.
typedef void (*TwiCallback)(Async *);

//------------------------------------------------------------------------------
/// TWI asynchronous transfer descriptor.
//------------------------------------------------------------------------------
typedef struct _AsyncTwi {

    /// Asynchronous transfer status.
    volatile unsigned char status;
    // Callback function to invoke when transfer completes or fails.
    TwiCallback callback;
    /// Pointer to the data buffer.
    unsigned char *pData;
    /// Total number of bytes to transfer.
    unsigned int num;
    /// Number of already transferred bytes.
    unsigned int transferred;

} AsyncTwi;

//------------------------------------------------------------------------------
//         Global functions
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
/// Initializes a TWI driver instance, using the given TWI peripheral. The
/// peripheral must have been initialized properly before calling this function.
/// \param pTwid  Pointer to the Twid instance to initialize.
/// \param pTwi  Pointer to the TWI peripheral to use.
//------------------------------------------------------------------------------
void TWID_Initialize(Twid *pTwid, AT91S_TWI *pTwi)
{
    trace_LOG(trace_DEBUG, "-D- TWID_Initialize()\n\r");
    SANITY_CHECK(pTwid);
    SANITY_CHECK(pTwi);

    // Initialize driver
    pTwid->pTwi = pTwi;
    pTwid->pTransfer = 0;
}

//------------------------------------------------------------------------------
/// Interrupt handler for a TWI peripheral. Manages asynchronous transfer
/// occuring on the bus. This function MUST be called by the interrupt service
/// routine of the TWI peripheral if asynchronous read/write are needed.
/// \param pTwid  Pointer to a Twid instance.
//------------------------------------------------------------------------------
void TWID_Handler(Twid *pTwid)
{
    unsigned char status;
    AsyncTwi *pTransfer = (AsyncTwi *) pTwid->pTransfer;
    AT91S_TWI *pTwi = pTwid->pTwi;

    SANITY_CHECK(pTwid);

    // Retrieve interrupt status
    status = TWI_GetMaskedStatus(pTwi);

    // Byte received
    if (TWI_STATUS_RXRDY(status)) {

        pTransfer->pData[pTransfer->transferred] = TWI_ReadByte(pTwi);
        pTransfer->transferred++;

        // Transfer finished ?
        if (pTransfer->transferred == pTransfer->num) {

            TWI_DisableIt(pTwi, AT91C_TWI_RXRDY);
            TWI_EnableIt(pTwi, AT91C_TWI_TXCOMP);
        }
        // Last byte ?
        else if (pTransfer->transferred == (pTransfer->num - 1)) {

            TWI_Stop(pTwi);
        }
    }
    // Byte sent
    else if (TWI_STATUS_TXRDY(status)) {

        // Transfer finished ?
        if (pTransfer->transferred == pTransfer->num) {

            TWI_DisableIt(pTwi, AT91C_TWI_TXRDY);
            TWI_EnableIt(pTwi, AT91C_TWI_TXCOMP);
        }
        // Bytes remaining
        else {

            TWI_WriteByte(pTwi, pTransfer->pData[pTransfer->transferred]);
            pTransfer->transferred++;
        }
    }
    // Transfer complete
    else if (TWI_STATUS_TXCOMP(status)) {

        TWI_DisableIt(pTwi, AT91C_TWI_TXCOMP);
        pTransfer->status = 0;
        if (pTransfer->callback) {
            
            pTransfer->callback((Async *) pTransfer);
        }
        pTwid->pTransfer = 0;
    }
}

//-----------------------------------------------------------------------------
/// Asynchronously reads data from a slave on the TWI bus. An optional
/// callback function is triggered when the transfer is complete.
/// Returns 0 if the transfer has been started; otherwise returns a TWI error
/// code.
/// \param pTwid  Pointer to a Twid instance.
/// \param address  TWI slave address.
/// \param iaddress  Optional slave internal address.
/// \param isize  Internal address size in bytes.
/// \param pData  Data buffer for storing received bytes.
/// \param num  Number of bytes to read.
/// \param pAsync  Asynchronous transfer descriptor.
//-----------------------------------------------------------------------------
unsigned char TWID_Read(
    Twid *pTwid,
    unsigned char address,
    unsigned int iaddress,
    unsigned char isize,
    unsigned char *pData,
    unsigned int num,
    Async *pAsync)
{
    AT91S_TWI *pTwi = pTwid->pTwi;
    AsyncTwi *pTransfer = (AsyncTwi *) pTwid->pTransfer;

    trace_LOG(trace_DEBUG, "-D- TWID_Read()\n\r");
    SANITY_CHECK(pTwid);
    SANITY_CHECK((address & 0x80) == 0);
    SANITY_CHECK((iaddress & 0xFF000000) == 0);
    SANITY_CHECK(isize < 4);

    // Check that no transfer is already pending
    if (pTransfer) {

        trace_LOG(trace_ERROR, "-E- TWID_Read: A transfer is already pending\n\r");
        return TWID_ERROR_BUSY;
    }

    // Set STOP signal if only one byte is sent
    if (num == 1) {

        TWI_Stop(pTwi);
    }

    // Asynchronous transfer
    if (pAsync) {
    
        // Update the transfer descriptor
        pTwid->pTransfer = pAsync;
        pTransfer = (AsyncTwi *) pAsync;
        pTransfer->status = ASYNC_STATUS_PENDING;
        pTransfer->pData = pData;
        pTransfer->num = num;
        pTransfer->transferred = 0;
        
        // Enable read interrupt and start the transfer
        TWI_EnableIt(pTwi, AT91C_TWI_RXRDY);
        TWI_StartRead(pTwi, address, iaddress, isize);
    }
    // Synchronous transfer
    else {

        // Start read
        TWI_StartRead(pTwi, address, iaddress, isize);

        // Read all bytes, setting STOP before the last byte
        while (num > 0) {

            // Last byte
            if (num == 1) {

                TWI_Stop(pTwi);
            }

            // Wait for byte then read and store it
            while (!TWI_ByteReceived(pTwi));
            *pData++ = TWI_ReadByte(pTwi);
            num--;
        }

        // Wait for transfer to be complete
        while (!TWI_TransferComplete(pTwi));
    }

    return 0;
}

//------------------------------------------------------------------------------
/// Asynchronously sends data to a slave on the TWI bus. An optional callback
/// function is invoked whenever the transfer is complete.
/// \param pTwid  Pointer to a Twid instance.
/// \param address  Slave address.
/// \param iaddress  Optional slave internal address.
/// \param isize  Number of internal address bytes.
/// \param pData  Data buffer to send.
/// \param num  Number of bytes to send.
/// \param pAsync  Pointer to an Asynchronous transfer descriptor.
//------------------------------------------------------------------------------
unsigned char TWID_Write(
    Twid *pTwid,
    unsigned char address,
    unsigned int iaddress,
    unsigned char isize,
    unsigned char *pData,
    unsigned int num,
    Async *pAsync)
{
    AT91S_TWI *pTwi = pTwid->pTwi;
    AsyncTwi *pTransfer = (AsyncTwi *) pTwid->pTransfer;

    trace_LOG(trace_DEBUG, "-D- TWID_Write()\n\r");
    SANITY_CHECK(pTwi);
    SANITY_CHECK((address & 0x80) == 0);
    SANITY_CHECK((iaddress & 0xFF000000) == 0);
    SANITY_CHECK(isize < 4);

    // Check that no transfer is already pending
    if (pTransfer) {

        trace_LOG(trace_ERROR, "-E- TWI_Write: A transfer is already pending\n\r");
        return TWID_ERROR_BUSY;
    }

    // Asynchronous transfer
    if (pAsync) {
    
        // Update the transfer descriptor
        pTwid->pTransfer = pAsync;
        pTransfer = (AsyncTwi *) pAsync;
        pTransfer->status = ASYNC_STATUS_PENDING;
        pTransfer->pData = pData;
        pTransfer->num = num;
        pTransfer->transferred = 1;
    
        // Enable write interrupt and start the transfer
        TWI_StartWrite(pTwi, address, iaddress, isize, *pData);
        TWI_EnableIt(pTwi, AT91C_TWI_TXRDY);
    }
    // Synchronous transfer
    else {

        // Start write
        TWI_StartWrite(pTwi, address, iaddress, isize, *pData++);
        num--;

        // Send all bytes
        while (num > 0) {
        
            // Wait before sending the next byte
            while (!TWI_ByteSent(pTwi));
            TWI_WriteByte(pTwi, *pData++);
            num--;
        }

        // Wait for actual end of transfer
        while (!TWI_TransferComplete(pTwi));
    }
    
    return 0;
}

