/**
 * Copyright (c) 2014, Analog Devices, Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted (subject to the limitations in the
 * disclaimer below) 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 Analog Devices, Inc. nor the names of its
 *   contributors may be used to endorse or promote products derived
 *   from this software without specific prior written permission.
 *
 * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE
 * GRANTED BY THIS LICENSE.  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.
 */
/**
 * \author Jim Paris <jim.paris@rigado.com>
 */

#include <string.h>
#include <stdint.h>

#include <aducrf101-contiki.h>

#include "contiki.h"
#include "contiki-net.h"
#include "net/netstack.h"
#include "radio.h"

#define MAX_PACKET_LEN 240

static uint8_t tx_buf[MAX_PACKET_LEN];

#ifndef ADUCRF101_RADIO_BASE_CONFIG
#define ADUCRF101_RADIO_BASE_CONFIG DR_38_4kbps_Dev20kHz
#endif

static RIE_BaseConfigs base_config = ADUCRF101_RADIO_BASE_CONFIG;
static int current_channel = 915000000;
static int current_power = 31;
static int radio_is_on = 0;

/*---------------------------------------------------------------------------*/
/* Sniffer configuration.  We can re-use the CC2538 sniffer application
   if we also accept CC2538_RF_CONF_SNIFFER. */
#ifndef ADUCRF101_RF_CONF_SNIFFER
#if CC2538_RF_CONF_SNIFFER
#define ADUCRF101_RF_CONF_SNIFFER 1
#endif
#endif

#if ADUCRF101_RF_CONF_SNIFFER
#include "dev/uart.h"
static const uint8_t magic[] = { 0x53, 0x6E, 0x69, 0x66 }; /* Snif */
#endif
/*---------------------------------------------------------------------------*/
/* "Channel" is really frequency, and can be within the bands:
   431000000 Hz to 464000000 Hz
   862000000 Hz to 928000000 Hz
 */
#define MIN_CHANNEL 431000000
#define MAX_CHANNEL 928000000
static int
_set_channel(int freq)
{
  if(freq < 431000000) {
    freq = 431000000;
  } else if(freq > 464000000 && freq < 663000000) {
    freq = 464000000;
  } else if(freq >= 663000000 && freq < 862000000) {
    freq = 862000000;
  } else if(freq > 928000000) {
    freq = 928000000;
  }
  current_channel = freq;
  if(RadioSetFrequency(freq) != RIE_Success) {
    return RADIO_RESULT_ERROR;
  }
  return RADIO_RESULT_OK;
}
/*---------------------------------------------------------------------------*/
/* "Power" covers both PA type and power level:
    0 through 15 means single-ended, power level 0 through 15
    16 through 31 means differential, power level 0 through 15 */
