/***************************************************************************//**
 * (c) Copyright 2012-2013 Microsemi SoC Products Group.  All rights reserved.
 *
 * USB MSC Class Storage Device example aplication to demonstrate the
 * SmartFusion2 MSS USB operations in device mode.
 *
 * Drivers used:
 * Smartfusion2 MSS USB Driver stack (inclusive of USBD-MSC class driver).
 * MSS_SPI driver is used to access on board flash at25df641.
 *
 * SVN $Revision: 5468 $
 * SVN $Date: 2013-03-29 15:38:01 +0530 (Fri, 29 Mar 2013) $
 */

#include "flash_drive_app.h"
#include "mss_assert.h"
#include "mss_gpio.h"
#include "winbondflash.h"
#include "mss_usb_device.h"
#include "mss_usb_device_msd.h"

#ifdef __cplusplus
extern "C" {
#endif

/******************************************************************************
  Private data structures
*/
/*
 Number of LUNs supported 2. LUN0 and LUN1
 When set to 2, two drives of capacity 4Mb each will appear on Host.
 When set to 1, one drive of capacity 8Mb will appear on Host.

 Note: Make sure to re-format the drive if you change this value.
 */

#define NUMBER_OF_LUNS_ON_DRIVE                                1u

/*Type to store information of each LUN*/
typedef struct flash_lun_data {
    uint32_t number_of_blocks;
    uint32_t erase_block_size;
    uint32_t lba_block_size;
} flash_lun_data_t;

/******************************************************************************
  Private function declarations
*/
uint8_t* usb_flash_media_inquiry(uint8_t lun, uint32_t *len);
uint8_t usb_flash_media_init (uint8_t lun);
uint8_t usb_flash_media_get_capacity(uint8_t lun, uint32_t *no_of_blocks, uint32_t *block_size);
uint8_t usb_flash_media_is_ready(uint8_t lun);
uint8_t usb_flash_media_is_write_protected(uint8_t lun);
uint32_t usb_flash_media_read(uint8_t lun, uint8_t **buf, uint32_t lba_addr, uint32_t len);
uint8_t* usb_flash_media_acquire_write_buf(uint8_t lun, uint32_t blk_addr, uint32_t *len);
uint32_t usb_flash_media_write_ready(uint8_t lun, uint32_t blk_addr, uint32_t len);
uint8_t usb_flash_media_get_max_lun(void);

/* Implementation of mss_usbd_msc_media_t needed by USB MSD Class Driver*/
mss_usbd_msc_media_t usb_flash_media = {
    usb_flash_media_init,
    usb_flash_media_get_capacity,
    usb_flash_media_is_ready,
    usb_flash_media_is_write_protected,
    usb_flash_media_read,
    usb_flash_media_acquire_write_buf,
    usb_flash_media_write_ready,
    usb_flash_media_get_max_lun,
    usb_flash_media_inquiry
};

extern mss_usbd_user_descr_cb_t flash_drive_descriptors_cb;

uint8_t  lun0_data_buffer[4096] = {0};
uint8_t  lun1_data_buffer[4096] = {0};
uint32_t lun1_start_addr = 0x400000;

/*
On board flash AT25DF641 is Controlled through MSS_SPI0.
Size of flash = 8MBytes (128 sectors of 64KBytes each)
SCSI protocol block size = 512
Number of SCSI logical blocks = 0x3FFF
Dividing flash into 2 halves for 2 LUNs
*/
#if (NUMBER_OF_LUNS_ON_DRIVE == 2)
flash_lun_data_t lun_data[NUMBER_OF_LUNS_ON_DRIVE] = {{0x1FFFu, 4096u, 512u},
                                                      {0x1FFFu, 4096u, 512u}};
#elif (NUMBER_OF_LUNS_ON_DRIVE == 1)
flash_lun_data_t lun_data[NUMBER_OF_LUNS_ON_DRIVE] = {{0x3FFFu, 4096u, 512u}};
#endif

#if (NUMBER_OF_LUNS_ON_DRIVE == 2)
mss_usbd_msc_scsi_inq_resp_t usb_flash_media_inquiry_data[NUMBER_OF_LUNS_ON_DRIVE] =
{
    {
        0x00u,                                          /* peripheral */
        0x80u,                                          /* removable */
        0x04u,                                          /* version */
        0x02u,                                          /* resp_data_format */
        0x20u,                                          /* additional_length */
        0x00u,                                          /* sccstp */
        0x00u,                                          /* bqueetc */
        0x00u,                                          /* cmd_que */
        "MSCC    ",                                     /* vendor_id[8] */
        "SmartFusion2_msd",                             /* product_id[16] */
        "1234"                                          /* product_rev[4] */
    }
    ,
    {
        0x00u,                                          /* peripheral */
        0x80u,                                          /* removable */
        0x04u,                                          /* version */
        0x02u,                                          /* resp_data_format */
        0x20u,                                          /* additional_length */
        0x00u,                                          /* sccstp */
        0x00u,                                          /* bqueetc */
        0x00u,                                          /* cmd_que */
        "MSCC    ",                                     /* vendor_id[8] */
        "SmartFusion2_msd",                             /* product_id[16] */
        "1234"                                          /* product_rev[4] */
    }
};
#elif (NUMBER_OF_LUNS_ON_DRIVE == 1)
mss_usbd_msc_scsi_inq_resp_t usb_flash_media_inquiry_data[NUMBER_OF_LUNS_ON_DRIVE] =
{{
    0x00u,                                              /* peripheral */
    0x80u,                                              /* removable */
    0x04u,                                              /* version */
    0x02u,                                              /* resp_data_format */
    0x20u,                                              /* additional_length */
    0x00u,                                              /* sccstp */
    0x00u,                                              /* bqueetc */
    0x00u,                                              /* cmd_que */
    "MSCC    ",                                         /* vendor_id[8] */
    "SmartFusion2_msd",                                 /* product_id[16] */
    "1234"                                              /* product_rev[4] */
}};
#endif

/******************************************************************************
  See flash_drive_app.h for details of how to use this function.
*/
void
FLASH_DRIVE_init
(
    void
)
{
                               /*Disable Watch-dog*/

    //MSS_GPIO_init();
    //MSS_GPIO_config(MSS_GPIO_29 , MSS_GPIO_OUTPUT_MODE);

    /*Keep USB PHY out of Reset*/
   // MSS_GPIO_set_output(MSS_GPIO_29 , 0);

    /*Initialize SPI flash. Initialization of mss_spi driver is taken care by this function.*/
    //FLASH_init();
    //FLASH_global_unprotect();

    /*Initialize USB driver*/
    //MSS_USBD_init(MSS_USB_DEVICE_HS);

    /*Assign call-back function Interface needed by USBD driver*/
    MSS_USBD_set_descr_cb_handler(&flash_drive_descriptors_cb);

    /*Assign call-back function handler structure needed by MSD class driver*/
    MSS_USBD_MSC_init(&usb_flash_media, MSS_USB_DEVICE_HS);
}

/******************************************************************************
  Local function definitions
*/
uint8_t*
usb_flash_media_inquiry
(
    uint8_t lun,
    uint32_t *len
)
{
    *len = sizeof(usb_flash_media_inquiry_data[lun]);
    return ((uint8_t*)&usb_flash_media_inquiry_data[lun]);
}

uint8_t
usb_flash_media_init
(
    uint8_t lun
)
{
    return 1;
}

uint8_t
usb_flash_media_get_max_lun
(
    void
)
{
    return NUMBER_OF_LUNS_ON_DRIVE;
}

uint8_t
usb_flash_media_get_capacity
(
    uint8_t lun,
    uint32_t *no_of_blocks,
    uint32_t *block_size
)
{
    *no_of_blocks = lun_data[lun].number_of_blocks;
    *block_size = lun_data[lun].lba_block_size;
    return 1;
}

uint32_t
usb_flash_media_read
(
    uint8_t lun,
    uint8_t **buf,
    uint32_t lba_addr,
    uint32_t len
)
{
    if(lun == 0)
    {
        if(len > sizeof(lun0_data_buffer))
        {
            len = sizeof(lun0_data_buffer);
        }
        FLASH_read(lba_addr, lun0_data_buffer, len);

        *buf = lun0_data_buffer;
    }
    else if(lun == 1)
    {
         if(len > sizeof(lun1_data_buffer))
        {
            len = sizeof(lun1_data_buffer);
        }

        FLASH_read(lba_addr + lun1_start_addr, lun1_data_buffer, len);

        *buf = lun1_data_buffer;
    }
    return len;
}

uint8_t*
usb_flash_media_acquire_write_buf
(
    uint8_t lun,
    uint32_t blk_addr,
    uint32_t *len
)
{
    uint32_t block_start_addr;
    uint32_t block_offset;

    block_start_addr = blk_addr & ~((uint32_t)lun_data[lun].erase_block_size - 1u);
    block_offset = blk_addr - block_start_addr;

    /*Restrict the write operation to current erase block*/
    *len = ((uint32_t)(lun_data[lun].erase_block_size) - block_offset);
    if(lun == 0)
    {
      return lun0_data_buffer;
    }
    else if(lun==1)
    {
      return lun1_data_buffer;
    }
    else
    {
        return((uint8_t*)0);
    }
}

uint32_t
usb_flash_media_write_ready
(
    uint8_t lun,
    uint32_t blk_addr,
    uint32_t len
)
{
    uint32_t block_start_addr;
    uint32_t block_offset;
    uint32_t idx;

    block_start_addr = blk_addr & ~((uint32_t)lun_data[lun].erase_block_size - 1u);
    block_offset = blk_addr - block_start_addr;

    ASSERT((block_start_addr + len) <
           (lun_data[lun].number_of_blocks * lun_data[lun].lba_block_size));

    ASSERT((block_offset + len) <= lun_data[lun].erase_block_size);

    if(lun == 0)
    {
        /*
        4k buffer corresponds to 4k erase block.
        Move data to offset location in buffer
        */
        if((block_offset != 0) && ((block_offset + len) <= lun_data[lun].erase_block_size))
        {
            for(idx = len; idx > 0; idx--)
            {
                lun0_data_buffer[block_offset + (idx - 1)] = lun0_data_buffer[idx - 1];
            }
        }

        /*Feel the buffer up-to offset location with current memory content*/
        FLASH_read(block_start_addr, lun0_data_buffer, (block_offset));

        /*Making sure that Block start address is within the flash size address space
          and Data write is happening within the erased block.
        */
        if((block_start_addr < (lun_data[lun].number_of_blocks*lun_data[lun].lba_block_size)) &&
            ((block_offset + len) <= lun_data[lun].erase_block_size))
        {
            /*Feel the remainder of buffer(if any) with current memory content*/
            FLASH_read((block_start_addr + block_offset + len),
                        &lun0_data_buffer[block_offset + len],
                        (lun_data[lun].erase_block_size - (block_offset + len)));

            /* Erase 4k block. */
            FLASH_erase_4k_block(block_start_addr);

            /*Write the new content of the buffer to the flash*/
            FLASH_program(block_start_addr,
                           lun0_data_buffer,
                           sizeof(lun0_data_buffer));
        }
    }
    else if (lun == 1)
    {
        if((block_offset != 0) &&
            ((block_offset + len) <= lun_data[lun].erase_block_size))
        {
            /*
            4k buffer corresponds to 4k erase block.
            Move data to offset location in buffer
            */
            for(idx = len; idx > 0; idx--)
            {
                lun1_data_buffer[block_offset + (idx - 1)] = lun1_data_buffer[idx - 1];
            }
        }

        /*Feel the buffer up-to offset location with current memory content*/
        FLASH_read((block_start_addr + lun1_start_addr), lun1_data_buffer, (block_offset));

        /*Making sure that Block start address is within the flash size address space
          and Data write is happening with the erased block.
        */
        if((block_start_addr < (lun_data[lun].number_of_blocks * lun_data[lun].lba_block_size)) &&
            ((block_offset + len) <= lun_data[lun].erase_block_size))
        {
            /*Feel the remainder of buffer(if any) with current content*/
            FLASH_read((lun1_start_addr + block_start_addr + block_offset + len),
                        &lun1_data_buffer[block_offset + len],
                        (lun_data[lun].erase_block_size - (block_offset + len)));

            /* Erase 4k block. */
            FLASH_erase_4k_block(block_start_addr + lun1_start_addr);

            /*Write the new content of the buffer to the flash*/
            FLASH_program((block_start_addr + lun1_start_addr),
                          lun1_data_buffer,
                          sizeof(lun1_data_buffer));
        }
    }
    else
    {
        return 0;
    }
    return 1;
}

uint8_t
usb_flash_media_is_ready
(
    uint8_t lun
)
{
    return 1;
}

uint8_t
usb_flash_media_is_write_protected
(
    uint8_t lun
)
{
    return 1;
}

#ifdef __cplusplus
}
#endif
