/* 2012-09-16: File added and changed by Sony Corporation */
/*
 * icx_spi.c
 *
 * Copyright 2011 Sony Corporation.
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 */

#include <common.h>

#if defined(CONFIG_ICX) || defined(CONFIG_ICD)

#include <asm/errno.h>

#include <stdbool.h>

#include "reg.h"
#include "icx_io.h"
#include "icx_smu.h"
#include "icx_spi.h"


/**************/
/*@ registers */
/**************/
#define SIO0_BASE           0xe0120000
#define SIO1_BASE           0xe0100000
#define SIO2_BASE           0xe10c0000
#define SIO3_BASE           0xe10d0000
#define SPI_BASE_OFFSET     0x1000


/***************/
/*@ definition */
/***************/

/* Mode setting:Enable/Disable */
#define	SIO_SWITCH_ENABLE		1
#define	SIO_SWITCH_DISABLE		0

/* Mode setting:SPI/Audio */
#define SIO_MODE_SWITCH_UNUSED	0
#define SIO_MODE_SWITCH_SPI		1
#define SIO_MODE_SWITCH_AUDIO	2

/* SPI mode:Master/Slave */
#define SPI_M_S_MASTER      (0 << 1)
#define SPI_M_S_SLAVE       (1 << 1)
#define SPI_M_S             SPI_M_S_MASTER

/* SPI mode:DMA on/off */
#define SPI_DMA_OFF         (0 << 0)
#define SPI_DMA_ON          (1 << 0)
#define SPI_DMA             SPI_DMA_OFF

/* SPI mode:NB */
#define SPI_NB_MIN          8
#define SPI_NB_MAX          32

/* SPI pol.CSW */
#define SPI_POL_CSW         (7 << 12)

/* SPI pol.mask */
#define SPI_CSW_MASK        0x0000F000
#define SPI_POL_MASK        0x00000007

/* SPx_CONTROL */
#define SPx_CONTROL_START   0x00000001
#define SPx_CONTROL_RD      0x00000004
#define SPx_CONTROL_WRT     0x00000008
#define SPx_CONTROL_RX_EMP  0x00000040
#define SPx_CONTROL_RST     0x00000100

/* SPx_FFCLR */
#define SPx_FFCLR_ALL       0x0000007F

/* SPx_RAW_STATUS */
#define SPx_RAW_STATUS_RX_OVR_RAW   0x00000001
#define SPx_RAW_STATUS_TX_UDR_RAW   0x00000002
#define SPx_RAW_STATUS_TERR_RAW     0x00000010

#define SPx_RAW_STATUS_TX_ALLERR_RAW    (   \
	SPx_RAW_STATUS_TX_UDR_RAW               \
)
#define SPx_RAW_STATUS_RX_ALLERR_RAW    (   \
	SPx_RAW_STATUS_RX_OVR_RAW               \
  | SPx_RAW_STATUS_TERR_RAW                 \
)
#define SPx_RAW_STATUS_ALLERR_RAW       (   \
	SPx_RAW_STATUS_TX_ALLERR_RAW            \
  | SPx_RAW_STATUS_RX_ALLERR_RAW            \
)

/* Read/Write */
#define SPI_READ            1
#define SPI_WRITE           2
#define SPI_RW              3

/* SP0 SO,SI,CS0,CLK */
#define	CHG_PINSEL_SP0				CHG_PINSEL_G096
#define CHG_PINSEL_SP0_MSK          0xF0000000		/* CHG_PINSEL_G096[31:28]	*/ 
#define CHG_PINSEL_SP0_VAL			0x00000000
#define CHG_PULL_SP0				CHG_PULL15
#define CHG_PULL_SP0_MSK			0xFFFF0000		/* CHG_PULL15[31:16]		*/
#define CHG_PULL_SP0_VAL			0x00700000
#define	CHG_PINSEL_SP0_CS12			CHG_PINSEL_G128
#define CHG_PINSEL_SP0_CS2_MSK		0x00040000
#define CHG_PINSEL_SP0_CS2_VAL		0x00000000
#define CHG_PULL_SP0_CS12			CHG_PULL18
#define CHG_PULL_SP0_CS2_MSK		0x00000F00		/* CHG_PULL18[11: 8]		*/
#define CHG_PULL_SP0_CS2_VAL		0x00000000

