//==========================================================================
//
//      mxc_spi.c
//
//      SPI support on Freescale MXC platforms
//
//==========================================================================
//####ECOSGPLCOPYRIGHTBEGIN####
// -------------------------------------------
// This file is part of eCos, the Embedded Configurable Operating System.
// Copyright (C) 1998, 1999, 2000, 2001, 2002 Red Hat, Inc.
//
// eCos 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 or (at your option) any later version.
//
// eCos 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 eCos; if not, write to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
//
// As a special exception, if other files instantiate templates or use macros
// or inline functions from this file, or you compile this file and link it
// with other works to produce a work based on this file, this file does not
// by itself cause the resulting work to be covered by the GNU General Public
// License. However the source code for this file must still be made available
// in accordance with section (3) of the GNU General Public License.
//
// This exception does not invalidate any other reasons why a work based on
// this file might be covered by the GNU General Public License.
//
// Alternative licenses for eCos may be arranged by contacting Red Hat, Inc.
// at http://sources.redhat.com/ecos/ecos-license/
// -------------------------------------------
//####ECOSGPLCOPYRIGHTEND####
//==========================================================================
//#####DESCRIPTIONBEGIN####
//
// Author(s):    Kevin Zhang <k.zhang@freescale.com>
// Contributors:
// Date:         2006-08-24
// Purpose:
// Description:
//
//####DESCRIPTIONEND####
//
//==========================================================================

#include <redboot.h>
#include <stdlib.h>
#include <pkgconf/hal.h>
#include <cyg/hal/hal_arch.h>
#include <cyg/hal/hal_cache.h>
#include <cyg/hal/hal_io.h>

#include <cyg/io/mxc_spi.h>

#ifdef CYGPKG_HAL_ARM_G23
#include <cyg/hal/kinpo_g23.h>             // Platform specifics
extern void get_cpld_board_phase(cyg_int8 *VR_phase);
extern void get_cpld_pcb_version(cyg_int8 *VR_pcb);
#endif

void clock_spi_enable(unsigned int spi_clk);

#undef MXC_SPI_DEBUG
//#define MXC_SPI_DEBUG

#ifdef MXC_SPI_DEBUG
#define diag_printf1    diag_printf
#else
#define diag_printf1(fmt,args...)
#endif

#if defined(MXC_SPI_VER_0_7) || defined(MXC_SPI_VER_0_4)
const unsigned int baud_rate_div[] = {
    4, 8, 16, 32, 64, 128, 256, 512,
};

#elif defined(MXC_SPI_VER_XX)
const unsigned int baud_rate_div[] = {
    3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128, 192, 256, 384, 512,
};

#else
#error No SPI version defined
#endif

const int BAUD_RATE_DIV_MAX = sizeof(baud_rate_div) / sizeof(unsigned int);

/*!
 * It is for the master mode operation to exchange a single word with
 * external device.
 *
 * @param   data        data to be transferred
 * @param   base        base address of the spi module
 *
 * @return              the value received from the Rx register
 */
unsigned int spi_xchg_single(unsigned int data, unsigned int base)
{
    volatile unsigned int cfg_reg = readl(base + SPI_CTRL_REG_OFF);

    writel(data, base + SPI_TX_REG_OFF);

    cfg_reg |= SPI_CTRL_REG_XCH_BIT;

    writel(cfg_reg, base + SPI_CTRL_REG_OFF);

    while ((readl(base + SPI_STAT_REG_OFF) & CSPI_STATREG_RR) == 0) {
    }

    return readl(base + SPI_RX_REG_OFF);
}

/*!
 * Initialize and enable a spi module
 *
 * @param   base        base address of spi module (also assigned for SPIx_CLK)
 * @param   baue        the desired data rate
 * @param   ctrl_val    control register value EXCEPT the data rate
 *
 * @return              0 if successful; non-zero otherwise
 */
int spi_init(unsigned int base, unsigned int baud, unsigned int ctrl_val)
{
    unsigned int clock = get_peri_clock(base);
    int i, div = clock / baud;

    clock_spi_enable(base);

    diag_printf1("base=0x%x, baue=%d, ctrl_val=0x%x, div=%d, clock=%d\n",
                base, baud, ctrl_val, div, clock);

    for (i = 0; i < BAUD_RATE_DIV_MAX; i++) {
        if (div <= baud_rate_div[i]) {
            break;
        }
    }
    if (i == BAUD_RATE_DIV_MAX) {
        diag_printf("Baud rate requested (%d) is too slow for spi(0x%x)\n",
                    baud, base);
        return -1;
    }

    // to adjust for differen spi versions
    if (BAUD_RATE_DIV_MAX > 8) {
        ctrl_val |= ((i + 1) << SPI_CTRL_REG_RATE_SH);
    } else
        ctrl_val |= (i << SPI_CTRL_REG_RATE_SH);


    diag_printf1("ctrl_val=0x%x, i=%d, SPI_CTRL_REG_RATE_SH=%d\n",
                ctrl_val, i, SPI_CTRL_REG_RATE_SH);

    writel(SPI_CTRL_EN, base + SPI_CTRL_REG_OFF);

    writel(ctrl_val, base + SPI_CTRL_REG_OFF);

    diag_printf1("requested data rate is: %d, actual rate is: %d\n",
                 baud, clock / baud_rate_div[i]);

    return 0;
}

