/*
 * Copyright (c) 2011, George Oikonomou - <oikonomou@users.sourceforge.net>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 * 3. Neither the name of the Institute 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 INSTITUTE 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 INSTITUTE 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.
 *
 * This file is part of the Contiki operating system.
 */

/**
 * \file
 *         Implementation of the cc2530 RF driver
 *
 * \author
 *         George Oikonomou - <oikonomou@users.sourceforge.net>
 */
#include "contiki.h"
#include "dev/radio.h"
#include "dev/cc2530-rf.h"
#include "cc253x.h"
#include "sfr-bits.h"
#include "sys/clock.h"
#include "sys/rtimer.h"

#include "net/packetbuf.h"
#include "net/rime/rimestats.h"
#include "net/linkaddr.h"
#include "net/netstack.h"

#include <string.h>
/*---------------------------------------------------------------------------*/
#define CHECKSUM_LEN 2
/*---------------------------------------------------------------------------*/
#if CC2530_RF_CONF_LEDS
#define CC2530_RF_LEDS CC2530_RF_CONF_LEDS
#else
#define CC2530_RF_LEDS 0
#endif

#if CC2530_RF_LEDS
#include "dev/leds.h"
#define RF_RX_LED_ON()		leds_on(LEDS_RED);
#define RF_RX_LED_OFF()		leds_off(LEDS_RED);
#define RF_TX_LED_ON()		leds_on(LEDS_GREEN);
#define RF_TX_LED_OFF()		leds_off(LEDS_GREEN);
#else
#define RF_RX_LED_ON()
#define RF_RX_LED_OFF()
#define RF_TX_LED_ON()
#define RF_TX_LED_OFF()
#endif
/*---------------------------------------------------------------------------*/
#define DEBUG 0
#if DEBUG
#include "debug.h"
#define PUTSTRING(...) putstring(__VA_ARGS__)
#define PUTHEX(...) puthex(__VA_ARGS__)
#else
#define PUTSTRING(...)
#define PUTHEX(...)
#endif
/*---------------------------------------------------------------------------*/
/* Local RF Flags */
#define RX_ACTIVE  0x80
#define WAS_OFF    0x10
#define RF_ON      0x01

/* Bit Masks for the last byte in the RX FIFO */
#define CRC_BIT_MASK 0x80
#define LQI_BIT_MASK 0x7F
/* RSSI Offset */
#define RSSI_OFFSET    73

/* 192 ms, radio off -> on interval */
#define ONOFF_TIME                    RTIMER_ARCH_SECOND / 3125

#define CC2530_RF_CHANNEL_SET_ERROR      -1

#define CC2530_RF_TX_POWER_TXCTRL_MIN_VAL 0x09 /* Value for min TX Power */
#define CC2530_RF_TX_POWER_TXCTRL_DEF_VAL 0x69 /* Reset Value */
/*---------------------------------------------------------------------------*/
#if CC2530_RF_CONF_HEXDUMP
#include "dev/io-arch.h"
static const uint8_t magic[] = { 0x53, 0x6E, 0x69, 0x66 }; /* Snif */
#endif
/*---------------------------------------------------------------------------*/
#ifdef CC2530_RF_CONF_AUTOACK
#define CC2530_RF_AUTOACK CC2530_RF_CONF_AUTOACK
#else
#define CC2530_RF_AUTOACK 1
#endif
/*---------------------------------------------------------------------------*/
static uint8_t CC_AT_DATA rf_flags;

static int on(void); /* prepare() needs our prototype */
static int off(void); /* transmit() needs our prototype */
static int channel_clear(void); /* transmit() needs our prototype */
/*---------------------------------------------------------------------------*/
/* TX Power dBm lookup table. Values from SmartRF Studio v1.16.0 */
typedef struct output_config {
  radio_value_t power;
  uint8_t txpower_val;
} output_config_t;

static const output_config_t output_power[] = {
  {  5, 0xF5 }, /* 4.5 */
  {  3, 0xE5 }, /* 2.5 */
  {  1, 0xD5 },
  {  0, 0xC5 }, /* -0.5 */
  { -1, 0xB5 }, /* -1.5 */
  { -3, 0xA5 },
  { -4, 0x95 },
  { -6, 0x85 },
  { -8, 0x75 },
  {-10, 0x65 },
  {-12, 0x55 },
  {-14, 0x45 },
  {-16, 0x35 },
  {-18, 0x25 },
  {-20, 0x15 },
  {-22, 0x05 },
  {-28, 0x05 }, /* TXCTRL must be set to 0x09 */
};

