/*
 * Copyright (C) 2012 Micrel, Inc.
 *
 * See file CREDITS for list of people who contributed to this
 * project.
 *
 * 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

 * GPIO based Micrel Switch SMI bitbang driver. The SMI is the Micrel Switch
 * non-standar MIMM interface interface that provides access to all Switch
 * configuration registers. The physical connection that incorporates 
 * the data line (SDA_MDIO) and the clock line (SCL_MDC).
 * The driver supports KSZ8873/KSZ8863/KSZ8893, and KSZ8895/KSZ8863.
 */

#include <config.h>
#include <common.h>
#include <asm/arch/ks8692Reg.h>
#include <asm/arch/platform.h>

#if defined (CONFIG_SMI)

/*-------------------------------------------------------------------------------
  
    The routines use Micrel KSZ9692 SOC GPIO bitbang for SDA_MDIO/SDA_MDC.
  
  -------------------------------------------------------------------------------
   
       DUT                     KSZ9692
    +-----------+
    |           |
    | SDA_MDIO  +--+------+ (TX/RX) GPIO6  (Output/Input)
    |           |    
    |           |            
    |           |
    | SDA_MDC   +---------+ (CLK) GPIO5 (Output)
    +-----------+

  -------------------------------------------------------------------------------

  From uboot prompt:
  boot> smi read <reg>           -> read 1 byte from register <reg>.
  boot> smi write <reg> <value>  -> write 1 byte <value> to register <reg>.
  boot> smitest    -> SMI selftest (read chip id, and write/read) for 256 times.
  -------------------------------------------------------------------------------*/

/* Commond SMI definition */
#define  PREAMBLELEN    32
#define  SOFLEN          2
#define  OPCODELEN       2
#define  REGADDRLEN     10
#define  TALEN           2
#define  IDLELEN         1 
#define  DATALEN        16

#define  SMI_MDC         5   /* GPIO5  for SMI interface SDA_MDC */
#define  SMI_MDIO        6   /* GPIO6 for SMI interface SDA_MDIO */

#define  PREAMBLE_BITS   1
#define  SOFRAME         0x01
#define  TABITS_READ     0x00 
#define  TABITS_WRITE    0x02
#define  IDLE_BITS       0

/* KS8873 SMI definition */
#define  KS8873_SMI_READ        0
#define  KS8873_SMI_WRITE       0

/* KS8895 SMI definition */
#define  KS8895_SMI_READ        2
#define  KS8895_SMI_WRITE       1

/* Standard MII MIDO definition */
#define  MII_READ        2
#define  MII_WRITE       1
#define  MII_PHYADDRLEN  5
#define  MII_REGADDRLEN  5
#define  MII_PHY_ADDR(phy)  (phy & 0x1F)
#define  MII_REG_ADDR(reg)  (reg & 0x1F)

#define  KS8873_PHY_ID   0x1430
#define  KS8895_PHY_ID   0x1450


/* GPIO Port Data Register mask bit */
#define IOPM(x)		  (1 << (x))    /* Signal Level of GPIO mask x position */
#define IOPD(val, x)  ((val) << (x))	/* Signal Level of GPIO update bit with x position */


struct host_gpio {
	/* SMI op code and reg addr deal with different Switch device */
	u8               chip_id;        /* chip id */
	u8               test_reg;       /* register to be test with write/read */

	u8               op_read;        /* SMI read OP code */
	u8               op_write;       /* SMI write OP code */
	unsigned short  (*smi_get_reg_for_read) ( struct host_gpio *, unsigned );
	unsigned short  (*smi_get_reg_for_write) ( struct host_gpio *, unsigned );

	/* GPIO read & write functions to deal with different GPIO I/O */
	unsigned        (*mdio_read) ( struct host_gpio *, unsigned );
	void            (*mdio_write) ( struct host_gpio *, unsigned, unsigned );
	int             (*gpio_direction_input) ( struct host_gpio *, unsigned );
	int             (*gpio_direction_output) ( struct host_gpio *, unsigned );

	/* 2-bit GPIO value for SDA_MDC, and SDA_MDIO */
	u32              smi_gpio_value;

} HOST_GPIO, *PHOST_GPIO;


struct host_gpio   target_gpio;
struct host_gpio  *pGpio = &target_gpio;

static int smi_init_done = 0;


#if (0)
#define	DEBUG
#endif

#ifdef  DEBUG
#define	DPRINT(a)	printf a;
#else   /* !DEBUG */
#define	DPRINT(a)
#endif