/* SP1 SO,SI,CS0,CLK */
#define	CHG_PINSEL_SP1				CHG_PINSEL_G128
#define CHG_PINSEL_SP1_MSK  		0x00780000		/* CHG_PINSEL_G128[22:19]	*/
#define CHG_PINSEL_SP1_VAL			0x00000000
#define CHG_PULL_SP1				CHG_PULL18
#define CHG_PULL_SP1_MSK			0x0FFFF000		/* CHG_PULL18[27:12]		*/
#define CHG_PULL_SP1_VAL			0x000F0000

/* SP2 SO,SI,CS0,CLK */
#define	CHG_PINSEL_SP2				CHG_PINSEL_G128
#define CHG_PINSEL_SP2_MSK  		0x19800000		/* CHG_PINSEL_G128[22:19]	*/
#define CHG_PINSEL_SP2_VAL			0x00000000
#define CHG_PULL_SP2_1				CHG_PULL18
#define CHG_PULL_SP2_1_MSK			0xF0000000		/* CHG_PULL18[31:28]		*/
#define CHG_PULL_SP2_1_VAL			0x30000000
#define CHG_PULL_SP2_2				CHG_PULL19
#define CHG_PULL_SP2_2_MSK			0x000FF00F		/* CHG_PULL19[19:12,3:0]	*/
#define CHG_PULL_SP2_2_VAL			0x00000000

/* SP3 SO,SI,CS0,CLK */
#define	CHG_PINSEL_MODE_SP3			CHG_PINSEL_LCD3
#define	CHG_PINSEL_MODE_SP3_MSK		0x00003000		/* CHG_PINSEL_LCD3[13:12]	*/
#define	CHG_PINSEL_MODE_SP3_VAL		0x00003000
#define CHG_PINSEL_SP3				CHG_PINSEL_G032
#define CHG_PINSEL_SP3_MSK			0x78000000		/* CHG_PINSEL_G032[30:27]	*/
#define CHG_PINSEL_SP3_VAL			0x00000000
#define CHG_PULL_SP3				CHG_PULL7
#define CHG_PULL_SP3_MSK			0x0FFFF000		/* CHG_PULL7[27:12]			*/
#define CHG_PULL_SP3_VAL			0x000F0000


/**************/
/*@ structure */
/**************/

/* SPI mode setting register info */
typedef struct {
	volatile u_int switch_en;	/* SPx_SWITCH_EN		(0x00)	*/
	volatile u_int mode_switch;	/* SPx_MODE_SWITCH		(0x04)	*/
} sio_mode_regs_t;

/* SPI register info */
typedef struct {
	volatile u_int mode;        /* SPx_MODE             (0x00)  */
	volatile u_int pol;         /* SPx_POL              (0x04)  */
	volatile u_int control;     /* SPx_CONTROL          (0x08)  */
			 u_int reserve1;    /* Reserve              (0x0C)  */
	volatile u_int tx_data;     /* SPx_TX_DATA          (0x10)  */
	volatile u_int rx_data;     /* SPx_RX_DATA          (0x14)  */
	volatile u_int status;      /* SPx_STATUS           (0x18)  */
	volatile u_int raw_status;  /* SPx_RAW_STATUS       (0x1C)  */
	volatile u_int enset;       /* SPx_ENSET            (0x20)  */
	volatile u_int enclr;       /* SPx_ENCLR            (0x24)  */
	volatile u_int ffclr;       /* SPx_FFCLR            (0x28)  */
			 u_int reserve2;    /* Reserve              (0x2C)  */
	volatile u_int rx_fifo_p;   /* SPx_RX_FIFO_P        (0x30)  */
	volatile u_int control2;    /* SPx_CONTROL2         (0x34)  */
	volatile u_int tiecs;       /* SPx_TIECS            (0x38)  */
} spi_regs_t;

