/**
 * main.c
 *
 * Main application entry point for Bigscreen Beyond application.
 * 
 * Copyright (c) 2022 Bigscreen, Inc.
 */

#include <string.h>
#include <asf.h>
#include <stdbool.h>
#include "flash_mutex.h"
#include "conf_board.h"

#define TASK_USBLOAD_STACK_SIZE				(1024/sizeof(portSTACK_TYPE))
#define TASK_USBLOAD_PRIORITY				(configMAX_PRIORITIES - 1)

extern void vApplicationStackOverflowHook(xTaskHandle *pxTask,
		signed char *pcTaskName);
extern void vApplicationIdleHook(void);
extern void vApplicationTickHook(void);
extern void vApplicationMallocFailedHook(void);
extern void xPortSysTickHandler(void);

void main_audio_callback(void);

TaskHandle_t xUSBLoadTaskHandle = NULL;

UBaseType_t task_watermarks[16];

I2C_Handle hi2c_1, hi2c_4;

// Pin change interrupt for HPD
static void hpd_irq(uint32_t source_id, uint32_t source_mask);
// Pin change interrupt for GPIO4 and GPIO7 from VXR7200
static void vxr_gpio_irq(uint32_t source_id, uint32_t source_mask);

/**
 * \brief Called if stack overflow during execution
 */
extern void vApplicationStackOverflowHook(xTaskHandle *pxTask,
		signed char *pcTaskName)
{
	printf("stack overflow %x %s\r\n", (unsigned int)pxTask, (portCHAR *)pcTaskName);
	/* If the parameters have been corrupted then inspect pxCurrentTCB to
	 * identify which task has overflowed its stack.
	 */
	
	crashdump_set_crash_reason(CRASH_STACK_OVERFLOW);
	crashdump_set_crashed_task(pxTask, pcTaskName);
	crashdump_set_irqn(__get_IPSR());
	crashdump_trigger();
	
	// Shouldn't get here.
	for (;;) {
	}
}

/**
 * \brief This function is called by FreeRTOS idle task
 */
extern void vApplicationIdleHook(void)
{
	static TickType_t previous_tick = 0;
	TickType_t current_tick = xTaskGetTickCount();
	
	if((current_tick - previous_tick) >= 100) {
		previous_tick = current_tick;
		task_watermarks[taskid_USBLoad] = uxTaskGetStackHighWaterMark(xUSBLoadTaskHandle);
		task_watermarks[taskid_HID_Command] = uxTaskGetStackHighWaterMark(xTaskGetHandle("HID Command"));
		task_watermarks[taskid_HID_Report] = uxTaskGetStackHighWaterMark(xTaskGetHandle("HID Report"));
		task_watermarks[taskid_I2C] = uxTaskGetStackHighWaterMark(xTaskGetHandle("I2C"));
		task_watermarks[taskid_TIMED_START] = uxTaskGetStackHighWaterMark(xTaskGetHandle("TIMED_START"));
		task_watermarks[taskid_ADC] = uxTaskGetStackHighWaterMark(xTaskGetHandle("ADC"));
		task_watermarks[taskid_LED] = uxTaskGetStackHighWaterMark(xTaskGetHandle("LED"));
		task_watermarks[taskid_FAN] = uxTaskGetStackHighWaterMark(xTaskGetHandle("FAN"));
		task_watermarks[taskid_video_proc] = uxTaskGetStackHighWaterMark(xTaskGetHandle("video_proc"));
		task_watermarks[taskid_USART] = uxTaskGetStackHighWaterMark(xTaskGetHandle("USART"));
		task_watermarks[taskid_IDLE] = uxTaskGetStackHighWaterMark(xTaskGetHandle("IDLE"));
		task_watermarks[taskid_Tmr_Svc] = uxTaskGetStackHighWaterMark(xTaskGetHandle("Tmr Svc"));
	}
}

/**
 * \brief This function is called by FreeRTOS each tick
 */
extern void vApplicationTickHook(void)
{

}

