/*
 * (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_SETMAC

#ifndef ETH_ALEN
#define ETH_ALEN 6
#endif

#define SERIAL_LOCATION 0x20
extern flash_info_t flash_info[]; /* info for FLASH chips */

static int mac_location[] = {
  0x0, /* eth0 */
  0x6, /* eth1 */
  0x1002, /* wlan0 */
};

typedef unsigned char bool;
#define true 1
#define false 0

/* from xyzmodem.c */
// Validate a hex character
__inline__ static bool
_is_hex(char c)
{
    return (((c >= '0') && (c <= '9')) ||
            ((c >= 'A') && (c <= 'F')) ||
            ((c >= 'a') && (c <= 'f')));
}

/* from xyzmodem.c */
// Convert a single hex nibble
__inline__ static u8
_from_hex(char c)
{
    u8 ret = 0;

    if ((c >= '0') && (c <= '9')) {
        ret = (c - '0');
    } else if ((c >= 'a') && (c <= 'f')) {
        ret = (c - 'a' + 0x0a);
    } else if ((c >= 'A') && (c <= 'F')) {
        ret = (c - 'A' + 0x0A);
    }
    return ret;
}



static int is_valid_mac_str(char *mac)
{
  /* mac must look like "00:11:22:33:44:55" */
  int i;

  if (strlen(mac) < 17)
    return 0;

  for (i=0; i <=15 ; i+=3) {
    if (!_is_hex(mac[i]) || !_is_hex(mac[i+1]))
      return 0;
    
    /* check for colons - last group doesn't end with a colon*/
    if (i != 15 && mac[i+2] != ':')
      return 0;
  }

  return 1;
}

static int parse_mac(u8 *dst, char *mac_str) {
  int i;
  int j;

  if (!is_valid_mac_str(mac_str))
    return 1;

  /* j = dst offset
   * i = mac_str offset
   */
  for (i=0, j=0; i<= 15; i+=3, j++) {
    dst[j] = _from_hex(mac_str[i]) << 4 | _from_hex(mac_str[i + 1]);
  }
  return 0;
}

/* copy calibration sector to the sector before calibration */
static int backup_calibration(u8 *buffer)
{
  int rc = -1;

  /* read */
  memcpy(buffer, (void *)BOARDCAL, CFG_FLASH_SECTOR_SIZE);

  /* erase */
	rc = flash_erase(flash_info, CAL_SECTOR-1, CAL_SECTOR-1);
  if (rc) {
    printf("Backup failed because flash erase failed! rc %d\n", rc);
    return 1;
  }

  /* cp */
	rc = write_buff(flash_info, buffer, BOARDCAL-CFG_FLASH_SECTOR_SIZE, CFG_FLASH_SECTOR_SIZE);
  if (rc) {
    printf("Backup failed because write to flash failed! rc %d\n", rc);
    return 1;
  }

  /* compare */
  if (memcmp(buffer, (void *)BOARDCAL-CFG_FLASH_SECTOR_SIZE, CFG_FLASH_SECTOR_SIZE)) {
    printf("Backup failed. Read back different value!\n");
    return 1;
  }

  return 0;
}

int write_mac(u8 *buffer, u8 macs[ARRAY_SIZE(mac_location)][ETH_ALEN])
{
  int i;
  int rc;

  /* read */
  memcpy(buffer, (void *)BOARDCAL, CFG_FLASH_SECTOR_SIZE);

  /* set macs */
  for(i=0; i<ARRAY_SIZE(mac_location); i++) {
    memcpy(buffer + mac_location[i], macs[i], ETH_ALEN);
  }

  /* erase */
	rc = flash_erase(flash_info, CAL_SECTOR, CAL_SECTOR);
  if (rc) {
    printf("Write mac failed because flash_erase failed! rc %d\n", rc);
    return 1;
  }

  /* write */
	rc = write_buff(flash_info, buffer, BOARDCAL, CFG_FLASH_SECTOR_SIZE);
  if (rc) {
    printf("Write mac failed because write_buff failed! rc %d\n", rc);
    return 1;
  }

  return 0;
}

