/**
 * @file pal_board.c
 *
 * @brief PAL board specific functionality
 *
 * This file implements PAL board specific functionality.
 *
 * $Id: pal_board.c 26129 2011-04-01 10:48:49Z felix.varghese $
 *
 * @author    Atmel Corporation: http://www.atmel.com
 * @author    Support email: avr@atmel.com
 */
/*
 * Copyright (c) 2009, Atmel Corporation All rights reserved.
 *
 * Licensed under Atmel's Limited License Agreement --> EULA.txt
 */

/* === Includes ============================================================ */

#include <stdbool.h>
#include <stdlib.h>
#include "pal.h"
#include "pal_boardtypes.h"
#include "pal_config.h"
#include "pal_usb.h"

#if (BOARD_TYPE == RCB_6_3_PLAIN_OR_STB)

/* === Macros ============================================================== */

/* command bytes */
#define AT25010_CMD_WREN  0x06  /* Set Write Enable Latch */
#define AT25010_CMD_WRDI  0x04  /* Reset Write Enable Latch */
#define AT25010_CMD_RDSR  0x05  /* Read Status Register */
#define AT25010_CMD_WRSR  0x01  /* Write Status Register */
#define AT25010_CMD_READ  0x03  /* Read Data from Memory Array */
#define AT25010_CMD_WRITE 0x02  /* Write Data to Memory Array */

/* status register bits */
#define AT25010_STATUS_NRDY 0x01 /* not ready */
#define AT25010_STATUS_WE   0x02 /* write enabled */
#define AT25010_BP0         0x04 /* block-protection bit 0 */
#define AT25010_BP1         0x08 /* block-protection bit 1 */

/* Values for Feature Byte 1: RF frontend features */

/** RF connector enabled */
#define CFG_FEATURE1_RFCONN             0x01

/** On-board antenna enabled */
#define CFG_FEATURE1_ANTENNA            0x02

/** External antenna diversity */
#define CFG_FEATURE1_EXDIV              0x04

/** Reserved */
#define CFG_FEATURE1_RESV3              0x08

/** Transceiver-controlled PA present */
#define CFG_FEATURE1_PA                 0x10

/** Transceiver-controlled LNA present */
#define CFG_FEATURE1_LNA                0x20

/** Reserved */
#define CFG_FEATURE1_RESV6              0x40

/** Reserved */
#define CFG_FEATURE1_RESV7              0x80


/** Maximal length of textual board name */
#define CFG_NAMELEN             (32 - sizeof(uint16_t))

/** Total space reserved for binary configuration data */
#define CFG_BINARYLEN           32

#if (EXTERN_EEPROM_AVAILABLE == 1)
#if defined(__GNUC__)
#include <util/crc16.h>
#define CRC_CCITT_UPDATE(crc, data) _crc_ccitt_update(crc, data)
#endif /* defined(__GNUC__) */

#if defined(__ICCAVR__)
#define CRC_CCITT_UPDATE(crc, data) crc_ccitt_update(crc, data)
/* Internal helper function for CRC_CCITT_UPDATE. */
uint16_t crc_ccitt_update(uint16_t crc, uint8_t data);
#endif /* __ICCAVR__ */
#endif /* EXTERN_EEPROM_AVAILABLE */

#define NUM_CHECK 10

/*
 * Various helper macros for accessing the memory mapped External RAM on the
 * Sensor Terminal Board
 */
#define XRAM_DATA_SETINP()   do{ XRAM_DATA_DDR = 0x00; XRAM_DATA_PORT = 0x00; }while(0)
#define XRAM_DATA_SETOUTP()  do{ XRAM_DATA_DDR = 0xFF; }while(0)
#define XRAM_CTRL_RD_LO()    do{ XRAM_CTRL_PORT &= ~XRAM_RD; }while(0)
#define XRAM_CTRL_RD_HI()    do{ XRAM_CTRL_PORT |= XRAM_RD; }while(0)
#define XRAM_CTRL_WR_LO()    do{ XRAM_CTRL_PORT &= ~XRAM_WR; }while(0)
#define XRAM_CTRL_WR_HI()    do{ XRAM_CTRL_PORT |= XRAM_WR; }while(0)

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

/**
 * Encoding of the board family in the board_family configuration
 * record member.
 */