/* export functions */
int  smi_init ( void );
int  smi_write ( unsigned char, unsigned short );
int  smi_read ( unsigned char, unsigned short * );
int  smi_selftest ( void );


/*---- KSZ9692 GPIO interface ----------------------------------------------*/

/*
 * Configure the GPIO line as an input.
 */
static int ks9692_gpio_direction_input
(
    struct host_gpio *p_gpio, 
    unsigned gpio
)
{
	unsigned long regData;

	DPRINT(("%s: gpio=%d\n", __func__, gpio));

	/* set pin as input */
	regData = ks8692_read_dword(KS8692_GPIO_MODE);
	regData &= ~IOPM(gpio);
	ks8692_write_dword(KS8692_GPIO_MODE, regData);
	DPRINT(("%s: 0x%X: %08x\n", __func__, KS8692_GPIO_MODE, regData));

	return 0;
}


/*
 * Configure the GPIO line as an output, with default state.
 */
static int ks9692_gpio_direction_output
(  
    struct host_gpio *p_gpio, 
	unsigned gpio
)
{
	unsigned long regData;

	DPRINT(("%s: gpio=%d\n", __func__, gpio));

	/* set pin as output */
	regData = ks8692_read_dword(KS8692_GPIO_MODE);
	regData |= IOPM(gpio);
	ks8692_write_dword(KS8692_GPIO_MODE, regData);
	DPRINT(("%s: 0x%X: %08x\n", __func__, KS8692_GPIO_MODE, regData));

	return 0;
}


/*
 * Set the state of an output GPIO line.
 */
static void ks9692_gpio_set_value
(
    struct host_gpio *p_gpio, 
	unsigned gpio, 
	unsigned bit
)
{
	unsigned long regData;
	unsigned long regWriteData;


	DPRINT(("%s: gpio=%d, bit=%d\n", __func__, gpio, bit));

	/* set output line state */

    regData = ks8692_read_dword(KS8692_GPIO_DATA);
	regWriteData = pGpio->smi_gpio_value;
	DPRINT(("%s 1: 0x%X: %08x (%08x)\n", __func__, 
		           KS8692_GPIO_DATA, regData, regWriteData));

	regWriteData &= ~IOPM(gpio);
	regWriteData |= IOPD(bit, gpio);

	regData &= ~IOPM(SMI_MDC);
	regData &= ~IOPM(SMI_MDIO);

    regData |= regWriteData;
	ks8692_write_dword(KS8692_GPIO_DATA, regData);

	pGpio->smi_gpio_value = regWriteData;
	DPRINT(("%s 2: 0x%X: %08x (%08x)\n", __func__, 
		           KS8692_GPIO_DATA, regData, regWriteData));
}


/*
 * Read the state of a GPIO line.
 */
static unsigned ks9692_gpio_get_value
(
    struct host_gpio *p_gpio, 
	unsigned gpio
)
{
	unsigned long regData;

	DPRINT(("%s: gpio=%d\n", __func__, gpio));

	/* get input line state */

	regData = ks8692_read_dword(KS8692_GPIO_DATA);
	DPRINT(("%s: 0x%X: %08x(%x)\n", __func__, 
		         KS8692_GPIO_DATA, regData, (regData >> gpio) & 0x01));

	return ( (unsigned)((regData >> gpio) & 0x01) );
}


/*
    Delay
   
    Description:
        With this clock delay, MDC clock is about 220KHz.
		If using uboot udelay, MDC clock is about 72KHz 
  
 */
static void Delay( unsigned long count)
{

	//udelay(count);

	while ( --count );
}

/*--------------------------------------------------------------------------*/


static void gpio_clock(void)
{
    pGpio->mdio_write(pGpio, SMI_MDC, 1 );
    Delay(1);
    pGpio->mdio_write(pGpio, SMI_MDC, 0 );
    Delay(1);
}

static void Preamble
(
    int PreambleLen
)
{
	int i;

	/* Set GPIO of SMI_MDIO as output */
    pGpio->gpio_direction_output(pGpio, SMI_MDIO );

	/* output Preamble bits */
	for (i=0; i<PreambleLen; i++) {
        pGpio->mdio_write(pGpio, SMI_MDIO, PREAMBLE_BITS);
		gpio_clock();
	}
}

