/*
 * This file is part of the coreboot project.
 *
 * Copyright (C) 2004 Tyan Computer
 * Written by Yinghai Lu <yhlu@tyan.com> for Tyan Computer.
 * Copyright (C) 2006,2007 AMD
 * Written by Yinghai Lu <yinghai.lu@amd.com> for AMD.
 * Copyright (C) 2007 Silicon Integrated Systems Corp. (SiS)
 * Written by Morgan Tsai <my_tsai@sis.com> for SiS.
 *
 * 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 St, Fifth Floor, Boston, MA  02110-1301 USA
 */

#include <console/console.h>
#include <device/device.h>
#include <device/smbus.h>
#include <device/pci.h>
#include <device/pci_ids.h>
#include <device/pci_ops.h>
#include <arch/io.h>
#include <delay.h>
#include "sis966.h"


uint8_t	SiS_SiS191_init[6][3]={
{0x04, 0xFF, 0x07},
{0x2C, 0xFF, 0x39},
{0x2D, 0xFF, 0x10},
{0x2E, 0xFF, 0x91},
{0x2F, 0xFF, 0x01},
{0x00, 0x00, 0x00}					//End of table
};


#define StatusReg       0x1
#define SMI_READ	0x0
#define SMI_REQUEST	0x10
#define TRUE            1
#define FALSE           0

uint16_t MacAddr[3];


void writeApcByte(int addr, uint8_t value)
{
    outb(addr,0x78);
    outb(value,0x79);
}
uint8_t readApcByte(int addr)
{
    uint8_t value;
    outb(addr,0x78);
    value=inb(0x79);
    return(value);
}

static void readApcMacAddr(void)
{
    uint8_t i;

// enable APC in south bridge sis966 D2F0

    outl(0x80001048,0xcf8);
    outl((inl(0xcfc) & 0xfffffffd),0xcfc ); // enable IO78/79h for APC Index/Data

    printk_debug("MAC addr in APC = ");
    for(i = 0x9 ; i <=0xe ; i++)
    {
        printk_debug("%2.2x",readApcByte(i));
    }
    printk_debug("\n");

    /* Set APC Reload */
    writeApcByte(0x7,readApcByte(0x7)&0xf7);
    writeApcByte(0x7,readApcByte(0x7)|0x0a);

    /* disable APC in south bridge */
    outl(0x80001048,0xcf8);
    outl(inl(0xcfc)&0xffffffbf,0xcfc);
}

static void set_apc(struct device *dev)
{
    uint16_t addr;
    uint16_t i;
    uint8_t   bTmp;

    /* enable APC in south bridge sis966 D2F0 */
    outl(0x80001048,0xcf8);
    outl((inl(0xcfc) & 0xfffffffd),0xcfc ); // enable IO78/79h for APC Index/Data

    for(i = 0 ; i <3; i++)
    {
       addr=0x9+2*i;
       writeApcByte(addr,(uint8_t)(MacAddr[i]&0xFF));
	writeApcByte(addr+1L,(uint8_t)((MacAddr[i]>>8)&0xFF));
        // printf("%x - ",readMacAddrByte(0x59+i));
    }

    /* Set APC Reload */
    writeApcByte(0x7,readApcByte(0x7)&0xf7);
    writeApcByte(0x7,readApcByte(0x7)|0x0a);

    /* disable APC in south bridge */
    outl(0x80001048,0xcf8);
    outl(inl(0xcfc)&0xffffffbf,0xcfc);

    // CFG reg0x73 bit=1, tell driver MAC Address load to APC
    bTmp = pci_read_config8(dev, 0x73);
    bTmp|=0x1;
    pci_write_config8(dev, 0x73, bTmp);
}

//-----------------------------------------------------------------------------
// Procedure:   ReadEEprom
//
// Description: This routine serially reads one word out of the EEPROM.
//
// Arguments:
//      Reg - EEPROM word to read.
//
// Returns:
//      Contents of EEPROM word (Reg).
//-----------------------------------------------------------------------------
#define LoopNum 200
static  unsigned long ReadEEprom( struct device *dev,  uint32_t base,  uint32_t Reg)
{
    uint32_t 	data;
    uint32_t 	i;
    uint32_t 	ulValue;


    ulValue = (0x80 | (0x2 << 8) | (Reg << 10));  //BIT_7

    write32(base+0x3c, ulValue);

    mdelay(10);

    for(i=0 ; i <= LoopNum; i++)
    {
        ulValue=read32(base+0x3c);

        if(!(ulValue & 0x0080)) //BIT_7
            break;

        mdelay(100);
    }

    mdelay(50);

    if(i==LoopNum)   data=0x10000;
    else{
    	ulValue=read32(base+0x3c);
    	data = ((ulValue & 0xffff0000) >> 16);
    }

    return data;
}

