//
// Copyright 2013 Ettus Research LLC
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

#include "chinch.h"

#define PCIE_MSG_REG_BASE   0xFB00
#define PCIE_MSG_DATA_REG   0 //Write: Data register for outbound requests and responses
                              //Read:  Latched data for inbound requests
#define PCIE_MSG_CTRL_REG   1 //Write: Control register for outbound requests and responses (Initiates xfer)
                              //Read:  Latched control word for inbound requests
#define PCIE_MSG_RESP_REG   2 //Read:  Latched response data for outbound requests
#define PCIE_MSG_STATUS_REG 3 //Read:  Status register for inbound and outbound transactions

// Transaction Word Format
//
// -Control- --Data--
// 63     32 31      0
// |       | |       |
// FXXA AAAA DDDD DDDD
//
// where:
// D = Data/Payload
// A = Address
// X = Reserved
// F = Flags: {Read_Response, Write_Request, Read_Request, Half_Word_Cycle}

#define PCIE_CTRL_REG_READ_RESP         (1<<31)
#define PCIE_CTRL_REG_WRITE             (1<<30)
#define PCIE_CTRL_REG_READ              (1<<29)
#define PCIE_CTRL_REG_HALF_WORD         (1<<28)
#define PCIE_CTRL_REG_ADDR_MASK         0x000FFFFF

#define PCIE_STATUS_REG_READ_PENDING    (1<<0)
#define PCIE_STATUS_REG_REQ_PENDING     (1<<1)
#define PCIE_STATUS_REG_RESP_PENDING    (1<<2)
#define PCIE_STATUS_REG_BUSY            (1<<4)

#define CHINCH_FPGA_CONFIG_REG          0x58
#define CHINCH_FLASH_WINDOW_REG0        0xC0
#define CHINCH_FLASH_WINDOW_REG1        0xE0
#define CHINCH_FLASH_2AAA_REG           0x400
#define CHINCH_FLASH_5555_REG           0x408
#define CHINCH_FLASH_WINDOW_BASE        0x60000
#define CHINCH_FLASH_WINDOW_SIZE        0x20000
#define CHINCH_FLASH_WINDOW_CONF        0x91

//-----------------------------------------------------
// Peek-Poke interface
//-----------------------------------------------------

bool chinch_poke(
    const uint32_t addr,
    const uint32_t data,
    bool half_word,
    uint32_t timeout
)
{
    //Build transaction control word
    uint32_t ctrl_word = 0, i;
    ctrl_word |= (addr & PCIE_CTRL_REG_ADDR_MASK);
    if (half_word) ctrl_word |= PCIE_CTRL_REG_HALF_WORD;
    ctrl_word |= PCIE_CTRL_REG_WRITE;

    //Wait for space in the transaction queue or timeout
    i = 0;
    while ((wb_peek32(SR_ADDR(PCIE_MSG_REG_BASE, PCIE_MSG_STATUS_REG)) & PCIE_STATUS_REG_BUSY) != 0) {
        if (++i > timeout) return false;
    }

    //Flush transaction control and data registers
    wb_poke32(SR_ADDR(PCIE_MSG_REG_BASE, PCIE_MSG_DATA_REG), data);
    wb_poke32(SR_ADDR(PCIE_MSG_REG_BASE, PCIE_MSG_CTRL_REG), ctrl_word);

    return true;
}

bool chinch_peek(
    const uint32_t addr,
    uint32_t* data,
    bool half_word,
    uint32_t timeout
)
{
    //Build transaction control word
    uint32_t ctrl_word = 0, i;
    ctrl_word |= (addr & PCIE_CTRL_REG_ADDR_MASK);
    if (half_word) ctrl_word |= PCIE_CTRL_REG_HALF_WORD;
    ctrl_word |= PCIE_CTRL_REG_READ;

    //Wait for space in the transaction queue or timeout
    i = 0;
    while ((wb_peek32(SR_ADDR(PCIE_MSG_REG_BASE, PCIE_MSG_STATUS_REG)) & PCIE_STATUS_REG_BUSY) != 0) {
        if (++i > timeout) return false;
    }

    //Flush transaction control register
    if (data) *data = 0;
    wb_poke32(SR_ADDR(PCIE_MSG_REG_BASE, PCIE_MSG_CTRL_REG), ctrl_word);

    //Wait for read completion or timeout
    i = 0;
    while ((wb_peek32(SR_ADDR(PCIE_MSG_REG_BASE, PCIE_MSG_STATUS_REG)) & PCIE_STATUS_REG_READ_PENDING) != 0) {
        if (++i > timeout) return false;
    }
    //Read transaction data register
    if (data) *data = wb_peek32(SR_ADDR(PCIE_MSG_REG_BASE, PCIE_MSG_RESP_REG));
    return true;
}

