/*******************************************************************************
 * Copyright (C) 2016 Maxim Integrated Products, Inc., All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL MAXIM INTEGRATED BE LIABLE FOR ANY CLAIM, DAMAGES
 * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * Except as contained in this notice, the name of Maxim Integrated 
 * Products, Inc. shall not be used except as stated in the Maxim Integrated 
 * Products, Inc. Branding Policy.
 *
 * The mere transfer of this software does not imply any licenses
 * of trade secrets, proprietary technology, copyrights, patents,
 * trademarks, maskwork rights, or any other form of intellectual
 * property whatsoever. Maxim Integrated Products, Inc. retains all 
 * ownership rights.
 *******************************************************************************
 */

/* config.h is the required application configuration; RAM layout, stack, chip type etc. */
#include "mxc_config.h"
#include "board.h"

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>

/* FreeRTOS */
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"

/* FreeRTOS+ */
#include "FreeRTOS_CLI.h"

/* Maxim CMSIS SDK */
#include "rtc.h"
#include "uart.h"
#include "lp.h"
#include "led.h"
#include "board.h"

/* FreeRTOS+CLI */
void vRegisterCLICommands(void);

/* Mutual exclusion (mutex) semaphores */
SemaphoreHandle_t xGPIOmutex;

/* Task IDs */
TaskHandle_t cmd_task_id;

/* Enables/disables tick-less mode */
unsigned int disable_tickless = 1;

/* Stringification macros */
#define STRING(x) STRING_(x)
#define STRING_(x) #x

/* Console ISR selection */
#if (CONSOLE_UART==0)
#define UARTx_IRQHandler UART0_IRQHandler
#define UARTx_IRQn UART0_IRQn
#elif (CONSOLE_UART==1)
#define UARTx_IRQHandler UART1_IRQHandler
#define UARTx_IRQn UART1_IRQn
#else
#error "Please update ISR macro for UART CONSOLE_UART"
#endif
mxc_uart_regs_t *ConsoleUART = MXC_UART_GET_UART(CONSOLE_UART);

/* Array sizes */
#define CMD_LINE_BUF_SIZE  80
#define OUTPUT_BUF_SIZE  512

/* =| vTask0 |============================================
 * 
 * This task blinks LED0 at a 0.5Hz rate, and does not
 *  drift due to the use of vTaskDelayUntil(). It may have
 *  jitter, however, due to any higher-priority task or
 *  interrupt causing delays in scheduling.
 *
 * =======================================================
 */
void vTask0(void *pvParameters)
{
  TickType_t xLastWakeTime;
  unsigned int x = LED_OFF;

  /* Get task start time */
  xLastWakeTime = xTaskGetTickCount();
  
  while (1) {
    /* Protect hardware access with mutex
     *
     * Note: This is not strictly necessary, since GPIO_SetOutVal() is implemented with bit-band
     * access, which is inherently task-safe. However, for other drivers, this would be required.
     *
     */
    if (xSemaphoreTake(xGPIOmutex, portMAX_DELAY) == pdTRUE) {
      if (x == LED_OFF) {
	x = LED_ON;
      } else {
	x = LED_OFF;
      }
      /* Return the mutex after we have modified the hardware state */
      xSemaphoreGive(xGPIOmutex);
    }
    /* Wait 1 second until next run */
    vTaskDelayUntil(&xLastWakeTime, configTICK_RATE_HZ);    
  }
}

/* =| vTask1 |============================================
 * 
 * This task blinks LED1 at a 0.5Hz rate, and does not
 *  drift due to the use of vTaskDelayUntil(). It may have
 *  jitter, however, due to any higher-priority task or
 *  interrupt causing delays in scheduling.
 *
 * NOTE: The MAX32660 EV Kit has only 1 LED, so this task
 *  does not blink an LED.
 *
 * =======================================================
 */
void vTask1(void *pvParameters)
{
  TickType_t xLastWakeTime;
  unsigned int x = LED_ON;

  /* Get task start time */
  xLastWakeTime = xTaskGetTickCount();
  
  while (1) {
    /* Protect hardware access with mutex
     *
     * Note: This is not strictly necessary, since GPIO_SetOutVal() is implemented with bit-band
     * access, which is inherently task-safe. However, for other drivers, this would be required.
     *
     */
    if (xSemaphoreTake(xGPIOmutex, portMAX_DELAY) == pdTRUE) {
      if (x == LED_OFF) {
	LED_On(0);
	x = LED_ON;
      } else {
	LED_Off(0);
	x = LED_OFF;
      }
      /* Return the mutex after we have modified the hardware state */
      xSemaphoreGive(xGPIOmutex);
    }
    /* Wait 1 second until next run */
    vTaskDelayUntil(&xLastWakeTime, configTICK_RATE_HZ);
  }
}

