/*
Copyright (C) 2008, 2009  GE Intelligent Platforms, Inc
 
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 2
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, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

#include <linux/string.h>
#include <lib_octeon.h>
#include <common.h>
#include <cvmx-config.h> 
#include "cvmx.h"
#include "cvmx-app-init.h"
#include "cvmx-llm.h"

#define	MIN(a,b) (((a)<(b))?(a):(b))

typedef struct
{
    uint32_t dfa_memcfg0_base;
    uint32_t dfa_memcfg1_base;
    uint32_t mrs_dat_p0bunk0;
    uint32_t mrs_dat_p0bunk1;
    uint32_t mrs_dat_p1bunk0;
    uint32_t mrs_dat_p1bunk1;
    uint8_t  p0_ena;
    uint8_t  p1_ena;
    uint8_t  bunkport;
} rldram_csr_config_t;

int rld_csr_config_generate(llm_descriptor_t *llm_desc_ptr, rldram_csr_config_t *cfg_ptr);

void print_rld_cfg(rldram_csr_config_t *cfg_ptr);
void write_rld_cfg(rldram_csr_config_t *cfg_ptr);

#ifndef CVMX_LLM_NUM_PORTS
#warning WARNING: default CVMX_LLM_NUM_PORTS used.  Defaults deprecated, please set in executive-config.h
#define CVMX_LLM_NUM_PORTS 2
#endif

#if (CVMX_LLM_NUM_PORTS != 1) && (CVMX_LLM_NUM_PORTS != 2)
#error "Invalid CVMX_LLM_NUM_PORTS value: must be 1 or 2\n"
#endif

/* initialize the global data pointer (gd) */
DECLARE_GLOBAL_DATA_PTR;

int npaInitLlm(void) 
{
	llm_descriptor_t *llm_desc_ptr;
	memset(&llm_desc_ptr, 0, sizeof(llm_desc_ptr));

	if ( cvmx_llm_initialize_desc(llm_desc_ptr) < 0)
	{
		printf("failed to initialize LLM desc!\n");
		return 0;
	}	
		return 1;
}

int cvmx_llm_get_default_descriptor(llm_descriptor_t *llm_desc_ptr)
{
    if (!llm_desc_ptr) {
		cvmx_dprintf("LLM descriptor empty\n");
        return -1;
	}

    memset(llm_desc_ptr, 0, sizeof(llm_descriptor_t));
    llm_desc_ptr->cpu_hz = (gd->cpu_clock_mhz * 1000000); 

    strcpy(llm_desc_ptr->addr_rld0_fb_str, "22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00");
    strcpy(llm_desc_ptr->addr_rld0_bb_str, "22 21 19 20 08 07 06 05 04 03 02 01 00 09 18 17 16 15 14 13 12 11 10");

    strcpy(llm_desc_ptr->addr_rld1_fb_str, "22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00");
    strcpy(llm_desc_ptr->addr_rld1_bb_str, "22 21 20 00 08 07 06 05 04 13 02 01 03 09 18 17 16 15 14 10 12 11 19");

    llm_desc_ptr->rld0_bunks = 2;
    llm_desc_ptr->rld1_bunks = 2;
    llm_desc_ptr->rld0_mbytes = 128;
    llm_desc_ptr->rld1_mbytes = 128;
 
    return(0);
}

int cvmx_llm_initialize_desc(llm_descriptor_t *llm_desc_ptr)
{

	llm_descriptor_t default_llm_desc;
    memset(&default_llm_desc, 0, sizeof(default_llm_desc));	
	
    if (!llm_desc_ptr)
    {
        if (0 > cvmx_llm_get_default_descriptor(&default_llm_desc))
            return -1;

        if (CVMX_LLM_NUM_PORTS == 1)
			default_llm_desc.rld0_bunks = 0;

        cvmx_dprintf("Using default LLM configuration\n");

        llm_desc_ptr = &default_llm_desc;
    }
    rldram_csr_config_t rldCfg;

    if (!rld_csr_config_generate(llm_desc_ptr, &rldCfg))
    {
        cvmx_dprintf("Configuring %d llm port(s).\n", !!llm_desc_ptr->rld0_bunks + !!llm_desc_ptr->rld1_bunks);
        write_rld_cfg(&rldCfg);
    }
    else
    {
        cvmx_dprintf("Error creating rldram configuration\n");
        return(-1);
    }

    if (!!llm_desc_ptr->rld0_bunks && !!llm_desc_ptr->rld1_bunks)
        llm_desc_ptr->rld0_mbytes = llm_desc_ptr->rld1_mbytes = MIN(llm_desc_ptr->rld0_mbytes, llm_desc_ptr->rld1_mbytes);

	cvmx_dprintf("RLDRAM size: %d MB\n", (((!!llm_desc_ptr->rld0_bunks) * llm_desc_ptr->rld0_mbytes
          + (!!llm_desc_ptr->rld1_bunks) * llm_desc_ptr->rld1_mbytes)));

    return(((!!llm_desc_ptr->rld0_bunks) * llm_desc_ptr->rld0_mbytes
          + (!!llm_desc_ptr->rld1_bunks) * llm_desc_ptr->rld1_mbytes) * 1024*1024);
}

