#include "flash_iface_imx6_ecspi.h"

#include "core/ccm_pll.h"
#include "registers/regsecspi.h"
#include <string.h>
#include <stdio.h>


#define IMX6_ECSPI_BUF_SIZE         64
#define IMX6_ECSPI_WRD_SIZE         4
#define IMX6_ECSPI_MAX_BURST_BUTES  512

typedef struct  {
    dev_ecspi_e dev;
    unsigned char spi_cs;
    unsigned spi_clk;
    unsigned onfly;
    unsigned wr_wrd;
    unsigned char wr_offs;
    unsigned rd_wrd;
    unsigned char rd_cnt;
    unsigned char first_offs;

} t_intf_internal_pars;

static inline t_flash_errs f_fill_clk(unsigned clk, unsigned char* prediv, unsigned char *postdiv) {
    int err = 0;
    int clk_div = (get_peri_clock(SPI_CLK)+clk-1)/clk;
    if (clk_div > 16)
        err = FLASH_ERR_INVALID_PARAMS;

    if (!err) {
        if (clk_div==0)
            clk_div = 1;

        *prediv = clk_div;
        *postdiv = 0;
    }
    return err;
}

static t_flash_errs f_config_spi(t_intf_internal_pars *pars) {
    unsigned char prediv, postdiv;
    t_flash_errs err;


    err = f_fill_clk(pars->spi_clk, &prediv, &postdiv);
    if (!err) {
        clock_gating_config(REGS_ECSPI_BASE(pars->dev), CLOCK_ON);

        uint32_t ch_msk = 1UL << pars->spi_cs;
        // Reset eCSPI controller
        HW_ECSPI_CONREG(pars->dev).B.EN = 0;

        HW_ECSPI_CONREG(pars->dev).B.CHANNEL_SELECT = pars->spi_cs;



        uint32_t value = HW_ECSPI_CONREG(pars->dev).B.CHANNEL_MODE;
        BW_ECSPI_CONREG_CHANNEL_MODE(pars->dev, value | ch_msk);


        HW_ECSPI_CONREG(pars->dev).B.PRE_DIVIDER = prediv - 1;
        HW_ECSPI_CONREG(pars->dev).B.POST_DIVIDER = postdiv;       

        HW_ECSPI_CONREG(pars->dev).B.EN = 1;



        value = HW_ECSPI_CONFIGREG(pars->dev).B.SCLK_PHA;
        HW_ECSPI_CONFIGREG(pars->dev).B.SCLK_PHA =  value & ~ch_msk;// value | ch_msk;

        value = HW_ECSPI_CONFIGREG(pars->dev).B.SCLK_POL;
        HW_ECSPI_CONFIGREG(pars->dev).B.SCLK_POL =  value & ~ch_msk;

        value = HW_ECSPI_CONFIGREG(pars->dev).B.SS_POL;
        HW_ECSPI_CONFIGREG(pars->dev).B.SS_POL =  value & ~ch_msk;

        HW_ECSPI_CONFIGREG(pars->dev).B.SS_CTL &= ~ch_msk;
    }

    return err;
}









static t_flash_errs f_send(struct st_flash_iface *iface,
                           const unsigned char* vals, size_t *psize) {
    t_flash_errs err=0;
    t_intf_internal_pars *pars = (t_intf_internal_pars*)iface->port_data;
    size_t snd_size = *psize;

    while (snd_size && (pars->onfly < IMX6_ECSPI_BUF_SIZE)) {
        size_t i;
        for (i=0; (pars->wr_offs != IMX6_ECSPI_WRD_SIZE) && (i< snd_size); i++) {
            pars->wr_wrd <<= 8;
            pars->wr_wrd |= (vals == NULL ? 0 : *vals++);
            pars->wr_offs++;
            snd_size--;
        }

        if(pars->wr_offs==IMX6_ECSPI_WRD_SIZE) {
            //printf("wr 0x%08X\n", pars->wr_wrd);
            HW_ECSPI_TXDATA_WR(pars->dev, pars->wr_wrd);
            pars->onfly++;
            pars->wr_offs = 0;
        }
    }

    *psize = snd_size;

    return err;

}