static void gpio_write 
( 
	unsigned data,
    int bit_len 
)
{
	int i;

	DPRINT(("%s: bit_len=%d, data=%08x\n", __func__, bit_len, data));

	/* Set GPIO of SMI_MDIO as output */
    pGpio->gpio_direction_output(pGpio, SMI_MDIO );

	for (i=bit_len; i>0; i--) {
		DPRINT(("%s (%d): %08x, %08x\n", __func__, i, data, (data >> (i-1) & 0x01)));
        pGpio->mdio_write(pGpio, SMI_MDIO, (data >> (i-1) & 0x01) );
		gpio_clock();
	}
}

static unsigned long gpio_read 
( 
    int bit_len 
)
{
	int i;
	unsigned regData=0;
	unsigned long retData=0;

	DPRINT(("%s: bit_len=%d\n", __func__, bit_len));

	/* Set GPIO of SMI_MDIO as input */
    pGpio->gpio_direction_input(pGpio, SMI_MDIO );

	for (i=0; i<bit_len; i++) {
        regData = pGpio->mdio_read(pGpio, SMI_MDIO);
		gpio_clock();
        retData <<= 1;
        retData |= regData;
	}

	return ( retData );
}

/*--------------------------------------------------------------------------*/

/*
 * Convert register offset to KS8873 SMI register address for read.
 */
static unsigned short ks8873_smi_get_regaddr_read 
( 
    struct host_gpio *p_gpio, 
    unsigned char  Reg
)
{

    return ( (unsigned short)( (Reg & 0xFF) | 0x200 ) );
}

/*
 * Convert register offset to KS8873 SMI register address for write.
 */
static unsigned short ks8873_smi_get_regaddr_write
( 
    struct host_gpio *p_gpio, 
    unsigned char  Reg
)
{
    return ( (unsigned short)( (Reg & 0xFF) | 0x0 ) );
}


/*
 * Convert register offset to KS8895 SMI register address.
 */
static unsigned short ks8895_smi_get_regaddr 
( 
    struct host_gpio *p_gpio, 
    unsigned char  Reg
)
{
    unsigned short  addr=0x0C0;

    addr = ((Reg << 2) & 0x300);
    addr |= ((Reg & 0x3F) | 0xC0);
    return ( addr );
}



/*--------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------
   
    mii_write
   
    Description:
        This is the core routine of standard of MII MDIO write operation 
		emulation through the GPIO bitbang. 

    Parameters:
        UCHAR Phy
            The Phy address.

        UCHAR Reg
            The register offset.

        UCHAR Data
            The data byte value to write.
    
    Return Value:
        None
    
*---------------------------------------------------------------------------*/
int mii_write 
(
    unsigned char  Phy,
    unsigned char  Reg,
    unsigned short Data 
)
{

	DPRINT(("%s: Phy=%x, Reg=%x, Data=%x\n", __func__, Phy, Reg, Data));

	/* Write 32-bit of Preamble bits */
    Preamble ( PREAMBLELEN );

	/* Write 2-bit of Start of Frame */
    gpio_write ( (unsigned)SOFRAME, (int)SOFLEN );

	/* Write 2-bit of OP code */
    gpio_write( (unsigned)MII_WRITE, (int)OPCODELEN );

	/* Write 5-bit of PHY addr */
    gpio_write( (unsigned)MII_PHY_ADDR(Phy), (int)MII_PHYADDRLEN );

	/* Write 5-bit of register addr */
    gpio_write( (unsigned)MII_REG_ADDR(Reg), (int)MII_REGADDRLEN );


	/* Write 2-bit of TA bits */
    gpio_write( (unsigned)TABITS_WRITE, (int)TALEN );

	/* Write 16-bit of data bits */
    gpio_write( (unsigned)Data, (int)DATALEN );

	/* Write 1-bit of IDLE bits */
    gpio_write( (unsigned)IDLE_BITS, (int)IDLELEN );

	return (0);
}

/*----------------------------------------------------------------------------
   
    mii_read
   
    Description:
        This is the core routine of standard of MII MDIO read operation 
		emulation through the GPIO bitbang. 
  
    Parameters:
        UCHAR Phy
            The Phy address.

        UCHAR Reg
            The register offset.

        USHORT *readData
           Address of WORD to contain data read from register.
  
    Return Value:
        None.
    
*---------------------------------------------------------------------------*/

