/* Copyright (c) 2007 Axel Wachtler
   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 following disclaimer.
   * Redistributions in binary form must reproduce the above copyright
     notice, this list of conditions and the following disclaimer in the
     documentation and/or other materials provided with the distribution.
   * Neither the name of the authors nor the names of its contributors
     may be used to endorse or promote products derived from this software
     without specific prior written permission.

   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 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. */

/* $Id: transceiver.h,v 1.27 2011/01/21 06:59:27 awachtler Exp $ */
/**
 * @file
 * @brief Interface for @ref grpTrx.
 * @ingroup grpTrx
 */
#ifndef TRANSCEIVER_H
#define TRANSCEIVER_H

/**
 *  @addtogroup grpTrx
 *  @{
 */

/* === Includes ============================================================== */
#include "board.h"

#if RADIO_TYPE == RADIO_AT86RF230 || defined(DOXYGEN)
# include "at86rf230a.h"
#elif RADIO_TYPE == RADIO_AT86RF230B
# include "at86rf230b.h"
#elif RADIO_TYPE == RADIO_AT86RF231
# include "at86rf231.h"
#elif RADIO_TYPE == RADIO_AT86RF212
# include "at86rf212.h"
#elif RADIO_TYPE == RADIO_ATMEGA128RFA1_A ||\
      RADIO_TYPE == RADIO_ATMEGA128RFA1_B ||\
      RADIO_TYPE == RADIO_ATMEGA128RFA1_C ||\
      RADIO_TYPE == RADIO_ATMEGA128RFA1_D
# include "atmega_rfa1.h"
#else
# error "RADIO_TYPE is not defined or wrong"
#endif
#include <stdbool.h>

/* === Externals ============================================================= */

/* === Types ================================================================= */

#if defined(DOXYGEN)

    /* this types are defined in at86rfXXX.{h,txt} in order
       to provide a radio abstraction */

    /** Data Type for Transceiver SRAM address
     */
    typedef uint8_t trx_ramaddr_t;

    /** Data Type for Transceiver register value
     */
    typedef uint8_t trx_regval_t;

    /** Data Type for Transceiver register address
     */
    typedef uint8_t trx_regaddr_t;

#endif

/** Data Type for Transceiver IRQ callback function
 */
typedef void (*trx_irq_handler_t)(uint8_t cause);

typedef enum
{
   CFG_FLASH,
   CFG_EEPROM,
   CFG_NONE
} trx_cfg_t;

/* === Macros ================================================================ */
/* error codes */
/** trx function succeeded */
#define TRX_OK        (0)
/** trx init function failed (TRX_OFF not reached after reset) */
#define TRX_INIT_FAIL (1)
/** trx pll check function failed (PLL_LOCK coult not be observed in PLL_ON) */
#define TRX_PLL_FAIL  (2)

#define INVALID_PART_NUM (2)  /**< flag for invalid part number */
#define INVALID_REV_NUM  (1)  /**< flag for invalid revision number */

/* Data Rate macros, generated by python Tools/cmdhash.py  `cat rates.txt` */
#define BPSK20 (0x52)
#define BPSK20_STR "BPSK20"
#define BPSK40 (0x92)
#define BPSK40_STR "BPSK40"
#define OQPSK100 (0x90)
#define OQPSK100_STR "OQPSK100"
#define OQPSK200 (0x93)
#define OQPSK200_STR "OQPSK200"
#define OQPSK250 (0x33)
#define OQPSK250_STR "OQPSK250"
#define OQPSK400 (0x95)
#define OQPSK400_STR "OQPSK400"
#define OQPSK500 (0x94)
#define OQPSK500_STR "OQPSK500"
#define OQPSK1000 (0x34)
#define OQPSK1000_STR "OQPSK1000"
#define OQPSK2000 (0x54)
#define OQPSK2000_STR "OQPSK2000"

#define RATE_NONE_HSH (0xFF)

/** Maximum size in bytes of an IEEE 802.15.4 frame */
#ifndef MAX_FRAME_SIZE
# define MAX_FRAME_SIZE (127)
#endif