int bit_extract ( int srcvec,
                  int srcbitpos
                )
{
    return(((1 << srcbitpos) & srcvec) >> srcbitpos);
}

int bit_insert ( int srcvec,
                 int dstbitpos,
                 int dstvec
               )
{
    return((srcvec << dstbitpos) | dstvec);
}

#ifdef PRINT_LLM_CONFIG
#define ll_printf cvmx_dprintf
#else
#define ll_printf(...)
#endif

int rld_csr_config_generate(llm_descriptor_t *llm_desc_ptr, rldram_csr_config_t *cfg_ptr)
{
    char *addr_rld0_fb_str;
    char *addr_rld0_bb_str;
    char *addr_rld1_fb_str;
    char *addr_rld1_bb_str;
    int eclk_ps;
    int mtype = 0;
    int trcmin = 20;
    int trc_cyc;
    int trc_mod;
    int trl_cyc;
    int twl_cyc;
    int tmrsc_cyc = 6;
    int mclk_ps;
    int rldcfg = 99;
    int mrs_odt = 0;
    int mrs_impmatch = 0;
    int mrs_dllrst = 1;
    uint32_t mrs_dat;
    int p0_ena = 0;
    int p1_ena = 0;
    int memport = 0;
    int membunk;
    int bunkport = 0;
    int pbunk = 0;
    int tref_ms = 32;
    int trefi_ns;
    int rows = 8;
    int ref512int;
    int ref512mod;
    int tskw_cyc = 0;
    int fprch = 1;
    int bprch = 0;
    int dfa_memcfg0_base = 0;
    int dfa_memcfg1_base = 0;
    int tbl = 1;
    int rw_dly;
    int wr_dly;
    int r2r = 1;
    int sil_lat = 1;

    if (!llm_desc_ptr)
        return -1;

    addr_rld0_fb_str = llm_desc_ptr->addr_rld0_fb_str;
    addr_rld0_bb_str = llm_desc_ptr->addr_rld0_bb_str;
    addr_rld1_fb_str = llm_desc_ptr->addr_rld1_fb_str;
    addr_rld1_bb_str = llm_desc_ptr->addr_rld1_bb_str;

    p0_ena = !!llm_desc_ptr->rld1_bunks;
    p1_ena = !!llm_desc_ptr->rld0_bunks;

    if (p0_ena && p1_ena)
    {
        memport = MIN(llm_desc_ptr->rld0_mbytes, llm_desc_ptr->rld1_mbytes);
        bunkport = MIN(llm_desc_ptr->rld0_bunks, llm_desc_ptr->rld1_bunks);
    }
    else if (p0_ena)
    {
        memport = llm_desc_ptr->rld1_mbytes;
        bunkport = llm_desc_ptr->rld1_bunks;
    }
    else if (p1_ena)
    {
        memport = llm_desc_ptr->rld0_mbytes;
        bunkport = llm_desc_ptr->rld0_bunks;
    }
    else
        return -1;

    // eclk_mhz = llm_desc_ptr->cpu_hz/1000000;
    uint32_t eclk_mhz = gd->cpu_clock_mhz;

    if (eclk_mhz <= 367)
    {
        tskw_cyc = 0;
    }
    else
    {
        tskw_cyc = 1;
    }

    eclk_ps = (1000000+(eclk_mhz-1)) / eclk_mhz;

    if (mtype != 0)
    {
        goto TERMINATE;
    }

    mclk_ps = eclk_ps * 2;
    trc_cyc = ((trcmin * 1000)/mclk_ps);
    trc_mod = ((trcmin * 1000) % mclk_ps);

    if (trc_mod != 0)
    {
        trc_cyc = trc_cyc + 1;
    }

    if (trc_cyc & 1)
    {
        trc_cyc = trc_cyc + 1;
    }

    if (trc_cyc < 4)
    {
        trc_cyc = 4;
    }
    else if (trc_cyc > 8)
    {
        goto TERMINATE;
    }

    trl_cyc = trc_cyc;
    twl_cyc = trl_cyc + 1;

    if (trc_cyc == 4)
    {
        rldcfg = 1;
    }
    else if (trc_cyc == 6)
    {
        rldcfg = 2;
    }
    else if (trc_cyc == 8)
    {
        rldcfg = 3;
    }
    else
    {
        goto TERMINATE;
    }

    mrs_dat = ( (mrs_odt << 9) | (mrs_impmatch << 8) | (mrs_dllrst << 7) | rldcfg );

    if (bunkport == 1)
    {
        goto CALC_PBUNK;
    }

    CALC_PBUNK:

    if (bunkport == 2)
    {
        membunk = (memport / 2);
    }
    else
    {
        membunk = memport;
    }
    if (membunk == 16)
    {
        pbunk = 0;
    }
    else if (membunk == 32)
    {
        pbunk = 1;
    }
    else if (membunk == 64)
    {
        pbunk = 2;
    }
    else if (membunk == 128)
    {
        pbunk = 3;
    }
    else if (membunk == 256)
    {
        pbunk = 4;
    }
    else if (membunk == 512)
    {
        if ( OCTEON_IS_MODEL(OCTEON_CN56XX_PASS1_X) ||
             OCTEON_IS_MODEL(OCTEON_CN63XX_PASS1_X) ||
             OCTEON_IS_MODEL(OCTEON_CN52XX_PASS1_X) ||
             OCTEON_IS_MODEL(OCTEON_CN58XX_PASS1_X) )
        {
            goto TERMINATE;
        }
    }
    trefi_ns = (tref_ms * 1000 * 1000) / (rows * 1024);
    ref512int = ((trefi_ns * 1000) / (eclk_ps * 512));
    ref512mod = ((trefi_ns * 1000) % (eclk_ps * 512));

#if 0
    if (tskw_ps ==  0)
    {
        tskw_cyc = 0;
    }
    else
    {
        tskw_cyc = (tskw_ps / eclk_ps);
        tskw_mod = (tskw_ps % eclk_ps);
        if (tskw_mod != 0)
        {
            tskw_cyc = tskw_cyc + 1;
        }
    }
#endif
    if (tskw_cyc > 3)
    {
        goto TERMINATE;
    }

    tbl = 1;

    rw_dly = ((((trl_cyc + tbl) * 2 + tskw_cyc + bprch) + 1) / 2);
    if (rw_dly & 1)
    {
        rw_dly = rw_dly + 1;
    }
    rw_dly = rw_dly - twl_cyc +1 ;
    if (rw_dly < 0)
    {
        goto TERMINATE;
    }

    wr_dly = (((twl_cyc + tbl) * 2 - tskw_cyc + fprch) / 2);
    if (wr_dly & 1)
    {
        wr_dly = wr_dly + 1;
    }
    wr_dly = wr_dly - trl_cyc + 1;
    if (wr_dly < 0)
    {
        goto TERMINATE;
    }

    dfa_memcfg0_base = 0;
    dfa_memcfg0_base = ( p0_ena |
                         (p1_ena << 1) |
                         (mtype << 3) |
                         (sil_lat << 4) |
                         (rw_dly << 6) |
                         (wr_dly << 10) |
                         (fprch << 14) |
                         (bprch << 16) |
                         (0 << 18) |
                         (pbunk << 19) |
                         (r2r << 22)
                       );

    dfa_memcfg1_base = 0;
    dfa_memcfg1_base = ( ref512int |
                         (tskw_cyc << 4) |
                         (trl_cyc << 8) |
                         (twl_cyc << 12) |
                         (trc_cyc << 16) |
                         (tmrsc_cyc << 20)
                       );

	cfg_ptr->dfa_memcfg0_base = dfa_memcfg0_base;
    cfg_ptr->dfa_memcfg1_base = dfa_memcfg1_base;	
	cfg_ptr->mrs_dat_p0bunk0 =  0x00000082;
    cfg_ptr->mrs_dat_p1bunk0 =  0x00000082;
    cfg_ptr->mrs_dat_p0bunk1 =  0x00020800;
    cfg_ptr->mrs_dat_p1bunk1 =  0x00020800;	
	cfg_ptr->p0_ena =           p0_ena;
    cfg_ptr->p1_ena =           p1_ena;
    cfg_ptr->bunkport =         bunkport;

    return(0);
    TERMINATE:
    return(-1);

}