extern void vApplicationMallocFailedHook(void)
{
	/* Called if a call to pvPortMalloc() fails because there is insufficient
	free memory available in the FreeRTOS heap.  pvPortMalloc() is called
	internally by FreeRTOS API functions that create tasks, queues, software
	timers, and semaphores.  The size of the FreeRTOS heap is set by the
	configTOTAL_HEAP_SIZE configuration constant in FreeRTOSConfig.h. */

	/* Force an assert. */
	configASSERT( ( volatile void * ) NULL );
}

/**
 * \brief Configure the console UART.
 */
static void configure_console(void)
{
	const usart_serial_options_t uart_serial_options = {
		.baudrate = CONF_UART_BAUDRATE,
#if (defined CONF_UART_CHAR_LENGTH)
		.charlength = CONF_UART_CHAR_LENGTH,
#endif
		.paritytype = CONF_UART_PARITY,
#if (defined CONF_UART_STOP_BITS)
		.stopbits = CONF_UART_STOP_BITS,
#endif
	};
#if defined (CONF_BOARD_UART_CONSOLE)
	/* Configure console UART. */
	stdio_serial_init(CONF_UART, &uart_serial_options);
#endif
	/* Specify that stdout should not be buffered. */
#if defined(__GNUC__)
	setbuf(stdout, NULL);
#else
	/* Already the case in IAR's Normal DLIB default configuration: printf()
	 * emits one character at a time.
	 */
#endif
}

/**
 *  \brief FreeRTOS Real Time Kernel example entry point.
 *
 *  \return Unused (ANSI-C compatibility).
 */
int main(void)
{
	
	// Initialize the SAM system and board
	board_init();

	// Set 32kHz in bypass
	#if BOARD == USER_EXT_BOARD
	SUPC->SUPC_MR |= SUPC_MR_KEY_PASSWD | SUPC_MR_OSCBYPASS;
	#endif
	sysclk_init();
	// Enable Real-Time Timer as a free running counter
	rtt_enable();
	
	// Create a mutex for Flash access
	Init_Flash_Mutex();
	
	// Initialize the console uart 
	configure_console();
	
	// Initialize PDM microphones 
	init_pdm();
	set_pdm_buffers(pdmmic_get_audio_left_buffer(), pdmmic_get_audio_right_buffer(), PCM_BUF_SIZE);
	start_pdm_transfer();
	
	// Start I2C module for communication with DDIC 
	#if BOARD == USER_EXT_BOARD
	hi2c_1.flexcom = DDIC_I2C_FLEXCOM;
	hi2c_1.SCL_Pin = PIN_DDIC_I2C_SCL;
	hi2c_1.SDA_Pin = PIN_DDIC_I2C_SDA;
	// I2C pins are initialized already in displayboard_init.c
	init_i2c(&hi2c_1, 100000UL);
	#else
	hi2c_1.flexcom = FLEXCOM3;
	hi2c_1.SCL_Pin = PIO_PA4_IDX;
	hi2c_1.SDA_Pin = PIO_PA3_IDX;
	// Init i2c pins
	sysclk_enable_peripheral_clock(ID_PIOA);
	pio_set_peripheral(PIOA, PIO_PERIPH_A, (1 << 4));
	pio_set_peripheral(PIOA, PIO_PERIPH_A, (1 << 3));
	init_i2c(&hi2c_1, 100000UL);
	#endif
	
	// Start I2C module for other processes
	hi2c_4.flexcom = HUB_I2C_FLEXCOM;
	hi2c_4.SCL_Pin = PIN_HUB_I2C_SCL;
	hi2c_4.SDA_Pin = PIN_HUB_I2C_SDA;
	// I2C pins are initialized already in displayboard_init.c
	init_i2c(&hi2c_4, 100000UL);
	
	// Initialize USB Device driver 	
	udc_start();
	vbus_monitor_start();
	
	// Initialize adc and temperature sensor 
	init_adc();

	// Set the audio class callback for loading data 
	udi_audio_register_callback(main_audio_callback);

	// Output demo information. 
	printf("-- Bigscreen Beyond --\n");
	printf("-- %s --\n", BOARD_NAME);
	printf("-- Version %s --\n", VERSION_STRING);
	printf("-- Compiled: %s %s --\n", __DATE__, __TIME__);

	if(xTaskCreate(task_usbload, "USBLoad", TASK_USBLOAD_STACK_SIZE, NULL,
			TASK_USBLOAD_PRIORITY, NULL) != pdPASS) {
				printf("Failed to create USB Loader task\r\n");
			}
	
	xUSBLoadTaskHandle = xTaskGetHandle("USBLoad");


	start_usbhid_task();
	start_test_tasks();
	start_video_task();
	
	usart_interface_init();
	
	// Enable HPD IRQ
	pio_handler_set_pin(PIN_DDIC_HPD, 0, hpd_irq);
	pio_handler_set_priority(DDIC_LOGIC_PIO, PIOA_IRQn, 3);
	// Enable this interrupt after we have finished applying startup settings
	// Now done in "test_and_debug.c"
	//pio_enable_interrupt(DDIC_LOGIC_PIO, pio_get_pin_group_mask(PIN_DDIC_HPD));
	
	// Enable GPIO4 IRQ (TX1 reset from VXR7200)
	pio_handler_set_pin(PIN_DDIC_GPIO4, 0, vxr_gpio_irq);
	pio_handler_set_priority(DDIC_GPIO4_PIO, PIOB_IRQn, 3);
	pio_enable_interrupt(DDIC_GPIO4_PIO, pio_get_pin_group_mask(PIN_DDIC_GPIO4));
	
	// Enable GPIO7 IRQ (TX0 reset from VXR7200)
	pio_handler_set_pin(PIN_DDIC_GPIO7, 0, vxr_gpio_irq);
	pio_handler_set_priority(DDIC_LOGIC_PIO, PIOA_IRQn, 3);
	pio_enable_interrupt(DDIC_LOGIC_PIO, pio_get_pin_group_mask(PIN_DDIC_GPIO7));
	
	// Enable the crash handler IRQ
	enable_crashdump();
	// Start the scheduler. 
	vTaskStartScheduler();


	// Will only get here if there was insufficient memory to create the idle task. 
	return 0;
}

