/*
 * avr.c
 *
 * AVR functions
 *
 * Copyright (C) 2006 Mihai Georgian <u-boot@linuxnotincluded.org.uk>
 *
 * SPDX-License-Identifier:	GPL-2.0+
 */
#include <common.h>
#include <ns16550.h>
#include <stdio_dev.h>

/* Button codes from the AVR */
#define PWRR			0x20		/* Power button release	*/
#define PWRP			0x21		/* Power button push	*/
#define RESR			0x22		/* Reset button release	*/
#define RESP			0x23		/* Reset button push	*/
#define AVRINIT			0x33		/* Init complete	*/
#define AVRRESET		0x31		/* Reset request	*/

/* LED commands */
#define PWRBLINKSTRT		'['		/* Blink power LED	*/
#define PWRBLINKSTOP		'Z'		/* Solid power LED	*/
#define HDDLEDON		'W'		/* HDD LED on		*/
#define HDDLEDOFF		'V'		/* HDD LED off		*/
#define HDDBLINKSTRT		'Y'		/* HDD LED start blink	*/
#define HDDBLINKSTOP		'X'		/* HDD LED stop blink	*/

/* Timings for LEDs blinking to show choice */
#define PULSETIME		250		/* msecs		*/
#define LONGPAUSE		(5 * PULSETIME)

/* Button press times */
#define PUSHHOLD		1000		/* msecs		*/
#define NOBUTTON		(6 * (LONGPAUSE+PULSETIME))

/* Boot and console choices */
#define MAX_BOOT_CHOICE		3

static char *consoles[] = {
	"serial",
#if defined(CONFIG_NETCONSOLE)
	"nc",
#endif
};
#define MAX_CONS_CHOICE		(sizeof(consoles)/sizeof(char *))

#if !defined(CONFIG_NETCONSOLE)
#define DEF_CONS_CHOICE		0
#else
#define DEF_CONS_CHOICE		1
#endif

#define perror(fmt, args...) printf("%s: " fmt, __FUNCTION__ , ##args)

extern void miconCntl_SendCmd(unsigned char dat);
extern void miconCntl_DisWDT(void);

static int boot_stop;

static int boot_choice = 1;
static int cons_choice = DEF_CONS_CHOICE;

static char envbuffer[16];

void init_AVR_DUART (void)
{
	NS16550_t AVR_port = (NS16550_t) CONFIG_SYS_NS16550_COM2;
	int clock_divisor = CONFIG_SYS_NS16550_CLK / 16 / 9600;

	/*
	 * AVR port init sequence taken from
	 * the original Linkstation init code
	 * Normal U-Boot serial reinit doesn't
	 * work because the AVR uses even parity
	 */
	AVR_port->lcr = 0x00;
	AVR_port->ier = 0x00;
	AVR_port->lcr = UART_LCR_BKSE;
	AVR_port->dll = clock_divisor & 0xff;
	AVR_port->dlm = (clock_divisor >> 8) & 0xff;
	AVR_port->lcr = UART_LCR_WLS_8 | UART_LCR_PEN | UART_LCR_EPS;
	AVR_port->mcr = 0x00;
	AVR_port->fcr = UART_FCR_FIFO_EN | UART_FCR_RXSR | UART_FCR_TXSR;

	miconCntl_DisWDT();

	boot_stop = 0;
	miconCntl_SendCmd(PWRBLINKSTRT);
}

static inline int avr_tstc(void)
{
	return (NS16550_tstc((NS16550_t)CONFIG_SYS_NS16550_COM2));
}

static inline char avr_getc(void)
{
	return (NS16550_getc((NS16550_t)CONFIG_SYS_NS16550_COM2));
}

static int push_timeout(char button_code)
{
	ulong push_start = get_timer(0);
	while (get_timer(push_start) <= PUSHHOLD)
		if (avr_tstc() && avr_getc() == button_code)
			return 0;
	return 1;
}