#define MIN_POWER 0
#define MAX_POWER 31
static int
_set_power(int power)
{
  RIE_Responses ret;
  if(power < 0) {
    power = 0;
  }
  if(power > 31) {
    power = 31;
  }
  if(power <= 15) {
    ret = RadioTxSetPA(SingleEndedPA, power);
  } else {
    ret = RadioTxSetPA(DifferentialPA, power - 16);
  }
  current_power = power;
  if(ret != RIE_Success) {
    return RADIO_RESULT_ERROR;
  }
  return RADIO_RESULT_OK;
}
/*---------------------------------------------------------------------------*/
PROCESS(aducrf101_rf_process, "ADuCRF101 RF driver");
/*---------------------------------------------------------------------------*/
/** Turn the radio on. */
static int
on(void)
{
  if(radio_is_on) {
    return 1;
  }

  /* Power radio on */
  if(RadioInit(base_config) != RIE_Success) {
    return 0;
  }

  /* Ensure channel and power are set */
  if(_set_channel(current_channel) != RADIO_RESULT_OK) {
    return 0;
  }
  if(_set_power(current_power) != RADIO_RESULT_OK) {
    return 0;
  }

  /* Enter receive mode */
  RadioRxPacketVariableLen();

  radio_is_on = 1;
  return 1;
}
/*---------------------------------------------------------------------------*/
/** Turn the radio off. */
static int
off(void)
{
  if(!radio_is_on) {
    return 1;
  }
  if(RadioPowerOff() != RIE_Success) {
    return 0;
  }
  radio_is_on = 0;
  return 1;
}
/*---------------------------------------------------------------------------*/
static int
init(void)
{
  off();
  on();
  process_start(&aducrf101_rf_process, NULL);
  return 1;
}
/*---------------------------------------------------------------------------*/
/** Prepare the radio with a packet to be sent. */
static int
prepare(const void *payload, unsigned short payload_len)
{
  /* Truncate long packets */
  if(payload_len > MAX_PACKET_LEN) {
    payload_len = MAX_PACKET_LEN;
  }
  memcpy(tx_buf, payload, payload_len);
  return 0;
}
/*---------------------------------------------------------------------------*/
/** Send the packet that has previously been prepared. */
static int
transmit(unsigned short transmit_len)
{
  if(!radio_is_on)
    return RADIO_TX_ERR;

  /* Transmit the packet */
  if(transmit_len > MAX_PACKET_LEN) {
    transmit_len = MAX_PACKET_LEN;
  }
  if(RadioTxPacketVariableLen(transmit_len, tx_buf) != RIE_Success) {
    return RADIO_TX_ERR;
  }
  while(!RadioTxPacketComplete())
    continue;

  /* Enter receive mode immediately after transmitting a packet */
  RadioRxPacketVariableLen();

  return RADIO_TX_OK;
}
/*---------------------------------------------------------------------------*/
/** Prepare & transmit a packet. */
static int
send(const void *payload, unsigned short payload_len)
{
  prepare(payload, payload_len);
  return transmit(payload_len);
}
/*---------------------------------------------------------------------------*/
/** Read a received packet into a buffer. */
static int
read(void *buf, unsigned short buf_len)
{
  uint8_t packet_len;
  int8_t rssi;

  if(!radio_is_on)
    return 0;

  if(buf_len > MAX_PACKET_LEN) {
    buf_len = MAX_PACKET_LEN;
  }

  /* Read already-received packet */
  if(RadioRxPacketRead(buf_len, &packet_len, buf, &rssi) != RIE_Success) {
    return 0;
  }

  if(packet_len > buf_len) {
    packet_len = buf_len;
  }

  /* Re-enter receive mode immediately after receiving a packet */
  RadioRxPacketVariableLen();

#if ADUCRF101_RF_CONF_SNIFFER
  uart_put(magic[0]);
  uart_put(magic[1]);
  uart_put(magic[2]);
  uart_put(magic[3]);
  uart_put(packet_len + 2);
  for(int i = 0; i < packet_len; i++) {
    uart_put(((uint8_t *)buf)[i]);
  }
  /* FCS value is Wireshark's "TI CC24xx format" option: */
  uart_put(rssi); /* RSSI */
  uart_put(0x80); /* CRC is OK, LQI correlation is 0 */
#endif

  return packet_len;
}
/*---------------------------------------------------------------------------*/
/** Perform a Clear-Channel Assessment (CCA) to find out if there is
    a packet in the air or not. */
