/**
 * @file    wm_fls.c
 *
 * @brief   flash Driver Module
 *
 * @author  dave
 *
 * Copyright (c) 2015 Winner Microelectronics Co., Ltd.
 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "list.h"
#include "wm_hostspi.h"
#include "wm_flash.h"
#include "wm_dbg.h"
#include "wm_mem.h"
#include "wm_fls_gd25qxx.h"


static struct tls_fls *spi_fls = NULL;

int tls_spifls_read_id(u32 * id)
{
    u32 cmd;
    int err;

    cmd = FLS_CMD_READ_DEV_ID;
    *id = 0;

    err = tls_spi_read_with_cmd((const u8 *) &cmd, 4, (u8 *) id, 3);

    if (err != TLS_SPI_STATUS_OK)
    {
        TLS_DBGPRT_ERR("flash read ID fail(%d)!\n", err);
        return TLS_FLS_STATUS_EIO;
    }

    TLS_DBGPRT_FLASH_INFO("flash ID - 0x%x.\n", *id);

    return TLS_FLS_STATUS_OK;
}


/**
 * @brief          This function is used to read data from the flash.
 *
 * @param[in]      addr                  is byte offset addr for read from the flash.
 * @param[in]      buf                   is user for data buffer of flash read
 * @param[in]      len                   is byte length for read.
 *
 * @retval         TLS_FLS_STATUS_OK	    if read sucsess
 * @retval         TLS_FLS_STATUS_EIO	    if read fail
 * @retval		   TLS_FLS_STATUS_EPERM     if flash driver module not beed installed
 * @retval         TLS_FLS_STATUS_ENODRV    if the current spi flash driver not installed
 * @retval         TLS_FLS_STATUS_EINVAL    if parameter error
 *
 * @note           None
 */
int tls_spifls_read(u32 addr, u8 * buf, u32 len)
{
    int err;
    u32 read_bytes;
    struct tls_fls_drv *drv;

    if (spi_fls == NULL)
    {
        TLS_DBGPRT_ERR("flash driver module not beed installed!\n");
        return TLS_FLS_STATUS_EPERM;
    }

    if (spi_fls->current_drv == NULL)
    {
        TLS_DBGPRT_ERR("the current spi flash driver not installed!\n");
        return TLS_FLS_STATUS_ENODRV;
    }

    if ((addr >= spi_fls->current_drv->total_size) || (len == 0)
        || (buf == NULL))
    {
        return TLS_FLS_STATUS_EINVAL;
    }
    tls_os_sem_acquire(spi_fls->fls_lock, 0);
    drv = spi_fls->current_drv;
    read_bytes =
        ((addr + len) > drv->total_size) ? (drv->total_size - addr) : len;

    err = TLS_FLS_STATUS_OK;

    err = drv->read(addr, buf, read_bytes);
    tls_os_sem_release(spi_fls->fls_lock);
    return err;
}

int tls_spifls_fast_read(u32 addr, u8 * buf, u32 len)
{
    int err;
    u32 read_bytes;
    struct tls_fls_drv *drv;

    if (spi_fls == NULL)
    {
        TLS_DBGPRT_ERR("flash driver module not beed installed!\n");
        return TLS_FLS_STATUS_EPERM;
    }

    if (spi_fls->current_drv == NULL)
    {
        TLS_DBGPRT_ERR("the current spi flash driver not installed!\n");
        return TLS_FLS_STATUS_ENODRV;
    }

    if ((addr >= spi_fls->current_drv->total_size) || (len == 0)
        || (buf == NULL))
    {
        return TLS_FLS_STATUS_EINVAL;
    }
    if ((spi_fls->current_drv->flags & TLS_FLS_FLAG_FAST_READ) !=
        TLS_FLS_FLAG_FAST_READ)
    {
        return TLS_FLS_STATUS_ENOSUPPORT;
    }

    drv = spi_fls->current_drv;
    read_bytes =
        ((addr + len) > drv->total_size) ? (drv->total_size - addr) : len;

    err = TLS_FLS_STATUS_OK;

    err = drv->fast_read(addr, buf, read_bytes);

    return err;
}