enum boardfamilycode
{
    CFG_BFAMILY_RADIO_EXTENDER, /**< Radio Extender boards */
    CFG_BFAMILY_RCB             /**< Radio Controller boards */
} SHORTENUM;

/**
 * Structure of the configuration record EEPROM data.  These data
 * reside at offset CFG_BASE_ADDR, and allocate the upper address
 * space of the EEPROM.  The lower address space is available for
 * further extensions, and/or customer use (the upper part can be
 * software write-protected on demand).
 *
 * The record is divided into 32 bytes of structured binary
 * configuration data, followed by up to 30 bytes of textual
 * configuration data (terminated with \0), and terminated by a CRC-16
 * checksum.  The CRC algorithm used is the same CCITT algorithm that
 * is also used to protect IEEE 802.15.4 frames.
 *
 * All multibyte integer values are stored in little-endia byte order.
 */
struct cfg_eeprom_data
{
    union
    {
        /** Raw access to binary configuration data. */
        uint8_t raw_binary[CFG_BINARYLEN];

        /* Structured access to binary configuration data. */
        struct
        {
            /** factory-supplied EUI-64 address */
            uint64_t mac_address;

            /** factory-supplied board serial number */
            uint64_t serial_number;

            /** board ID: family */
            enum boardfamilycode board_family;

            /** board ID: major, minor, revision */
            uint8_t board_id[3];

            /** feature byte 1: RF frontend features */
            uint8_t feature1;

            /** 16 MHz crystal oscillator calibration value */
            uint8_t cal_16mhz;

            /** RC oscillator calibration value, Vcc = 3.6 V */
            uint8_t cal_rc_36;

            /** RC oscillator calibration value, Vcc = 2.0 V */
            uint8_t cal_rc_20;

            /** antenna gain, 1/10 dB */
            int8_t antenna_gain;

            /* 7 bytes reserved, leave as 0xFF */
        }
        struct_binary;
    }
    binary_data;

    /** Textual board name, ASCIZ string */
    char board_name[CFG_NAMELEN];

    /** CRC-16 over binary_data and board_name[] */
    uint16_t crc;
};

typedef struct
{
    uint16_t addr;
    uint8_t val;
}mem_test_t;


/* === Globals ============================================================= */

static uint8_t led_state = 0x00;
static board_t board_type;
/* === Prototypes ========================================================== */

#if (EXTERN_EEPROM_AVAILABLE == 1)
static uint8_t at25010_read_byte(uint8_t addr);
static void at25010_spi_setup(void);
static void at25010_spi_cleanup(void);
#endif
static void led_helper_func(void);

/* === Implementation ======================================================= */

/**
 * @brief Calibrates the internal RC oscillator
 *
 * This function calibrates the internal RC oscillator, based
 * on the external 32.768 Hz crystal clock source.
 *
 * @return True if calibration is successful, false otherwise.
 */
bool pal_calibrate_rc_osc(void)
{
    /* Nothing to be done. */
    return true;
}



/**
 * @brief Setup the SPI subsystem for accessing an AT25010 EEPROM
 *
 * The SPI subsystem on the RCB 6.3 is located on Port B which is
 * either used as the data port when connecting to an FT245 USB
 * converter, or available as general IO to the user otherwise.  For
 * that reason, PAL will only enable the SPI subsystem in order to
 * access the feature EEPROM (AT25010), and does not touch port B
 * otherwise.
 *
 * The AT25010's chip select signal is located on PG5, outside port B.
 */
#if (EXTERN_EEPROM_AVAILABLE == 1)
static void at25010_spi_setup(void)
{
    /* Deactivate read and write signals. */
    PORTE |= _BV(4);
    PORTE |= _BV(5);
    DDRE |= _BV(4);
    DDRE |= _BV(5);

    /* Sensor terminal address setting */
    PORTD |= _BV(6);
    PORTD |= _BV(7);
    DDRD |= _BV(6);
    DDRD |= _BV(7);

    /* Deactivate ALE signal. */
    PORTG &= ~_BV(2);
    DDRG |= _BV(2);

    /* Activate EEPROM CS. */
    PORTG &= ~_BV(5);
    DDRG |= _BV(5);

#ifdef USB0
    USB_DATA_DDR = 0x00;
    USB_DATA_PORT = 0x00;
#endif
    /* Ensure /SS, SCK and MOSI become outputs. */
    DDRB = _BV(PB0) | _BV(PB1) | _BV(PB2);
    /*
     * Set SPI speed very low to allow proper EEPROM reading
     * even with large capacitors on pins (e.g. for debugging
     * on Breakout Board light.
     * Here the SPI speed is put to fosc/128 by setting
     * SPR1 and SPR0 bit.
     */
    SPCR = _BV(SPE) | _BV(MSTR) | _BV(SPR1) | _BV(SPR0);
}
#endif  /*EXTERN_EEPROM_AVAILABLE  */

