/**
 * \file
 *
 * Copyright (C) 2009 Atmel Corporation. All rights reserved.
 *
 * \page License
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. The name of Atmel may not be used to endorse or promote products derived
 * from this software without specific prior written permission.
 *
 * 4. This software may only be redistributed and used in connection with an
 * Atmel AVR product.
 *
 * THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
 * EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */
#include <buffer.h>
#include <debug.h>
#include <dmapool.h>
#include <interrupt.h>
#include <watchdog.h>
#include <workqueue.h>
#include <timer_tc.h>
#include <board/init.h>
#include <chip/memory-map.h>
#include <chip/clk.h>
#include <dmac/dmaca.h>
#include <sdmmc/sdmmc.h>
#include <sdmmc/mcihost.h>
#include <block/device.h>
#include <block/sdmmc.h>
#include <block/aesblk.h>
#include <usb/udc.h>
#include <usb/function_core.h>
#include <usb/msc_function.h>
#include <usb/request.h>
#include <usb/usb_protocol.h>
#include <status-codes.h>
#include <malloc.h>
#include <string.h>
#include <aes.h>
#include <atomic.h>
#include <board/led.h>

#include <app/usb_strings.h>


/* Set LEDs to signal power on and card detect */
#define CARD_DETECT_LED		BOARD_LED1_ID
#define POWER_ON_LED		BOARD_LED0_ID

/* Including terminating '\0' */
#define MAX_SERIAL_NUMBER_LEN	13

const char *usb_dev_string_table[] = {
	[USB_STRING_LANGID]		= "",
	[USB_STRING_DEV_MANUFACTURER]	= "Atmel",
	[USB_STRING_DEV_PRODUCT]	= "USB Mass Storage",
#ifndef CONFIG_CPU_AT32AP7200
	[USB_STRING_DEV_SERIAL]		= (const char *)FLASH_USER_PAGE_BASE,
#endif
};

static struct usb_configuration_descriptor config_desc = {
	.bLength = sizeof(struct usb_configuration_descriptor),
	.bDescriptorType	= USB_DT_CONFIGURATION,
	.bNumInterfaces		= 1,
	.bConfigurationValue	= 1,
	.bmAttributes		= 0xc0,
	.bMaxPower		= 2,
};

struct timer		main_timer;
struct workqueue	main_workqueue;

const char *get_serial_number(void)
{
	return (const char *)FLASH_USER_PAGE_BASE;
}

static void card_event(struct block_device *bdev, void *data)
{
#ifdef CONFIG_USE_AES
	struct aes_block_device *aes_bdev = data;
	struct udc		*udc = aes_bdev->udc;
#else
	struct udc		*udc = data;
#endif

	if (!bdev) {
		led_deactivate(CARD_DETECT_LED);
		udc_detach(udc);
	} else if (!test_bit(BDEV_PRESENT, &bdev->flags)) {
		led_deactivate(CARD_DETECT_LED);
		udc_detach(udc);
	} else if (!test_bit(BDEV_WRITEABLE, &bdev->flags)) {
		led_deactivate(CARD_DETECT_LED);
		udc_detach(udc);
	} else {
#ifdef CONFIG_USE_AES
		aesblk_update(aes_bdev);
#endif
		udc_attach(udc);
		led_activate(CARD_DETECT_LED);
	}
}

int main(void)
{
	struct udc		*udc;
	struct usb_func_iface	*msc_iface;
	struct usb_func_config	*config;
	struct sdmmc_host	*sdmmc_host;
	struct sdmmc_slot	*slot;
	struct block_device	*bdev;

#ifdef CONFIG_USE_AES
	struct aes_block_device aes_bdev;
	uint32_t cipher_key[8];

	cipher_key[0] =	  0x603deb10;
	cipher_key[1] =	  0x15ca71be;
	cipher_key[2] =	  0x2b73aef0;
	cipher_key[3] =	  0x857d7781;
	cipher_key[4] =	  0x1f352c07;
	cipher_key[5] =	  0x3b6108d7;
	cipher_key[6] =	  0x2d9810a3;
	cipher_key[7] =	  0x0914dff4;
#endif

	cpu_irq_enable();

	/* Use PLL0 at 12 MHz * 11 / 2 = 66 MHz as main clock */
	pm_init_pll(0, PM_PLL_SRC_OSC0, 1, 11, PM_PLL_OUTPUT_DIV_BY_2);
	pm_enable_pll_sync(0);
	pm_set_main_clock(PM_MAINCLK_SRC_PLL0);

	board_init();
	led_activate(POWER_ON_LED);

	dbg_init();

#ifdef CONFIG_USE_AES
	dbg_info("USB Mass Storage device with AES running\n");
#else
	dbg_info("USB Mass Storage device running\n");
#endif

	dma_pool_init();
	buffer_pool_init();
	dmaca_init();
	usb_init();

	timer_init(&main_timer, (void *)TC0_BASE, 1000);
	workqueue_init(&main_workqueue);

	udc = udc_init();
	if (!udc) {
		dbg_panic("UDC initialization failed\n");
		return 1;
	}

	if (!msc_serial_number_is_valid(
				usb_dev_string_table[USB_STRING_DEV_SERIAL],
				MAX_SERIAL_NUMBER_LEN)) {
		dbg_panic("Invalid serial number\n");
		return 1;
	}

	config = usb_func_add_config(&config_desc);

	sdmmc_host = sdmmc_mcihost_init();
	if (!sdmmc_host) {
		dbg_panic("Failed to initialize MMC host controller\n");
		return 1;
	}

	slot = sdmmc_host->get_slot(sdmmc_host, 0);
	if (!slot) {
		dbg_panic("Could not get SDMMC slot\n");
		return 1;
	}

#ifdef CONFIG_USE_AES
	aes_bdev.udc = udc;

	bdev = sdmmc_blkdev_init_new(slot, card_event, &aes_bdev);
#else
	bdev = sdmmc_blkdev_init_new(slot, card_event, udc);
#endif
	if (!bdev) {
		dbg_panic("Could not initialize block device\n");
		return 1;
	}

	sdmmc_host_enable(sdmmc_host);

#ifdef CONFIG_USE_AES
	aes_init(&aes_bdev.module, AES_KEYSIZE_128 | AES_OPMODE_ECB);
	aes_load_key(&aes_bdev.module, cipher_key);

	aesblk_init(&aes_bdev, bdev);

	/*
	 * Configure USB Mass Storage driver and associated block device
	 * interface
	 */
	msc_iface = usb_msc_func_init(&aes_bdev.bdev);
#else
	msc_iface = usb_msc_func_init(bdev);
#endif
	if (!msc_iface) {
		dbg_panic("Failed to initialize MSC interface\n");
		return 1;
	}

	watchdog_set_timeout(100);
	watchdog_enable();

	usb_func_add_interface(config, msc_iface);

	for (;;) {
		watchdog_reset();
		workqueue_do_one_item(&main_workqueue);
	}

	return 0;
}
