/*
 * (C) Copyright 2015
 * Alexander Couzens, lynxis@fe80.eu
 *
 * See file CREDITS for list of people who contributed to this
 * project.
 *
 * 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.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 */

#include <common.h>
#include <command.h>

#ifdef CFG_CMD_BOOTCYCLE

#ifndef MAX_REBOOTS
#define MAX_REBOOTS 10
#endif

#define SRAM_BOOT_CYCLE 0xbd000100

#define BOOT_CYCLE_MAGIC 0xfeedb00d
#define BOOT_CYCLE_VERSION 1

struct sram_boot_cycle
{
  uint32_t magic;
  uint32_t version;
  uint32_t counter;
  uint32_t crc;
};

static int valid_boot_cycle(struct sram_boot_cycle *sram)
{
  if (sram->magic != BOOT_CYCLE_MAGIC)
    return 0;

  if (sram->version != BOOT_CYCLE_VERSION)
    return 0;

  uint32_t crc = crc32(0xdeadbeef, (void *)sram, sizeof(*sram) - 4);
  if (crc != sram->crc)
    return 0;

  return 1;
}

static void reset_boot_cycle(struct sram_boot_cycle *sram)
{
  sram->magic = BOOT_CYCLE_MAGIC;
  sram->version = BOOT_CYCLE_VERSION;
  sram->counter = 0;
  sram->crc = (uint32_t) crc32(0xdeadbeef, (void *)sram, sizeof(*sram) - 4);
}

void increase_boot_cycle(struct sram_boot_cycle *sram, uint32_t value)
{
  if (!valid_boot_cycle(sram))
    reset_boot_cycle(sram);

  sram->counter += value;
  sram->crc = (uint32_t) crc32(0xdeadbeef, (void *)sram, sizeof(*sram) - 4);
}

int do_inccycle(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) {
  uint32_t value = 1;

  if (argc == 2) {
    int given_value = (int)simple_strtol(argv[1], NULL, 10);
    if (given_value > 0) {
      value = given_value;
    } else {
      printf("Ignoring invalid argument %s\n", argv[1]);
    }
  }

  increase_boot_cycle((void *) SRAM_BOOT_CYCLE, value);
	return CMD_RET_SUCCESS;
}

int do_resetcycle(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) {
  reset_boot_cycle((void *) SRAM_BOOT_CYCLE);
	return CMD_RET_SUCCESS;
}

/* report failure when we need to go to rescue */
int do_checkcycle(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
  uint32_t threshold = MAX_REBOOTS;

  if (argc == 2) {
    int given_threshold = (int)simple_strtol(argv[1], NULL, 10);
    if (given_threshold > 0) {
      threshold = given_threshold;
    } else {
      printf("Ignoring invalid argument %s\n", argv[1]);
    }
  }

  struct sram_boot_cycle *sram = (void *) SRAM_BOOT_CYCLE;
  /* when called we should ever have a valid boot cycle entry
   * if it's invalid we have most likely a broken bootloader or hw? */
  if (!valid_boot_cycle(sram)) {
    printf("Invalid boot cycle partition!\n");
    return CMD_RET_FAILURE;
  }
  printf("bootcycle counter: %d\n", sram->counter);

  if (sram->counter >= threshold) {
    printf("Reached maximum reboot.\n");
    return CMD_RET_FAILURE;
  }

	return CMD_RET_SUCCESS;
}

U_BOOT_CMD(
	checkcycle,	2,	0,	do_checkcycle,
	"checkcycle - check if maximum reboots reached.\n",
	"[max_reboots]"
);

U_BOOT_CMD(
	inccycle,	2,	0,	do_inccycle,
	"inccycle - increase boot cycle counter\n",
	NULL
);

U_BOOT_CMD(
	resetcycle,	1,	0,	do_resetcycle,
	"resetcycle - reset boot cycle counter to 0\n",
	NULL
);

#endif	/* CFG_CMD_BOOTCYCLE */