int do_setmac(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
	u8 buffer[CFG_FLASH_SECTOR_SIZE];
  u8 macs[ARRAY_SIZE(mac_location)][ETH_ALEN];
  int i;
  bool do_backup = true;

  /* valid arguments are:
   * set_mac <mac0> <mac1> [..] or set_mac <mac0> <mac1> [..] nobackup
   * check if enough arguments given
   */
  if (argc != (ARRAY_SIZE(mac_location)+1) &&
      argc != (ARRAY_SIZE(mac_location)+2))
    return CMD_RET_FAILURE;

  /* check if nobackup was given */
  if (argc == (ARRAY_SIZE(mac_location)+2)) {
      if(!strncmp("nobackup", argv[argc-1], 8)) {
        do_backup = false;
      } else {
        printf("Unknown argument %s!\n", argv[argc-1]);
        return CMD_RET_FAILURE;
      }
  }

  /* all other arguments are mac address */
  for (i=0; i < ARRAY_SIZE(mac_location); i++) {
    if (!is_valid_mac_str(argv[i+1])) {
        printf("Invalid MAC address %s!\n", argv[i+1]);
        return CMD_RET_FAILURE;
    }
  }

  /* parse macs and copy into macs */
  for (i=0; i < ARRAY_SIZE(mac_location); i++) {
    if (parse_mac(macs[i], argv[i+1])) {
      printf("Can not parse mac %s!\n", argv[i+1]);
      return CMD_RET_FAILURE;
    }
  }

  if (do_backup) {
    if (backup_calibration(buffer)) {
      printf("Backup failed to the sector before calibration. Not updating calibration sector!");
      return CMD_RET_FAILURE;
    }
  }

  if (!write_mac(buffer, macs)) {
    return CMD_RET_FAILURE;
  }

  return CMD_RET_SUCCESS;
}

int do_showmac(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
  int i = 0;
  u8 buffer[CFG_FLASH_SECTOR_SIZE];
  u8 *mac;
  memcpy(buffer, (void *)BOARDCAL, CFG_FLASH_SECTOR_SIZE);

  for (i=0; i < ARRAY_SIZE(mac_location); i++) {
    mac = buffer + mac_location[i];
    printf("mac %d = %02x:%02x:%02x:%02x:%02x:%02x\n", i,
          mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
  }

  return CMD_RET_SUCCESS;
}

int do_setserial(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
  u8 buffer[CFG_FLASH_SECTOR_SIZE];
  int32_t *serial;
  int32_t serial_number;
  int rc;

  if (argc != 2)
    return CMD_RET_FAILURE;

  serial_number = simple_strtoul(argv[1], NULL, 16);
  if (serial_number <= 0) {
    printf("Invalid serialnumber. <= 0!\n");
    return CMD_RET_FAILURE;
  }

  /* read */
  memcpy(buffer, (void *)BOARDCAL, CFG_FLASH_SECTOR_SIZE);

  /* set serial number */
  serial = (int32_t *) (buffer + SERIAL_LOCATION);
  *serial = serial_number;

  /* erase */
	rc = flash_erase(flash_info, CAL_SECTOR, CAL_SECTOR);
  if (rc) {
    printf("Write serial failed because flash_erase failed! rc %d\n", rc);
    return 1;
  }

  /* write */
	rc = write_buff(flash_info, buffer, BOARDCAL, CFG_FLASH_SECTOR_SIZE);
  if (rc) {
    printf("Write serial failed because write_buff failed! rc %d\n", rc);
    return 1;
  }

  printf("Set serial to %d\n", serial_number);

  return CMD_RET_SUCCESS;
}


int do_showserial(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
  int i = 0;
  u8 buffer[CFG_FLASH_SECTOR_SIZE];
  int32_t *serial;
  memcpy(buffer, (void *)BOARDCAL, CFG_FLASH_SECTOR_SIZE);

  serial = buffer + SERIAL_LOCATION;
  printf("serial %d\n", serial);

  return CMD_RET_SUCCESS;
}

U_BOOT_CMD(
    setmac, ARRAY_SIZE(mac_location)+2, 0, do_setmac,
    "setmac - Set ethernet MAC addresses\n",
    "setmac [<eth0> <eth1> <wlan0> [nobackup]]\n"
    "      without arguments it shows actual configuration\n"
    "      <eth0> mac address\n"
    "      <eth1> mac address\n"
    "      <wlan0> mac address\n"
    "        mac address 00:aa:bb:cc:dd:ee\n"
    "      nobackup - don't do a backup in the sector before calibration"
);

U_BOOT_CMD(
    showmac, 1, 0, do_showmac,
    "showmac - Show ethernet MAC addresses\n",
);

U_BOOT_CMD(
    setserial, 2, 0, do_setserial,
    "setserial - Set the serial number\n",
    "setserial serial\n"
);

U_BOOT_CMD(
    showserial, 1, 0, do_showserial,
    "showserial - Show serial number\n",
);


#endif /* CFG_CMD_SETMAC */