/* channel handling */
#define TRX_NEXT_CHANNEL(x) ((channel_t)(x+1) > TRX_MAX_CHANNEL ? TRX_MAX_CHANNEL : x+1)
#define TRX_PREV_CHANNEL(x) ((channel_t)(x-1) < TRX_MIN_CHANNEL ? TRX_MIN_CHANNEL : x-1)
#define TRX_NEXT_CHANNEL_WRAP(x) ((channel_t)(x+1) > TRX_MAX_CHANNEL ? TRX_MIN_CHANNEL : x+1 )
#define TRX_PREV_CHANNEL_WRAP(x) ((channel_t)(x-1) < TRX_MIN_CHANNEL ? TRX_MAX_CHANNEL : x-1 )


#if defined (SR_MASK_AMI) || defined(DOXYGEN)
/** @brief Enable AMI IRQ. */
# define TRX_IRQ_AMI_EI() trx_bit_write(SR_MASK_AMI, 1);
/** @brief Disable AMI IRQ. */
# define TRX_IRQ_AMI_DI() trx_bit_write(SR_MASK_AMI, 0);
#endif

#if defined (SR_MASK_BAT_LOW) || defined(DOXYGEN)
/** @brief Enable BAT_LOW IRQ. */
# define TRX_IRQ_BAT_LOW_EI() trx_bit_write(SR_MASK_BAT_LOW, 1);
/** @brief Disable BAT_LOW IRQ. */
# define TRX_IRQ_BAT_LOW_DI() trx_bit_write(SR_MASK_BAT_LOW, 0);
#endif

#if defined (SR_MASK_CCA_ED_READY) || defined(DOXYGEN)
/** @brief Enable CCA_ED_READY IRQ. */
# define TRX_IRQ_CCA_ED_READY_EI() trx_bit_write(SR_MASK_CCA_ED_READY, 1);
/** @brief Disable CCA_ED_READY IRQ. */
# define TRX_IRQ_CCA_ED_READY_DI() trx_bit_write(SR_MASK_CCA_ED_READY, 0);
#endif

#if defined (SR_MASK_PLL_UNLOCK) || defined(DOXYGEN)
/** @brief Enable PLL_UNLOCK IRQ. */
# define TRX_IRQ_PLL_UNLOCK_EI() trx_bit_write(SR_MASK_PLL_UNLOCK, 1);
/** @brief Disable PLL_UNLOCK IRQ. */
# define TRX_IRQ_PLL_UNLOCK_DI() trx_bit_write(SR_MASK_PLL_UNLOCK, 0);
#endif

#if defined (SR_MASK_RX_START) || defined(DOXYGEN)
/** @brief Enable RX_START IRQ. */
# define TRX_IRQ_RX_START_EI() trx_bit_write(SR_MASK_RX_START, 1);
/** @brief Disable RX_START IRQ. */
# define TRX_IRQ_RX_START_DI() trx_bit_write(SR_MASK_RX_START, 0);
#endif

#if defined (SR_MASK_TRX_IRQ_END) || defined(DOXYGEN)
/** @brief Enable TRX_IRQ_END IRQ. */
# define TRX_IRQ_TRX_IRQ_END_EI() trx_bit_write(SR_MASK_TRX_IRQ_END, 1);
/** @brief Disable TRX_IRQ_END IRQ. */
# define TRX_IRQ_TRX_IRQ_END_DI() trx_bit_write(SR_MASK_TRX_IRQ_END, 0);
#endif

#if defined (SR_MASK_TRX_IRQ_START) || defined(DOXYGEN)
/** @brief Enable TRX_IRQ_START IRQ. */
# define TRX_IRQ_TRX_IRQ_START_EI() trx_bit_write(SR_MASK_TRX_IRQ_START, 1);
/** @brief Disable TRX_IRQ_START IRQ. */
# define TRX_IRQ_TRX_IRQ_START_DI() trx_bit_write(SR_MASK_TRX_IRQ_START, 0);
#endif

#if defined (SR_MASK_UR) || defined(DOXYGEN)
/** @brief Enable TX/RX underun IRQ. */
# define TRX_IRQ_UR_EI() trx_bit_write(SR_MASK_UR, 1);
/** @brief Disable TX/RX underun IRQ. */
# define TRX_IRQ_UR_DI() trx_bit_write(SR_MASK_UR, 0);
#endif