void main_system_shutdown(void) {
	const RGB_Color_t shutdown_color =
	{	.r = 0,
		.g = 0,
		.b = 0
	};
	
	// Disables all enabled functional blocks and interrupts, hard shuts down the RTOS.
	// This function should only be used when attempting to jump to the bootloader
	// by keeping it safe from interruptions.
	
	Power_Safe_Shutdown(); // Need interrupts working here since we use RTOS for timing.
	vTaskSuspendAll(); // Disable all RTOS context switches. This task will continue running.
	// Turn off Fan and RGB LED
	set_fan_pwm(0);
	rgb_led_set_color(MCU_I2C(), &shutdown_color);
	
	__disable_irq(); // Now make sure we are not interrupted in this function.
	
	disable_i2c(DDIC_I2C());
	disable_i2c(MCU_I2C());
	disable_fan();
	udc_stop(); // exit USB and force a disconnect
	disable_pdm(); 
	
	// Disable the RTOS. Need to stop any Systick
	SysTick->CTRL &= (~SysTick_CTRL_ENABLE_Msk);
	// The PendSV and SVCall interrupts could still trigger,
	// but they are all activated by Systick. If execution is
	// at this point then there can't be any pending interrupts 
	// anyway. Interrupts were disabled a few lines up. If they
	// had been pending, they would have been serviced before
	// execution reached this point.
	
}

__always_inline I2C_Handle* DDIC_I2C(void) {
	return &hi2c_1;
}
__always_inline I2C_Handle* MCU_I2C(void) {
	return &hi2c_4;
}

void main_audio_enable(void) {
	
	pdmmic_audio_enable();
}

void main_audio_disable(void) {
	pdmmic_audio_disable();
}