/* SMU info */
typedef struct {
	u_int reset;                /* reset                        */
	u_int sclk_div;             /* SPIx_SCLK_DIV                */
	u_int pclk;                 /* pclk                         */
	u_int sclk;                 /* sclk                         */
	u_int pclk_ctrl;            /* pclk control                 */
	u_int sclk_ctrl;            /* sclk control                 */
} spi_smu_t;

/* SPI info */
typedef struct {
	sio_mode_regs_t	*mode_regs;	/* SPI mode setting register	*/
	spi_regs_t		*regs;		/* SPI register info            */
	spi_smu_t		*smu;		/* SMU info                     */
	u_int			probe;		/* probed or not                */ 
} spi_info_t;


/***************/
/*@ prototypes */
/***************/

static int	spi_xferbytes(spi_info_t *spi, u_int *data, int mode_flag);
static int	spi_config(spi_info_t *spi, spi_config_t *config);
static int	spi_set_sclk_div(u_char dev, spi_info_t *spi, u_int sclk);
static void spi_power_on(spi_info_t *spi);
static void spi_power_off(spi_info_t *spi);
static void spi_soft_reset(spi_info_t *spi, spi_config_t *config);


/********************/
/*@ local variables */
/********************/

/* SMU register(SP0) */
static spi_smu_t spi_smu_sp0 = {
	.reset      = SMU_RST_USIA_S0_S | SMU_RST_USIA_S0_A,
	.sclk_div   = SMU_USIASCLKDIV,
	.pclk       = SMU_CLK_USIA_S0_P | SMU_CLK_USIA_S0_H,
	.sclk       = SMU_CLK_USIA_S0_S,
	.pclk_ctrl  = SMU_CLKCTRL_USIAS0PCLK,
	.sclk_ctrl  = SMU_CLKCTRL_USIAS0,
};

/* SMU register(SP1) */
static spi_smu_t spi_smu_sp1 = {
	.reset      = SMU_RST_USIA_S1_A | SMU_RST_USIA_S1_S,
	.sclk_div   = SMU_USIASCLKDIV,
	.pclk       = SMU_CLK_USIA_S1_P | SMU_CLK_USIA_S1_H,
	.sclk       = SMU_CLK_USIA_S1_S,
	.pclk_ctrl  = SMU_CLKCTRL_USIAS1PCLK,
	.sclk_ctrl  = SMU_CLKCTRL_USIAS1,
};

/* SMU register(SP2) */
static spi_smu_t spi_smu_sp2 = {
	.reset      = SMU_RST_USIB_S2_A | SMU_RST_USIB_S2_S,
	.sclk_div   = SMU_USIB0SCLKDIV,
	.pclk       = SMU_CLK_USIB_S2_P | SMU_CLK_USIB_S2_H,
	.sclk       = SMU_CLK_USIB_S2_S,
	.pclk_ctrl  = SMU_CLKCTRL_USIBS2PCLK,
	.sclk_ctrl  = SMU_CLKCTRL_USIBS2,
};

/* SMU register(SP3) */
static spi_smu_t spi_smu_sp3 = {
	.reset      = SMU_RST_USIB_S3_A | SMU_RST_USIB_S3_S,
	.sclk_div   = SMU_USIB0SCLKDIV,
	.pclk       = SMU_CLK_USIB_S3_P | SMU_CLK_USIB_S3_H,
	.sclk       = SMU_CLK_USIB_S3_S,
	.pclk_ctrl  = SMU_CLKCTRL_USIBS3PCLK,
	.sclk_ctrl  = SMU_CLKCTRL_USIBS3,
};

/* SPI info */
static spi_info_t spi_info[] = {
	{   /* SP0 */
		.mode_regs	= (sio_mode_regs_t *)SIO0_BASE,
		.regs   	= (spi_regs_t *)(SIO0_BASE + SPI_BASE_OFFSET),
		.smu		= &spi_smu_sp0,
		.probe		= 0,
	},
	{   /* SP1 */
		.mode_regs	= (sio_mode_regs_t *)SIO1_BASE,
		.regs   	= (spi_regs_t *)(SIO1_BASE + SPI_BASE_OFFSET),
		.smu		= &spi_smu_sp1,
		.probe		= 0,
	},
	{	/* SP2 */
		.mode_regs	= (sio_mode_regs_t *)SIO2_BASE,
		.regs   	= (spi_regs_t *)(SIO2_BASE + SPI_BASE_OFFSET),
		.smu		= &spi_smu_sp2,
		.probe		= 0,
	},
	{   /* SP3 */
		.mode_regs	= (sio_mode_regs_t *)SIO3_BASE,
		.regs   	= (spi_regs_t *)(SIO3_BASE + SPI_BASE_OFFSET),
		.smu		= &spi_smu_sp3,
		.probe		= 0,
	},
};

