/*
 * labsw.c - Lab Switch initialization and main loop
 *
 * Written 2011 by Werner Almesberger
 * Copyright 2011 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 <stdint.h>

#include "regs.h"
#include "usb.h"

#include "config.h"
#include "io.h"
#include "labsw/ep0.h"


int local = 1;


/*
 * SDCC complains a lot about code that's unreachable due to constant
 * conditional expressions.
 */

#pragma disable_warning 126	/* unreachable code */


/* ----- Button debouncing ------------------------------------------------- */


/* In the version of 2011-09-11, 10000 cycles are about 128 ms */

#define	PREBOUNCE_CYCLES	2000
#define	POSTBOUNCE_CYCLES	5000


#define	TURN(ch, on)			\
	do {				\
		CH##ch##_RELAY = on;	\
		CH##ch##_OPT = !on;	\
		if (on)			\
			LED(CH##ch, R);	\
		else			\
			LED(CH##ch, G);	\
	} while (0)


#define	DEBOUNCE(BUT, but)					\
	do {							\
		last_##but = but_##but;				\
		if (postbounce_##but) {				\
			postbounce_##but--;			\
			break;					\
		}						\
		if (but_##but == !BUT_##BUT) {			\
			prebounce_##but = 0;			\
			break;					\
		}						\
		if (prebounce_##but != PREBOUNCE_CYCLES) {	\
			prebounce_##but++;			\
			break;					\
		}						\
		prebounce_##but = 0;				\
		postbounce_##but = POSTBOUNCE_CYCLES;		\
		but_##but = !but_##but;				\
	} while (0)


#define	PRESSED(but)	(but_##but && !last_##but)
#define	RELEASED(but)	(!but_##but && last_##but)


/* ----- LED and channel control ------------------------------------------- */


#define	IS_ON(ch)	(CH##ch##_RELAY || !CH##ch##_OPT)

#define	LED_R_COLOR_R	1
#define	LED_R_COLOR_G	0
#define	LED_R_COLOR_OFF	0
#define	LED_G_COLOR_R	0
#define	LED_G_COLOR_G	1
#define	LED_G_COLOR_OFF	0


#define	LED(ch, color)					\
	do {						\
		LED_##ch##_R = LED_R_COLOR_##color;	\
		LED_##ch##_G = LED_G_COLOR_##color;	\
	} while (0)


/* ----- I/O setup --------------------------------------------------------- */


static void init_io(void)
{
	P0SKIP = 0xff;
	P1SKIP = 0xff;
	P2SKIP = 0xff;

	/* @@@ we need this while using the boot loader of cntr */
	P0MDOUT = 0;
	P1MDOUT = 0;
	P2MDOUT = 0;

	LED_MAIN_R_MODE |= 1 << LED_MAIN_R_BIT;
	LED_MAIN_G_MODE |= 1 << LED_MAIN_G_BIT;
	LED_CH1_R_MODE |= 1 << LED_CH1_R_BIT;
	LED_CH1_G_MODE |= 1 << LED_CH1_G_BIT;
	LED_CH2_R_MODE |= 1 << LED_CH2_R_BIT;
	LED_CH2_G_MODE |= 1 << LED_CH2_G_BIT;

	CH1_RELAY = 0;
	CH2_RELAY = 0;
	CH1_RELAY_MODE |= 1 << CH1_RELAY_BIT;
	CH2_RELAY_MODE |= 1 << CH2_RELAY_BIT;
}


/* ----- Main loop --------------------------------------------------------- */


void main(void)
{
	int prebounce_main = 0, postbounce_main = 0;
	int last_main = 0, but_main = 0;
	int prebounce_ch1 = 0, postbounce_ch1 = 0, last_ch1 = 0, but_ch1 = 0;
	int prebounce_ch2 = 0, postbounce_ch2 = 0, last_ch2 = 0, but_ch2 = 0;
	init_io();

	usb_init();
	ep0_init();

	LED(MAIN, G);
	TURN(1, 0);
	TURN(2, 0);

	while (1) {
		DEBOUNCE(MAIN, main);
		DEBOUNCE(CH1, ch1);
		DEBOUNCE(CH2, ch2);

		/*
		 * Pressing MAIN forces local mode. Pressing it in local mode,
		 * it turns off both front channels.
		 */
		if (PRESSED(main)) {
			if (local) {
				TURN(1, 0);
				TURN(2, 0);
			}
			local = 1;
		}

		/*
		 * Update LEDs in local mode.
		 */
		if (local) {
			LED(MAIN, G);
			if (IS_ON(1))
				TURN(1, 1);
			else
				TURN(1, 0);
			if (IS_ON(2))
				TURN(2, 1);
			else
				TURN(2, 0);
		}

		/*
		 * In local mode, buttons CH1 and CH2 toggle the respective
		 * channel.
		 */
		if (local) {
			if (PRESSED(ch1))
				TURN(1, !IS_ON(1));
			if (PRESSED(ch2))
				TURN(2, !IS_ON(2));
		}
		
		usb_poll();
	}
}