static int
channel_clear(void)
{
  /* Not implemented; assume clear */
  return 1;
}
/*---------------------------------------------------------------------------*/
/** Check if the radio driver is currently receiving a packet */
static int
receiving_packet(void)
{
  /* Not implemented; assume no. */
  return 0;
}
/*---------------------------------------------------------------------------*/
/** Check if the radio driver has just received a packet */
static int
pending_packet(void)
{
  if(RadioRxPacketAvailable()) {
    return 1;
  }
  return 0;
}
/*---------------------------------------------------------------------------*/
/** Get a radio parameter value. */
static radio_result_t
get_value(radio_param_t param, radio_value_t *value)
{
  if(!value) {
    return RADIO_RESULT_INVALID_VALUE;
  }

  switch(param) {
  case RADIO_PARAM_RSSI:
  {
    int8_t dbm;
    if(!radio_is_on || RadioRadioGetRSSI(&dbm) != RIE_Success) {
      return RADIO_RESULT_ERROR;
    }
    *value = dbm;
    return RADIO_RESULT_OK;
  }

  case RADIO_PARAM_CHANNEL:
    *value = current_channel;
    return RADIO_RESULT_OK;
  case RADIO_CONST_CHANNEL_MIN:
    *value = MIN_CHANNEL;
    return RADIO_RESULT_OK;
  case RADIO_CONST_CHANNEL_MAX:
    *value = MAX_CHANNEL;
    return RADIO_RESULT_OK;

  case RADIO_PARAM_TXPOWER:
    *value = current_power;
    return RADIO_RESULT_OK;
  case RADIO_CONST_TXPOWER_MIN:
    *value = MIN_POWER;
    return RADIO_RESULT_OK;
  case RADIO_CONST_TXPOWER_MAX:
    *value = MAX_POWER;
    return RADIO_RESULT_OK;

  default:
    return RADIO_RESULT_NOT_SUPPORTED;
  }
}
/*---------------------------------------------------------------------------*/
/** Set a radio parameter value. */
static radio_result_t
set_value(radio_param_t param, radio_value_t value)
{
  switch(param) {
  case RADIO_PARAM_CHANNEL:
    return _set_channel(value);

  case RADIO_PARAM_TXPOWER:
    return _set_power(value);

  default:
    return RADIO_RESULT_NOT_SUPPORTED;
  }
}
/*---------------------------------------------------------------------------*/
/**
 * Get a radio parameter object. The argument 'dest' must point to a
 * memory area of at least 'size' bytes, and this memory area will
 * contain the parameter object if the function succeeds.
 */
static radio_result_t
get_object(radio_param_t param, void *dest, size_t size)
{
  return RADIO_RESULT_NOT_SUPPORTED;
}
/*---------------------------------------------------------------------------*/
/**
 * Set a radio parameter object. The memory area referred to by the
 * argument 'src' will not be accessed after the function returns.
 */
static radio_result_t
set_object(radio_param_t param, const void *src, size_t size)
{
  return RADIO_RESULT_NOT_SUPPORTED;
}
/*---------------------------------------------------------------------------*/
/**
 * \brief Implementation of the ADuCRF101 RF driver process
 *
 *        This process is started by init().  It waits for events triggered
 *        by packet reception.
 */
PROCESS_THREAD(aducrf101_rf_process, ev, data)
{
  int len;
  PROCESS_BEGIN();

  while(1) {
    PROCESS_YIELD_UNTIL(ev == PROCESS_EVENT_POLL);

    packetbuf_clear();
    len = read(packetbuf_dataptr(), PACKETBUF_SIZE);

    if(len > 0) {
      packetbuf_set_datalen(len);

      NETSTACK_RDC.input();
    }
  }

  PROCESS_END();
}
/*---------------------------------------------------------------------------*/
/**
 * \brief Trigger function called by ADI radio engine upon packet RX.
 */
void
aducrf101_rx_packet_hook(void)
{
  process_poll(&aducrf101_rf_process);
}
/*---------------------------------------------------------------------------*/
const struct radio_driver aducrf101_radio_driver = {
  .init = init,
  .prepare = prepare,
  .transmit = transmit,
  .send = send,
  .read = read,
  .channel_clear = channel_clear,
  .receiving_packet = receiving_packet,
  .pending_packet = pending_packet,
  .on = on,
  .off = off,
  .get_value = get_value,
  .set_value = set_value,
  .get_object = get_object,
  .set_object = set_object,
};