static u_int spi_clock_table[] = {
	1436,   /* 1434KHz		*/
	2867,   /* 2.867MHz		*/
	5734,   /* 5.734MHz		*/
	11469,  /* 11.469MHz	*/
	22938,  /* 22.938MHz	*/
	38229,  /* 38.229MHz	*/
	1060,	/* 1.06MHz		*/
};

static unsigned int spi_plldiv_table[] = {
	SMU_DIV(160),   /* 1500KHz(1434KHz)		*/
	SMU_DIV(80),    /* 3MHz   (2.87MHz)		*/
	SMU_DIV(40),    /* 6MHz   (5.73MHz)		*/
	SMU_DIV(20),    /* 12MHz  (11.47MHz)	*/
	SMU_DIV(10),    /* 24MHz  (22.94MHz)	*/
	SMU_DIV(6),     /* 48MHz  (38.23MHz)	*/
	SMU_DIV(224),	/* 1MHz   (1.06MHz)		*/
};

/*-----------------------------------------------------------------------------
 * Function	: spi_config_check
 * Abstruct : check SPI config.
 * Input    : config    : SPI config
 * Return   : 0         : success
 *          : -EINVAL   : invalid parameters
 *          : -ENODEV   : no device
 *----------------------------------------------------------------------------*/
static int spi_config_check(spi_config_t *config)
{
    /* check the parameters */
	if (config == NULL)
		return -EINVAL;

#ifdef CONFIG_ICX
#ifdef CONFIG_BROWNIE
	if ((config->dev != SPI_DEV_SP0) && (config->dev != SPI_DEV_SP1))
		return -ENODEV;
#else
	if ((config->dev != SPI_DEV_SP0) && (config->dev != SPI_DEV_SP3))
		return -ENODEV;
#endif /* CONFIG_BROWNIE */

	if (config->cs != SPI_CS_SEL_CS0)
		return -ENODEV;
#else /* CONFIG_ICD */
	if ((config->dev != SPI_DEV_SP0) && (config->dev != SPI_DEV_SP2))
		return -ENODEV;

	if (config->cs != SPI_CS_SEL_CS0)
		return -ENODEV;
#endif

    return 0;
}

/********************/
/*@ global routines */
/********************/

/*-----------------------------------------------------------------------------
 * Function	: spi_init
 * Abstruct : Initialize SPI.
 * Input    : none
 * Return   : none
 * Note     : used by ICX SP0_CS0 (PowerIC)
 *          :             SP1_CS0 (DAC (Brownie:LCD))
 *          :             SP3_CS0 (LCD (Brownie:DAC))
 *          : used by ICD SP0_CS0 (PowerIC)
 *          :             SP2_CS0 (LCD)
 *----------------------------------------------------------------------------*/
void spi_init(void)
{
	/* SPO SO,SI,CS0,CLK */
	setl(CHG_PINSEL_SP0_VAL, CHG_PINSEL_SP0_MSK, CHG_PINSEL_SP0);
	setl(CHG_PULL_SP0_VAL, CHG_PULL_SP0_MSK, CHG_PULL_SP0);

#ifdef CONFIG_ICX
#ifdef CONFIG_BROWNIE
	/* SP1 SO,SI,CS0,CLK */
	setl(CHG_PINSEL_SP1_VAL, CHG_PINSEL_SP1_MSK, CHG_PINSEL_SP1);
	setl(CHG_PULL_SP1_VAL, CHG_PULL_SP1_MSK, CHG_PULL_SP1);
#else
	/* SP3 SO,SI,CS0,CLK */
	setl(CHG_PINSEL_MODE_SP3_VAL, CHG_PINSEL_MODE_SP3_MSK, CHG_PINSEL_MODE_SP3);
	setl(CHG_PINSEL_SP3_VAL, CHG_PINSEL_SP3_MSK, CHG_PINSEL_SP3);
	setl(CHG_PULL_SP3_VAL, CHG_PULL_SP3_MSK, CHG_PULL_SP3);
#endif /* CONFIG_BROWNIE */
#else  /* CONFIG_ICD */
    /* SP2 SO,SI,CS0,CLK */
    setl(CHG_PINSEL_SP2_VAL, CHG_PINSEL_SP2_MSK, CHG_PINSEL_SP2);
    setl(CHG_PULL_SP2_1_VAL, CHG_PULL_SP2_1_MSK, CHG_PULL_SP2_1);
    setl(CHG_PULL_SP2_2_VAL, CHG_PULL_SP2_2_MSK, CHG_PULL_SP2_2);
#endif /* CONFIG_ICX */
}