int tls_spifls_page_write(u32 page, u8 * buf, u32 page_cnt)
{
    int err;
    u32 write_pages;
    u32 i;
    struct tls_fls_drv *drv;

    if (spi_fls == NULL)
    {
        TLS_DBGPRT_ERR("flash driver module not beed installed!\n");
        return TLS_FLS_STATUS_EPERM;
    }

    if (spi_fls->current_drv == NULL)
    {
        TLS_DBGPRT_ERR("the current spi flash driver not installed!\n");
        return TLS_FLS_STATUS_ENODRV;
    }

    if ((page >= (spi_fls->current_drv->total_size / spi_fls->current_drv->page_size))
        || (page_cnt == 0) || (buf == NULL))
    {
        return TLS_FLS_STATUS_EINVAL;
    }

    drv = spi_fls->current_drv;
    write_pages = ((page + page_cnt) > (drv->total_size / drv->page_size)) ?
                    ((drv->total_size / drv->page_size) -page) : page_cnt;

    err = TLS_FLS_STATUS_OK;

    for (i = 0; i < write_pages; i++)
    {
        err = drv->page_write(page + i, buf + i * drv->page_size);
        if (err != TLS_FLS_STATUS_OK)
        {
            TLS_DBGPRT_ERR("flash page write fail(page %d)!\n", (page + i));
            break;
        }
    }

    return err;
}

/**
 * @brief          This function is used to write data to the flash.
 *
 * @param[in]      addr     is byte offset addr for write to the flash
 * @param[in]      buf       is the data buffer want to write to flash
 * @param[in]      len       is the byte length want to write
 *
 * @retval         TLS_FLS_STATUS_OK	        if write flash success
 * @retval         TLS_FLS_STATUS_EPERM	    	if flash struct point is null
 * @retval         TLS_FLS_STATUS_ENODRV	    if flash driver is not installed
 * @retval         TLS_FLS_STATUS_EINVAL	    if argument is invalid
 * @retval         TLS_FLS_STATUS_EIO           if io error
 * @retval         TLS_FLS_STATUS_ENOMEM        if memory allocation fail
 *
 * @note           None
 */
int tls_spifls_write(u32 addr, u8 * buf, u32 len)
{
    u8 *cache;
    int err;
    u32 sector_addr;
    u32 sector_off;
    u32 sector_remain;
    u32 cntforFF;
    u32 write_bytes;
    u32 write_page = 0;
    u32 i;
    struct tls_fls_drv *drv;

    if (spi_fls == NULL)
    {
        TLS_DBGPRT_ERR("flash driver module not beed installed!\n");
        return TLS_FLS_STATUS_EPERM;
    }

    if (spi_fls->current_drv == NULL)
    {
        TLS_DBGPRT_ERR("the current spi flash driver not installed!\n");
        return TLS_FLS_STATUS_ENODRV;
    }

    if ((addr >= spi_fls->current_drv->total_size) || (len == 0)
        || (buf == NULL))
    {
        return TLS_FLS_STATUS_EINVAL;
    }
    tls_os_sem_acquire(spi_fls->fls_lock, 0);
    drv = spi_fls->current_drv;

    err = TLS_FLS_STATUS_OK;

    cache = tls_mem_alloc(drv->sector_size);
    if (cache == NULL)
    {
        tls_os_sem_release(spi_fls->fls_lock);
        TLS_DBGPRT_ERR("allocate sector cache memory(%dB) fail!\n",
                       drv->sector_size);
        return TLS_FLS_STATUS_ENOMEM;
    }

    sector_addr = addr / drv->sector_size;				//Section addr
    sector_off = (addr % drv->sector_size);			//Offset in section
    sector_remain = drv->sector_size - sector_off;
    write_bytes = len;
    TLS_DBGPRT_FLASH_INFO("write to flash: sector address - %d, sector off - %x.\n", sector_addr,
                          sector_off);

    if(write_bytes <= sector_remain)
    {
        sector_remain = write_bytes;								//Not bigger with remain size in section
    }
    while (1)
    {
        TLS_DBGPRT_FLASH_INFO("firstly, read the sector - %d to cache.\n", sector_addr);
        err = drv->read(sector_addr * drv->sector_size, cache, drv->sector_size);
        if (err != TLS_FLS_STATUS_OK)
        {
            TLS_DBGPRT_ERR("flash read fail(sector %d)!\n", sector_addr);
            break;
        }

        cntforFF = 0;
        for(i = 0; i < sector_remain; i++)
        {
            if(cache[i + sector_off] == 0xFF)
            {
                cntforFF++;
            }
            cache[i + sector_off] = buf[i];
        }

        //If the flash address to be written is all 0xFF, then erase sector is not necessary;
        if( cntforFF != sector_remain)
        {
            TLS_DBGPRT_FLASH_INFO("second, erase the sector - %d.\n", sector_addr);
            err = drv->erase(sector_addr);
            if (err != TLS_FLS_STATUS_OK)
            {
                TLS_DBGPRT_ERR("flash erase fail(sector %d)!\n", sector_addr);
                break;
            }
        }

        if( (cntforFF != sector_remain ) || ((sector_off == 0) && (sector_remain == drv->sector_size)))
        {
            for(i = 0; i < (drv->sector_size / drv->page_size); i++)
            {
                err = drv->page_write(sector_addr * drv->sector_size / drv->page_size + i, &cache[i * drv->page_size]);
                if (err != TLS_FLS_STATUS_OK)
                {
                    TLS_DBGPRT_ERR("flash page write fail(page %d)!\n", i);
                    tls_mem_free(cache);
                    tls_os_sem_release(spi_fls->fls_lock);
                    return err;
                }
            }
        }
        else
        {
            write_page = (sector_off / drv->page_size) == 15 ? 16 : (sector_off + sector_remain) / drv->page_size + 1;
            //As long as this sector is not erased in this round, we only program the changed pages instead of all of them.
            for(i = (sector_off / drv->page_size); i < write_page; i++)
            {
                err = drv->page_write(sector_addr * drv->sector_size / drv->page_size + i, &cache[i * drv->page_size]);
                if (err != TLS_FLS_STATUS_OK)
                {
                    TLS_DBGPRT_ERR("flash page write fail(page %d)!\n", i);
                    tls_mem_free(cache);
                    tls_os_sem_release(spi_fls->fls_lock);
                    return err;
                }
            }
        }

        if(write_bytes == sector_remain)
        {
            break;
        }
        else
        {
            sector_addr++;
            sector_off = 0;
            buf += sector_remain;
            write_bytes -= sector_remain;
            if(write_bytes > (drv->sector_size))
                sector_remain = drv->sector_size;
            else
                sector_remain = write_bytes;					//Next section will finish
        }
    }

    tls_mem_free(cache);
    tls_os_sem_release(spi_fls->fls_lock);
    return err;
}