int mii_read 
(
    unsigned char  Phy,
    unsigned char  Reg,
    unsigned short *readData
)
{
	unsigned long  Data;


	DPRINT(("%s: Phy=%x, Reg=%x\n", __func__, Phy, Reg));

	/* Write 32-bit of Preamble bits */
    Preamble ( PREAMBLELEN );

	/* Write 2-bit of Start of Frame */
    gpio_write ( (unsigned)SOFRAME, (int)SOFLEN );

	/* Write 2-bit of OP code */
    gpio_write( (unsigned)MII_READ, (int)OPCODELEN );

	/* Write 5-bit of PHY addr */
    gpio_write( (unsigned)MII_PHY_ADDR(Phy), (int)MII_PHYADDRLEN );

	/* Write 5-bit of register addr */
    gpio_write( (unsigned)MII_REG_ADDR(Reg), (int)MII_REGADDRLEN );

	/* Write 2-bit TA bits */
    gpio_write( (unsigned)TABITS_READ, (int)TALEN );

	/* Read 16-bit of data bits */
    Data = gpio_read( (int)DATALEN );

	/* Write 1-bit of IDLE bits */
    gpio_write( (unsigned)IDLE_BITS, (int)IDLELEN );

    *readData = (unsigned short)Data;

	return (0);
}


int mii_selftest 
(
    unsigned short  phy_id
)
{
    unsigned short RegData;
    unsigned char  PhyAddr=1;
    unsigned char  RegOffset;
    int i;
	int status=0;
	int max_count=0xff;


    /* test MMI read chip id */
	status = 0;
	RegOffset = 3; /* phy ID register */
	printf("MMI selftest: read phy chip id: ");
    for ( i=0; i<max_count; i++ ) {
        mii_read( PhyAddr, RegOffset, &RegData );
        if ( RegData != phy_id ) {
			status = -1;
            printf( "\n read phy id %x is not correct to %x\n", RegData, phy_id );
            break;
        }
    }
	printf( "%s.\n", (status==0) ? "OK": "FAIL");


    /* test MII read\write */
	status = 0;
	RegOffset = 4; /* auto-nego advert register */
	printf("MII selftest: write\\read data to 0x%x address: ", RegOffset);
    for ( i=0; i<0x0f; i++ ) {
        mii_write( PhyAddr, RegOffset, (unsigned short)((i << 5) | 0x01) );
        mii_read( PhyAddr, RegOffset, &RegData );
        if ( RegData != (unsigned short)((i << 5) | 0x01) ) {
			status = -1;
            printf( "\n read data value %x is not equal to write data %x\n", 
				        RegData, ((i << 5) | 0x01 ) );
            break;
        }
    }
	printf( "%s.\n", (status==0) ? "OK": "FAIL");

	return ( status );
}


/*--------------------------------------------------------------------------*/

int smi_init()
{
    unsigned short phy_id=0;
    unsigned char  PhyAddr=1;
    unsigned char  RegOffset=3;
	int status = -1;

    if (smi_init_done != 0) 
       return;

    /* Assign GPIO operation to KSZ9692 GPIO */
    pGpio->mdio_read = ks9692_gpio_get_value;
    pGpio->mdio_write = ks9692_gpio_set_value;
    pGpio->gpio_direction_input = ks9692_gpio_direction_input;
    pGpio->gpio_direction_output = ks9692_gpio_direction_output;

    pGpio->smi_gpio_value = 0;
    pGpio->gpio_direction_output(pGpio, SMI_MDC );
    pGpio->gpio_direction_output(pGpio, SMI_MDIO );
    pGpio->mdio_write(pGpio, SMI_MDC, 0);
    pGpio->mdio_write(pGpio, SMI_MDIO, 0);

	/* read phy id */
    mii_read( PhyAddr, RegOffset, &phy_id );

	if (phy_id == KS8873_PHY_ID) {

        pGpio->chip_id = 0x88;    /* KS8873 serial chip id */
        pGpio->test_reg = 0x60;   /* KS8873 serial register TOS */

        pGpio->op_read = KS8873_SMI_READ;
        pGpio->op_write = KS8873_SMI_WRITE;
        pGpio->smi_get_reg_for_read = ks8873_smi_get_regaddr_read;
        pGpio->smi_get_reg_for_write = ks8873_smi_get_regaddr_write;   

	    status = 0;
        smi_init_done = 1;
    }
	else if (phy_id == KS8895_PHY_ID) {

        pGpio->chip_id = 0x95;   /* KS8895 serial chip id */
        pGpio->test_reg = 0x90;   /* KS8873 serial register TOS */

        pGpio->op_read = KS8895_SMI_READ;
        pGpio->op_write = KS8895_SMI_WRITE;
        pGpio->smi_get_reg_for_read = ks8895_smi_get_regaddr;
        pGpio->smi_get_reg_for_write = ks8895_smi_get_regaddr;

	    status = 0;
        smi_init_done = 1;
    }

	DPRINT(("%s: phy_id=%x\n", __func__, phy_id));

	return (status);
}