static void next_boot_choice(void)
{
	ulong return_start;
	ulong pulse_start;
	int on_times;
	int button_on;
	int led_state;
	char c;

	button_on = 0;
	return_start = get_timer(0);

	on_times = boot_choice;
	led_state = 0;
	miconCntl_SendCmd(HDDLEDOFF);
	pulse_start = get_timer(0);

	while (get_timer(return_start) <= NOBUTTON || button_on) {
		if (avr_tstc()) {
			c = avr_getc();
			if (c == PWRP)
				button_on = 1;
			else if (c == PWRR) {
				button_on = 0;
				return_start = get_timer(0);
				if (++boot_choice > MAX_BOOT_CHOICE)
					boot_choice = 1;
				sprintf(envbuffer, "bootcmd%d", boot_choice);
				if (getenv(envbuffer)) {
					sprintf(envbuffer, "run bootcmd%d", boot_choice);
					setenv("bootcmd", envbuffer);
				}
				on_times = boot_choice;
				led_state = 1;
				miconCntl_SendCmd(HDDLEDON);
				pulse_start = get_timer(0);
			} else {
				perror("Unexpected code: 0x%02X\n", c);
			}
		}
		if (on_times && get_timer(pulse_start) > PULSETIME) {
			if (led_state == 1) {
				--on_times;
				led_state = 0;
				miconCntl_SendCmd(HDDLEDOFF);
			} else {
				led_state = 1;
				miconCntl_SendCmd(HDDLEDON);
			}
			pulse_start = get_timer(0);
		}
		if (!on_times && get_timer(pulse_start) > LONGPAUSE) {
			on_times = boot_choice;
			led_state = 1;
			miconCntl_SendCmd(HDDLEDON);
			pulse_start = get_timer(0);
		}
	}
	if (led_state)
		miconCntl_SendCmd(HDDLEDOFF);
}

void next_cons_choice(int console)
{
	ulong return_start;
	ulong pulse_start;
	int on_times;
	int button_on;
	int led_state;
	char c;

	button_on = 0;
	cons_choice = console;
	return_start = get_timer(0);

	on_times = cons_choice+1;
	led_state = 1;
	miconCntl_SendCmd(HDDLEDON);
	pulse_start = get_timer(0);

	while (get_timer(return_start) <= NOBUTTON || button_on) {
		if (avr_tstc()) {
			c = avr_getc();
			if (c == RESP)
				button_on = 1;
			else if (c == RESR) {
				button_on = 0;
				return_start = get_timer(0);
				cons_choice = (cons_choice + 1) % MAX_CONS_CHOICE;
				console_assign(stdin, consoles[cons_choice]);
				console_assign(stdout, consoles[cons_choice]);
				console_assign(stderr, consoles[cons_choice]);
				on_times = cons_choice+1;
				led_state = 0;
				miconCntl_SendCmd(HDDLEDOFF);
				pulse_start = get_timer(0);
			} else {
				perror("Unexpected code: 0x%02X\n", c);
			}
		}
		if (on_times && get_timer(pulse_start) > PULSETIME) {
			if (led_state == 0) {
				--on_times;
				led_state = 1;
				miconCntl_SendCmd(HDDLEDON);
			} else {
				led_state = 0;
				miconCntl_SendCmd(HDDLEDOFF);
			}
			pulse_start = get_timer(0);
		}
		if (!on_times && get_timer(pulse_start) > LONGPAUSE) {
			on_times = cons_choice+1;
			led_state = 0;
			miconCntl_SendCmd(HDDLEDOFF);
			pulse_start = get_timer(0);
		}
	}
	if (led_state);
	miconCntl_SendCmd(HDDLEDOFF);
}

int avr_input(void)
{
	char avr_button;

	if (!avr_tstc())
		return 0;

	avr_button = avr_getc();
	switch (avr_button) {
	case PWRP:
		if (push_timeout(PWRR)) {
			/* Timeout before power button release */
			boot_stop = ~boot_stop;
			if (boot_stop)
				miconCntl_SendCmd(PWRBLINKSTOP);
			else
				miconCntl_SendCmd(PWRBLINKSTRT);
			/* Wait for power button release */
			while (avr_getc() != PWRR)
				;
		} else
			/* Power button released */
			next_boot_choice();
		break;
	case RESP:
		/* Wait for Reset button release */
		while (avr_getc() != RESR)
			;
		next_cons_choice(cons_choice);
		break;
	case AVRINIT:
		return 0;
	default:
		perror("Unexpected code: 0x%02X\n", avr_button);
		return 0;
	}
	if (boot_stop)
		return (-3);
	else
		return (-2);
}

void avr_StopBoot(void)
{
	boot_stop = ~0;
	miconCntl_SendCmd(PWRBLINKSTOP);
}