int tls_spifls_erase(u32 sector)
{
    int err;
    struct tls_fls_drv *drv;

    if (spi_fls == NULL)
    {
        TLS_DBGPRT_ERR("flash driver module not beed installed!\n");
        return TLS_FLS_STATUS_EPERM;
    }

    if (spi_fls->current_drv == NULL)
    {
        TLS_DBGPRT_ERR("the current spi flash driver not installed!\n");
        return TLS_FLS_STATUS_ENODRV;
    }

    if (sector >= (spi_fls->current_drv->total_size / spi_fls->current_drv->sector_size))
    {
        TLS_DBGPRT_ERR("the sector to be erase overflow!\n");
        return TLS_FLS_STATUS_EINVAL;
    }
    tls_os_sem_acquire(spi_fls->fls_lock, 0);
    drv = spi_fls->current_drv;

    err = TLS_FLS_STATUS_OK;

    err = drv->erase(sector);
    tls_os_sem_release(spi_fls->fls_lock);
    return err;
}

int tls_spifls_chip_erase(void)
{
    int err;
    struct tls_fls_drv *drv;

    if (spi_fls == NULL)
    {
        TLS_DBGPRT_ERR("flash driver module not beed installed!\n");
        return TLS_FLS_STATUS_EPERM;
    }

    if (spi_fls->current_drv == NULL)
    {
        TLS_DBGPRT_ERR("the current spi flash driver not installed!\n");
        return TLS_FLS_STATUS_ENODRV;
    }

    drv = spi_fls->current_drv;

    err = TLS_FLS_STATUS_OK;

    err = drv->chip_erase();

    return err;
}

int tls_spifls_get_param(u8 type, void *param)
{
    int err;
    struct tls_fls_drv *drv;

    if (spi_fls == NULL)
    {
        TLS_DBGPRT_ERR("flash driver module not beed installed!\n");
        return TLS_FLS_STATUS_EPERM;
    }

    if (spi_fls->current_drv == NULL)
    {
        TLS_DBGPRT_ERR("the current spi flash driver not installed!\n");
        return TLS_FLS_STATUS_ENODRV;
    }

    if (param == NULL)
    {
        return TLS_FLS_STATUS_EINVAL;
    }
    tls_os_sem_acquire(spi_fls->fls_lock, 0);
    drv = spi_fls->current_drv;
    err = TLS_FLS_STATUS_OK;
    switch (type)
    {
        case TLS_FLS_PARAM_TYPE_ID:
            *((u32 *) param) = drv->id;
            break;

        case TLS_FLS_PARAM_TYPE_SIZE:
            *((u32 *) param) = drv->total_size;
            break;

        case TLS_FLS_PARAM_TYPE_PAGE_SIZE:
            *((u32 *) param) = drv->page_size;
            break;

        case TLS_FLS_PARAM_TYPE_PROG_SIZE:
            *((u32 *) param) = drv->program_size;
            break;

        case TLS_FLS_PARAM_TYPE_SECTOR_SIZE:
            *((u32 *) param) = drv->sector_size;
            break;

        default:
            TLS_DBGPRT_WARNING("invalid parameter ID!\n");
            err = TLS_FLS_STATUS_EINVAL;
            break;
    }
    tls_os_sem_release(spi_fls->fls_lock);
    return err;
}