/*-----------------------------------------------------------------------------
 * Function	: spi_probe
 * Abstruct : Probe SPI.
 * Input    : config    : SPI config
 * Return   : 0         : success
 *          : -EINVAL   : invalid parameters
 *          : -ENODEV   : no device
 * Note     : use SP0_CS0 (PowerIC)
 *          :     SP1_CS0 (LCD : Brownie)
 *          :     SP3_CS0 (LCD)
 *----------------------------------------------------------------------------*/
int spi_probe(spi_config_t *config)
{
	spi_info_t *spi;
	int ret = 0;

    ret = spi_config_check(config);
    if(ret) 
        return ret;

	/* power on and configuration */
	spi = &spi_info[config->dev];

	if (spi->probe == 0) {
		spi_power_on(spi);

		/* set mode switch */
		spi->mode_regs->switch_en	= SIO_SWITCH_ENABLE;
		spi->mode_regs->mode_switch	= SIO_MODE_SWITCH_SPI;
		spi->mode_regs->switch_en	= SIO_SWITCH_DISABLE;

		/* software reset */
		spi_soft_reset(spi, config);

		spi->probe = 1;
	}

	/* configuration */
	ret = spi_config(spi, config);

	return ret;
}

/*-----------------------------------------------------------------------------
 * Function	: spi_remove
 * Abstruct : Remove SPI.
 * Input    : config    : SPI config
 * Return   : 0         : success
 *          : -EINVAL   : invalid parameters
 *          : -ENODEV   : no device
 *----------------------------------------------------------------------------*/
int spi_remove(spi_config_t *config)
{
    int ret;
	spi_info_t *spi;

    ret = spi_config_check(config);
    if(ret) 
        return ret;

	spi = &spi_info[config->dev];

	if (spi->probe != 0) {
		spi_power_off(spi);

		spi->probe = 0;
	}

	return 0;
}

/*-----------------------------------------------------------------------------
 * Function	: spi_write
 * Abstruct : Write a data to SPI.
 * Input    : config    : SPI config
 *          : buf       : data buffer
 *          : count     : data length
 * Return   : >= 0      : success
 *          : -EINVAL   : invalid parameters
 *          : -ENODEV   : no device
 *          : -EPERM    : invalid parameter
 *----------------------------------------------------------------------------*/
int spi_write(spi_config_t *config, u_int *buf, u_int count)
{
	spi_info_t	*spi;
	u_int		i;
	int			ret;

	/* check the parameters */
	if ((config == NULL) || (buf == NULL))
		return -EINVAL;

    ret = spi_config_check(config);
    if(ret) 
        return ret;

	spi = &spi_info[config->dev];

	if (spi->probe == 0)
		return -EPERM;

	/* send data */
	ret = 0;
	for (i = 0; i < count; i++) {
		ret = spi_xferbytes(spi, &buf[i], SPI_WRITE);
		if (ret < 0) {
			spi_soft_reset(spi, config);
			break;
		}
	}
	if (ret == 0)
		ret = count;

	return ret;
}

