/*
 * fw/image.c - Image sweep
 *
 * Written 2012 by Werner Almesberger
 * Copyright 2012 Werner Almesberger
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 */


#include <stdbool.h>
#include <stdint.h>

#include <avr/io.h>
#include <avr/interrupt.h>

#include "image.h"
#include "sweep.h"


volatile bool sweeping = 0;


static volatile uint32_t t_up;	/* uptime, in timer ticks (wraps in 4295 s) */
static volatile uint32_t t_sw;	/* cumulative number of timer ticks in sweep */

static uint16_t wait_periods;	/* number of periods to wait before image */
static uint16_t wait_period;	/* ticks in wait period */
static uint16_t wait_short;	/* ticks to wait after periods */

static uint16_t pixel_ticks;	/* number of ticks per pixel */
static const struct line *curr_line;
static const struct line *end_line;
static bool forward;


ISR(TIMER1_OVF_vect)
{
	uint16_t t;

	/* update the time counters */

	t = ICR1;
	t_sw += t;
	t_up += t;

	/* if at the end of the image, only update the time */

	if (curr_line == end_line) {
		PORTB &= 0x3f;
		PORTC = 0;
		PORTD = 0;
		ICR1 = 0xffff;
		sweeping = 0;
		return;
	}

	/* wait before starting the image */

	if (wait_periods) {
		if (!--wait_periods)
			ICR1 = wait_short;
		return;
	}

	/* if done, lights out and slow down */

	/* output the next line */

	PORTB = (PORTB & 0x3f) | (curr_line->cb & 0xc0);
	PORTC = curr_line->cb;
	PORTD = curr_line->d;

	/* move to the next line */

	if (forward)
		curr_line++;
	else
		curr_line--;

	/* wait until the next pixel */

	ICR1 = pixel_ticks;
}


uint32_t uptime_irq(void)
{
	uint32_t t;

	t = t_up+TCNT1;
	if (TIFR1 & TOV1)
		t += ICR1;
	return t;
}


uint32_t uptime(void)
{
	uint32_t a, b;
	uint16_t d;

	do {
		cli();
		a = t_up;
		d = TCNT1;
		cli();
		b = t_up;
		sei();
	}
	while (a != b);
	return a+d;
}


void sweep_image(const struct sweep *sweep)
{
	uint32_t t;

	/* calculate start time */

	t = uptime();
	if (sweep->start_ticks <= t)
		return;
	t = sweep->start_ticks-t;

	TCCR1B = 0;	/* stop the timer */

	cli();

	/* shut down all the LEDs, in case we're still in a sweep */

	PORTB &= 0x3f;
	PORTC = 0;
	PORTD = 0;

	/* image pointers and sweep direction */

	forward = sweep->forward;
	if (forward) {
		curr_line = image+sweep->left;
		end_line = image+sweep->right+1;
	} else {
		curr_line = image+sweep->right;
		end_line = image+sweep->left-1;
	}

	/* timing parameters */

	pixel_ticks = sweep->pixel_ticks;
	wait_periods = t >> 16;
	if (wait_periods) {
	 	wait_short = t;
		wait_period = 0xffff;

		/*
		 * Make sure we have plenty of time to update ICR1 after an
		 * interrupt.
		 */
		if (wait_short < 256) {
			wait_period -= 128;
			wait_short += wait_periods << 7;
		}
		ICR1 = wait_period;
	} else {
	 	ICR1 = t;
	}

	/* prepare the hardware timer */

	t_sw = 0;		/* reset the time */
	TCNT1 = 0;
	TIFR1 = 1 << TOV1;	/* clear interrupt */
	
	/* start the timer */

	TCCR1B =
	    1 << WGM13 |	/* WG Mode 14, continued */
	    1 << WGM12 |
	    1 << CS11;		/* clkIO/8 */

	sweeping = 1;

	sei();
}


void sweep_init(void)
{
	TCCR1A =
	    1 << WGM11;		/* WG Mode 14 (Fast PWM to ICR1) */

	TCNT1 = 0;

	TIMSK1 = 1 << TOIE1;	/* enable timer interrupts */
}