#ifdef PMIC_SPI_BASE
void mxc_pmic_init(void)
{

#ifdef CYGPKG_HAL_ARM_G23
    cyg_int8 VR_pcb, VR_phase;
    get_cpld_pcb_version(&VR_pcb);
    get_cpld_board_phase(&VR_phase);
#endif		

    spi_init(PMIC_SPI_BASE, 16000000,     // 16MHz data rate
             SPI_CTRL_REG_BIT_COUNT32 |
             SPI_CTRL_CS0 |
             SPI_CTRL_SSPOL_HIGH |
             SPI_CTRL_MODE_MASTER |
             SPI_CTRL_EN
             );

#ifdef CYGPKG_HAL_ARM_G23
    /* PMIC register initial */
    pmic_reg(12, 0x000000, 1);
    pmic_reg(13, 0xC0BE44, 1);
    if (VR_pcb == Japan && VR_phase >= BOARD_PHASE_DVT){
        pmic_reg(15, 0x000080, 1);
    }else{
        pmic_reg(15, 0x000000, 1);
    }


    pmic_reg(16, 0x200000, 1);
    pmic_reg(24, 0x00038E, 1);
    pmic_reg(25, 0x01C71C, 1);
    pmic_reg(26, 0x024924, 1);
    pmic_reg(27, 0x024924, 1);
    pmic_reg(28, 0x220201, 1);
    pmic_reg(29, 0x020201, 1);
    pmic_reg(30, 0x0522AC, 1);
    pmic_reg(31, 0x000E7F, 1);
    pmic_reg(32, 0x60A24D, 1);
    pmic_reg(33, 0x0C9A80, 1);
#endif
}

static void mxc_pmic_info(void)
{
    volatile unsigned int rev_id;

    rev_id = pmic_reg(7, 0, 0);
	diag_printf("PMIC ID: 0x%08x [Rev: ", rev_id);
    switch (rev_id & 0x1F) {
    case 0x1:
        diag_printf("1.0");
        break;
    case 0x9:
        diag_printf("1.1");
        break;
    case 0xA:
        diag_printf("1.2");
        break;
    case 0x10:
        diag_printf("2.0");
        break;
    case 0x11:
        diag_printf("2.1");
        break;
    case 0x18:
        diag_printf("3.0");
        break;
    case 0x19:
        diag_printf("3.1");
        break;
    case 0x1A:
        diag_printf("3.2");
        break;
    case 0x2:
        diag_printf("3.2A");
        break;
    case 0x1B:
        diag_printf("3.3");
        break;
    case 0x1D:
        diag_printf("3.5");
        break;
    default:
        diag_printf("unknown");
        break;
    }
    diag_printf("]\n");
}

RedBoot_init(mxc_pmic_info, RedBoot_INIT_PRIO(100));

static void do_pmic(int argc, char *argv[]);
RedBoot_cmd("pmic",
            "Read/Write internal PMIC register",
            "<reg num> [value to be written]",
            do_pmic
           );

static void do_pmic(int argc,char *argv[])
{
    unsigned int reg, temp, val = 0, write = 0;

    if (argc == 1) {
        diag_printf("\tRead:  pmic <reg num>\n");
        diag_printf("\tWrite: pmic <reg num> <value to be written>\n");
        return;
    }

    if (!parse_num(*(&argv[1]), (unsigned long *)&reg, &argv[1], ":")) {
        diag_printf("Error: Invalid parameter\n");
        return;
    }

    if (argc == 3) {
        if (!parse_num(*(&argv[2]), (unsigned long *)&val, &argv[2], ":")) {
            diag_printf("Error: Invalid parameter\n");
            return;
        }
        write = 1;
    }

    temp = pmic_reg(reg, val, write);

    diag_printf("\tval: 0x%08x\n\n", temp);
}

/*!
 * To read/write to a PMIC register. For write, it does another read for the
 * actual register value.
 *
 * @param   reg         register number inside the PMIC
 * @param   val         data to be written to the register; don't care for read
 * @param   write       0 for read; 1 for write
 *
 * @return              the actual data in the PMIC register
 */
unsigned int pmic_reg(unsigned int reg, unsigned int val, unsigned int write)
{
    unsigned int temp;

    if (reg > 63 || write > 1 ) {
        diag_printf("<reg num> = %d is invalide. Should be less then 63\n", reg);
        return 0;
    }
    val = (write << 31) | (reg << 25) | (val & 0x00FFFFFF);
    diag_printf1("reg=0x%x, val=0x%08x\n", reg, val);

    temp = spi_xchg_single(val, PMIC_SPI_BASE);
    // have to do another xfer to get SPI read works properly on MX27
    //temp = spi_xchg_single(val, PMIC_SPI_BASE);

    if (write) {
        val &= ~(1 << 31);
        temp = spi_xchg_single(val, PMIC_SPI_BASE);
    }

    return temp;
}

/*!
 * To write to a PMIC register with a mask.
 *
 * @param   reg         register number inside the PMIC
 * @param   val         data to be written to the register
 * @param   mask        register mask
 *
 * @return              the actual data in the PMIC register
 */
unsigned int pmic_reg_set(unsigned int reg, unsigned int val, unsigned int mask)
{
    unsigned int temp;

    temp = pmic_reg(reg, 0, 0);
    temp = (temp & (~mask)) | (val & mask);
    pmic_reg(reg, temp, 1);
}
#endif // PMIC_SPI_BASE
