/*
 * Copyright (c) 2013 - 2014, Freescale Semiconductor, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * o Redistributions of source code must retain the above copyright notice, this list
 *   of conditions and the following disclaimer.
 *
 * o 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.
 *
 * o Neither the name of Freescale Semiconductor, Inc. nor the names of its
 *   contributors may be used to endorse or promote products derived from this
 *   software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS 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 "spi_flash_internal.h"
#include "fsl_os_abstraction.h"
#include "spi_flash/fsl_spi_abstraction.h"
#include "spi_flash/spi_flash.h"
#include "fsl_misc_utilities.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static spi_slave_dev spi_slave;

////////////////////////////////////////////////////////////////////////////////
// Definitions
////////////////////////////////////////////////////////////////////////////////

/*!
 * @brief    weak function to activate SS gpio pin.
 *
 * @param[in] spi     spi device.
 *
 * @return    status  error code.
 */
#pragma weak spi_cs_activate
int32_t spi_cs_activate(spi_slave_dev *spi)
{
    return 0;
}

/*!
 * @brief    weak function to de-activate SS gpio pin.
 *
 * @param[in] spi     spi device.
 *
 * @return    status  error code.
 */
#pragma weak spi_cs_deactivate
int32_t spi_cs_deactivate(spi_slave_dev *spi)
{
    return 0;
}

/*!
 * @brief    Convert address to command array.
 *
 * @param[in] addr     read or write address.
 * @param[out] cmd     command array.
 */
static void spi_flash_addr(uint32_t addr, uint8_t *cmd)
{
    /* cmd[0] is actual command */
    cmd[1] = addr >> 16;
    cmd[2] = addr >> 8;
    cmd[3] = addr >> 0;
}

/*!
 * @brief    Common funcion to read and write a spi flash.
 *
 * @param[in] instance     spi instance index.
 * @param[in] spi          spi slave device.
 * @param[in] cmd          command that will be sent to flash.
 * @param[in] cmd_len      Length of command.
 * @param[out] data_out    Output data buffer.
 * @param[in] data_in      input data buffer.
 * @param[in] data_len     data length.
 *
 * @return    status       error code.
 */
static int32_t spi_flash_read_write(uint32_t instance, spi_slave_dev *spi,
                const uint8_t *cmd, size_t cmd_len,
                const uint8_t *data_out, uint8_t *data_in,
                size_t data_len)
{
    int32_t ret;
    spi_slave_dev *spi_dev = spi;

    spi_cs_activate(spi);

    ret = spi_xfer(spi_dev, cmd, NULL, cmd_len);
    if (ret)
    {
#ifdef DEBUG_SF
        printf("SF: Failed to send command (%zu bytes): %d\r\n",
                cmd_len, ret);
#endif
    }
    else if (data_len != 0)
    {
        ret = spi_xfer(spi_dev, data_out, data_in, data_len);
        if (ret)
        {
#ifdef DEBUG_SF
            printf("SF: Failed to transfer %u bytes of data: %d\r\n",
                    data_len, ret);
#endif
        }
    }
    spi_cs_deactivate(spi);

    return ret;
}

/*!
 * @brief    Common funcion to send a command to spi flash.
 *
 * @param[in] instance     spi instance index.
 * @param[in] spi          spi slave device.
 * @param[in] cmd          command that will be sent to flash.
 * @param[out] response    Output response data.
 * @param[in] len          response data length.
 *
 * @return    status       error code.
 */
int32_t spi_flash_cmd(uint32_t instance, spi_slave_dev *spi, uint8_t cmd, void *response, size_t len)
{
    return spi_flash_cmd_read(instance, spi, &cmd, 1, response, len);
}

/*!
 * @brief    Common funcion to read data from a spi flash.
 *
 * @param[in] instance     spi instance index.
 * @param[in] spi          spi slave device.
 * @param[in] cmd          command that will be sent to flash.
 * @param[in] cmd_len      Length of command.
 * @param[out] data        Output data buffer.
 * @param[in] data_len     data length.
 *
 * @return    status       error code.
 */
int32_t spi_flash_cmd_read(uint32_t instance, spi_slave_dev *spi, const uint8_t *cmd,
        size_t cmd_len, uint8_t *data, size_t data_len)
{
    return spi_flash_read_write(instance, spi, cmd, cmd_len, NULL, data, data_len);
}