//-----------------------------------------------------
// Flash access
//-----------------------------------------------------

uint32_t    g_cached_win_reg0;
uint32_t    g_cached_win_reg1;

bool chinch_flash_init()
{
    chinch_peek32(CHINCH_FLASH_WINDOW_REG0, &g_cached_win_reg0);
    chinch_peek32(CHINCH_FLASH_WINDOW_REG1, &g_cached_win_reg1);

    bool status = true, passed = true;
    STATUS_MERGE(chinch_poke32(CHINCH_FLASH_WINDOW_REG0, CHINCH_FLASH_WINDOW_BASE | CHINCH_FLASH_WINDOW_CONF), status);

    //Run a loopback test to ensure that we will not corrupt the flash.
    STATUS_MERGE(chinch_poke32(0x200, 0xDEADBEEF), status);
    STATUS_MERGE(chinch_poke16(0x204, 0x5678), status);
    uint32_t reg_val;
    STATUS_MERGE(chinch_peek16(0x0, &reg_val), status);
    STATUS_MERGE(chinch_poke16(0x206, reg_val), status);
    STATUS_MERGE(chinch_peek32(0x200, &reg_val), status);
    passed &= (reg_val == 0xDEADBEEF);
    STATUS_MERGE(chinch_peek32(0x204, &reg_val), status);
    passed &= (reg_val == 0x7AD05678);

    return status && passed;
}

void chinch_flash_cleanup()
{
    chinch_poke32(CHINCH_FLASH_WINDOW_REG0, g_cached_win_reg0);
    chinch_poke32(CHINCH_FLASH_WINDOW_REG1, g_cached_win_reg1);
}

bool chinch_flash_select_sector(uint32_t sector)
{
    return chinch_poke32(CHINCH_FLASH_WINDOW_REG1, sector * CHINCH_FLASH_WINDOW_SIZE);
}

bool chinch_flash_erase_sector()
{
    bool status = true;
    STATUS_CHAIN(chinch_poke16(CHINCH_FLASH_5555_REG,    0x00AA), status);    //Unlock #1
    STATUS_CHAIN(chinch_poke16(CHINCH_FLASH_2AAA_REG,    0x0055), status);    //Unlock #2
    STATUS_CHAIN(chinch_poke16(CHINCH_FLASH_5555_REG,    0x0080), status);    //Setup
    STATUS_CHAIN(chinch_poke16(CHINCH_FLASH_5555_REG,    0x00AA), status);    //Unlock #1
    STATUS_CHAIN(chinch_poke16(CHINCH_FLASH_2AAA_REG,    0x0055), status);    //Unlock #2
    STATUS_CHAIN(chinch_poke16(CHINCH_FLASH_WINDOW_BASE, 0x0030), status);    //Erase

    if (status) {
        uint32_t read_data;
        while (true) {
            status = chinch_peek16(CHINCH_FLASH_WINDOW_BASE, &read_data);    //Wait for sector to erase
            if (((read_data & 0xFFFF) == 0xFFFF) || !status) break;
        }
    }
    return status;
}

bool chinch_flash_read_buf(uint32_t offset, uint16_t* buf, uint32_t size)
{
    bool status = true;
    uint32_t base_addr = CHINCH_FLASH_WINDOW_BASE | (offset & 0x3FFFF);
    for (uint32_t i = 0; (i < size) && status; i++) {
        uint32_t word;
        STATUS_CHAIN(chinch_peek16(base_addr + (i * 2), &word), status);
        buf[i] = (uint16_t)word;
    }
    return status;
}

bool chinch_flash_write_buf(uint32_t offset, uint16_t* buf, uint32_t size)
{
    if (size > CHINCH_FLASH_MAX_BUF_WRITES || buf == 0) return false;
    bool status = true;

    //Setup buffered write
    STATUS_CHAIN(chinch_poke16(CHINCH_FLASH_5555_REG,    0x00AA), status);      //Unlock #1
    STATUS_MERGE(chinch_poke16(CHINCH_FLASH_2AAA_REG,    0x0055), status);      //Unlock #2
    STATUS_CHAIN(chinch_poke16(CHINCH_FLASH_WINDOW_BASE, 0x0025), status);      //Setup write
    STATUS_CHAIN(chinch_poke16(CHINCH_FLASH_WINDOW_BASE, size - 1), status);    //Num words

    //Write the data
    uint32_t base_addr = CHINCH_FLASH_WINDOW_BASE | (offset & 0x3FFFF);
    for (uint32_t i = 0; i < size; i++) {
        STATUS_CHAIN(chinch_poke16(base_addr + (i * 2), buf[i]), status);
    }

    //Commit write
    STATUS_CHAIN(chinch_poke16(CHINCH_FLASH_WINDOW_BASE, 0x0029), status);

    //Poll for completion
    //Bit 7 of the data at the final address is the status bit.
    //It is set to the inverse of bit 7 of the final data to be
    //written until the final write is completed.
    uint32_t read_data;
    do {
        STATUS_MERGE(chinch_peek16(base_addr + ((size - 1) * 2), &read_data), status);
    } while (status && (((uint16_t)read_data ^ buf[size - 1]) & (1 << 7)));

    return status;
}