/*-----------------------------------------------------------------------------
 * Function	: spi_read
 * Abstruct : Read a data from SPI.
 * Input    : config    : SPI config
 *          : cmd       : command buffer
 *          : buf       : data buffer
 *          : flag      : read mode (1cycle/2cycle)
 * Return   : 0         : success
 *          : -EINVAL   : invalid parameters
 *          : -ENODEV   : no device
 *          : -EPERM    : invalid parameter
 * Note     : noly applied to read with command
 *----------------------------------------------------------------------------*/
int spi_read(spi_config_t *config, u_int *cmd, u_int *buf, u_int flag)
{
	spi_info_t	*spi;
	u_int		rw_data;
	int			ret;

	/* check the parameters */
	if ((config == NULL) || (buf == NULL))
		return -EINVAL;

    ret = spi_config_check(config);
    if(ret) 
        return ret;

	spi = &spi_info[config->dev];

	if (spi->probe == 0)
		return -EPERM;

	if (flag == SPI_READ_1CYCLE) {
		/* send the command and recieve a data */
		ret = spi_config(spi, config);
		if (ret == 0) {
			rw_data = *cmd << config->nbr;
			ret = spi_xferbytes(spi, &rw_data, SPI_RW);
			if (ret < 0) 
				spi_soft_reset(spi, config);
			*buf = rw_data;
		}
	}
	else if (flag == SPI_READ_2CYCLE) {
		/* send the command */
		ret = spi_config(spi, config);
		if (ret == 0) {
			ret = spi_xferbytes(spi, cmd, SPI_WRITE);
			if (ret < 0) 
				spi_soft_reset(spi, config);
		}

		/* recieve a data */
		if (ret == 0) {
			ret = spi_config(spi, config);
			if (ret == 0) {
				ret = spi_xferbytes(spi, buf, SPI_READ);
				if (ret < 0) 
					spi_soft_reset(spi, config);
			}
		}
	}
	else {
		ret = -EPERM;
	}

	return ret;
}

/*-----------------------------------------------------------------------------
 * Function	: spi_write_tiecs
 * Abstruct : Write a data to SPI(use TIECS).
 * Input    : config    : SPI config
 *          : buf       : data buffer
 *          : count     : data length
 * Return   : >= 0      : success
 *          : -EINVAL   : invalid parameters
 *          : -ENODEV   : no device
 *----------------------------------------------------------------------------*/
int spi_write_tiecs(spi_config_t *config, u_int *buf, u_int count)
{
	int			ret;
	spi_info_t	*spi;
	u_int		cs_num;
	bool		is_cs_pol_neg;
	int			i;

	if ((config == NULL) || (buf == NULL))
		return -EINVAL;

	spi = &spi_info[config->dev];
	is_cs_pol_neg = (SPI_CS_POL_NEG == (config->pol & SPI_CS_POL_MASK)) ? true : false;

	cs_num = (config->cs >> 4);

	ret = spi_config(spi, config);
	if (ret < 0)
		return ret;

	/* setup pol pos/neg */
	if (is_cs_pol_neg)
		spi->regs->pol &= ~(0x1 << (3 * cs_num));
	else
		spi->regs->pol |= (0x1 << (3 * cs_num));

	/* setup tiecs */
	spi->regs->tiecs |= (SPI_TIECS_FIXED << cs_num);

	/* send data */
	ret = 0;
	for (i = 0; i < count; i++) {
		ret = spi_xferbytes(spi, &buf[i], SPI_WRITE);
		if (ret < 0) {
			spi_soft_reset(spi, config);
			break;
		}
	}
	if (ret == 0)
		ret = count;

	/* restore tiecs */
	spi->regs->tiecs &= ~(SPI_TIECS_FIXED << cs_num);

	/* restore pol pos/neg */
	if (is_cs_pol_neg)
		spi->regs->pol |= 0x1 << (3 * cs_num);
	else
		spi->regs->pol &= ~(0x1 << (3 * cs_num));

	return ret;
}

/*-----------------------------------------------------------------------------
 * Function	: spi_read_tiecs
 * Abstruct : Read a data to SPI(use TIECS).
 * Input    : config    : SPI config
 *          : buf       : data buffer
 *          : flag      : read mode (1cycle/2cycle)
 * Return   : >= 0      : success
 *          : -EINVAL   : invalid parameters
 *          : -ENODEV   : no device
 *----------------------------------------------------------------------------*/