/*!
 * @brief    Common funcion to write a page of data to a spi flash.
 *
 * @param[in] instance     spi instance index.
 * @param[in] spi          spi slave device.
 * @param[in] cmd          command that will be sent to flash.
 * @param[in] cmd_len      Length of command.
 * @param[in] data         input data buffer.
 * @param[in] data_len     data length.
 *
 * @return    status       error code.
 */
int32_t spi_flash_cmd_write(uint32_t instance, spi_slave_dev *spi, const uint8_t *cmd, size_t cmd_len,
        const uint8_t *data, size_t data_len)
{
    return spi_flash_read_write(instance, spi, cmd, cmd_len, data, NULL, data_len);
}

/*!
 * @brief    Common funcion to write multiply page of data to a spi flash.
 *
 * @param[in] flash        spi flash structure.
 * @param[in] offset       offset in spi flash.
 * @param[in] len          data length.
 * @param[in] buf          input data buffer.
 *
 * @return    status       error code.
 */
int32_t spi_flash_cmd_write_multi(spi_flash *flash, uint32_t offset,
        size_t len, const uint8_t *buf)
{
    uint32_t page_addr, byte_addr, page_size;
    size_t chunk_len, actual;
    int32_t ret = 0;
    uint8_t cmd[4];

    page_size = flash->page_size;
    page_addr = offset / page_size;
    byte_addr = offset % page_size;

    cmd[0] = CMD_PAGE_PROGRAM;
    for (actual = 0; actual < len; actual += chunk_len)
    {
        chunk_len = MIN(len - actual, page_size - byte_addr);

        cmd[1] = page_addr >> 8;
        cmd[2] = page_addr;
        cmd[3] = byte_addr;

#ifdef DEBUG_SF
        printf("PP: 0x%p => cmd = { 0x%x 0x%x 0x%x 0x%x } chunk_len = %u\r\n",
              (buf + actual), cmd[0], cmd[1], cmd[2], cmd[3], chunk_len);
#endif

        ret = spi_flash_cmd_write_enable(flash);
        if (ret < 0)
        {
            printf("SF: enabling write failed\r\n");
        }
	else
	{
            ret = spi_flash_cmd_write(flash->instance, flash->spi, cmd, 4,
                          (buf + actual), chunk_len);
            if (ret < 0)
            {
                printf("SF: write failed\r\n");
            }
	    else
	    {
                ret = spi_flash_cmd_wait_ready(flash, SPI_FLASH_PROG_TIMEOUT);
                if (!ret)
                {
                    byte_addr += chunk_len;
                    if (byte_addr == page_size)
                    {
                        page_addr++;
                        byte_addr = 0;
                    }
                }
            }
        }
    }

    printf("SF: program %s %u bytes @ 0x%x\r\n",
          ret ? "failure" : "success", len, (unsigned int)offset);

    return ret;
}

/*!
 * @brief    Common funcion to read data from a spi flash.
 *
 * @param[in] flash        spi flash structure.
 * @param[in] offset       offset in spi flash.
 * @param[in] len          data length.
 * @param[in] buf          output data buffer.
 *
 * @return    status       error code.
 */
int32_t spi_flash_cmd_read_fast(spi_flash *flash, uint32_t offset,
        size_t len, uint8_t *data)
{
    uint8_t cmd[5];
    int32_t ret;

    cmd[0] = CMD_READ_ARRAY_FAST;
    spi_flash_addr(offset, cmd);
    cmd[4] = 0x00;

    ret = spi_flash_cmd_read(flash->instance, flash->spi, cmd, sizeof(cmd), data, len);
    printf("SF: read %s %u bytes @ 0x%x\r\n",
          ret ? "failure" : "success", len, (unsigned int)offset);
    return ret;
}

/*!
 * @brief    Send a command to the device and wait for some bit to clear itself.
 *
 * @param[in] flash        spi flash structure.
 * @param[in] timeout      timeout duration.
 * @param[in] cmd          status command.
 * @param[in] poll_bit     status bit that need to be checked.
 *
 * @return    status       error code.
 */