/* =| vTickTockTask |============================================
 * 
 * This task writes the current RTOS tick time to the console
 *
 * =======================================================
 */
void vTickTockTask(void *pvParameters)
{
  TickType_t ticks = 0;
  TickType_t xLastWakeTime;

  /* Get task start time */
  xLastWakeTime = xTaskGetTickCount();
  
  while (1) {
    ticks = xTaskGetTickCount();
    printf("Uptime is 0x%08x (%u seconds), tickless-idle is %s\n",
	   ticks, ticks / configTICK_RATE_HZ,
	   disable_tickless ? "disabled" : "ENABLED");
    vTaskDelayUntil(&xLastWakeTime, (configTICK_RATE_HZ * 60));
  }
}

/* =| UART0_IRQHandler |======================================
 * 
 * This function overrides the weakly-declared interrupt handler
 *  in system_max326xx.c and is needed for asynchronous UART
 *  calls to work properly
 *
 * ===========================================================
 */
void UARTx_IRQHandler(void)
{
    UART_Handler(ConsoleUART);
}

/* =| vCmdLineTask_cb |======================================
 * 
 * Callback on asynchronous reads to wake the waiting command
 *  processor task
 *
 * ===========================================================
 */
void vCmdLineTask_cb(uart_req_t *req, int error)
{
  BaseType_t xHigherPriorityTaskWoken;

  /* Wake the task */
  xHigherPriorityTaskWoken = pdFALSE;
  vTaskNotifyGiveFromISR(cmd_task_id, &xHigherPriorityTaskWoken);
  portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

/* =| vCmdLineTask |======================================
 * 
 * The command line task provides a prompt on the serial
 *  interface and takes input from the user to evaluate
 *  via the FreeRTOS+CLI parser.
 *
 * NOTE: FreeRTOS+CLI is part of FreeRTOS+ and has 
 *  different licensing requirements. Please see 
 *  http://www.freertos.org/FreeRTOS-Plus for more information
 *
 * =======================================================
 */
void vCmdLineTask(void *pvParameters)
{
  unsigned char tmp;
  unsigned int index;     /* Index into buffer */
  unsigned int x;
  char buffer[CMD_LINE_BUF_SIZE];        /* Buffer for input */
  char output[OUTPUT_BUF_SIZE];        /* Buffer for output */
  BaseType_t xMore;
  uart_req_t async_read_req;
  gpio_cfg_t uart_rx_pin = {PORT_0, PIN_10, GPIO_FUNC_IN, GPIO_PAD_NONE};

  memset(buffer, 0, CMD_LINE_BUF_SIZE);
  index = 0;
    
  /* Register available CLI commands */
  vRegisterCLICommands();

#if configUSE_TICKLESS_IDLE
  /* Configure wake-up for GPIO pin corresponding to the UART RX line */
  LP_EnableGPIOWakeup(&uart_rx_pin);
  GPIO_IntConfig(&uart_rx_pin, GPIO_INT_EDGE, GPIO_INT_FALLING);
#endif

  /* Enable UART0 interrupt */
  NVIC_ClearPendingIRQ(UARTx_IRQn);
  NVIC_DisableIRQ(UARTx_IRQn);
  NVIC_SetPriority(UARTx_IRQn, 1);
  NVIC_EnableIRQ(UARTx_IRQn);

  /* Async read will be used to wake process */
  async_read_req.data = &tmp;
  async_read_req.len = 1;
  async_read_req.callback = vCmdLineTask_cb;
  
  printf("\nEnter 'help' to view a list of available commands.\n");
  printf("cmd> ");
  fflush(stdout);
  while (1) {
    /* Register async read request */
    if (UART_ReadAsync(ConsoleUART, &async_read_req) != E_NO_ERROR) {
      printf("Error registering async request. Command line unavailable.\n");
      vTaskDelay(portMAX_DELAY);
    }
    /* Hang here until ISR wakes us for a character */
    ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
    /* Check that we have a valid character */
    if (async_read_req.num > 0) {
      /* Process character */
      do {
	if (tmp == 0x08) {
	  /* Backspace */
	  if (index > 0) {
	    index--;
	    printf("\x08 \x08");
	  }
	  fflush(stdout);
	} else if (tmp == 0x03) {
	  /* ^C abort */
	  index = 0;
	  printf("^C");
	  printf("\ncmd> ");
	  fflush(stdout);
	} else if ((tmp == '\r') ||
		   (tmp == '\n')) {
	  printf("\r\n");
	  /* Null terminate for safety */
	  buffer[index] = 0x00;
	  /* Evaluate */
	  do {
	    xMore = FreeRTOS_CLIProcessCommand(buffer, output, OUTPUT_BUF_SIZE);
	    /* If xMore == pdTRUE, then output buffer contains no null termination, so 
	     *  we know it is OUTPUT_BUF_SIZE. If pdFALSE, we can use strlen.
	     */
	    for (x = 0; x < (xMore == pdTRUE ? OUTPUT_BUF_SIZE : strlen(output)) ; x++) {
	      putchar(*(output+x));
	    }
	  } while (xMore != pdFALSE);
	  /* New prompt */
	  index = 0;
	  printf("\ncmd> ");
	  fflush(stdout);
	} else if (index < CMD_LINE_BUF_SIZE) {
	  putchar(tmp);
	  buffer[index++] = tmp;
	  fflush(stdout);
	} else {
	  /* Throw away data and beep terminal */
	  putchar(0x07);
	  fflush(stdout);
	}
	/* If more characters are ready, process them here */
      } while ((UART_NumReadAvail(MXC_UART_GET_UART(CONSOLE_UART)) > 0) &&
	       UART_Read(MXC_UART_GET_UART(CONSOLE_UART), (uint8_t *)&tmp, 1, NULL));
    }
  }
}

#if configUSE_TICKLESS_IDLE
/* =| freertos_permit_tickless |==========================
 * 
 * Determine if any hardware activity should prevent 
 *  low-power tickless operation.
 *
 * =======================================================
 */
int freertos_permit_tickless(void)
{
  if (disable_tickless == 1) {
    return E_BUSY;
  }

  return UART_PrepForSleep(MXC_UART_GET_UART(CONSOLE_UART));
}
#endif

void RTC_IRQHandler(void)
{
  MXC_RTC->ctrl &= ~(MXC_F_RTC_CTRL_ALSF);
}

/* =| main |==============================================
 * 
 * This program demonstrates FreeRTOS tasks, mutexes, 
 *  and the FreeRTOS+CLI extension.
 *
 * =======================================================
 */
int main(void)
{
#if configUSE_TICKLESS_IDLE
  uart_cfg_t uart_cfg = {
    .parity = UART_PARITY_DISABLE,
    .size   = UART_DATA_SIZE_8_BITS,
    .stop   = UART_STOP_1,
    .flow   = UART_FLOW_CTRL_DIS,
    .pol    = UART_FLOW_POL_DIS,
    .baud   = 115200,
    .clksel = UART_CLKSEL_SYSTEM
  };
  sys_cfg_uart_t uart_sys_cfg = {MAP_A, Enable};

  /* The RTC must be enabled for tickless operation */
  RTC_Init(MXC_RTC, 0, 0, NULL);
  RTC_EnableRTCE(MXC_RTC);
  NVIC_ClearPendingIRQ(RTC_IRQn);
  NVIC_EnableIRQ(RTC_IRQn);
  LP_EnableRTCAlarmWakeup();
  /* If running tickless idle, must reduce baud rate to avoid losing character */
  if (UART_Init(ConsoleUART, &uart_cfg, &uart_sys_cfg) != E_NO_ERROR) {
    MXC_ASSERT_FAIL();
  }
#endif
  
  /* Print banner (RTOS scheduler not running) */
  printf("\n-=- %s FreeRTOS (%s) Demo -=-\n", STRING(TARGET), tskKERNEL_VERSION_NUMBER);
#if configUSE_TICKLESS_IDLE
  printf("Tickless idle is configured. Type 'tickless 1' to enable.\n");
#endif

  /* Create mutexes */
  xGPIOmutex = xSemaphoreCreateMutex();
  if (xGPIOmutex == NULL) {
    printf("xSemaphoreCreateMutex failed to create a mutex.\n");
  } else {
    /* Configure task */
    if ((xTaskCreate(vTask0, (const char *)"Task0",
		     configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY+1, NULL) != pdPASS) ||
	(xTaskCreate(vTask1, (const char *)"Task1",
		     configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY+1, NULL) != pdPASS) ||
	(xTaskCreate(vTickTockTask, (const char *)"TickTock",
		     2*configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY+2, NULL) != pdPASS) ||
	(xTaskCreate(vCmdLineTask, (const char *)"CmdLineTask",
		     configMINIMAL_STACK_SIZE+CMD_LINE_BUF_SIZE+OUTPUT_BUF_SIZE, NULL, tskIDLE_PRIORITY+1, &cmd_task_id) != pdPASS)) {
      printf("xTaskCreate() failed to create a task.\n");
    } else {
      /* Start scheduler */
      printf("Starting scheduler.\n");
      vTaskStartScheduler();
    }
  }
  
  /* This code is only reached if the scheduler failed to start */
  printf("ERROR: FreeRTOS did not start due to above error!\n");
  while (1) {
    __NOP();
  }

  /* Quiet GCC warnings */
  return -1;
}