/*
 * cc110x.c
 *
 *  Created on: Jul 20, 2012
 *      Author: tkl
 */

#include <stddef.h>
#include <stdbool.h>
#include "board.h"
#include "stack.h"
#include "queue.h"
#include "thread.h"
#include "schedule.h"
#include "irq.h"
#include "sys_tick.h"
#include "cc110x.h"

static volatile bool tx_condition;
extern volatile struct thread_context *current_thread;

static void it_cb(const struct cc110x *cc110x, enum cc110x_it_type it_type);

int cc110x_open(const struct cc110x *cc110x)
{
	if(NULL == cc110x)
		return (-1);

	tx_condition = false;
	cc110x->fp->init(cc110x);
	cc110x->fp->set_it_callback(cc110x->arch_dep_device, it_cb, cc110x);
	cc110x_set_radio_mode(cc110x, CC110X_RADIO_MODE_PWD);
	cc110x_write_pa_table(cc110x);
	return (0);
}

int cc110x_close(const struct cc110x *cc110x)
{
	cc110x_set_radio_mode(cc110x, CC110X_RADIO_MODE_PWD);
	blocking_read_wakeup(cc110x);
	return 0;
}

void cc110x_set_radio_mode(const struct cc110x *cc110x,
		enum cc110x_radio_mode mode)
{
	if(NULL == cc110x) {
		return;
	}
	*(cc110x->radio_mode) = mode;
	enum cc110x_strobe_cmd cmd = STROBE_SIDLE;
	switch(mode) {
	case CC110X_RADIO_MODE_NONE:	return;
	case CC110X_RADIO_MODE_IDLE:	cmd = STROBE_SIDLE;	break;
	case CC110X_RADIO_MODE_RX:		cmd = STROBE_SRX;	break;
	case CC110X_RADIO_MODE_TX:		cmd = STROBE_STX;	break;
	case CC110X_RADIO_MODE_PWD:		cmd = STROBE_SPWD;	break;
	}
	cc110x->fp->strobe(cc110x->arch_dep_device, cmd);
}

enum cc110x_radio_mode cc110x_get_radio_mode(const struct cc110x *cc110x)
{
	if(NULL == cc110x)
		return (CC110X_RADIO_MODE_NONE);
	return (*(cc110x->radio_mode));
}

void cc110x_write_pa_table(const struct cc110x *cc110x)
{
	if(NULL == cc110x)
		return;
	cc110x->fp->write_pa_table(cc110x->arch_dep_device);
}

void cc110x_strobe(const  struct cc110x *cc110x, enum cc110x_strobe_cmd cmd)
{
	if(NULL == cc110x)
		return;
	cc110x->fp->strobe(cc110x->arch_dep_device, cmd);
}

int cc110x_write(const struct cc110x *cc110x, const char *buffer,
		int size)
{
	enum cc110x_radio_mode mode;
	int ret;
	if((NULL == cc110x) || (NULL == buffer))
		return (-1);
	mode = cc110x_get_radio_mode(cc110x);
	cc110x_set_radio_mode(cc110x, CC110X_RADIO_MODE_IDLE);
	cc110x_strobe(cc110x, STROBE_SFTX);
	ret = cc110x->fp->write(cc110x->arch_dep_device, buffer, size);
	tx_condition = true;
	cc110x_set_radio_mode(cc110x, CC110X_RADIO_MODE_TX);
	while(tx_condition);
	cc110x_set_radio_mode(cc110x, CC110X_RADIO_MODE_IDLE);
	cc110x_strobe(cc110x, STROBE_SFTX);
	cc110x_set_radio_mode(cc110x, mode);
	return (ret);
}

int cc110x_read(const struct cc110x *cc110x, char *buffer, int size)
{
	int ret;
	if((NULL == cc110x) || (NULL == buffer))
		return (-1);
	if(0 == cc110x_get_rx_count(cc110x)) {
		unsigned int irq = disable_irq();
		current_thread->status = THREAD_STATUS_BLOCKING;
		current_thread->wakeup_blocking_source = (void*) cc110x;
		restore_irq(irq);
		schedule();
	}
	enum cc110x_radio_mode mode = cc110x_get_radio_mode(cc110x);
	cc110x_set_radio_mode(cc110x, CC110X_RADIO_MODE_IDLE);
	ret = cc110x_get_rx_count(cc110x);
	ret = cc110x->fp->read(cc110x->arch_dep_device, buffer, ret);
	cc110x_strobe(cc110x, STROBE_SFRX);
	cc110x_set_radio_mode(cc110x, mode);
	return (ret);
}

int cc110x_get_rx_count(const struct cc110x *cc110x)
{
	if(NULL == cc110x)
		return (-1);
	return (cc110x->fp->get_rx_count(cc110x->arch_dep_device));
}

static void it_cb(const struct cc110x *cc110x,
		enum cc110x_it_type it_type)
{
	if(NULL == cc110x)
		return;
	switch(it_type) {
	case IT_TYPE_NONE:
		break;
	case IT_TYPE_RX_COMPLETE:
		blocking_read_wakeup(cc110x);
		break;
	case IT_TYPE_TX_COMPLETE:
		tx_condition = false;
		break;
	}
}