void write_rld_cfg(rldram_csr_config_t *cfg_ptr)
{
    if ((OCTEON_IS_MODEL(OCTEON_CN38XX) || OCTEON_IS_MODEL(OCTEON_CN58XX)))
    {
        uint32_t dfa_memcfg0;

        cvmx_write_csr(CVMX_DFA_MEMCFG0, cfg_ptr->dfa_memcfg0_base);
        ll_printf("CVMX_DFA_MEMCFG0: 0x%08x\n", cfg_ptr->dfa_memcfg0_base);
        cvmx_write_csr(CVMX_DFA_MEMCFG1, cfg_ptr->dfa_memcfg1_base);
        ll_printf("CVMX_DFA_MEMCFG1: 0x%08x\n", cfg_ptr->dfa_memcfg1_base);

        if (cfg_ptr->p0_ena ==1)
        {
            cvmx_write_csr(CVMX_DFA_MEMRLD,  cfg_ptr->mrs_dat_p0bunk0);
            ll_printf("CVMX_DFA_MEMRLD: 0x%08x\n", cfg_ptr->mrs_dat_p0bunk0);
            dfa_memcfg0 = ( cfg_ptr->dfa_memcfg0_base |
                            (1 << 23) |
                            (1 << 25)
                          );

            cvmx_write_csr(CVMX_DFA_MEMCFG0, dfa_memcfg0);
            ll_printf("CVMX_DFA_MEMCFG0: 0x%08x\n", dfa_memcfg0);
            cvmx_wait(RLD_INIT_DELAY);
            ll_printf("Delay.....\n");
            cvmx_write_csr(CVMX_DFA_MEMCFG0, cfg_ptr->dfa_memcfg0_base);
            ll_printf("CVMX_DFA_MEMCFG0: 0x%08x\n", cfg_ptr->dfa_memcfg0_base);
        }

        if (cfg_ptr->p1_ena ==1)
        {
            cvmx_write_csr(CVMX_DFA_MEMRLD,  cfg_ptr->mrs_dat_p1bunk0);
            ll_printf("CVMX_DFA_MEMRLD: 0x%08x\n", cfg_ptr->mrs_dat_p1bunk0);
            dfa_memcfg0 = ( cfg_ptr->dfa_memcfg0_base |
                            (1 << 24) |
                            (1 << 25)
                          );

            cvmx_write_csr(CVMX_DFA_MEMCFG0, dfa_memcfg0);
            ll_printf("CVMX_DFA_MEMCFG0: 0x%08x\n", dfa_memcfg0);
            cvmx_wait(RLD_INIT_DELAY);
            ll_printf("Delay.....\n");
            cvmx_write_csr(CVMX_DFA_MEMCFG0, cfg_ptr->dfa_memcfg0_base);
            ll_printf("CVMX_DFA_MEMCFG0: 0x%08x\n", cfg_ptr->dfa_memcfg0_base);
        }

        if ((cfg_ptr->p0_ena ==1) && (cfg_ptr->bunkport == 2))
        {
            cvmx_write_csr(CVMX_DFA_MEMRLD,  cfg_ptr->mrs_dat_p0bunk1);
            ll_printf("CVMX_DFA_MEMRLD: 0x%08x\n", cfg_ptr->mrs_dat_p0bunk1);
            dfa_memcfg0 = ( cfg_ptr->dfa_memcfg0_base |
                            (1 << 23) |
                            (2 << 25)
                          );
            cvmx_write_csr(CVMX_DFA_MEMCFG0, dfa_memcfg0);
            ll_printf("CVMX_DFA_MEMCFG0: 0x%08x\n", dfa_memcfg0);
            cvmx_wait(RLD_INIT_DELAY);
            ll_printf("Delay.....\n");
            if (cfg_ptr->p1_ena == 1)
            {
                cvmx_write_csr(CVMX_DFA_MEMCFG0, cfg_ptr->dfa_memcfg0_base);
                ll_printf("CVMX_DFA_MEMCFG0: 0x%08x\n", cfg_ptr->dfa_memcfg0_base);
            }
        }

        if ((cfg_ptr->p1_ena == 1) && (cfg_ptr->bunkport == 2))
        {
            cvmx_write_csr(CVMX_DFA_MEMRLD,  cfg_ptr->mrs_dat_p1bunk1);
            ll_printf("CVMX_DFA_MEMRLD: 0x%08x\n", cfg_ptr->mrs_dat_p1bunk1);
            dfa_memcfg0 = ( cfg_ptr->dfa_memcfg0_base |
                            (1 << 24) |
                            (2 << 25)
                          );
            cvmx_write_csr(CVMX_DFA_MEMCFG0, dfa_memcfg0);
            ll_printf("CVMX_DFA_MEMCFG0: 0x%08x\n", dfa_memcfg0);
        }
        cvmx_wait(RLD_INIT_DELAY);
        ll_printf("Delay.....\n");

        dfa_memcfg0 = cfg_ptr->dfa_memcfg0_base |((cfg_ptr->bunkport >= 1) << 25) | ((cfg_ptr->bunkport == 2) << 26);
        cvmx_write_csr(CVMX_DFA_MEMCFG0, dfa_memcfg0);
        ll_printf("CVMX_DFA_MEMCFG0: 0x%08x\n", dfa_memcfg0);
        cvmx_wait(RLD_INIT_DELAY);
        ll_printf("Delay.....\n");
    }
}