static t_flash_errs f_recv(struct st_flash_iface *iface,
                           unsigned char* vals, size_t *psize) {
    t_flash_errs err=0;
    t_intf_internal_pars *pars = (t_intf_internal_pars*)iface->port_data;
    size_t rcv_size = *psize;

    if (pars->rd_cnt!=0) {
        while ((rcv_size!=0) && (pars->rd_cnt!=0)) {
            if (vals!=NULL)
                *vals++ = (pars->rd_wrd >> 24) & 0xFF;
            pars->rd_wrd <<= 8;
            pars->rd_cnt--;
            rcv_size--;
        }
    }

    while ((rcv_size!=0) && (HW_ECSPI_STATREG(pars->dev).B.RR!=0)) {
        pars->rd_wrd = HW_ECSPI_RXDATA_RD(pars->dev);
        pars->rd_cnt = IMX6_ECSPI_WRD_SIZE;
        pars->onfly--;
        //printf("rd 0x%08X\n", pars->rd_wrd);

        if (pars->first_offs) {
            pars->rd_wrd <<= 8*pars->first_offs;
            pars->rd_cnt -= pars->first_offs;
            pars->first_offs = 0;
        }

        while ((rcv_size!=0) && (pars->rd_cnt!=0)) {
            if (vals!=NULL)
                *vals++ = (pars->rd_wrd >> 24) & 0xFF;
            pars->rd_wrd <<= 8;
            pars->rd_cnt--;
            rcv_size--;
        }
    }



    *psize = rcv_size;
    return err;
}


static t_flash_errs f_select(t_flash_iface *iface, size_t total_size) {

    t_flash_errs err =0;
    //printf("select %d\n", total_size);
    if (total_size > IMX6_ECSPI_MAX_BURST_BUTES) {
        err = FLASH_ERR_INVALID_PARAMS;
    } else {
        t_intf_internal_pars *pars = (t_intf_internal_pars*)iface->port_data;
        pars->wr_wrd = pars->rd_wrd = 0;
        pars->onfly = 0;
        pars->rd_cnt = 0;
        pars->first_offs = total_size & 3;
        if (pars->first_offs!=0)
            pars->first_offs = 4 - pars->first_offs;
        pars->wr_offs =  pars->first_offs;

        HW_ECSPI_CONREG(pars->dev).B.BURST_LENGTH = total_size*8 - 1;

        HW_ECSPI_STATREG_WR(pars->dev, BM_ECSPI_STATREG_RO | BM_ECSPI_STATREG_TC);

        HW_ECSPI_CONREG(pars->dev).B.SMC = 1;
    }

    return err;
}

static t_flash_errs f_unselect(t_flash_iface *iface) {
    t_intf_internal_pars *pars = (t_intf_internal_pars*)iface->port_data;
    HW_ECSPI_STATREG_WR(pars->dev, BM_ECSPI_STATREG_TC);
    return 0;
}


t_flash_errs flash_iface_init_imx6_ecspi(t_flash_iface *iface, dev_ecspi_e port,
                                         uint8_t cs, uint32_t clk) {
    t_flash_errs err = (iface == NULL) || (cs > 4) || (port > DEV_ECSPI5) ? FLASH_ERR_INVALID_PARAMS : 0;
    if (!err) {
        t_intf_internal_pars *pars = calloc(1, sizeof(t_intf_internal_pars));
        if (pars == NULL) {
            err = FLASH_ERR_RESOURCE_ALLOC;
        } else {
            pars->dev = port;
            pars->spi_cs = cs;
            pars->spi_clk = clk;
            err = f_config_spi(pars);
        }

        if (!err) {
            memset(iface, 0, sizeof(t_flash_iface));
            iface->port_data = pars;
            iface->max_transf_size = IMX6_ECSPI_MAX_BURST_BUTES;
            iface->port_exec_tout = 1;
            iface->select = f_select;
            iface->unselect = f_unselect;
            iface->send = f_send;
            iface->recv = f_recv;
            iface->flush = NULL;
        }


        if (err)
            free(pars);
    }

    return err;
}
