/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#include <string.h>

#include "os/mynewt.h"
#include <hal/hal_spi.h>
#include <hal/hal_gpio.h>
#include <hal/hal_flash.h>
#include <hal/hal_flash_int.h>
#include <at45db/at45db.h>

/**
 * Memory Architecture:
 *
 * Device can be addressed using pages, blocks or sectors.
 *
 * 1) Page
 *    - device has 8192 pages of 512 (or 528) bytes.
 *
 * 2) Block
 *    - device has 1024 blocks of 4K (or 4K + 128) bytes.
 *    - Each block contains 8 pages, eg, Block 0 == Page 0 to 7, etc.
 *
 * 3) Sector
 *    - Sector 0 == Block 0.
 *    - Sector 1 == Blocks 1 to 63 (252K + 8064).
 *    - Sector 2 to 16 contain 64 blocks (256K + 8192).
 */

#define MEM_READ                    0x52  /* Read page bypassing buffer */
#define BUF1_READ                   0x54
#define BUF2_READ                   0x56
#define MEM_TO_BUF1_TRANSFER        0x53
#define MEM_TO_BUF2_TRANSFER        0x55
#define MEM_TO_BUF1_CMP             0x60
#define MEM_TO_BUF2_CMP             0x61
#define BUF1_WRITE                  0x84
#define BUF2_WRITE                  0x87
#define BUF1_TO_MEM_ERASE           0x83
#define BUF2_TO_MEM_ERASE           0x86
#define BUF1_TO_MEM_NO_ERASE        0x88
#define BUF2_TO_MEM_NO_ERASE        0x89
#define PAGE_ERASE                  0x81
#define BLOCK_ERASE                 0x50

#define STATUS_REGISTER             0x57

#define STATUS_BUSY                 (1 << 7)
#define STATUS_CMP                  (1 << 6)

#define MAX_PAGE_SIZE               528

static const struct hal_flash_funcs at45db_flash_funcs = {
    .hff_read         = at45db_read,
    .hff_write        = at45db_write,
    .hff_erase_sector = at45db_erase_sector,
    .hff_sector_info  = at45db_sector_info,
    .hff_init         = at45db_init,
};

static struct at45db_dev at45db_default_dev = {
    /* struct hal_flash for compatibility */
    .hal = {
        .hf_itf        = &at45db_flash_funcs,
        .hf_base_addr  = 0,
        .hf_size       = 8192 * 512,  /* FIXME: depends on page size */
        .hf_sector_cnt = 8192,
        .hf_align      = 0,
        .hf_erased_val = 0xff,
    },

    /* SPI settings + updates baudrate on _init */
    .settings           = NULL,

    /* Configurable fields that must be populated by user app */
    .spi_num            = 0,
    .spi_cfg            = NULL,
    .ss_pin             = 0,
    .baudrate           = 100,
    .page_size          = 512,
    .disable_auto_erase = 0,
};

static struct hal_spi_settings at45db_default_settings = {
    .data_order = HAL_SPI_MSB_FIRST,
    .data_mode  = HAL_SPI_MODE3,
    .baudrate   = 100,
    .word_size  = HAL_SPI_WORD_SIZE_8BIT,
};

static uint8_t g_page_buffer[MAX_PAGE_SIZE];

static uint8_t
at45db_read_status(struct at45db_dev *dev)
{
    uint8_t val;

    hal_gpio_write(dev->ss_pin, 0);

    hal_spi_tx_val(dev->spi_num, STATUS_REGISTER);
    val = hal_spi_tx_val(dev->spi_num, 0xff);

    hal_gpio_write(dev->ss_pin, 1);

    return val;
}

static inline bool
at45db_device_ready(struct at45db_dev *dev)
{
    return ((at45db_read_status(dev) & STATUS_BUSY) != 0);
}

static inline bool
at45db_buffer_equal(struct at45db_dev *dev)
{
    return ((at45db_read_status(dev) & STATUS_CMP) == 0);
}

/* FIXME: add timeout */
static inline void
at45db_wait_ready(struct at45db_dev *dev)
{
    while (!at45db_device_ready(dev)) {
        os_time_delay(OS_TICKS_PER_SEC / 10000);
    }
}