/*----------------------------------------------------------------------------
   
    smi_write
   
    Description:
        This is the core routine of SMI MDIO write operation emulation
		through the GPIO bitbang. 
  
    Parameters:
        UCHAR Reg
            The register offset.

        UCHAR Data
            The data byte value to write.
    
    Return Value:
        None
    
*---------------------------------------------------------------------------*/
int smi_write 
(
    unsigned char  Reg,
    unsigned short Data 
)
{
    unsigned short addr=pGpio->smi_get_reg_for_write(pGpio, Reg);
    unsigned char  op_code=pGpio->op_write;


	DPRINT(("%s: Reg=%x, addr=%x, Data=%x\n", __func__, Reg, addr, Data));

	/* Write 32-bit of Preamble bits */
    Preamble ( PREAMBLELEN );

	/* Write 2-bit of Start of Frame */
    gpio_write ( (unsigned)SOFRAME, (int)SOFLEN );

	/* Write 2-bit of OP code */
    gpio_write( (unsigned)op_code, (int)OPCODELEN );

	/* Write 8-bit of register addr */
    gpio_write( (unsigned)addr, (int)REGADDRLEN );

	/* Write 2-bit of TA bits */
    gpio_write( (unsigned)TABITS_WRITE, (int)TALEN );

	/* Write 16-bit of data bits */
    gpio_write( (unsigned)Data, (int)DATALEN );

	/* Write 1-bit of IDLE bits */
    gpio_write( (unsigned)IDLE_BITS, (int)IDLELEN );

	return (0);
}


/*----------------------------------------------------------------------------
   
    smi_read
   
    Description:
        This is the core routine of SMI MDIO read peration emulation
		through the GPIO bitbang. 
  
    Parameters:
        UCHAR Reg
            The register offset.

        USHORT *readData
           Address of WORD to contain data read from register.
  
    Return Value:
        None.
    
*---------------------------------------------------------------------------*/
int smi_read 
(
    unsigned char  Reg,
    unsigned short *readData
)
{
    unsigned short addr=pGpio->smi_get_reg_for_read(pGpio, Reg);
    unsigned char  op_code=pGpio->op_read;
	unsigned long  Data;


	DPRINT(("%s: Reg=%x, addr=%x\n", __func__, Reg, addr));

	/* Write 32-bit of Preamble bits */
    Preamble ( PREAMBLELEN );

	/* Write 2-bit of Start of Frame */
    gpio_write ( (unsigned)SOFRAME, (int)SOFLEN );

	/* Write 2-bit of OP code */
    gpio_write( (unsigned)op_code, (int)OPCODELEN );

	/* Write 8-bit of register addr */
    gpio_write( (unsigned)addr, (int)REGADDRLEN );

	/* Write 2-bit TA bits */
    gpio_write( (unsigned)TABITS_READ, (int)TALEN );

	/* Read 16-bit of data bits */
    Data = gpio_read( (int)DATALEN );

	/* Write 1-bit of IDLE bits */
    gpio_write( (unsigned)IDLE_BITS, (int)IDLELEN );

    *readData = (unsigned short)Data;

	return (0);
}


int smi_selftest ( void )
{
    unsigned short RegData;
    unsigned char  RegOffset;
    int i;
	int status=0;
	int max_count=0xff;


    /* test SMI read chip id */
	status = 0;
	RegOffset = 0; /* chip ID register */
	printf("SMI selftest: read chip id: ");
    for ( i=0; i<max_count; i++ ) {
        smi_read( RegOffset, &RegData );
        if ( (unsigned char)RegData != pGpio->chip_id ) {
			status = -1;
            printf( "\n read chip id %x is not correct to %x\n", 
				        RegData, pGpio->chip_id );
            break;
        }
    }
	printf( "%s.\n", (status==0) ? "OK": "FAIL");


    /* test SMI read\write */
	status = 0;
	RegOffset = pGpio->test_reg; /* TOS register */
	printf("SMI selftest: write\\read data to 0x%x address: ", RegOffset);
    for ( i=0; i<=max_count; i++ ) {
        smi_write( RegOffset, (unsigned short)i );
        smi_read( RegOffset, &RegData );
        if ( (unsigned char)RegData != (unsigned char)i ) {
			status = -1;
            printf( "\n read data value %x is not equal to write data %x\n", 
				        RegData, i );
            break;
        }
    }
	printf( "%s.\n", (status==0) ? "OK": "FAIL");

	return ( status );
}

#endif /* #if defined (CONFIG_SMI) */