/**
 * @brief Clean up the SPI subsystem after accessing an AT25010 EEPROM
 *
 * This frees up the SPI subsystem completely, and thus, leaves port B
 * back to either the FT245 USB converter, or to the user.
 */
#if (EXTERN_EEPROM_AVAILABLE == 1)
static void at25010_spi_cleanup(void)
{
    SPCR = 0;
    DDRB &= ~_BV(PB0);

#ifdef USB0
    /* In case we have USB, the USB needs to be restored here. */
    sio_usb_init();
#endif  /* USB0 */
}
#endif  /*EXTERN_EEPROM_AVAILABLE  */

/**
 * @brief Read one byte from an AT25010 EEPROM.
 *
 * Due to the connection between MCU, TRX and EEPROM, an EEPROM access
 * causes a transceiver reset. Therefore an entire transceiver configuration
 * is necessary after EEPROM access.
 *
 * @param addr Byte address to read from
 *
 * @return Data read from EEPROM
 */
#if (EXTERN_EEPROM_AVAILABLE == 1)
static uint8_t at25010_read_byte(uint8_t addr)
{
    uint8_t read_value;

    EXT_EE_CS_PORT &= ~EXT_EE_CS_PIN;

    PAL_WAIT_1_US();

    SPDR = AT25010_CMD_READ;
    while ((SPSR & _BV(SPIF)) == 0)
        /* wait */;
    SPDR = addr & 0x7F; // mask out Bit 7 for 128x8 EEPROM
    while ((SPSR & _BV(SPIF)) == 0)
        /* wait */;

    SPDR = 0;   // dummy value to start SPI transfer
    while ((SPSR & _BV(SPIF)) == 0)
        /* wait */;
    read_value = SPDR;

    EXT_EE_CS_PORT |= EXT_EE_CS_PIN;

    return read_value;
}
#endif



/**
 * @brief Get data from external EEPROM
 *
 * @param[in]  start_offset Start offset within EEPROM
 * @param[in]  length Number of bytes to read from EEPROM
 * @param[out] value Data from persistence storage
 *
 * @return MAC_SUCCESS  if external EERPOM is available and contains valid contents
 *         FAILURE else
 */
#if (EXTERN_EEPROM_AVAILABLE == 1)
retval_t extern_eeprom_get(uint8_t start_offset, uint8_t length, void *value)
{
    uint8_t i;
    struct cfg_eeprom_data cfg;
    uint8_t *up;
    uint16_t crc;

    // Read data from external EEPROM.
    at25010_spi_setup();
    for (i = 0, up = (uint8_t *)&cfg; i < sizeof(cfg); i++, up++)
    {
        *up = at25010_read_byte(i);
    }
    at25010_spi_cleanup();

    // Calcute CRC to validate data correctness
    for (i = 0, crc = 0, up = (uint8_t *)&cfg; i < sizeof(cfg); i++, up++)
    {
        crc = CRC_CCITT_UPDATE(crc, *up);
    }
    if (crc != 0)
    {
        return FAILURE;
    }

    /* Copy data from local cfg structure variable to target storage location */
    memcpy(value, ((uint8_t *)&cfg) + start_offset, length);

    return MAC_SUCCESS;
}
#endif



#if defined(__ICCAVR__) || defined(DOXYGEN)
/* This function is available in WINAVR library */
/**
 * @brief Computes the CCITT-CRC16 on a byte by byte basis
 *
 * This function computes the CCITT-CRC16 on a byte by byte basis.
 * It updates the CRC for transmitted and received data using the CCITT 16bit
 * algorithm (X^16 + X^12 + X^5 + 1).
 *
 * @param crc Current crc value
 * @param data Next byte that should be included into the CRC16
 *
 * @return updated CRC16
 */