int32_t spi_flash_cmd_poll_bit(spi_flash *flash, unsigned long timeout,
               uint8_t cmd, uint8_t poll_bit)
{
    spi_slave_dev *spi = flash->spi;
    int ret;
    uint8_t status = 0xff;

    spi_cs_activate(spi);
    ret = spi_xfer(spi, &cmd, NULL, 1);
    if (ret)
    {
#ifdef DEBUG_SF
        printf("SF: Failed to send command %02x: %d\r\n", cmd, ret);
#endif
        return ret;
    }

    do {
        ret = spi_xfer(spi, NULL, &status, 1);
        if (ret)
        {
            return 1;
        }

        if ((status & poll_bit) == 0)
        {
            break;
        }
        time_delay(1);
    } while (timeout--);

    spi_cs_deactivate(spi);

    if ((status & poll_bit) == 0)
    {
        return 0;
    }

    /* Timed out */
    printf("SF: time out!\r\n");
    return 1;
}

/*!
 * @brief    Send the read status command to the device and wait for the wip (write-in-progress) bit to clear itself.
 *
 * @param[in] flash        spi flash structure.
 * @param[in] timeout      timeout duration.
 *
 * @return    status       error code.
 */
int32_t spi_flash_cmd_wait_ready(spi_flash *flash, unsigned long timeout)
{
    return spi_flash_cmd_poll_bit(flash, timeout,
        CMD_READ_STATUS, STATUS_WIP);
}

/*!
 * @brief    Common funcion erase blocks in a spi flash.
 *
 * @param[in] flash        spi flash structure.
 * @param[in] offset       offset in spi flash to erase from.
 * @param[in] len          length in spi flash to be erased.
 *
 * @return    status       error code.
 */
int32_t spi_flash_cmd_erase(spi_flash *flash, uint32_t offset, size_t len)
{
    uint32_t erase_start, erase_end, erase_size;
    int ret;
    uint8_t cmd[4];
    uint32_t instance = flash->instance;

    erase_size = flash->sector_size;
    if ((offset % erase_size) || (len % erase_size))
    {
        printf("SF: Erase offset/length not multiple of erase size\r\n");
        return 1;
    }

    if (erase_size == 4096)
    {
        cmd[0] = CMD_ERASE_4K;
    }
    else
    {
        cmd[0] = CMD_ERASE_64K;
    }
    erase_start = offset;
    erase_end = erase_start + len;

    while (offset < erase_end)
    {
        spi_flash_addr(offset, cmd);
        offset += erase_size;
#ifdef DEBUG_SF
        printf("SF: erase %x %x %x %x (%x)\r\n", cmd[0], cmd[1],
              cmd[2], cmd[3], offset);
#endif

        ret = spi_flash_cmd_write_enable(flash);
        if (ret)
        {
            return ret;
        }

        ret = spi_flash_cmd_write(instance, flash->spi, cmd, sizeof(cmd), NULL, 0);
        if (ret)
        {
            return ret;
        }

        ret = spi_flash_cmd_wait_ready(flash, SPI_FLASH_PAGE_ERASE_TIMEOUT);
        if (ret)
        {
            return ret;
        }
    }

    printf("SF: Successfully erased %u bytes @ 0x%x\r\n", len, (unsigned int)erase_start);

    return ret;
}

/*!
 * @brief    Program the status register.
 *
 * @param[in] flash        spi flash instance.
 * @param[in] sr           value that will be set to status register.
 *
 * @return    status       error code.
 */
int32_t spi_flash_cmd_write_status(spi_flash *flash, uint8_t sr)
{
    uint8_t cmd;
    int32_t ret;
    uint32_t instance = flash->instance;

    ret = spi_flash_cmd_write_enable(flash);
    if (ret < 0)
    {
        printf("SF: enabling write failed\r\n");
        return ret;
    }

    cmd = CMD_WRITE_STATUS;
    ret = spi_flash_cmd_write(instance, flash->spi, &cmd, 1, &sr, 1);
    if (ret)
    {
        printf("SF: fail to write status register\r\n");
        return ret;
    }

    ret = spi_flash_cmd_wait_ready(flash, SPI_FLASH_PROG_TIMEOUT);
    if (ret < 0)
    {
#ifdef DEBUG_SF
        printf("SF: write status register timed out\r\n");
#endif
        return ret;
    }

    return 0;
}

