//
// Copyright 2011-2012,2014 Ettus Research LLC
// Copyright 2018 Ettus Research, a National Instruments Company
//
// SPDX-License-Identifier: GPL-3.0-or-later
//

#include <uhd/exception.hpp>
#include <uhd/utils/log.hpp>
#include <uhdlib/usrp/cores/i2c_core_200.hpp>
#include <chrono>
#include <mutex>
#include <thread>

#define REG_I2C_WR_PRESCALER_LO (1 << 3) | 0
#define REG_I2C_WR_PRESCALER_HI (1 << 3) | 1
#define REG_I2C_WR_CTRL         (1 << 3) | 2
#define REG_I2C_WR_DATA         (1 << 3) | 3
#define REG_I2C_WR_CMD          (1 << 3) | 4
#define REG_I2C_RD_DATA         (0 << 3) | 3
#define REG_I2C_RD_ST           (0 << 3) | 4

//
// STA, STO, RD, WR, and IACK bits are cleared automatically
//

#define I2C_CTRL_EN (1 << 7) // core enable
#define I2C_CTRL_IE (1 << 6) // interrupt enable

#define I2C_CMD_START  (1 << 7) // generate (repeated) start condition
#define I2C_CMD_STOP   (1 << 6) // generate stop condition
#define I2C_CMD_RD     (1 << 5) // read from slave
#define I2C_CMD_WR     (1 << 4) // write to slave
#define I2C_CMD_NACK   (1 << 3) // when a rcvr, send ACK (ACK=0) or NACK (ACK=1)
#define I2C_CMD_RSVD_2 (1 << 2) // reserved
#define I2C_CMD_RSVD_1 (1 << 1) // reserved
#define I2C_CMD_IACK   (1 << 0) // set to clear pending interrupt

#define I2C_ST_RXACK (1 << 7) // Received acknowledgement from slave (1 = NAK, 0 = ACK)
#define I2C_ST_BUSY \
    (1 << 6) // 1 after START signal detected; 0 after STOP signal detected
#define I2C_ST_AL     (1 << 5) // Arbitration lost.  1 when core lost arbitration
#define I2C_ST_RSVD_4 (1 << 4) // reserved
#define I2C_ST_RSVD_3 (1 << 3) // reserved
#define I2C_ST_RSVD_2 (1 << 2) // reserved
#define I2C_ST_TIP    (1 << 1) // Transfer-in-progress
#define I2C_ST_IP     (1 << 0) // Interrupt pending

using namespace uhd;

i2c_core_200::~i2c_core_200(void)
{
    /* NOP */
}

class i2c_core_200_impl : public i2c_core_200
{
public:
    i2c_core_200_impl(wb_iface::sptr iface, const size_t base, const size_t readback)
        : _iface(iface), _base(base), _readback(readback)
    {
        // init I2C FPGA interface.
        this->poke(REG_I2C_WR_CTRL, 0x0000);
        // set prescalers to operate at 400kHz: WB_CLK is 64MHz...
        static const uint32_t i2c_datarate = 400000;
        static const uint32_t wishbone_clk = 64000000; // FIXME should go somewhere else
        uint16_t prescaler                 = wishbone_clk / (i2c_datarate * 5) - 1;
        this->poke(REG_I2C_WR_PRESCALER_LO, prescaler & 0xFF);
        this->poke(REG_I2C_WR_PRESCALER_HI, (prescaler >> 8) & 0xFF);
        this->poke(REG_I2C_WR_CTRL, I2C_CTRL_EN); // enable I2C core
    }

    void write_i2c(uint16_t addr, const byte_vector_t& bytes) override
    {
        this->poke(REG_I2C_WR_DATA, (addr << 1) | 0); // addr and read bit (0)
        this->poke(REG_I2C_WR_CMD,
            I2C_CMD_WR | I2C_CMD_START | (bytes.empty() ? I2C_CMD_STOP : 0));

        // wait for previous transfer to complete
        if (not wait_chk_ack()) {
            this->poke(REG_I2C_WR_CMD, I2C_CMD_STOP);
            return;
        }

        for (size_t i = 0; i < bytes.size(); i++) {
            this->poke(REG_I2C_WR_DATA, bytes[i]);
            this->poke(REG_I2C_WR_CMD,
                I2C_CMD_WR | ((i == (bytes.size() - 1)) ? I2C_CMD_STOP : 0));
            if (!wait_chk_ack()) {
                this->poke(REG_I2C_WR_CMD, I2C_CMD_STOP);
                return;
            }
        }
    }

    byte_vector_t read_i2c(uint16_t addr, size_t num_bytes) override
    {
        byte_vector_t bytes;
        if (num_bytes == 0)
            return bytes;

        while (this->peek(REG_I2C_RD_ST) & I2C_ST_BUSY) {
            /* NOP */
        }

        this->poke(REG_I2C_WR_DATA, (addr << 1) | 1); // addr and read bit (1)
        this->poke(REG_I2C_WR_CMD, I2C_CMD_WR | I2C_CMD_START);
        // wait for previous transfer to complete
        if (not wait_chk_ack()) {
            this->poke(REG_I2C_WR_CMD, I2C_CMD_STOP);
        }
        for (size_t i = 0; i < num_bytes; i++) {
            this->poke(REG_I2C_WR_CMD,
                I2C_CMD_RD | ((num_bytes == i + 1) ? (I2C_CMD_STOP | I2C_CMD_NACK) : 0));
            i2c_wait();
            bytes.push_back(this->peek(REG_I2C_RD_DATA));
        }
        return bytes;
    }

private:
    void i2c_wait(void)
    {
        for (size_t i = 0; i < 100; i++) {
            if ((this->peek(REG_I2C_RD_ST) & I2C_ST_TIP) == 0)
                return;
            std::this_thread::sleep_for(std::chrono::milliseconds(1));
        }
        UHD_LOGGER_ERROR("CORES") << "i2c_core_200: i2c_wait timeout";
    }

    bool wait_chk_ack(void)
    {
        i2c_wait();
        return (this->peek(REG_I2C_RD_ST) & I2C_ST_RXACK) == 0;
    }

    void poke(const size_t what, const uint8_t cmd)
    {
        std::lock_guard<std::mutex> lock(_mutex);
        _iface->poke32(_base, (what << 8) | cmd);
    }

    uint8_t peek(const size_t what)
    {
        std::lock_guard<std::mutex> lock(_mutex);
        _iface->poke32(_base, what << 8);
        return uint8_t(_iface->peek32(_readback));
    }

    wb_iface::sptr _iface;
    const size_t _base;
    const size_t _readback;
    std::mutex _mutex;
};

i2c_core_200::sptr i2c_core_200::make(
    wb_iface::sptr iface, const size_t base, const size_t readback)
{
    return sptr(new i2c_core_200_impl(iface, base, readback));
}
