/****************************************************************************
 * boards/arm/sama5/sama5d4-ek/src/at25_main.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <debug.h>
#include <hex2bin.h>

#include <nuttx/streams.h>
#include <arch/irq.h>

#include "sama5d4-ek.h"

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/* Configuration ************************************************************/

#ifndef HAVE_AT25
#  error The AT25 Serial FLASH is not available
#endif

#ifndef CONFIG_SAMA5D4EK_AT25_BLOCKMOUNT
#  error CONFIG_SAMA5D4EK_AT25_BLOCKMOUNT must be selected
#endif

#ifndef CONFIG_SAMA5D4EK_AT25_CHARDEV
#  error CONFIG_SAMA5D4EK_AT25_CHARDEV must be selected
#endif

#ifdef CONFIG_BOOT_SDRAM_DATA
#  error CONFIG_BOOT_SDRAM_DATA must NOT be selected
#endif

#if !defined(CONFIG_SAMA5D4EK_AT25_PROGSIZE) || CONFIG_SAMA5D4EK_AT25_PROGSIZE < 128
#  error Large CONFIG_SAMA5D4EK_AT25_PROGSIZE must be selected
#endif

#define DEVNAME_MAXSIZE 12
#define IOBUFFER_SIZE   512

/****************************************************************************
 * Private Types
 ****************************************************************************/

/****************************************************************************
 * Private Data
 ****************************************************************************/

static uint8_t g_iobuffer[IOBUFFER_SIZE];
static char g_at25dev[DEVNAME_MAXSIZE];

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: at25_main
 *
 * Description:
 *   at25_main is a tiny program that runs in ISRAM.  at25_main will
 *   configure DRAM and the AT25 serial FLASH, present a prompt, and load
 *   an Intel HEX file into the AT25 serial FLASH (after first buffering
 *   the binary data into memory).  On re-boot, the program loaded into
 *   the AT25 FLASH should then execute.
 *
 *   On entry:
 *   - SDRAM has already been initialized (we are using it for .bss!)
 *   - SPI0 chip select has already been configured.
 *
 ****************************************************************************/

int at25_main(int argc, char *argv[])
{
  struct lib_rawinstream_s rawinstream;
  struct lib_memsostream_s memoutstream;
  const uint8_t *src;
  ssize_t nwritten;
  ssize_t nread;
  ssize_t remaining;
  size_t rdsize;
  int fd;
  int ret;

  /* Configure the AT25 as the a character device.  */

  ret = sam_at25_automount(AT25_MINOR);
  if (ret < 0)
    {
      fprintf(stderr, "ERROR: Failed to mount AT25 FLASH: %d\n", ret);
      return EXIT_FAILURE;
    }

  /* Open the AT25 device for writing */

  snprintf(g_at25dev, DEVNAME_MAXSIZE, "/dev/mtd%d", AT25_MINOR);
  fd = open(g_at25dev, O_WRONLY);
  if (fd < 0)
    {
      int errcode = errno;
      fprintf(stderr, "ERROR: Failed to open %s: %d\n", g_at25dev, errcode);
      return EXIT_FAILURE;
    }

  /* Wrap stdin as an IN stream that can the HEX data over the serial port */

  lib_rawinstream(&rawinstream, 0);

  /* Define a memory buffer of size CONFIG_SAMA5D4EK_AT25_PROGSIZE at the
   * beginning of SDRAM. Wrap the memory buffer as a seek-able OUT stream in
   * which we can buffer the binary data.
   */

  lib_memsostream(&memoutstream, (char *)SAM_DDRCS_VSECTION,
                  CONFIG_SAMA5D4EK_AT25_PROGSIZE);

  /* We are ready to load the Intel HEX stream into DRAM.
   *
   * Hmm.. With no hardware handshake, there is a possibility of data loss
   * to overrunning incoming data buffer.  So far I have not seen this at
   * 115200 8N1, but still it is a possibility.
   */

  printf("Send Intel HEX file now\n");
  fflush(stdout);

  ret = hex2bin(&rawinstream.public, &memoutstream.public,
                SAM_ISRAM_VSECTION,
                SAM_ISRAM_VSECTION + CONFIG_SAMA5D4EK_AT25_PROGSIZE,
                0);
  if (ret < 0)
    {
      /* We failed the load */

      fprintf(stderr, "ERROR: Intel HEX file load failed: %d\n", ret);
      close(fd);
      return EXIT_FAILURE;
    }

  /* Replace the vector at offset 0x14 with the size of the image to load
   * into SRAM.  The RomBOOT loader expects to find this size at that
   * location.
   */

  *(uint32_t *)(SAM_DDRCS_VSECTION + 0x14) = memoutstream.public.nput;

  /* The HEX file load was successful, write the data to FLASH */

  printf("Successfully loaded the Intel HEX file into memory...\n");
  printf("  Writing %d bytes to the AT25 Serial FLASH\n",
         memoutstream.public.nput);

  remaining = memoutstream.public.nput;
  src = (uint8_t *)SAM_DDRCS_VSECTION;

  do
    {
      nwritten = write(fd, src, memoutstream.public.nput);
      if (nwritten <= 0)
        {
          int errcode = errno;
          if (errno != EINTR)
            {
              fprintf(stderr, "ERROR: Write failed: %d\n", errcode);
              close(fd);
              return EXIT_FAILURE;
            }
        }
      else
        {
          remaining -= nwritten;
          src += nwritten;
        }
    }
  while (remaining > 0);

  close(fd);

  /* Now verify that the image in memory and the image in FLASH are truly
   * the same.
   */

  printf("  Verifying %d bytes in the AT25 Serial FLASH\n",
         memoutstream.public.nput);

  /* Open the AT25 device for writing */

  fd = open(g_at25dev, O_RDONLY);
  if (fd < 0)
    {
      int errcode = errno;
      fprintf(stderr, "ERROR: Failed to open %s: %d\n", g_at25dev, errcode);
      return EXIT_FAILURE;
    }

  remaining = memoutstream.public.nput;
  src = (const uint8_t *)SAM_DDRCS_VSECTION;

  do
    {
      rdsize = remaining;
      if (rdsize > IOBUFFER_SIZE)
        {
          rdsize = IOBUFFER_SIZE;
        }

      nread = read(fd, g_iobuffer, rdsize);
      if (nread <= 0)
        {
          int errcode = errno;
          if (errno != EINTR)
            {
              fprintf(stderr, "ERROR: Read failed: %d\n", errcode);
              close(fd);
              return EXIT_FAILURE;
            }
        }
      else
        {
          if (memcmp(g_iobuffer, src, nread) != 0)
            {
              fprintf(stderr, "ERROR: Verify failed at offset %d\n",
                      memoutstream.public.nput - remaining);
              close(fd);
              return EXIT_FAILURE;
            }

          remaining -= nwritten;
          src += nwritten;
        }
    }
  while (remaining > 0);

  printf("  Successfully verified %d bytes in the AT25 Serial FLASH\n",
         memoutstream.public.nput);

  close(fd);
  return EXIT_SUCCESS;
}