//-----------------------------------------------------
// FPGA Configuration
//-----------------------------------------------------
void chinch_start_config()
{
    chinch_poke32(CHINCH_FPGA_CONFIG_REG, 0x1);
}

config_status_t chinch_get_config_status()
{
    bool status = true;
    uint32_t read_data;
    STATUS_MERGE(chinch_peek32(CHINCH_FPGA_CONFIG_REG, &read_data), status);
    return status ? (config_status_t)read_data : CHINCH_CONFIG_ERROR;
}

//-----------------------------------------------------
// Read-back interface for the user initiated
// PCIe register transactions
//-----------------------------------------------------
pcie_register_xact_t    g_pcie_reg_xact_info;
uint32_t                g_pcie_res_timeout;

bool _respond_to_pcie_xact_request(uint32_t response, uint32_t timeout)
{
    //Wait for space in the transaction queue or timeout
    uint32_t i = 0;
    while ((wb_peek32(SR_ADDR(PCIE_MSG_REG_BASE, PCIE_MSG_STATUS_REG)) & PCIE_STATUS_REG_BUSY) != 0) {
        if (++i > g_pcie_res_timeout) return false;
    }

    //First write data and then the control register to ensure coherency
    wb_poke32(SR_ADDR(PCIE_MSG_REG_BASE, PCIE_MSG_DATA_REG), response);
    wb_poke32(SR_ADDR(PCIE_MSG_REG_BASE, PCIE_MSG_CTRL_REG), PCIE_CTRL_REG_READ_RESP);

    return true;
}

bool check_pcie_user_regport(pcie_register_xact_t** xact_info_hdl)
{
    //Check for pending transaction requests
    if ((wb_peek32(SR_ADDR(PCIE_MSG_REG_BASE, PCIE_MSG_STATUS_REG)) & PCIE_STATUS_REG_REQ_PENDING) != 0) {
        //Attach responder to transaction info
        g_pcie_reg_xact_info.respond = _respond_to_pcie_xact_request;

        //First read data and then the control register to ensure coherency
        g_pcie_reg_xact_info.data = wb_peek32(SR_ADDR(PCIE_MSG_REG_BASE, PCIE_MSG_DATA_REG));
        uint32_t xact_control = wb_peek32(SR_ADDR(PCIE_MSG_REG_BASE, PCIE_MSG_CTRL_REG));

        g_pcie_reg_xact_info.addr = xact_control & PCIE_CTRL_REG_ADDR_MASK;
        g_pcie_reg_xact_info.size =
            (xact_control & PCIE_CTRL_REG_HALF_WORD) == 0 ? PCIE_XACT_32_BIT : PCIE_XACT_16_BIT;
        if ((xact_control & PCIE_CTRL_REG_READ) != 0)
            g_pcie_reg_xact_info.type = PCIE_XACT_READ;
        else if ((xact_control & PCIE_CTRL_REG_WRITE) != 0)
            g_pcie_reg_xact_info.type = PCIE_XACT_WRITE;
        else
            g_pcie_reg_xact_info.type = PCIE_XACT_ERROR;

        *xact_info_hdl = &g_pcie_reg_xact_info;
        return true;
    } else {
        *xact_info_hdl = 0;
        return false;
    }
}

bool forward_pcie_user_xact_to_wb()
{
    pcie_register_xact_t* xact_info;
    if (check_pcie_user_regport(&xact_info)) {
        if (xact_info->size == PCIE_XACT_32_BIT) {
            //Only respond to 32-bit transactions because that is all the LVFPGA interface can send
            if (xact_info->type == PCIE_XACT_WRITE) {
                wb_poke32(xact_info->addr, xact_info->data);
                return true;
            } else if (xact_info->type == PCIE_XACT_READ) {
                return xact_info->respond(wb_peek32(xact_info->addr), CHINCH_DEFAULT_XACT_TIMEOUT);
            }
        }
    }
    return false;
}