/*!
 * @brief The following table holds all device probe functions
 *
 *! shift:  number of continuation bytes before the ID
 *! idcode: the expected IDCODE or 0xff for non JEDEC devices
 *! probe:  the function to call
 *!
 *! Non JEDEC devices should be ordered in the table such that
 *! the probe functions with best detection algorithms come first.
 *!
 *! Several matching entries are permitted, they will be tried
 *! in sequence until a probe function returns non NULL.
 *!
 *! IDCODE_CONT_LEN may be redefined if a device needs to declare a
 *! larger "shift" value.  IDCODE_PART_LEN generally shouldn't be
 *! changed.  This is the max number of bytes probe functions may
 *! examine when looking up part-specific identification info.
 *!
 *! Probe functions will be given the idcode buffer starting at their
 *! manu id byte (the "idcode" in the table below).  In other words,
 *! all of the continuation bytes will be skipped (the "shift" below).
 */
#define IDCODE_CONT_LEN 0
#define IDCODE_PART_LEN 5
static const struct
{
    const uint8_t shift;
    const uint8_t idcode;
    spi_flash *(*probe) (uint32_t instance, spi_slave_dev *spi, uint8_t *id_code);
} flashes[] =
{
    { 0, 0x1f, spi_flash_probe_atmel },
};
#define IDCODE_LEN (IDCODE_CONT_LEN + IDCODE_PART_LEN)

/*!
 * @brief    Probe a spi flash.
 *
 * @param[in] bus          spi master device index.
 * @param[in] cs           spi slave device SS pin index.
 * @param[in] max_hz       max hz supported by spi flash.
 * @param[in] mode         clock phase and polarity mode.
 *
 * @return    spi_flash    instance for a detected spi flash.
 */
spi_flash *spi_flash_probe(uint32_t bus, uint32_t cs,
        uint32_t max_hz, uint32_t spi_mode)
{
    spi_flash *flash = NULL;
    int ret, i, shift;
    uint8_t idcode[IDCODE_LEN], *idp;

    spi_init(&spi_slave, bus, cs, max_hz, spi_mode);

    spi_cs_gpio_init(bus);

    sw_timer_init_service();

    /* Read the ID codes */
    ret = spi_flash_cmd(bus, &spi_slave, CMD_READ_ID, &idcode, sizeof(idcode));
    if (ret)
    {
        return NULL;
    }

#ifdef DEBUG_SF
    printf("SF: Got idcode %x %x %x %x %x\r\n",
        idcode[0], idcode[1], idcode[2], idcode[3], idcode[4]);
#endif

    /* count the number of continuation bytes */
    shift = 0;
    idp = idcode;
    while (shift < IDCODE_CONT_LEN)
    {
        if (*idp != 0x7f)
	{
            break;
        }
        ++shift;
        ++idp;
    }

    /* search the table for matches in shift and id */
    for (i = 0; i < ARRAY_SIZE(flashes); ++i)
    {
        if ((flashes[i].shift == shift) && (flashes[i].idcode == *idp))
	{
            /* we have a match, call probe */
            flash = flashes[i].probe(bus, &spi_slave, idp);
            if (flash)
            {
                break;
            }
        }
    }

    if (!flash) {
        printf("SF: Unsupported manufacturer %02x\r\n", *idp);
        return NULL;
    }

    flash->instance = bus;
    flash->spi = &spi_slave;

    printf("SF: Detected %s with page size %d, total %d\r\n",
           flash->name, (int)flash->sector_size, (int)flash->size);

    return flash;
}

/*!
 * @brief    Alloc access functions for a detected spi flash.
 *
 * @param[in] offset       spi_flash structure offset in spi flash structure.
 * @param[in] size         size of spi flash structure.
 * @param[in] spi          spi slave device for the spi flash.
 * @param[in] name         id of the spi flash.
 *
 * @return    pointer      pointer of spi flash sturcture.
 */
void *spi_flash_alloc(int32_t offset, int32_t size, void *sf, spi_slave_dev *spi,
             const char *name)
{
    spi_flash *flash;
    uint8_t *ptr;

    ptr = (uint8_t *)sf;
    memset(ptr, '\0', size);
    flash = (spi_flash *)(ptr + offset);

    /* Set up some basic fields - caller will sort out sizes */
    flash->spi = spi;
    flash->name = name;

    flash->read = spi_flash_cmd_read_fast;
    flash->write = spi_flash_cmd_write_multi;
    flash->erase = spi_flash_cmd_erase;

    return (void *)ptr;
}

/*!
 * @brief    free a spi flash device.
 *
 * @param[in] flash        spi flash instance.
 *
 * @return    void
 */
void spi_flash_free(spi_flash *flash)
{
}

/*******************************************************************************
 * EOF
 ******************************************************************************/