#if (EXTERN_EEPROM_AVAILABLE == 1)
uint16_t crc_ccitt_update(uint16_t crc, uint8_t data)
{
    data ^= crc & 0xFF;
    data ^= data << 4;

    return ((((uint16_t)data << 8) | ((crc & 0xFF00) >> 8)) ^ \
            (uint8_t)(data >> 4) ^ \
            ((uint16_t)data << 3));
}
#endif /* EXTERN_EEPROM_AVAILABLE */
#endif /* __ICCAVR__ || defined(DOXYGEN) */



/**
 * @brief Initialize LEDs
 */
void pal_led_init(void)
{
    switch(board_type)
    {
        case SENSOR_TERM_BOARD:
        {
            led_state = 0x00;

            /* Switch LEDs off. Both LEDs are low active. */
            LED_PORT_DIR |= (1 << LED_BIT_0);
            LED_PORT_DIR |= (1 << LED_BIT_1);

            LED_PORT |= (1 << LED_BIT_0);
            LED_PORT |= (1 << LED_BIT_1);

            led_helper_func();
            break;
        }

        case PLAIN:
        {
            LED_PORT_PLAIN |= (1 << LED_PIN_PLAIN_0);
            LED_PORT_DIR_PLAIN |= (1 << LED_PIN_PLAIN_0);
            LED_PORT_PLAIN |= (1 << LED_PIN_PLAIN_1);
            LED_PORT_DIR_PLAIN |= (1 << LED_PIN_PLAIN_1);
            LED_PORT_PLAIN |= (1 << LED_PIN_PLAIN_2);
            LED_PORT_DIR_PLAIN |= (1 << LED_PIN_PLAIN_2);
            break;
        }
    }
}



/**
 * @brief Control LED status
 *
 * @param[in]  led_no LED ID
 * @param[in]  led_setting LED_ON, LED_OFF, LED_TOGGLE
 */
void pal_led(led_id_t led_no, led_action_t led_setting)
{
  switch(board_type)
    {
        case SENSOR_TERM_BOARD:
        {
            uint8_t pin;
            /* New values of LED pins based on new LED state. */
            uint8_t led_pin_value;
            /*
             * Original value of LED port before writing new value.
             * This value needs to be restored.
             */
            uint8_t orig_led_port = LED_PORT & ~LED_BIT_MASK;

            /* Both LEDs need to be updated, since several peripherals
             * are dealing with the same port for this board
             * (USB, EEPROM, LEDs, Button).
             */
            LED_PORT_DIR |= (1 << LED_BIT_0);
            LED_PORT_DIR |= (1 << LED_BIT_1);

            switch (led_no)
            {
                case LED_1:
                    pin = LED_BIT_0;
                    break;
                case LED_2:
                    pin = LED_BIT_1;
                    break;
                default:
                    return;
            }

            switch (led_setting)
            {
                case LED_ON:
                    led_state |= _BV(pin);
                    break;

                case LED_OFF:
                    led_state &= ~_BV(pin);
                    break;

                case LED_TOGGLE:
                default:
                    if (led_state & _BV(pin))
                    {
                        /*
                         * LED is currently on,
                         * Switch it off
                         */
                        led_state &= ~_BV(pin);
                    }
                    else
                    {
                        /*
                         * LED is currently off,
                         * Switch it on
                         */
                        led_state |= _BV(pin);
                    }
                    break;
            }

            led_pin_value = (uint8_t)(~(uint16_t)led_state);  // Implicit casting required to avoid IAR Pa091.
            led_pin_value &= LED_BIT_MASK;

            LED_PORT = orig_led_port | led_pin_value;

            led_helper_func();
            break;
        }

        case PLAIN:
        {
            switch (led_no)
            {
                default:
                case 0:
                    switch (led_setting)
                    {
                        case LED_ON:    LED_PORT_PLAIN &= ~(1 << LED_PIN_PLAIN_0); break;
                        case LED_OFF:   LED_PORT_PLAIN |= (1 << LED_PIN_PLAIN_0); break;
                        case LED_TOGGLE: LED_PORT_PLAIN ^= (1 << LED_PIN_PLAIN_0); break;
                    }
                    break;

                case 1:
                    switch (led_setting)
                    {
                        case LED_ON:    LED_PORT_PLAIN &= ~(1 << LED_PIN_PLAIN_1); break;
                        case LED_OFF:   LED_PORT_PLAIN |= (1 << LED_PIN_PLAIN_1); break;
                        case LED_TOGGLE: LED_PORT_PLAIN ^= (1 << LED_PIN_PLAIN_1); break;
                    }
                    break;

                case 2:
                    switch (led_setting)
                    {
                        case LED_ON:    LED_PORT_PLAIN &= ~(1 << LED_PIN_PLAIN_2); break;
                        case LED_OFF:   LED_PORT_PLAIN |= (1 << LED_PIN_PLAIN_2); break;
                        case LED_TOGGLE: LED_PORT_PLAIN ^= (1 << LED_PIN_PLAIN_2); break;
                    }
                    break;
            }
            break;
        }
    }
}