int spi_read_tiecs(spi_config_t *config, u_int *cmd, u_int *buf, u_int flag)
{
	/* TODO: implement this. */
	return -EIO;
}


/*******************/
/*@ local Routines */
/*******************/

/*-----------------------------------------------------------------------------
 * Function	: spi_xferbytes
 * Abstruct : Send datas via SPI.
 * Input    : spi       : SPI information
 *          : data      : data buffer
 *          : mode_flag : access mode(READ/WRITE/RW)
 * Return   : 0         : success
 *          : -EIO      : failed to send
 *          : -EPERM    : invalid parameter
 *----------------------------------------------------------------------------*/
static int spi_xferbytes(spi_info_t *spi, u_int *data, int mode_flag)
{
	int   ret = 0;

	/* clear error status */
	spi->regs->ffclr = SPx_FFCLR_ALL;

	if (mode_flag == SPI_READ) {
		/* set the control */
		spi->regs->control = (SPx_CONTROL_RD | SPx_CONTROL_START);

		/* wait */
		while ((spi->regs->control & (SPx_CONTROL_START | SPx_CONTROL_RX_EMP)) != 0) {
			if ((spi->regs->raw_status & SPx_RAW_STATUS_RX_ALLERR_RAW) != 0) {
				ret = -EIO;
				break;
			}
		}

		/* recieve a data */
		*data = spi->regs->rx_data;
	}
	else if (mode_flag == SPI_WRITE) {
		/* send the data */
		spi->regs->tx_data = *data;
		spi->regs->control = (SPx_CONTROL_WRT | SPx_CONTROL_START);

		/* wait */
		while ((spi->regs->control & SPx_CONTROL_START) != 0) {
			if ((spi->regs->raw_status & SPx_RAW_STATUS_TX_ALLERR_RAW) != 0) {
				ret = -EIO;
				break;
			}
		}
	}
	else if (mode_flag == SPI_RW) {
		/* send the command */
		spi->regs->tx_data = *data;
		spi->regs->control = (SPx_CONTROL_RD | SPx_CONTROL_WRT | SPx_CONTROL_START);

		/* wait */
		while ((spi->regs->control & (SPx_CONTROL_START | SPx_CONTROL_RX_EMP)) != 0) {
			if ((spi->regs->raw_status & SPx_RAW_STATUS_ALLERR_RAW) != 0) {
				ret = -EIO;
				break;
			}
		}

		/* recieve a data */
		*data = spi->regs->rx_data;
	}
	else {
		ret = -EPERM;
	}

	return ret;
}

/*-----------------------------------------------------------------------------
 * Function : spi_config
 * Abstruct : Configurate SPI.
 * Input    : spi       : SPI information
 *          : config    : SPI config
 * Return   : 0         : success
 *          : -EINVAL   : invalid parameters
 * Note     : Not use TIECS
 *----------------------------------------------------------------------------*/
static int spi_config(spi_info_t *spi, spi_config_t *config)
{
	u_int mode    = SPI_M_S | SPI_DMA;
	u_int csw_pol = 0;
	u_int pol     = 0;
	u_int pol_shift;
	u_int nb;

	/* set mode.nb */
	nb = config->nbr + config->nbw;

	if ((nb < SPI_NB_MIN) || (SPI_NB_MAX < nb))
		return -EINVAL;

	mode |= (nb - 1) << 8;

	/* set mode.cs and pol */
	switch (config->cs) {
		case SPI_CS_SEL_CS0 :
		case SPI_CS_SEL_CS2 :
			mode |= config->cs;
			break;
		default :
			return -EINVAL;
	}

	pol_shift = (3 * (config->cs >> 4));
	pol = (config->pol & SPI_POL_MASK) << pol_shift;
	csw_pol = spi->regs->pol;
	csw_pol &= ~(SPI_CSW_MASK | (SPI_POL_MASK << pol_shift));
	csw_pol |= (SPI_POL_CSW | pol);

	/* set sclk */
	if (spi_set_sclk_div(config->dev, spi, config->clk) < 0)
		return -EINVAL;

	/* set registers */
	spi->regs->mode = mode;
	spi->regs->pol  = csw_pol;

	/* software reset */
	spi_soft_reset(spi, config);

	return 0;
}