void main_audio_callback(void) {
	// Simply unblock the USBload task
	
	BaseType_t xHigherPriorityTaskWoken;
	xHigherPriorityTaskWoken = pdFALSE;
	vTaskNotifyGiveFromISR(xUSBLoadTaskHandle, &xHigherPriorityTaskWoken);
	
	portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
	
}

void vbus_monitor_start(void) {
	//ioport_set_pin_dir(USB_VBUS_PIN, IOPORT_DIR_INPUT);
}

bool vbus_is_present(void) {
	return true;
	//return ioport_get_pin_level(USB_VBUS_PIN);
}



// I2C handler
#if BOARD == USER_EXT_BOARD
void FLEXCOM1_Handler(void) {
#else
void FLEXCOM3_Handler(void) {
#endif
	// Simply call the irq handler in the I2C driver
	i2c_irq_handler(&hi2c_1);
}

void FLEXCOM4_Handler(void) {
	i2c_irq_handler(&hi2c_4);
}

static void hpd_irq(uint32_t source_id, uint32_t source_mask) {
	(void)source_id;
	(void)source_mask;
	
	bool hpd_level = ioport_get_pin_level(PIN_DDIC_HPD);
	
	if(hpd_level) {
		// HPD is asserted, signal to the Linkbox
		linkbox_hpd_set_active();
	} else {
		linkbox_hpd_set_inactive();
	}
}

static volatile bool gpio4_status = false;
static volatile bool gpio7_status = false;

bool main_left_display_reset_status(void) {
	return gpio7_status;
}

bool main_right_display_reset_status(void) {
	return gpio4_status;
}

static void vxr_gpio_irq(uint32_t source_id, uint32_t source_mask) {
	(void)source_id;
	(void)source_mask;
	
	gpio4_status = ioport_get_pin_level(PIN_DDIC_GPIO4);
	gpio7_status = ioport_get_pin_level(PIN_DDIC_GPIO7);
	/* commenting this out for now, just release reset at boot
	if(gpio7_status) {
		// release reset as soon as the first display needs to turn on
		// if we wait for both TX0 and TX1 reset pins, we will miss TX0 initialization
		ioport_set_pin_level(PIN_OLED_RESX, true);
	} else {
		ioport_set_pin_level(PIN_OLED_RESX, false);
	}
	*/
	
}

/*
void HardFault_Handler(void)
{
	
	crashdump_set_crash_reason(CRASH_HARD_FAULT);
	crashdump_set_irqn(__get_IPSR());
	crashdump_trigger();
	// In case we get stuck repeatedly triggering a hard fault, we need to ensure that we
	// can return from this handler and keep executing.
	// So let's skip the program counter ahead past the faulting instruction.
	
	// Fun story, this function begins with a "push {r3, lr}" so we have to add 2 more
	// registers to find the stacked PC
	uint32_t* sptr = __get_MSP();
	sptr += 2; // Back to original stack at exception entry (R0)
	sptr += 6; // R1, R2, R3, R12, LR, then finally PC
	*sptr += 2; // skip to next instruction.
	// if this was a 32-bit instruction, who cares. we'll skip it fully the next time in hard fault.
	
	//__asm("bkpt");

	// Force a NRST pin reset, which completely resets the processor and all peripherals
	//	uint32_t rstc_mode_reg;
	//	rstc_mode_reg = RSTC->RSTC_MR;
	//	rstc_mode_reg &= ~(RSTC_MR_ERSTL_Msk | RSTC_MR_KEY_Msk);
		// rstc_mode_reg |= RSTC_MR_ERSTL(0xFF); // longest possible reset time (about 2 seconds)
	//	rstc_mode_reg |= RSTC_MR_ERSTL(0x07); // About 2^8 / 32768 seconds (7.8ms)
	//	rstc_mode_reg |= RSTC_MR_KEY_PASSWD;
	//	RSTC->RSTC_MR = rstc_mode_reg;
	//	RSTC->RSTC_CR = RSTC_CR_KEY_PASSWD | RSTC_CR_EXTRST; // assert reset pin	
	
	// We can't do an infinite loop here. It will never leave hard fault handler!
	//for(;;){
	//}
}

*/