/**
 * @brief Helper function for LEDs
 */
static void led_helper_func(void)
{
    /*
     * Enable LED address decoding.
     * This is similar to USB, but with other settings.
     */
    LED_ADDR_DEC_PORT |= _BV(6);    // Different to USB
    LED_ADDR_DEC_DDR |= _BV(6);
    LED_ADDR_DEC_PORT &= ~_BV(7);
    LED_ADDR_DEC_DDR |= _BV(7);

    /* Enable desired LED state. */
    DDRE |= _BV(4);
    /* Set PE4 to 0 and back to 1. */
    PORTE &= ~_BV(4);
    PORTE |= _BV(4);

    /*
     * Disable LED address decoding.
     * This enables USB again.
     */
    LED_ADDR_DEC_PORT &= ~_BV(6);
    LED_ADDR_DEC_DDR |= _BV(6);
    LED_ADDR_DEC_PORT &= ~_BV(7);
    LED_ADDR_DEC_DDR |= _BV(7);
}


/**
 * @brief Initialize the button
 */
void pal_button_init(void)
{
    switch(board_type)
    {
        case SENSOR_TERM_BOARD:
        {
            /* Nothing to do here. */
            break;
        }

        case PLAIN:
        {
            BUTTON_PORT_PLAIN |= (1 << BUTTON_PIN_PLAIN_0);
            BUTTON_PORT_DIR_PLAIN &= ~(1 << BUTTON_PIN_PLAIN_0);
            break;
        }
    }
}

/**
 * @brief Read button
 *
 * @param button_no Button ID
 */
button_state_t pal_button_read(button_id_t button_no)
{
    /* Keep compiler happy. */
    button_no = button_no;

    switch(board_type)
    {
        case SENSOR_TERM_BOARD:
        {
            uint8_t cur_button_state;

            /*
             * Enable button address decoding.
             * This is similar to USB, but with other settings.
             */
            BUTTON_ADDR_DEC_PORT |= _BV(6);    // Different to USB
            BUTTON_ADDR_DEC_DDR |= _BV(6);
            BUTTON_ADDR_DEC_PORT &= ~_BV(7);
            BUTTON_ADDR_DEC_DDR |= _BV(7);


            PORTE &= ~_BV(5);
            DDRE |= _BV(5);

            /* Switch port to input. */
            BUTTON_PORT |= (1 << BUTTON_PIN_0);
            BUTTON_PORT_DIR &= ~(1 << BUTTON_PIN_0);

            cur_button_state = BUTTON_INPUT_PINS;

            PORTE |= _BV(5);

            /* Switch port back to output. */
            BUTTON_PORT_DIR |= (1 << BUTTON_PIN_0);

            /*
             * Disable button address decoding.
             * This enables USB again.
             */
            BUTTON_ADDR_DEC_PORT &= ~_BV(6);
            BUTTON_ADDR_DEC_DDR |= _BV(6);
            BUTTON_ADDR_DEC_PORT &= ~_BV(7);
            BUTTON_ADDR_DEC_DDR |= _BV(7);

            if (cur_button_state & 0x01)
            {
                return BUTTON_PRESSED;
            }
            else
            {
                return BUTTON_OFF;
            }
            //break;
        }

        case PLAIN:
        {
            if ((BUTTON_INPUT_PINS_PLAIN & (1 << BUTTON_PIN_PLAIN_0)) == 0x00)
            {
                return BUTTON_PRESSED;
            }
            else
            {
                return BUTTON_OFF;
            }
            //break;
        }

        default:
        {
            break;
        }
    }
    return BUTTON_OFF;
}