/* === Prototypes ============================================================ */
#ifdef __cplusplus
extern "C" {
#endif

#if !defined(TRX_IF_RFA1)
/**
 * @brief
 *    SPI Initialization (RADIO_TYPE == AT86RF230)
 *
 *    Init the Serial Peripherial Interface of the AVR
 *
 * @param spirate Configuration Byte of the SPI Control Register (SPCR)
 *
 */
void trx_io_init (uint8_t spirate);
#else  /* RFA1 */
#define trx_io_init(dummy) do { /* dummy macro */ } while (0)
#endif


/** set function pointer for IRQ handler
 */
void trx_set_irq_handler(trx_irq_handler_t irqhandler);

/**
 * @brief Write Register
 *
 * This function write to a transceiver register.
 * @param addr Address of the Register in the Transceiver (Offset) that should be written
 * @param val Byte that will be written into the Register
 */
void trx_reg_write(trx_regaddr_t addr, trx_regval_t val);

/**
 * @brief Read Register
 *
 * This function reads a transceiver register.
 * @param addr Address of the Register in the Transceiver (Offset) that should be read
 * @return Contents of the Register
 *
 */
uint8_t trx_reg_read(trx_regaddr_t addr);



/**
 * @brief subregister read
 *
 * @param   addr  offset of the register
 * @param   mask  bit mask of the subregister
 * @param   pos   bit position of the subregister
 * @retval  data  pointer where the read and demuxed value is stored
 *
 * @code
 *   pos = 4, mask = 0xf0
 *   register value = 0xA5
 *   *data = 0x0A
 * @endcode
 *
 */
trx_regval_t trx_bit_read(trx_regaddr_t addr, trx_regval_t mask, uint8_t pos);


/**
 * @brief subregister write
 *
 * @param   addr  offset of the register
 * @param   mask  bit mask of the subregister
 * @param   pos   bit position of the subregister
 * @retval  value  data, which is muxed into the register
 *
 * @code
 *   pos = 4, mask = 0xf0
 *   register value = 0xA5 (before operation)
 *   value = 0x05
 *   register value = 0x55 (after operation)
 * @endcode
 *
 */
void trx_bit_write(trx_regaddr_t addr, trx_regval_t mask, uint8_t pos, trx_regval_t value);

/**
 * @brief Frame Write
 *
 * This function writes a frame to the transceiver.
 *
 * @param length Length of the frame that should be written into the frame buffer
 * @param data Pointer to an array of (Payload-) bytes that should be sent
 * @note SLP_TR! (RADIO_TYPE == AT86RF230)
 *
 */
void trx_frame_write(uint8_t length, uint8_t *data);

/**
 * @brief Frame Read
 *
 * This function reads a frame from the transceiver.
 *
 * @retval data Pointer to an array of (Payload-) bytes that should be sent
 * @param datasz maximum number of bytes, which fit in the data buffer.
 * @retval lqi Pointer where the LQI value is stored
 * @return length of the downloaded frame (including the LQI byte [RADIO_TYPE == AT86RF230])
 *
 */
uint8_t trx_frame_read(uint8_t *data, uint8_t datasz, uint8_t *lqi);


/**
 * @brief Frame Read with CRC check (and crc value kept)
 *
 * This function reads a frame from the transceiver.
 * While the upload is in progress, the CRC16 value is caluculated
 * and compared against the last two bytes.
 * The two crc bytes @b are copied in the data buffer.
 *
 * @retval data Pointer to an array of (Payload-) bytes that should be sent
 * @param  datasz maximum number of bytes, which fit in the data buffer.
 * @retval crc_ok Result of the CRC16 check.
 *
 * @return length of the downloaded frame (including the LQI byte [RADIO_TYPE == AT86RF230])
 *
 * @todo implement this function
 */
uint8_t trx_frame_read_crc(uint8_t *data, uint8_t datasz, bool *crc_ok);


/**
 * @brief Frame Read with CRC check (and crc value suppressed)
 *
 * This function reads a frame from the transceiver.
 * While the upload is in progress, the CRC16 value is caluculated
 * and compared against the last two bytes.
 * The two crc bytes are @b not copied in the data buffer.
 *
 * @retval data Pointer to an array of (Payload-) bytes that should be sent
 * @param datasz maximum number of bytes, which fit in the data buffer.
 * @retval lqi Pointer where the LQI value is stored
 * @retval crc_ok Result of the CRC16 check.
 *
 * @return length of the downloaded frame (including the LQI byte [RADIO_TYPE == AT86RF230])
 *
 * @todo implement this function
 */
uint8_t trx_frame_read_data_crc(uint8_t *data, uint8_t datasz, uint8_t *lqi, bool *crc_ok);

/**
 * @brief Get length of a received frame
 *
 *  @return length of the received frame
 *
 */

uint8_t trx_frame_get_length(void);

/**
 * @brief Write SRAM
 *
 * This function writes into the SRAM of the transceiver.
 *
 * @param addr Address in the TRX's SRAM where the write burst should start
 * @param length Length of the write burst
 * @param data Pointer to an array of bytes that should be written
 *
 */
void trx_sram_write(trx_ramaddr_t addr, uint8_t length, uint8_t *data);

/**
 * @brief Read SRAM
 *
 * @param addr Address in the TRX's SRAM where the read burst should start
 * @param length Length of the write burst
 * @retval data Pointer to an array of bytes that should be read
 *
 */
void trx_sram_read(trx_ramaddr_t addr, uint8_t length, uint8_t *data);

/**
 * @brief Get static transceiver parameters
 *
 * This function reads the static transceiver parameters,
 * defined in a structure and protects it with a CRC16.
 *
 *  @param p pointer to the data structure.
 *
 */
void trx_parms_get(trx_param_t *p);

/**
 * @brief Set static transceiver parameters
 *
 * This function writes the static transceiver parameters,
 * defined in a structure  protects it with a CRC16.
 *
 *  @param p pointer to the data structure.
 *  @param no_crc_check if this parameter is true, the
 *                       the CRC given in the sructure is not checked.
 *  @return 0 if OK, 1 if error.
 *
 */
uint8_t trx_parms_set(trx_param_t *p);

/**
 * @brief set data rate
 *
 * The transceiver is forced to state TRX_OFF in order to
 * switch the data rate.
 *
 * @param rate_type type code (the data rate hash codes,
 * e.g. @ref OQPSK250, e.g. *PSK macros)
 * of the desired data rate.
 * @return the value of rate_type parameter or @ref RATE_NONE
*/
uint8_t trx_set_datarate(uint8_t rate_type);

/**
 * @brief get current adjusted data rate.
 * @return hash code of currently set data rate.
 */
uint8_t trx_get_datarate(void);

/**
 * @brief return the number of supported data rates.
 *
 */
uint8_t trx_get_number_datarates(void);

/**
 * @brief return a pointer to a datarate string in the programm
 *        memory.
 *
 * This function can be used to get a list of data rates,
 * supported of the current radio transceiver.
 *
 * @param  idx Index of the data rate.
 * @return Program memory  string pointer.
 */
void * trx_get_datarate_str_p(uint8_t idx);

/**
 * @brief Decodes a hash value and returns a datarate
 *        string pointer in program memory.
 * @param rhash Hash value of a data rate.
 * @return a string pointer in the programm memory
 */
void * trx_decode_datarate_p(uint8_t rhash);

/**
 * @brief return a copy of a data rate in a buffer.
 *
 * @param  idx Index of the data rate.
 * @retval rstr pointer to the buffer, where data rate is copied,
 * @param  nlen maximum length of rstr
 * @return 255 in case the index @c idx is out of range, otherwise 0.
 *
 */
uint8_t trx_get_datarate_str(uint8_t idx, char * rstr, uint8_t nlen);

/**
 * @brief Decodes a hash value and returns a datarate
 *        string pointer.
 * @param rhash Hash value of a data rate.
 * @retval rstr pointer to the buffer, where data rate is copied,
 * @param  nlen maximum length of rstr
 * @return 255 in case the hashvalue @c rhash is not supported by
 * the transceiver or invalid, otherwise 0.
 */
uint8_t trx_decode_datarate(uint8_t rhash, char * rstr, uint8_t nlen);

/*=== Inline Functions ================================================*/
/*
 * This are functions that are usually called once in an application,
 * so we decalre it inline here
 */

/**
 * @brief Basic radio initialization function,
 */
static inline uint8_t trx_init(void)
{
uint8_t val;

    /* reset transceiver */
    TRX_RESET_LOW();
    TRX_SLPTR_LOW();
    DELAY_US(TRX_RESET_TIME_US);
    TRX_RESET_HIGH();

    /* set TRX_OFF (for the case we come from P_ON) */
    trx_reg_write(RG_TRX_STATE, CMD_TRX_OFF);
    DELAY_US(TRX_INIT_TIME_US);
    val = trx_reg_read(RG_TRX_STATUS);
    return (val != TRX_OFF) ? TRX_OK : TRX_INIT_FAIL;
}


static inline uint8_t trx_check_pll_lock(void)
{
uint8_t val, cnt = 255;

    trx_reg_write(RG_TRX_STATE, CMD_FORCE_TRX_OFF);
    trx_reg_write(RG_IRQ_MASK, TRX_IRQ_PLL_LOCK);
    trx_reg_write(RG_TRX_STATE, CMD_PLL_ON);
    cnt = 255;
    do
    {
        DELAY_US(TRX_PLL_LOCK_TIME_US);
        val = trx_reg_read(RG_IRQ_STATUS);
        if (val & TRX_IRQ_PLL_LOCK)
        {
            break;
        }
    }
    while(--cnt);

    /* clear pending IRQs*/
    trx_reg_write(RG_TRX_STATE, CMD_FORCE_TRX_OFF);
    trx_reg_read(RG_IRQ_STATUS);
    return (cnt > 0) ? TRX_OK : TRX_PLL_FAIL;
}


/**
 * @brief Verify that correct radio type is used.
 *
 * @return status value, with the following meaning:
 *        - 0 if part and revision number match
 *        - 1 if revision number does @b not match
 *        - 2 if part number does @b not match
 *        - 3 if part and revision number does @b not match
 */
static inline int trx_identify(void)
{
    int ret = 0;

    if(RADIO_PART_NUM != trx_reg_read(RG_PART_NUM))
    {
        ret |= INVALID_PART_NUM;
    }

    if(RADIO_VERSION_NUM != trx_reg_read(RG_VERSION_NUM))
    {
        ret |= INVALID_REV_NUM;
    }
    return ret;
}

/**
 * @brief Write the PANID to the address filter registers
 */
static inline void trx_set_panid(uint16_t panid)
{
    trx_reg_write(RG_PAN_ID_0,(panid&0xff));
    trx_reg_write(RG_PAN_ID_1,(panid>>8));
}

/**
 * @brief Write the 16 bit short address to the
 * address filter registers
 */
static inline void trx_set_shortaddr(uint16_t shortaddr)
{
    trx_reg_write(RG_SHORT_ADDR_0,(shortaddr&0xff));
    trx_reg_write(RG_SHORT_ADDR_1,(shortaddr>>8));
}

/**
 * @brief Write the 64 bit long address (MAC address) to the
 * address filter registers
 */
static inline void trx_set_longaddr(uint64_t longaddr)
{
    trx_reg_write(RG_IEEE_ADDR_0, (uint8_t)(longaddr>>0) );
    trx_reg_write(RG_IEEE_ADDR_1, (uint8_t)(longaddr>>8) );
    trx_reg_write(RG_IEEE_ADDR_2, (uint8_t)(longaddr>>16));
    trx_reg_write(RG_IEEE_ADDR_3, (uint8_t)(longaddr>>24));
    trx_reg_write(RG_IEEE_ADDR_4, (uint8_t)(longaddr>>32));
    trx_reg_write(RG_IEEE_ADDR_5, (uint8_t)(longaddr>>40));
    trx_reg_write(RG_IEEE_ADDR_6, (uint8_t)(longaddr>>48));
    trx_reg_write(RG_IEEE_ADDR_7, (uint8_t)(longaddr>>56));
}

/* todo add and test a fucntion for setting the ext address */

#ifdef __cplusplus
} /* extern "C" */
#endif

/**
 *  @}
 */
#endif /* TRANSCEIVER_H */