/*-----------------------------------------------------------------------------
 * Function	: spi_set_sclk_div
 * Abstruct : Set a divisor of SPI.
 * Input    : dev		: SPI device
 *			: spi       : SPI information
 *          : sclk      : clock frequency
 * Return   : 0         : success
 *          : -EINVAL   : invalid parameters
 *----------------------------------------------------------------------------*/
static int spi_set_sclk_div(u_char dev, spi_info_t *spi, u_int sclk)
{
	u_int mask = 0x0000FFFF;
	u_int shift;
	u_int div, regval;
	
	switch (dev) {
		case SPI_DEV_SP0 :
		case SPI_DEV_SP2 :
			shift = 0;
			break;
		case SPI_DEV_SP1 :
		case SPI_DEV_SP3 :
			shift = 16;
			break;
		default :
			return -EINVAL;
	}

	switch (sclk) {
		case SPI_CLK_1500KHZ :
		case SPI_CLK_3MHZ :
		case SPI_CLK_6MHZ :
		case SPI_CLK_12MHZ :
		case SPI_CLK_24MHZ :
		case SPI_CLK_48MHZ :
		case SPI_CLK_1MHZ :
			break;
		default :
			return -EINVAL;
	}

	mask <<= shift;
	div    = (SMU_PLLSEL_PLL3 | spi_plldiv_table[sclk]) << shift;
	regval = (inl(spi->smu->sclk_div) & ~mask) | div;

	outl(regval, spi->smu->sclk_div);

	return 0;
}

/*-----------------------------------------------------------------------------
 * Function	: spi_power_on
 * Abstruct : Power on SPI.
 * Input    : spi   : SPI information
 * Return   : none 
 *----------------------------------------------------------------------------*/
static void spi_power_on(spi_info_t *spi)
{
	smu_autoclock_control(spi->smu->sclk_ctrl, SMU_AUTOCLKCTRL_DISABLE);
	smu_autoclock_control(spi->smu->pclk_ctrl, SMU_AUTOCLKCTRL_DISABLE);

	smu_clockgate_control(spi->smu->pclk, SMU_GCLKCTRL_OPEN);
	smu_clockgate_control(spi->smu->sclk, SMU_GCLKCTRL_OPEN);

	smu_reset_control(spi->smu->reset, SMU_RESETREQ_UNRESET);

	smu_autoclock_control(spi->smu->pclk_ctrl, SMU_AUTOCLKCTRL_ENABLE);
	smu_autoclock_control(spi->smu->sclk_ctrl, SMU_AUTOCLKCTRL_ENABLE);
}

/*-----------------------------------------------------------------------------
 * Function	: spi_power_off
 * Abstruct : Power off SPI.
 * Input    : spi   : SPI information
 * Return   : none
 *----------------------------------------------------------------------------*/
static void spi_power_off(spi_info_t *spi)
{
	smu_autoclock_control(spi->smu->sclk_ctrl, SMU_AUTOCLKCTRL_DISABLE);
	smu_autoclock_control(spi->smu->pclk_ctrl, SMU_AUTOCLKCTRL_DISABLE);

	smu_reset_control(spi->smu->reset, SMU_RESETREQ_RESET);

	smu_clockgate_control(spi->smu->pclk, SMU_GCLKCTRL_CLOSE);
	smu_clockgate_control(spi->smu->sclk, SMU_GCLKCTRL_CLOSE);
}

/*-----------------------------------------------------------------------------
 * Function : spi_soft_reset
 * Abstruct : Reset SPI.
 * Input    : spi       : SPI information
 *          : config    : SPI config
 * Return   : none 
 *----------------------------------------------------------------------------*/
static void spi_soft_reset(spi_info_t *spi, spi_config_t *config)
{
	spi->regs->control |= SPx_CONTROL_RST;
	udelay((1000 * 4 / spi_clock_table[config->clk]) + 1);
	spi->regs->control &= ~SPx_CONTROL_RST;
	udelay(200);
}

#endif /* CONFIG_ICX || CONFIG_ICD */