static int phy_read(uint32_t  base, unsigned phy_addr, unsigned phy_reg)
{
    uint32_t   ulValue;
    uint32_t   Read_Cmd;
    uint16_t   usData;



	   Read_Cmd = ((phy_reg << 11) |
                       (phy_addr << 6) |
     		       SMI_READ |
     		       SMI_REQUEST);

           // SmiMgtInterface Reg is the SMI management interface register(offset 44h) of MAC
          write32(base+0x44, Read_Cmd);

           // Polling SMI_REQ bit to be deasserted indicated read command completed
           do
           {
	       // Wait 20 usec before checking status
		   mdelay(20);
    	       ulValue = read32(base+0x44);
           } while((ulValue & SMI_REQUEST) != 0);
            //printk_debug("base %x cmd %lx ret val %lx\n", tmp,Read_Cmd,ulValue);
           usData=(ulValue>>16);



	return usData;

}

// Detect a valid PHY
// If there exist a valid PHY then return TRUE, else return FALSE
static int phy_detect(uint32_t base,uint16_t *PhyAddr) //BOOL PHY_Detect()
{
    int	              bFoundPhy = FALSE;
    uint16_t		usData;
    int		       PhyAddress = 0;


        // Scan all PHY address(0 ~ 31) to find a valid PHY
        for(PhyAddress = 0; PhyAddress < 32; PhyAddress++)
        {
		usData=phy_read(base,PhyAddress,StatusReg);  // Status register is a PHY's register(offset 01h)

           // Found a valid PHY

           if((usData != 0x0) && (usData != 0xffff))
           {
               bFoundPhy = TRUE;
               break;
           }
        }


	if(!bFoundPhy)
	{
	    printk_debug("PHY not found !!!! \n");
	}

       *PhyAddr=PhyAddress;

	return bFoundPhy;
}


static void nic_init(struct device *dev)
{
        int val;
        uint16_t  PhyAddr;
        uint32_t base;
        struct resource *res;


        print_debug("NIC_INIT:---------->\n");


//-------------- enable NIC (SiS19x) -------------------------
{
        uint8_t  temp8;
        int i=0;
        while(SiS_SiS191_init[i][0] != 0)
	{
                temp8 = pci_read_config8(dev, SiS_SiS191_init[i][0]);
                temp8 &= SiS_SiS191_init[i][1];
                temp8 |= SiS_SiS191_init[i][2];
                pci_write_config8(dev, SiS_SiS191_init[i][0], temp8);
                i++;
	};
}
//-----------------------------------------------------------

{
        unsigned long  i;
        unsigned long ulValue;

	res = find_resource(dev, 0x10);

	if(!res)
	{
		printk_debug("NIC Cannot find resource..\r\n");
		return;
	}
	base = res->base;
        printk_debug("NIC base address %lx\n",base);

	if(!(val=phy_detect(base,&PhyAddr)))
	{
	       printk_debug("PHY detect fail !!!!\r\n");
		return;
	}

        ulValue=read32(base + 0x38L);   //  check EEPROM existing

        if((ulValue & 0x0002))
        {

          //	read MAC address from EEPROM at first

          //	if that is valid we will use that

			printk_debug("EEPROM contents %x \n",ReadEEprom( dev,  base,  0LL));
			for(i=0;i<3;i++) {
				//status = smbus_read_byte(dev_eeprom, i);
				ulValue=ReadEEprom( dev,  base,  i+3L);
				if (ulValue ==0x10000) break;  // error

				MacAddr[i] =ulValue & 0xFFFF;

			}
        }else{
                 // read MAC address from firmware
		 printk_debug("EEPROM invalid!!\nReg 0x38h=%.8lx \n",ulValue);
		 MacAddr[0]=read16(0xffffffc0); // mac address store at here
		 MacAddr[1]=read16(0xffffffc2);
		 MacAddr[2]=read16(0xffffffc4);
        }

        set_apc(dev);

        readApcMacAddr();

#if DEBUG_NIC
{
        int i;

        print_debug("****** NIC PCI config ******");
        print_debug("\n    03020100  07060504  0B0A0908  0F0E0D0C");

        for(i=0;i<0xff;i+=4){
                if((i%16)==0){
                        print_debug("\r\n");
                        print_debug_hex8(i);
                        print_debug(": ");
                }
                print_debug_hex32(pci_read_config32(dev,i));
                print_debug("  ");
        }
        print_debug("\r\n");
}


#endif

}

print_debug("NIC_INIT:<----------\n");
return;


}

static void lpci_set_subsystem(device_t dev, unsigned vendor, unsigned device)
{
	pci_write_config32(dev, 0x40,
		((device & 0xffff) << 16) | (vendor & 0xffff));
}

static struct pci_operations lops_pci = {
	.set_subsystem	= lpci_set_subsystem,
};

static struct device_operations nic_ops  = {
	.read_resources	= pci_dev_read_resources,
	.set_resources	= pci_dev_set_resources,
	.enable_resources	= pci_dev_enable_resources,
	.init		= nic_init,
	.scan_bus	= 0,
//	.enable		= sis966_enable,
	.ops_pci	= &lops_pci,
};

static const struct pci_driver nic_driver __pci_driver = {
	.ops	= &nic_ops,
	.vendor	= PCI_VENDOR_ID_SIS,
	.device	= PCI_DEVICE_ID_SIS_SIS966_NIC,
};