static inline uint16_t
at45db_calc_page_count(struct at45db_dev *dev, uint32_t addr, size_t len)
{
    uint16_t page_count;
    uint16_t page_size = dev->page_size;

    page_count = 1 + (len / (page_size + 1));
    if ((addr % page_size) + len > page_size * page_count) {
        page_count++;
    }

    return page_count;
}

static inline uint32_t
at45db_page_start_address(struct at45db_dev *dev, uint32_t addr)
{
    /* FIXME: works only for 512? (powers of 2) */
    return (addr & ~(dev->page_size - 1));
}

static inline uint32_t
at45db_page_next_addr(struct at45db_dev *dev, uint32_t addr)
{
    return (at45db_page_start_address(dev, addr) + dev->page_size);
}

/* FIXME: assume buf has enough space? */
static uint16_t
at45db_read_page(struct at45db_dev *dev, uint32_t addr,
                 uint16_t len, uint8_t *buf)
{
    uint16_t amount;
    uint16_t pa;
    uint16_t ba;
    uint16_t n;
    uint8_t val;
    uint16_t page_size;

    hal_gpio_write(dev->ss_pin, 0);

    hal_spi_tx_val(dev->spi_num, MEM_READ);

    page_size = dev->page_size;
    pa = addr / page_size;
    ba = addr % page_size;

    val = (pa >> 6) & ~0x80;
    hal_spi_tx_val(dev->spi_num, val);

    if (page_size <= 512) {
        val = (pa << 2) | ((ba >> 8) & 1);
    } else {
        val = (pa << 2) | ((ba >> 8) & 3);
    }
    hal_spi_tx_val(dev->spi_num, val);

    hal_spi_tx_val(dev->spi_num, ba);

    hal_spi_tx_val(dev->spi_num, 0xff);
    hal_spi_tx_val(dev->spi_num, 0xff);
    hal_spi_tx_val(dev->spi_num, 0xff);
    hal_spi_tx_val(dev->spi_num, 0xff);

    if (len + ba <= page_size) {
        amount = len;
    } else {
        amount = page_size - ba;
    }

    for (n = 0; n < amount; n++) {
        buf[n] = hal_spi_tx_val(dev->spi_num, 0xff);
    }

    hal_gpio_write(dev->ss_pin, 1);

    return amount;
}

int
at45db_read(const struct hal_flash *hal_flash_dev, uint32_t addr, void *buf,
        uint32_t len)
{
    uint16_t page_count;
    uint16_t amount;
    uint16_t index;
    uint8_t *u8buf;
    struct at45db_dev *dev;

    dev = (struct at45db_dev *) hal_flash_dev;

    page_count = at45db_calc_page_count(dev, addr, len);
    u8buf = (uint8_t *) buf;
    index = 0;

    while (page_count--) {
        at45db_wait_ready(dev);

        amount = at45db_read_page(dev, addr, len, &u8buf[index]);

        addr = at45db_page_next_addr(dev, addr);
        index += amount;
        len -= amount;
    }

    return 0;
}