int tls_spifls_drv_register(struct tls_fls_drv *fls_drv)
{
    u32 cpu_sr;
    struct tls_fls_drv *drv;

    if (fls_drv == NULL)
    {
        TLS_DBGPRT_ERR("flash driver module not beed installed!\n");
        return TLS_FLS_STATUS_EINVAL;
    }

    dl_list_for_each(drv, &spi_fls->fls_drvs, struct tls_fls_drv, drv_list)
    {
        if (drv->id == fls_drv->id)
        {
            TLS_DBGPRT_WARNING
                ("corresponding spi flash driver has registered!\n");
            return TLS_FLS_STATUS_EEXIST;
        }
    }

    cpu_sr = tls_os_set_critical();
    dl_list_add_tail((struct dl_list *) &spi_fls->fls_drvs,
                     (struct dl_list *) &fls_drv->drv_list);
    tls_os_release_critical(cpu_sr);

    TLS_DBGPRT_FLASH_INFO("the spi flash driver is registered successfully!\n");

    return TLS_FLS_STATUS_OK;
}

int tls_spifls_drv_unregister(struct tls_fls_drv *fls_drv)
{
    TLS_DBGPRT_WARNING
        ("unregister spi flash driver operation is not supported!\n");
    return TLS_FLS_STATUS_EPERM;
}

int tls_spifls_probe(void)
{
    int err;
    u32 id;
    struct tls_fls_drv *drv;

    if (spi_fls == NULL)
    {
        TLS_DBGPRT_ERR("flash driver module not beed installed!\n");
        return TLS_FLS_STATUS_EPERM;
    }
    if (spi_fls->current_drv != NULL)
    {
        TLS_DBGPRT_ERR("the current spi flash has fount the matched driver!\n");
        return TLS_FLS_STATUS_EBUSY;
    }

    TLS_DBGPRT_FLASH_INFO("try to read the current spi flash ID.\n");
    err = tls_spifls_read_id(&id);
    if (err != TLS_FLS_STATUS_OK)
    {
        return err;
    }

    TLS_DBGPRT_FLASH_INFO("current spi flash ID - 0x%x.\n", id);

    dl_list_for_each(drv, &spi_fls->fls_drvs, struct tls_fls_drv, drv_list)
    {
        err = drv->probe(id);
        if (err != TLS_FLS_STATUS_OK)
        {
            return err;
        }

        tls_spi_setup(drv->mode, drv->cs_active, drv->clock);

        TLS_DBGPRT_FLASH_INFO("matched the spi flash driver.\n");
        spi_fls->current_drv = drv;
        break;
    }

    if (spi_fls->current_drv == NULL)
    {
        TLS_DBGPRT_WARNING("not found the matched spi flash driver!\n");
        return TLS_FLS_STATUS_ENODRV;
    }

    return TLS_FLS_STATUS_OK;
}

int tls_spifls_init(void)
{
    struct tls_fls *fls;
    int err;

    if (spi_fls != NULL)
    {
        TLS_DBGPRT_ERR("flash driver module has been installed!\n");
        return TLS_FLS_STATUS_EBUSY;
    }

    fls = (struct tls_fls *) tls_mem_alloc(sizeof(struct tls_fls));
    if (fls == NULL)
    {
        TLS_DBGPRT_ERR("allocate @spi_fls fail!\n");
        return TLS_FLS_STATUS_ENOMEM;
    }

    memset(fls, 0, sizeof(*fls));
    dl_list_init((struct dl_list *) &fls->fls_drvs);
    err = tls_os_sem_create(&fls->fls_lock, 1);
    if (err != TLS_OS_SUCCESS)
    {
        tls_mem_free(fls);
        TLS_DBGPRT_ERR("create semaphore @fls_lock fail!\n");
        return TLS_FLS_STATUS_ENOMEM;
    }
    spi_fls = fls;

    tls_spifls_drv_install();

    return TLS_FLS_STATUS_OK;
}

int tls_spifls_exit(void)
{
    TLS_DBGPRT_FLASH_INFO("Not support flash driver module uninstalled!\n");
    return TLS_FLS_STATUS_EPERM;
}