#define OUTPUT_CONFIG_COUNT (sizeof(output_power) / sizeof(output_config_t))

/* Max and Min Output Power in dBm */
#define OUTPUT_POWER_MIN    (output_power[OUTPUT_CONFIG_COUNT - 1].power)
#define OUTPUT_POWER_MAX    (output_power[0].power)
/*---------------------------------------------------------------------------*/
/**
 * \brief Get the current operating channel
 * \return Returns a value in [11,26] representing the current channel
 */
static uint8_t
get_channel()
{
  return (uint8_t)((FREQCTRL + 44) / 5);
}
/*---------------------------------------------------------------------------*/
/**
 * \brief Set the current operating channel
 * \param channel The desired channel as a value in [11,26]
 * \return Returns a value in [11,26] representing the current channel
 *         or a negative value if \e channel was out of bounds
 */
static int8_t
set_channel(uint8_t channel)
{
  PUTSTRING("RF: Set Chan\n");

  if((channel < CC2530_RF_CHANNEL_MIN) || (channel > CC2530_RF_CHANNEL_MAX)) {
    return CC2530_RF_CHANNEL_SET_ERROR;
  }

  /* Changes to FREQCTRL take effect after the next recalibration */
  /* Changes to FREQCTRL take effect after the next recalibration */
  off();
  FREQCTRL = (CC2530_RF_CHANNEL_MIN
      + (channel - CC2530_RF_CHANNEL_MIN) * CC2530_RF_CHANNEL_SPACING);
  on();

  return (int8_t)channel;
}
/*---------------------------------------------------------------------------*/
static radio_value_t
get_pan_id(void)
{
  return (radio_value_t)(PAN_ID1 << 8 | PAN_ID0);
}
/*---------------------------------------------------------------------------*/
static void
set_pan_id(uint16_t pan)
{
  PAN_ID0 = pan & 0xFF;
  PAN_ID1 = pan >> 8;
}
/*---------------------------------------------------------------------------*/
static radio_value_t
get_short_addr(void)
{
  return (radio_value_t)(SHORT_ADDR1 << 8 | SHORT_ADDR0);
}
/*---------------------------------------------------------------------------*/
static void
set_short_addr(uint16_t addr)
{
  SHORT_ADDR0 = addr & 0xFF;
  SHORT_ADDR1 = addr >> 8;
}
/*---------------------------------------------------------------------------*/
/**
 * \brief Reads the current signal strength (RSSI)
 * \return The current RSSI in dBm
 *
 * This function reads the current RSSI on the currently configured
 * channel.
 */
static radio_value_t
get_rssi(void)
{
  int8_t rssi;

  /* If we are off, turn on first */
  if(RXENABLE == 0) {
    rf_flags |= WAS_OFF;
    on();
  }

  /* Wait on RSSI_VALID */
  while((RSSISTAT & RSSISTAT_RSSI_VALID) == 0);

  rssi = (radio_value_t)RSSI - RSSI_OFFSET;

  /* If we were off, turn back off */
  if((rf_flags & WAS_OFF) == WAS_OFF) {
    rf_flags &= ~WAS_OFF;
    off();
  }

  return rssi;
}
/*---------------------------------------------------------------------------*/
/* Returns the current CCA threshold in dBm */
static radio_value_t
get_cca_threshold(void)
{
  return (int8_t)CCACTRL0 - RSSI_OFFSET;
}
/*---------------------------------------------------------------------------*/
/* Sets the CCA threshold in dBm */
static void
set_cca_threshold(radio_value_t value)
{
  CCACTRL0 = (value + RSSI_OFFSET) & 0xFF;
}
/*---------------------------------------------------------------------------*/
/* Returns the current TX power in dBm */
static radio_value_t
get_tx_power(void)
{
  int i;
  uint8_t reg_val = TXPOWER;

  if(TXCTRL == CC2530_RF_TX_POWER_TXCTRL_MIN_VAL) {
    return OUTPUT_POWER_MIN;
  }

  /*
   * Find the TXPOWER value in the lookup table
   * If the value has been written with set_tx_power, we should be able to
   * find the exact value. However, in case the register has been written in
   * a different fashion, we return the immediately lower value of the lookup
   */
  for(i = 0; i < OUTPUT_CONFIG_COUNT; i++) {
    if(reg_val >= output_power[i].txpower_val) {
      return output_power[i].power;
    }
  }
  return OUTPUT_POWER_MIN;
}
/*---------------------------------------------------------------------------*/
/*
 * Set TX power to 'at least' power dBm
 * This works with a lookup table. If the value of 'power' does not exist in
 * the lookup table, TXPOWER will be set to the immediately higher available
 * value
 */