int
at45db_write(const struct hal_flash *hal_flash_dev, uint32_t addr,
        const void *buf, uint32_t len)
{
    uint16_t pa;
    uint16_t bfa;
    uint32_t n;
    uint32_t start_addr;
    uint16_t index;
    uint16_t amount;
    int page_count;
    uint8_t *u8buf;
    uint16_t page_size;
    struct at45db_dev *dev;

    dev = (struct at45db_dev *) hal_flash_dev;

    page_size = dev->page_size;
    page_count = at45db_calc_page_count(dev, addr, len);
    u8buf = (uint8_t *) buf;
    index = 0;

    while (page_count--) {
        at45db_wait_ready(dev);

        bfa = addr % page_size;
        start_addr = at45db_page_start_address(dev, addr);

        /**
         * If the page is not being written from the beginning,
         * read first the current data to rewrite back.
         *
         * The whole page is read here for the case of some the
         * real data ending before the end of the page, data must
         * be written back again.
         */
        if (bfa || len < page_size) {
            at45db_read_page(dev, start_addr, page_size, g_page_buffer);
            at45db_wait_ready(dev);
        }

        hal_gpio_write(dev->ss_pin, 0);

        /* TODO: ping-pong between page 1 and 2? */
        hal_spi_tx_val(dev->spi_num, BUF1_WRITE);

        hal_spi_tx_val(dev->spi_num, 0xff);

        /* Always write at offset 0 of internal buffer */
        hal_spi_tx_val(dev->spi_num, 0);
        hal_spi_tx_val(dev->spi_num, 0);

        /**
         * Write back extra stuff at the beginning of page.
         */
        if (bfa) {
            amount = addr - start_addr;
            for (n = 0; n < amount; n++) {
                hal_spi_tx_val(dev->spi_num, g_page_buffer[n]);
            }
        }

        if (len + bfa <= page_size) {
            amount = len;
        } else {
            amount = page_size - bfa;
        }

        /**
         * Write the stuff we're really want to write!
         */
        for (n = 0; n < amount; n++) {
            hal_spi_tx_val(dev->spi_num, u8buf[index++]);
        }

        /**
         * Write back extra stuff at the ending of page.
         */
        if (bfa + len < page_size) {
            for (n = len + bfa; n < page_size; n++) {
                hal_spi_tx_val(dev->spi_num, g_page_buffer[n]);
            }
        }

        hal_gpio_write(dev->ss_pin, 1);

        at45db_wait_ready(dev);

        hal_gpio_write(dev->ss_pin, 0);

        if (dev->disable_auto_erase) {
            hal_spi_tx_val(dev->spi_num, BUF1_TO_MEM_NO_ERASE);
        } else {
            hal_spi_tx_val(dev->spi_num, BUF1_TO_MEM_ERASE);
        }

        /* FIXME: check that pa doesn't overflow capacity */
        pa = addr / page_size;

        hal_spi_tx_val(dev->spi_num, (pa >> 6) & ~0x80);
        hal_spi_tx_val(dev->spi_num, pa << 2);
        hal_spi_tx_val(dev->spi_num, 0xff);

        hal_gpio_write(dev->ss_pin, 1);

        addr = at45db_page_next_addr(dev, addr);
        len -= amount;
    }

    return 0;
}

int
at45db_erase_sector(const struct hal_flash *hal_flash_dev,
        uint32_t sector_address)
{
    struct at45db_dev *dev;
    uint16_t pa;

    dev = (struct at45db_dev *) hal_flash_dev;
    pa = sector_address / dev->page_size;

    at45db_wait_ready(dev);

    hal_gpio_write(dev->ss_pin, 0);

    hal_spi_tx_val(dev->spi_num, PAGE_ERASE);
    hal_spi_tx_val(dev->spi_num, (pa >> 6) & ~0x80);
    hal_spi_tx_val(dev->spi_num, pa << 2);
    hal_spi_tx_val(dev->spi_num, 0xff);

    hal_gpio_write(dev->ss_pin, 1);

    return 0;
}

int
at45db_sector_info(const struct hal_flash *hal_flash_dev, int idx,
        uint32_t *address, uint32_t *sz)
{
    struct at45db_dev *dev = (struct at45db_dev *) hal_flash_dev;

    *address = idx * dev->page_size;
    *sz = dev->page_size;
    return 0;
}

struct at45db_dev *
at45db_default_config(void)
{
    struct at45db_dev *dev;

    dev = malloc(sizeof(at45db_default_dev));
    if (!dev) {
        return NULL;
    }

    memcpy(dev, &at45db_default_dev, sizeof(at45db_default_dev));
    return dev;
}

int
at45db_init(const struct hal_flash *hal_flash_dev)
{
    int rc;
    struct hal_spi_settings *settings;
    struct at45db_dev *dev;

    dev = (struct at45db_dev *) hal_flash_dev;

    /* only alloc new settings if using non-default */
    if (dev->baudrate == at45db_default_settings.baudrate) {
        dev->settings = &at45db_default_settings;
    } else {
        settings = malloc(sizeof(at45db_default_settings));
        if (!settings) {
            return -1;
        }
        memcpy(settings, &at45db_default_settings, sizeof(at45db_default_settings));
        at45db_default_settings.baudrate = dev->baudrate;
    }

    hal_gpio_init_out(dev->ss_pin, 1);

    rc = hal_spi_init(dev->spi_num, dev->spi_cfg, HAL_SPI_TYPE_MASTER);
    if (rc) {
        return (rc);
    }

    rc = hal_spi_config(dev->spi_num, dev->settings);
    if (rc) {
        return (rc);
    }

    hal_spi_set_txrx_cb(dev->spi_num, NULL, NULL);
    rc = hal_spi_enable(dev->spi_num);
    return (rc);
}
