/*
 * spi.h
 *
 *  Created on: 29.07.2018
 *      Author: eyck
 */

#ifndef SPI_H_
#define SPI_H_

#include <sifive/devices/spi.h>
#include "util/bit_field.h"
#include <array>
#include <cstdint>

template<uint32_t BASE_ADDR>
class spi_regs {
public:
    // storage declarations
    BEGIN_BF_DECL(sckdiv_t, uint32_t);
        BF_FIELD(div, 0, 12);
    END_BF_DECL();

    BEGIN_BF_DECL(sckmode_t, uint32_t);
        BF_FIELD(pha, 0, 1);
        BF_FIELD(pol, 1, 1);
    END_BF_DECL();

    uint32_t r_csid;

    uint32_t r_csdef;

    BEGIN_BF_DECL(csmode_t, uint32_t);
        BF_FIELD(mode, 0, 2);
    END_BF_DECL();

    BEGIN_BF_DECL(delay0_t, uint32_t);
        BF_FIELD(cssck, 0, 8);
        BF_FIELD(sckcs, 16, 8);
    END_BF_DECL();

    BEGIN_BF_DECL(delay1_t, uint32_t);
        BF_FIELD(intercs, 0, 16);
        BF_FIELD(interxfr, 16, 8);
    END_BF_DECL();

    BEGIN_BF_DECL(fmt_t, uint32_t);
        BF_FIELD(proto, 0, 2);
        BF_FIELD(endian, 2, 1);
        BF_FIELD(dir, 3, 1);
        BF_FIELD(len, 16, 4);
    END_BF_DECL();

    BEGIN_BF_DECL(txdata_t, uint32_t);
        BF_FIELD(data, 0, 8);
        BF_FIELD(full, 31, 1);
    END_BF_DECL() r_txdata;

    BEGIN_BF_DECL(rxdata_t, uint32_t);
        BF_FIELD(data, 0, 8);
        BF_FIELD(empty, 31, 1);
    END_BF_DECL();

    BEGIN_BF_DECL(txmark_t, uint32_t);
        BF_FIELD(txmark, 0, 3);
    END_BF_DECL();

    BEGIN_BF_DECL(rxmark_t, uint32_t);
        BF_FIELD(rxmark, 0, 3);
    END_BF_DECL();

    BEGIN_BF_DECL(fctrl_t, uint32_t);
        BF_FIELD(en, 0, 1);
    END_BF_DECL();

    BEGIN_BF_DECL(ffmt_t, uint32_t);
        BF_FIELD(cmd_en, 0, 1);
        BF_FIELD(addr_len, 1, 2);
        BF_FIELD(pad_cnt, 3, 4);
        BF_FIELD(cmd_proto, 7, 2);
        BF_FIELD(addr_proto, 9, 2);
        BF_FIELD(data_proto, 11, 2);
        BF_FIELD(cmd_code, 16, 8);
        BF_FIELD(pad_code, 24, 8);
    END_BF_DECL();

    BEGIN_BF_DECL(ie_t, uint32_t);
        BF_FIELD(txwm, 0, 1);
        BF_FIELD(rxwm, 1, 1);
    END_BF_DECL();

    BEGIN_BF_DECL(ip_t, uint32_t);
        BF_FIELD(txwm, 0, 1);
        BF_FIELD(rxwm, 1, 1);
    END_BF_DECL();

    static inline sckdiv_t& sckdiv_reg(){
        return *reinterpret_cast<sckdiv_t*>(BASE_ADDR+SPI_REG_SCKDIV);
    }

    static inline sckmode_t& sckmode_reg(){
        return *reinterpret_cast<sckmode_t*>(BASE_ADDR+SPI_REG_SCKMODE);
    }

    static inline uint32_t& csid_reg(){
        return *reinterpret_cast<uint32_t*>(BASE_ADDR+SPI_REG_CSID);
    }

    static inline uint32_t& csdef_reg(){
        return *reinterpret_cast<uint32_t*>(BASE_ADDR+SPI_REG_CSDEF);
    }

    static inline csmode_t& csmode_reg(){
        return *reinterpret_cast<csmode_t*>(BASE_ADDR+SPI_REG_CSMODE);
    }

    static inline delay0_t& dcssck_reg(){
        return *reinterpret_cast<delay0_t*>(BASE_ADDR+SPI_REG_DCSSCK);
    }

    static inline uint32_t& dsckcs_reg(){
        return *reinterpret_cast<uint32_t*>(BASE_ADDR+SPI_REG_DSCKCS);
    }

    static inline delay1_t& dintercs_reg(){
        return *reinterpret_cast<delay1_t*>(BASE_ADDR+SPI_REG_DINTERCS);
    }

    static inline uint32_t& dinterxfr_reg(){
        return *reinterpret_cast<uint32_t*>(BASE_ADDR+SPI_REG_DINTERXFR);
    }

    static inline fmt_t& fmt_reg(){
        return *reinterpret_cast<fmt_t*>(BASE_ADDR+SPI_REG_FMT);
    }

    static inline txdata_t& txfifo_reg(){
        return *reinterpret_cast<txdata_t*>(BASE_ADDR+SPI_REG_TXFIFO);
    }

    static inline rxdata_t& rxfifo_reg(){
        return *reinterpret_cast<rxdata_t*>(BASE_ADDR+SPI_REG_RXFIFO);
    }

    static inline txmark_t& txctrl_reg(){
        return *reinterpret_cast<txmark_t*>(BASE_ADDR+SPI_REG_TXCTRL);
    }

    static inline rxmark_t& rxctrl_reg(){
        return *reinterpret_cast<rxmark_t*>(BASE_ADDR+SPI_REG_RXCTRL);
    }

    static inline fctrl_t& fctrl_reg(){
        return *reinterpret_cast<fctrl_t*>(BASE_ADDR+SPI_REG_FCTRL);
    }

    static inline ffmt_t& ffmt_reg(){
        return *reinterpret_cast<ffmt_t*>(BASE_ADDR+SPI_REG_FFMT);
    }

    static inline ie_t& ie_reg(){
        return *reinterpret_cast<ie_t*>(BASE_ADDR+SPI_REG_IE);
    }

    static inline ip_t& ip_reg(){
        return *reinterpret_cast<ip_t*>(BASE_ADDR+SPI_REG_IP);
    }

    template<size_t SIZE>
    static bool transfer(std::array<uint8_t, SIZE>& bytes){
        csmode_reg().mode=2; // HOLD mode
        rxctrl_reg().rxmark=bytes.size()-1; // trigger irq if more than 2 bytes are received;
        ie_reg().rxwm=1;
        // write data bytes
        for(size_t i=0; i<bytes.size(); ++i)
            txfifo_reg()=bytes[i];
        // wait until SPI is done
        spi_active=true;
        do{
            asm("wfi");
            asm("nop");
        }while(spi_active);
        // deactivate SPI
        csmode_reg().mode=0; // AUTO mode, deactivates CS
        // fetch results
        for(size_t i=0; i<bytes.size(); ++i) bytes[i]=rxfifo_reg();
        return true;
    }

    static void spi_rx_interrupt_handler(){
        ip_reg().rxwm=0;
        ie_reg().rxwm=0;
        spi_active=false;
    }

private:
    static volatile bool spi_active;
};

#endif /* SPI_H_ */
