/* 
 * 2013 by Fabian Vogt
 */

#include <stdint.h>
#include "interrupt.h"

#define NSPIRE_GPIO_SECTION_SIZE 0x40
#define NSPIRE_GPIO_DIRECTION_OFFSET 		0x10
#define NSPIRE_GPIO_OUTPUT_OFFSET 		0x14
#define NSPIRE_GPIO_BIT(x) (x&7)
#define NSPIRE_GPIO_SECTION_OFFSET(x) (((x>>3)&3)*NSPIRE_GPIO_SECTION_SIZE)
#define NSPIRE_GPIO(x, t) (*(uint32_t*)(0x90000000 + \
		NSPIRE_GPIO_SECTION_OFFSET(x) + NSPIRE_GPIO_##t##_OFFSET))
		
static inline void gpio_dir_out(uint8_t pin, uint8_t value)
{
	uint32_t val = NSPIRE_GPIO(pin, OUTPUT);
	if(value)
		val |= 1<<NSPIRE_GPIO_BIT(pin);
	else
		val &= ~(1<<NSPIRE_GPIO_BIT(pin));

	NSPIRE_GPIO(pin, OUTPUT) = val;
	val = NSPIRE_GPIO(pin, DIRECTION);
	val &= ~(1<<NSPIRE_GPIO_BIT(pin));
	NSPIRE_GPIO(pin, DIRECTION) = val;
}

#define TIMER_CONTROL_ONESHOT (1<<0)
#define TIMER_CONTROL_32BIT (1<<1)
#define TIMER_CONTROL_DIV16 (0b01<<2)
#define TIMER_CONTROL_DIV256 (0b10<<2)
#define TIMER_CONTROL_INTEN (1<<5)
#define TIMER_CONTROL_MODE_PERIODIC (1<<6)
#define TIMER_CONTROL_ENABLE (1<<7)

#define TIMER_LOAD 0x0
#define TIMER_CONTROL 0x8
#define TIMER_IRQ_CLEAR 0xC
#define TIMER_BG_LOAD 0x18

#define SECOND_TIMER_IRQ 19
#define FIRST_TIMER_IRQ 18
#define FAST_TIMER_IRQ 17

#define FAST_TIMER_1 0x90010000
#define FAST_TIMER_2 0x90010020
#define FIRST_TIMER 0x900C0000
#define SECOND_TIMER 0x900d0000

#define FAST_TIMER_FREQ 33000000

#define GPIO 0x90000000

#define REG(base, x) (*(uint32_t*)(base + x))

static void setup_timer(uint32_t timer, uint32_t control, uint32_t load)
{
	REG(timer, TIMER_CONTROL) = 0;
	REG(timer, TIMER_IRQ_CLEAR) = 1;
	
	if(control & TIMER_CONTROL_MODE_PERIODIC)
		REG(timer, TIMER_BG_LOAD) = load;
	else
		REG(timer, TIMER_LOAD) = load;
	
	REG(timer, TIMER_CONTROL) = control;
}

static uint32_t fast_timer_control;
static uint32_t second_timer_control;
static uint32_t fast_timer_load;
static uint32_t second_timer_load;
static uint32_t gpio_save_dir;
static uint32_t gpio_save_out;
extern uint32_t* buffer;
extern uint32_t buffer_size;

extern void do_pwm();

static uint8_t setup = 0;

void play(uint32_t* abuffer, uint32_t abuffer_size, uint32_t samplerate)
{
	if(!setup)
	{
		/* Save GPIO state */
		gpio_save_out = REG(GPIO, 0x14);
		gpio_save_dir = REG(GPIO, 0x10);
		/* As output (and low) */
		gpio_dir_out(4, 0);
		
		/* Register FIQ handler */
		init_interrupts(do_pwm, 0);
		
		/* Save timer state */
		second_timer_control = REG(FAST_TIMER_2, TIMER_CONTROL);
		fast_timer_control = REG(FAST_TIMER_1, TIMER_CONTROL);
		second_timer_load = REG(FAST_TIMER_2, TIMER_LOAD);
		fast_timer_load = REG(FAST_TIMER_1, TIMER_LOAD);
		
		/* Setup pwm */
		setup_timer(FAST_TIMER_1, TIMER_CONTROL_ENABLE | TIMER_CONTROL_INTEN | TIMER_CONTROL_ONESHOT | TIMER_CONTROL_MODE_PERIODIC, 1);
		
		/* Set fast timer as FIQ source */
		set_as_fiq(FAST_TIMER_IRQ);
		enable_irq(FAST_TIMER_IRQ);
		
		setup = 1;
	}
	
	buffer = abuffer;
	buffer_size = abuffer_size;
	
	setup_timer(FAST_TIMER_2, TIMER_CONTROL_ENABLE | TIMER_CONTROL_INTEN | TIMER_CONTROL_32BIT | TIMER_CONTROL_MODE_PERIODIC, FAST_TIMER_FREQ / samplerate);
}

void stop()
{
	if(setup)
	{
		deactivate_ints();
		
		REG(GPIO, 0x14) = gpio_save_out;
		REG(GPIO, 0x10) = gpio_save_dir;

		REG(FAST_TIMER_2, TIMER_LOAD) = second_timer_load;
		REG(FAST_TIMER_1, TIMER_LOAD) = fast_timer_load;

		REG(FAST_TIMER_2, TIMER_CONTROL) = second_timer_control & ~TIMER_CONTROL_ENABLE;
		REG(FAST_TIMER_1, TIMER_CONTROL) = fast_timer_control & ~TIMER_CONTROL_ENABLE;

		REG(FAST_TIMER_2, TIMER_IRQ_CLEAR) = 1;
		REG(FAST_TIMER_1, TIMER_IRQ_CLEAR) = 1;

		/* This enables interrupts again */
		unregister();

		REG(FAST_TIMER_2, TIMER_CONTROL) = second_timer_control;
		REG(FAST_TIMER_1, TIMER_CONTROL) = fast_timer_control;
		
		setup = 0;
	}
}