static void
set_tx_power(radio_value_t power)
{
  int i;

  if(power <= output_power[OUTPUT_CONFIG_COUNT - 1].power) {
    TXCTRL = CC2530_RF_TX_POWER_TXCTRL_MIN_VAL;
    TXPOWER = output_power[OUTPUT_CONFIG_COUNT - 1].txpower_val;
    return;
  }

  for(i = OUTPUT_CONFIG_COUNT - 2; i >= 0; --i) {
    if(power <= output_power[i].power) {
      /* Perhaps an earlier call set TXCTRL to 0x09. Restore */
      TXCTRL = CC2530_RF_TX_POWER_TXCTRL_DEF_VAL;
      TXPOWER = output_power[i].txpower_val;
      return;
    }
  }
}
/*---------------------------------------------------------------------------*/
static void
set_frame_filtering(uint8_t enable)
{
  if(enable) {
    FRMFILT0 |= FRMFILT0_FRAME_FILTER_EN;
  } else {
    FRMFILT0 &= ~FRMFILT0_FRAME_FILTER_EN;
  }
}
/*---------------------------------------------------------------------------*/
static void
set_auto_ack(uint8_t enable)
{
  if(enable) {
    FRMCTRL0 |= FRMCTRL0_AUTOACK;
  } else {
    FRMCTRL0 &= ~FRMCTRL0_AUTOACK;
  }
}
/*---------------------------------------------------------------------------*/
/* Netstack API radio driver functions */
/*---------------------------------------------------------------------------*/
static int
init(void)
{
  PUTSTRING("RF: Init\n");

  if(rf_flags & RF_ON) {
    return 0;
  }

#if CC2530_RF_LOW_POWER_RX
  /* Reduce RX power consumption current to 20mA at the cost of sensitivity */
  RXCTRL = 0x00;
  FSCTRL = 0x50;
#else
  RXCTRL = 0x3F;
  FSCTRL = 0x55;
#endif /* CC2530_RF_LOW_POWER_RX */

  CCACTRL0 = CC2530_RF_CCA_THRES;

  /*
   * According to the user guide, these registers must be updated from their
   * defaults for optimal performance
   *
   * Table 23-6, Sec. 23.15.1, p. 259
   */
  TXFILTCFG = 0x09; /* TX anti-aliasing filter */
  AGCCTRL1 = 0x15;  /* AGC target value */
  FSCAL1 = 0x00;    /* Reduce the VCO leakage */

  /* Auto ACKs and CRC calculation, default RX and TX modes with FIFOs */
  FRMCTRL0 = FRMCTRL0_AUTOCRC;
#if CC2530_RF_AUTOACK
  FRMCTRL0 |= FRMCTRL0_AUTOACK;
#endif

  /* Disable source address matching and autopend */
  SRCMATCH = 0; /* investigate */

  /* MAX FIFOP threshold */
  FIFOPCTRL = CC2530_RF_MAX_PACKET_LEN;

  TXPOWER = CC2530_RF_TX_POWER;

  RF_TX_LED_OFF();
  RF_RX_LED_OFF();

  rf_flags |= RF_ON;

  return 1;
}
/*---------------------------------------------------------------------------*/
static int
prepare(const void *payload, unsigned short payload_len)
{
  uint8_t i;

  PUTSTRING("RF: Prepare 0x");
  PUTHEX(payload_len + CHECKSUM_LEN);
  PUTSTRING(" bytes\n");

  /*
   * When we transmit in very quick bursts, make sure previous transmission
   * is not still in progress before re-writing to the TX FIFO
   */
  while(FSMSTAT1 & FSMSTAT1_TX_ACTIVE);

  if((rf_flags & RX_ACTIVE) == 0) {
    on();
  }

  CC2530_CSP_ISFLUSHTX();

  PUTSTRING("RF: data = ");
  /* Send the phy length byte first */
  RFD = payload_len + CHECKSUM_LEN; /* Payload plus FCS */
  for(i = 0; i < payload_len; i++) {
    RFD = ((unsigned char *)(payload))[i];
    PUTHEX(((unsigned char *)(payload))[i]);
  }
  PUTSTRING("\n");

  /* Leave space for the FCS */
  RFD = 0;
  RFD = 0;

  return 0;
}
/*---------------------------------------------------------------------------*/
static int
transmit(unsigned short transmit_len)
{
  uint8_t counter;
  int ret = RADIO_TX_ERR;
  rtimer_clock_t t0;
  transmit_len; /* hush the warning */

  if(!(rf_flags & RX_ACTIVE)) {
    t0 = RTIMER_NOW();
    on();
    rf_flags |= WAS_OFF;
    while(RTIMER_CLOCK_LT(RTIMER_NOW(), t0 + ONOFF_TIME));
  }

  if(channel_clear() == CC2530_RF_CCA_BUSY) {
    RIMESTATS_ADD(contentiondrop);
    return RADIO_TX_COLLISION;
  }

  /*
   * prepare() double checked that TX_ACTIVE is low. If SFD is high we are
   * receiving. Abort transmission and bail out with RADIO_TX_COLLISION
   */
  if(FSMSTAT1 & FSMSTAT1_SFD) {
    RIMESTATS_ADD(contentiondrop);
    return RADIO_TX_COLLISION;
  }

  /* Start the transmission */
  RF_TX_LED_ON();
  ENERGEST_OFF(ENERGEST_TYPE_LISTEN);
  ENERGEST_ON(ENERGEST_TYPE_TRANSMIT);

  CC2530_CSP_ISTXON();

  counter = 0;
  while(!(FSMSTAT1 & FSMSTAT1_TX_ACTIVE) && (counter++ < 3)) {
    clock_delay_usec(6);
  }

  if(!(FSMSTAT1 & FSMSTAT1_TX_ACTIVE)) {
    PUTSTRING("RF: TX never active.\n");
    CC2530_CSP_ISFLUSHTX();
    ret = RADIO_TX_ERR;
  } else {
    /* Wait for the transmission to finish */
    while(FSMSTAT1 & FSMSTAT1_TX_ACTIVE);
    ret = RADIO_TX_OK;
  }
  ENERGEST_OFF(ENERGEST_TYPE_TRANSMIT);
  ENERGEST_ON(ENERGEST_TYPE_LISTEN);

  if(rf_flags & WAS_OFF) {
    off();
  }

  RIMESTATS_ADD(lltx);

  RF_TX_LED_OFF();

  /* OK, sent. We are now ready to send more */
  return ret;
}
/*---------------------------------------------------------------------------*/
static int
send(const void *payload, unsigned short payload_len)
{
  prepare(payload, payload_len);
  return transmit(payload_len);
}
/*---------------------------------------------------------------------------*/
static int
read(void *buf, unsigned short bufsize)
{
  uint8_t i;
  uint8_t len;
  uint8_t crc_corr;
  int8_t rssi;

  PUTSTRING("RF: Read\n");

  /* Check the length */
  len = RFD;

  /* Check for validity */
  if(len > CC2530_RF_MAX_PACKET_LEN) {
    /* Oops, we must be out of sync. */
    PUTSTRING("RF: bad sync\n");

    RIMESTATS_ADD(badsynch);
    CC2530_CSP_ISFLUSHRX();
    return 0;
  }

  if(len <= CC2530_RF_MIN_PACKET_LEN) {
    PUTSTRING("RF: too short\n");

    RIMESTATS_ADD(tooshort);
    CC2530_CSP_ISFLUSHRX();
    return 0;
  }

  if(len - CHECKSUM_LEN > bufsize) {
    PUTSTRING("RF: too long\n");

    RIMESTATS_ADD(toolong);
    CC2530_CSP_ISFLUSHRX();
    return 0;
  }

#if CC2530_RF_CONF_HEXDUMP
  /* If we reach here, chances are the FIFO is holding a valid frame */
  io_arch_writeb(magic[0]);
  io_arch_writeb(magic[1]);
  io_arch_writeb(magic[2]);
  io_arch_writeb(magic[3]);
  io_arch_writeb(len);
#endif

  RF_RX_LED_ON();

  PUTSTRING("RF: read (0x");
  PUTHEX(len);
  PUTSTRING(" bytes) = ");
  len -= CHECKSUM_LEN;
  for(i = 0; i < len; ++i) {
    ((unsigned char *)(buf))[i] = RFD;
#if CC2530_RF_CONF_HEXDUMP
    io_arch_writeb(((unsigned char *)(buf))[i]);
#endif
    PUTHEX(((unsigned char *)(buf))[i]);
  }
  PUTSTRING("\n");

  /* Read the RSSI and CRC/Corr bytes */
  rssi = ((int8_t) RFD) - RSSI_OFFSET;
  crc_corr = RFD;

#if CC2530_RF_CONF_HEXDUMP
  io_arch_writeb(rssi);
  io_arch_writeb(crc_corr);
  io_arch_flush();
#endif

  /* MS bit CRC OK/Not OK, 7 LS Bits, Correlation value */
  if(crc_corr & CRC_BIT_MASK) {
    packetbuf_set_attr(PACKETBUF_ATTR_RSSI, rssi);
    packetbuf_set_attr(PACKETBUF_ATTR_LINK_QUALITY, crc_corr & LQI_BIT_MASK);
    RIMESTATS_ADD(llrx);
  } else {
    RIMESTATS_ADD(badcrc);
    CC2530_CSP_ISFLUSHRX();
    RF_RX_LED_OFF();
    return 0;
  }

  /* If FIFOP==1 and FIFO==0 then we had a FIFO overflow at some point. */
  if((FSMSTAT1 & (FSMSTAT1_FIFO | FSMSTAT1_FIFOP)) == FSMSTAT1_FIFOP) {
    /*
     * If we reach here means that there might be more intact packets in the
     * FIFO despite the overflow. This can happen with bursts of small packets.
     *
     * Only flush if the FIFO is actually empty. If not, then next pass we will
     * pick up one more packet or flush due to an error.
     */
    if(!RXFIFOCNT) {
      CC2530_CSP_ISFLUSHRX();
    }
  }

  RF_RX_LED_OFF();

  return (len);
}
/*---------------------------------------------------------------------------*/
static int
channel_clear(void)
{
  if(FSMSTAT1 & FSMSTAT1_CCA) {
    return CC2530_RF_CCA_CLEAR;
  }
  return CC2530_RF_CCA_BUSY;
}
/*---------------------------------------------------------------------------*/
static int
receiving_packet(void)
{
  PUTSTRING("RF: Receiving\n");

  /*
   * SFD high while transmitting and receiving.
   * TX_ACTIVE high only when transmitting
   *
   * FSMSTAT1 & (TX_ACTIVE | SFD) == SFD <=> receiving
   */
  return (FSMSTAT1 & (FSMSTAT1_TX_ACTIVE | FSMSTAT1_SFD) == FSMSTAT1_SFD);
}
/*---------------------------------------------------------------------------*/
static int
pending_packet(void)
{
  return (FSMSTAT1 & FSMSTAT1_FIFOP);
}
/*---------------------------------------------------------------------------*/
static int
on(void)
{
  if(!(rf_flags & RX_ACTIVE)) {
    CC2530_CSP_ISFLUSHRX();
    CC2530_CSP_ISRXON();

    rf_flags |= RX_ACTIVE;
  }

  ENERGEST_ON(ENERGEST_TYPE_LISTEN);
  return 1;
}
/*---------------------------------------------------------------------------*/
static int
off(void)
{
  CC2530_CSP_ISRFOFF();
  CC2530_CSP_ISFLUSHRX();

  rf_flags &= ~RX_ACTIVE;

  ENERGEST_OFF(ENERGEST_TYPE_LISTEN);
  return 1;
}
/*---------------------------------------------------------------------------*/
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_POWER_MODE:
    *value = RXENABLE == 0 ? RADIO_POWER_MODE_OFF : RADIO_POWER_MODE_ON;
    return RADIO_RESULT_OK;
  case RADIO_PARAM_CHANNEL:
    *value = (radio_value_t)get_channel();
    return RADIO_RESULT_OK;
  case RADIO_PARAM_PAN_ID:
    *value = get_pan_id();
    return RADIO_RESULT_OK;
  case RADIO_PARAM_16BIT_ADDR:
    *value = get_short_addr();
    return RADIO_RESULT_OK;
  case RADIO_PARAM_RX_MODE:
    *value = 0;
    if(FRMFILT0 & FRMFILT0_FRAME_FILTER_EN) {
      *value |= RADIO_RX_MODE_ADDRESS_FILTER;
    }
    if(FRMCTRL0 & FRMCTRL0_AUTOACK) {
      *value |= RADIO_RX_MODE_AUTOACK;
    }
    return RADIO_RESULT_OK;
  case RADIO_PARAM_TXPOWER:
    *value = get_tx_power();
    return RADIO_RESULT_OK;
  case RADIO_PARAM_CCA_THRESHOLD:
    *value = get_cca_threshold();
    return RADIO_RESULT_OK;
  case RADIO_PARAM_RSSI:
    *value = get_rssi();
    return RADIO_RESULT_OK;
  case RADIO_CONST_CHANNEL_MIN:
    *value = CC2530_RF_CHANNEL_MIN;
    return RADIO_RESULT_OK;
  case RADIO_CONST_CHANNEL_MAX:
    *value = CC2530_RF_CHANNEL_MAX;
    return RADIO_RESULT_OK;
  case RADIO_CONST_TXPOWER_MIN:
    *value = OUTPUT_POWER_MIN;
    return RADIO_RESULT_OK;
  case RADIO_CONST_TXPOWER_MAX:
    *value = OUTPUT_POWER_MAX;
    return RADIO_RESULT_OK;
  default:
    return RADIO_RESULT_NOT_SUPPORTED;
  }
}
/*---------------------------------------------------------------------------*/
static radio_result_t
set_value(radio_param_t param, radio_value_t value)
{
  switch(param) {
  case RADIO_PARAM_POWER_MODE:
    if(value == RADIO_POWER_MODE_ON) {
      on();
      return RADIO_RESULT_OK;
    }
    if(value == RADIO_POWER_MODE_OFF) {
      off();
      return RADIO_RESULT_OK;
    }
    return RADIO_RESULT_INVALID_VALUE;
  case RADIO_PARAM_CHANNEL:
    if(value < CC2530_RF_CHANNEL_MIN || value > CC2530_RF_CHANNEL_MAX) {
      return RADIO_RESULT_INVALID_VALUE;
    }
    if(set_channel(value) == CC2530_RF_CHANNEL_SET_ERROR) {
      return RADIO_RESULT_ERROR;
    }
    return RADIO_RESULT_OK;
  case RADIO_PARAM_PAN_ID:
    set_pan_id(value & 0xffff);
    return RADIO_RESULT_OK;
  case RADIO_PARAM_16BIT_ADDR:
    set_short_addr(value & 0xffff);
    return RADIO_RESULT_OK;
  case RADIO_PARAM_RX_MODE:
    if(value & ~(RADIO_RX_MODE_ADDRESS_FILTER |
                 RADIO_RX_MODE_AUTOACK)) {
      return RADIO_RESULT_INVALID_VALUE;
    }

    set_frame_filtering((value & RADIO_RX_MODE_ADDRESS_FILTER) != 0);
    set_auto_ack((value & RADIO_RX_MODE_AUTOACK) != 0);

    return RADIO_RESULT_OK;
  case RADIO_PARAM_TXPOWER:
    if(value < OUTPUT_POWER_MIN || value > OUTPUT_POWER_MAX) {
      return RADIO_RESULT_INVALID_VALUE;
    }

    set_tx_power(value);
    return RADIO_RESULT_OK;
  case RADIO_PARAM_CCA_THRESHOLD:
    set_cca_threshold(value);
    return RADIO_RESULT_OK;
  default:
    return RADIO_RESULT_NOT_SUPPORTED;
  }
}
/*---------------------------------------------------------------------------*/
static radio_result_t
get_object(radio_param_t param, void *dest, size_t size)
{
  uint8_t *target;
  int i;

  if(param == RADIO_PARAM_64BIT_ADDR) {
    if(size != 8 || !dest) {
      return RADIO_RESULT_INVALID_VALUE;
    }

    target = dest;
    for(i = 0; i < 8; i++) {
      target[i] = ((uint8_t *)&EXT_ADDR0)[7 - i] & 0xFF;
    }

    return RADIO_RESULT_OK;
  }
  return RADIO_RESULT_NOT_SUPPORTED;
}
/*---------------------------------------------------------------------------*/
static radio_result_t
set_object(radio_param_t param, const void *src, size_t size)
{
  int i;

  if(param == RADIO_PARAM_64BIT_ADDR) {
    if(size != 8 || !src) {
      return RADIO_RESULT_INVALID_VALUE;
    }

    for(i = 0; i < 8; i++) {
      ((uint8_t *)&EXT_ADDR0)[i] = ((uint8_t *)src)[7 - i];
    }

    return RADIO_RESULT_OK;
  }
  return RADIO_RESULT_NOT_SUPPORTED;
}
/*---------------------------------------------------------------------------*/
const struct radio_driver cc2530_rf_driver = {
  init,
  prepare,
  transmit,
  send,
  read,
  channel_clear,
  receiving_packet,
  pending_packet,
  on,
  off,
  get_value,
  set_value,
  get_object,
  set_object
};
/*---------------------------------------------------------------------------*/