/**
 * @brief Initializes the GPIO used for the extern EEPROM
 *
 * This function is used to initialize the port pins connecting
 * the microcontroller to the extern EEPROM.
 */
#if (EXTERN_EEPROM_AVAILABLE == 1)
void extern_eeprom_init(void)
{
    EXT_EE_CS_PORT |= EXT_EE_CS_PIN;
    EXT_EE_CS_DDR |= EXT_EE_CS_PIN;
}
#endif

/**
 * @brief Read XRAM
 *
 * @param
 */
uint8_t pal_xram_read(uint16_t addr)
{
    uint8_t data;

    /* Set HIGH before switching to output to prevent RD cycle */
    XRAM_CTRL_PORT |= XRAM_RD;
    XRAM_CTRL_DDR |= XRAM_RD;

    /* Set HIGH before switching to output to prevent WR cycle */
    XRAM_CTRL_PORT |= XRAM_WR;
    XRAM_CTRL_DDR |= XRAM_WR;


    PORTB = (uint8_t)(0x00FF & addr);
    DDRB = 0xFF;

    PORTC = (uint8_t)(0x000F & (addr>>8));
    DDRC |= 0x0F;

    PORTD = (uint8_t)(0x00F0 & (addr>>8));
    DDRD |= 0xF0;

    XRAM_ALE_DDR |= XRAM_ALE_PIN;
    XRAM_ALE_PORT |= XRAM_ALE_PIN;
    nop();
    nop();
    XRAM_ALE_PORT &= ~XRAM_ALE_PIN;

    /* Read a character from the fifo address. */
    XRAM_DATA_SETINP();
    XRAM_CTRL_RD_LO();
    nop();
    nop();
    data = XRAM_DATA_PIN;
    XRAM_CTRL_RD_HI();

    return data;
}

/**
 * @brief Read XRAM
 *
 * @param
 */
void pal_xram_write(uint16_t addr, uint8_t data)
{
    /* Set HIGH before switching to output to prevent RD cycle */
    XRAM_CTRL_PORT |= XRAM_RD;
    XRAM_CTRL_DDR |= XRAM_RD;

    /* Set HIGH before switching to output to prevent WR cycle */
    XRAM_CTRL_PORT |= XRAM_WR;
    XRAM_CTRL_DDR |= XRAM_WR;

    /* Set Address */
    PORTB = (uint8_t)(0x00FF & addr);
    DDRB = 0xFF;

    PORTC = (uint8_t)(0x000F & (addr>>8));
    DDRC |= 0x0F;

    PORTD = (uint8_t)(0x00F0 & (addr>>8));
    DDRD |= 0xF0;

    XRAM_ALE_DDR |= XRAM_ALE_PIN;
    XRAM_ALE_PORT |= XRAM_ALE_PIN;
    nop();
    nop();
    XRAM_ALE_PORT &= ~XRAM_ALE_PIN;
    XRAM_DATA_SETOUTP();
    XRAM_DATA_PORT = data; // XRAM_FIFO_AD >> 8; /* high byte of FIFO address */
    XRAM_CTRL_WR_LO();
    nop();
    nop();
    XRAM_CTRL_WR_HI();
    XRAM_DATA_SETINP();
}

/**
 * @brief Identifies the board based on presence/absence of external RAM
 */
board_t board_identify()
{
    uint8_t i;
    uint8_t count = 0;
    mem_test_t mem_vals[NUM_CHECK];
    for(i = 0; i < NUM_CHECK ; i++)
    {
        mem_vals[i].addr = 0x8000 + (i * 10);
        mem_vals[i].val = rand();
        pal_xram_write(mem_vals[i].addr,mem_vals[i].val);
    }
    for(i = 0; i < NUM_CHECK ; i++)
    {
        if(mem_vals[i].val == pal_xram_read(mem_vals[i].addr))
        {
            count++;
        }
    }
    if(count == NUM_CHECK)
    {
        board_type = SENSOR_TERM_BOARD;
    }
    else
    {
        board_type = PLAIN;
    }
    return board_type;
}
#endif /* RCB_6_3_PLAIN_OR_STB */

/* EOF */
