/*
 * $Id: natsemi_eeprom.c,v 1.7 2001/10/25 17:52:07 thockin Exp $
 *
 * Copyright (C) 2001 Sun Microsystems, Inc
 * All Rights Reserved
 *
 * Program to write MAC info into a National Semiconductor
 * dp83815 (MacPhyter) ethernet controller
 *
 * May 19, 2000 - Moshen Chan <mchan@cobalt.com>
 * May 17, 2001 - Erik Gilling <erik.gilling@sun.com>
 */

#include <stdio.h>
#include <sys/io.h>
#include <string.h>

#include "enet_eeprom.h"

/* EEPROM_Ctrl bit masks. */
#define EE_SHIFT_CLK    0x04    /* EEPROM shift clock. */
#define EE_CS           0x08    /* EEPROM chip select. */
#define EE_DATA_WRITE   0x01    /* EEPROM chip data in. */
#define EE_DATA_READ    0x02    /* EEPROM chip data out. */
#define EE_ENB          (0x4800 | EE_CS)
#define EE_WRITE_0      0x4808
#define EE_WRITE_1      0x4809
#define EE_OFFSET       0x08

/* 
 * these are the opcodes for the EEPROM, shifted by an amount to
 * leave room for the address and data fields.
 */
#define EE_READ_CMD     (6 << 22)
#define EE_WRITE_CMD    (5 << 22)
#define EE_ENABLE_OPCODE (4 << 22)

#define EE_SIZE         12

#define NAT_PME_BIT (0x1000)

#define NAT_WAKE_ON_PHY_BIT      (0x10000)
#define NAT_WAKE_ON_UCAST_BIT    (0x08000)
#define NAT_WAKE_ON_MCAST_BIT    (0x04000)
#define NAT_WAKE_ON_BCAST_BIT    (0x02000)
#define NAT_WAKE_ON_ARP_BIT      (0x01000)
#define NAT_WAKE_ON_MAGIC_BIT    (0x00800)
#define NAT_WAKE_ON_SOMAGIC_BIT  (0x00400)


#define SWAP_BITS(x)    	( (((x) & 0x01) << 7) \
                                | (((x) & 0x02) << 5) \
                                | (((x) & 0x04) << 3) \
                                | (((x) & 0x08) << 1) \
                                | (((x) & 0x10) >> 1) \
                                | (((x) & 0x20) >> 3) \
                                | (((x) & 0x40) >> 5) \
                                | (((x) & 0x80) >> 7))

#define SWAP_BYTES(a, b)	do { \
					unsigned char tmp; \
					tmp = a; \
					a = b; \
					b = tmp; \
				} while (0)



/* 
 * eeprom data - fixed values come from National Semiconductor spec 
 * first 2 words are Sun's PCI id (bit-flipped) and 0 
 * - these go into PCI subsys vendor/device fields
 */
static unsigned short natsemi_default_data[] = {
    0x7108,  0x0000, 0x2cd0, 0xdf82,
    0x0000, 0x0000, 0x0000, 0x0000,
    0x0000, 0x0000, /*0xa098*/ 0x0098, 0x0055 
};

/* read and write the the entire eeprom */
static int natsemi_read_eeprom(unsigned short ioaddr, unsigned short *eeprom);
static int natsemi_write_eeprom(unsigned short ioaddr, unsigned short *eeprom);

/* read/write an arbitrary word/byte from eeprom */
static int natsemi_read_byte(unsigned short ioaddr, unsigned char *data, 
	int offset);
static int natsemi_read_word(unsigned short ioaddr, unsigned short *data, 
	int offset);
static int natsemi_write_byte(unsigned short ioaddr, unsigned char data, 
	int offset);
static int natsemi_write_word(unsigned short ioaddr, unsigned short data, 
	int offset);

/* update the checksum of the eeprom data */
static int natsemi_update_checksum(unsigned short *eeprom);

/* get the size of the device */
static int natsemi_get_size(void);

/* reload default values */
static int natsemi_load_default(unsigned short *eeprom);

/* read/write the mac address */
static int natsemi_read_mac(unsigned short *eeprom, unsigned char *mac);
static int natsemi_write_mac(unsigned short *eeprom, unsigned char *mac);

/* read/write wake on lan info */
static int natsemi_read_wol(unsigned short *eeprom, unsigned long *wol);
static int natsemi_write_wol(unsigned short *eeprom, unsigned long wol);

/* read/write SecureOn password */
static int natsemi_read_sopass(unsigned short *eeprom, unsigned char *pass);
static int natsemi_write_sopass(unsigned short *eeprom, unsigned char *pass);

/* static functions */
static void eeprom_delay(unsigned short ioaddr);
static int do_eeprom_cmd(unsigned short ioaddr, int cmd, int cmd_len);
static unsigned short read_ee_word(unsigned short ioaddr, int offset);
static void write_ee_word(unsigned short ioaddr, int off, unsigned short word);
static void scramble(unsigned char data[], unsigned short sdata[]);
static void unscramble(unsigned short sdata[], unsigned char data[]);
static int write_scrambled(unsigned short *eeprom, unsigned char *data, 
	int idx);
static int read_scrambled(unsigned short *eeprom, unsigned char *data, 
	int idx);

static void 
eeprom_delay(unsigned short ioaddr) 
{
    int i;
    for (i=0; i<500; i++) 
	inw(ioaddr + EE_OFFSET);
}

static int 
do_eeprom_cmd(unsigned short ioaddr, int cmd, int cmd_len) 
{
    unsigned retval = 0;
    short dataval;
    int ee_addr = ioaddr + EE_OFFSET;
    
    outw(EE_ENB | EE_SHIFT_CLK, ee_addr);

    /* Shift the command bits out. */
    do {
       /*  write bit 1 or 0 to register? */
	if (cmd & (1 << cmd_len))
	    dataval = EE_WRITE_1;
	else
	    dataval = EE_WRITE_0;
	
	outw(dataval, ee_addr);
	eeprom_delay(ioaddr);
	/* shift clock */
	outw(dataval | EE_SHIFT_CLK, ee_addr);
	eeprom_delay(ioaddr);
	/* get data from register, take a 1 or 0 from EE_DATA_READ bit */
	retval = (retval << 1) | ((inw(ee_addr) & EE_DATA_READ) ? 1 : 0);
    } while (--cmd_len >= 0);
    outw(EE_ENB, ee_addr);
    
    /* Terminate the EEPROM access. */
    outw(EE_ENB & ~EE_CS, ee_addr);
    return retval;
}

/* read 16 bit word at offset */
static unsigned short 
read_ee_word(unsigned short ioaddr, int offset) 
{
    unsigned short value;
    
    value = do_eeprom_cmd(ioaddr, EE_READ_CMD | (offset << 16), 25);
    return value;
}
    
/* write 16 bit word at offset */
static void 
write_ee_word(unsigned short ioaddr, int offset, unsigned short word) 
{
    /* enable eeprom writes */
    do_eeprom_cmd(ioaddr, (EE_ENABLE_OPCODE | ((short)48 << 16)), 27);
    
    do_eeprom_cmd(ioaddr, (EE_WRITE_CMD | (offset << 16)) | word, 27);
    /* disable eeprom writes */
    do_eeprom_cmd(ioaddr, (EE_ENABLE_OPCODE | ((short)0 << 16)), 27);

}

/*
 * device methods
 */

static int 
natsemi_read_eeprom(unsigned short ioaddr, unsigned short *eeprom) 
{
    int i;

    for (i=0; i<EE_SIZE; i++) {
	natsemi_read_word(ioaddr, &eeprom[i], i);
    }
    return 0;
}

static int 
natsemi_write_eeprom(unsigned short ioaddr, unsigned short *eeprom) 
{
    int i;

    for (i=0; i<EE_SIZE; i++) {
	if (natsemi_write_word(ioaddr, eeprom[i], i) < 0)
	    return -1;
    }
    return 0;
}

static int 
natsemi_read_byte(unsigned short ioaddr, unsigned char *data, int offset) 
{
    unsigned short value;

    if ((int) (offset /2) > (EE_SIZE - 1))
	return -1;

    value = read_ee_word(ioaddr, (int)(offset/2));
    if ((offset % 2) == 0)
	*data = (unsigned char)(value & 0xff);
    else
	*data = (unsigned char)(value >> 8);
    
    return 0;
}

static int 
natsemi_read_word(unsigned short ioaddr, unsigned short *data, int offset) 
{
    if (offset > (EE_SIZE - 1))
	return -1;
    *data = read_ee_word(ioaddr, offset);
    
    return 0;
}

static int 
natsemi_write_byte(unsigned short ioaddr, unsigned char data, int offset) 
{
    unsigned short value;
    int wordoffset;

    wordoffset = (int) offset/2;

    if (wordoffset > (EE_SIZE - 1))
	return -1;

    value = read_ee_word(ioaddr, wordoffset);
    if ((offset % 2) == 0) {
	write_ee_word(ioaddr, wordoffset, (value & 0xff00) | data);
	/* verify */
	if (read_ee_word(ioaddr, wordoffset) != ((value & 0xff00) | data))
	    return -1;
    } else {
	write_ee_word(ioaddr, wordoffset, (value & 0xff) | (data << 8)); 
	if (read_ee_word(ioaddr, wordoffset) != ((value & 0xff) | (data << 8)))
	    return -1;
    }
    return 0;
}
	    
static int 
natsemi_write_word(unsigned short ioaddr, unsigned short data, int offset) 
{
    if (offset > (EE_SIZE -1))
	return -1;
    write_ee_word(ioaddr, offset, data);
    /* verify */
    if (read_ee_word(ioaddr, offset) != data) 
	return -2;
    return 0;
}

static int 
natsemi_update_checksum(unsigned short *eeprom) 
{
    int i;
    unsigned char  sum = 0x55;

    for (i=0; i<22; i++)
	sum += ((unsigned char *)eeprom)[i];

    sum = 0-sum;
    
    ((unsigned char *)eeprom)[23] = sum;
    return 0;
}

static int 
natsemi_get_size(void)
{
    return EE_SIZE;
}

static int 
natsemi_load_default(unsigned short *eeprom)
{
    int i;
    for( i=0 ; i<EE_SIZE ; i++ )
    {
	eeprom[i] = natsemi_default_data[i];
    }
    return 0;
}

static int 
natsemi_read_mac(unsigned short *eeprom, unsigned char *mac_data) 
{
    return read_scrambled(eeprom, mac_data, 6);
}

static int
read_scrambled(unsigned short *eeprom, unsigned char *data, int idx)
{
    unsigned short sdata[3];
    unsigned char byte;

    /* read the odd bit */
    byte = eeprom[idx] & 0x1;

    /* and the rest of the bits */
    sdata[0] = eeprom[idx+1];
    sdata[1] = eeprom[idx+2];
    sdata[2] = eeprom[idx+3] & ~0x1;

    /* shuffle them into 3 words proper */
    sdata[2] >>= 1; sdata[2] |= (sdata[1] & 0x1)<<15;
    sdata[1] >>= 1; sdata[1] |= (sdata[0] & 0x1)<<15;
    sdata[0] >>= 1; sdata[0] |= (byte & 0x1)<<15;
    
    unscramble(sdata, data);

    return 0;
}

static int 
natsemi_write_mac(unsigned short *eeprom, unsigned char *mac) 
{
    return write_scrambled(eeprom, mac, 6);
}

    extern int print_eeprom(unsigned short *);
static int
write_scrambled(unsigned short *eeprom, unsigned char *data, int idx)
{
    unsigned short sdata[3];
    
    /* turn 6 bytes of data into 3 words */
    scramble(data, sdata);

    /* clear/protect the odd bits due to being shifted by one */
    eeprom[idx] &= ~0x0001;
    eeprom[idx+1] = 0;
    eeprom[idx+2] = 0;
    eeprom[idx+3] &= 0x0001;

    /* update MAC */
    eeprom[idx] |= ((sdata[0] >> 15) & 0x01);
    eeprom[idx+1] = sdata[0] << 1; eeprom[idx+1] |= (sdata[1] >> 15) & 0x1;
    eeprom[idx+2] = sdata[1] << 1; eeprom[idx+2] |= (sdata[2] >> 15) & 0x1;
    eeprom[idx+3] |= sdata[2] << 1;
    
    return 0;
}
    
/* read wake on lan info */
static int 
natsemi_read_wol(unsigned short *eeprom, unsigned long *wol)
{
    unsigned int wo_val;
    
    *wol = 0x0;

    wo_val = eeprom[0xa] + (eeprom[0x9]<<16);

    if( wo_val & NAT_WAKE_ON_PHY_BIT )
	*wol |= WAKE_ON_PHY;
    
    if( wo_val & NAT_WAKE_ON_UCAST_BIT )
	*wol |= WAKE_ON_UCAST;
    
    if( wo_val & NAT_WAKE_ON_MCAST_BIT )
	*wol |= WAKE_ON_MCAST;
    
    if( wo_val & NAT_WAKE_ON_BCAST_BIT )
	*wol |= WAKE_ON_BCAST;
    
    if( wo_val & NAT_WAKE_ON_ARP_BIT )
	*wol |= WAKE_ON_ARP;
    
    if( wo_val & NAT_WAKE_ON_MAGIC_BIT )
	*wol |= WAKE_ON_MAGIC;

    if( wo_val & NAT_WAKE_ON_SOMAGIC_BIT )
	*wol |= WAKE_ON_SOMAGIC;

    return 0;
}

static int 
natsemi_read_sopass(unsigned short *eeprom, unsigned char *pass)
{
    return read_scrambled(eeprom, pass, 3);
}

/* write wake on lan info */
static int 
natsemi_write_wol(unsigned short *eeprom, unsigned long wol)
{
    unsigned int wo_val;

    /* set PME bit if we have any sort of wake up to do */
    if( wol )
	eeprom[0x3] |= NAT_PME_BIT;
    else
	eeprom[0x3] &= ~NAT_PME_BIT;

    /* blank out WOL bits */
    wo_val = eeprom[0xa] + (eeprom[0x9]<<16);
    wo_val &= ~( NAT_WAKE_ON_PHY_BIT | NAT_WAKE_ON_UCAST_BIT |
			   NAT_WAKE_ON_MCAST_BIT | NAT_WAKE_ON_BCAST_BIT |   
			   NAT_WAKE_ON_ARP_BIT | NAT_WAKE_ON_MAGIC_BIT |
			   NAT_WAKE_ON_SOMAGIC_BIT );

    if( wol & WAKE_ON_PHY )
	wo_val |= NAT_WAKE_ON_PHY_BIT;

    if( wol & WAKE_ON_UCAST )
	wo_val |= NAT_WAKE_ON_UCAST_BIT;

    if( wol & WAKE_ON_MCAST )
	wo_val |= NAT_WAKE_ON_MCAST_BIT;

    if( wol & WAKE_ON_BCAST )
	wo_val |= NAT_WAKE_ON_BCAST_BIT;

    if( wol & WAKE_ON_ARP )
	wo_val |= NAT_WAKE_ON_ARP_BIT;

    if( wol & WAKE_ON_MAGIC )
	wo_val |= NAT_WAKE_ON_MAGIC_BIT;

    if( wol & WAKE_ON_SOMAGIC )
	wo_val |= NAT_WAKE_ON_SOMAGIC_BIT;

    eeprom[0x9] = wo_val >> 16;
    eeprom[0xa] = wo_val & 0xffff;

    return 0;
}

static int 
natsemi_write_sopass(unsigned short *eeprom, unsigned char *pass)
{
    return write_scrambled(eeprom, pass, 3);
}

/* given 6 bytes of data, scramble it into the screwy shifted words */
static void 
scramble(unsigned char data[], unsigned short sdata[])
{
    unsigned char *p = (unsigned char *)sdata;
    int i;

    /* swap bits all the way around */
    for (i = 0; i<6; i++) { 
	p[i] = SWAP_BITS(data[i]); 
    } 

    /* swap bytes */
    SWAP_BYTES(p[0], p[1]);
    SWAP_BYTES(p[2], p[3]);
    SWAP_BYTES(p[4], p[5]);
}

/* given 3 words from the EEPROM, turn it into 6 bytes */
static void
unscramble(unsigned short sdata[], unsigned char data[]) 
{
    unsigned short *p = (unsigned short *)data;
    int i;

    p[2] = sdata[2];
    p[1] = sdata[1];
    p[0] = sdata[0];

    /* swap bits all the way around */
    for (i = 0; i<6; i++) { 
	data[i] = SWAP_BITS(data[i]); 
    } 

    /* swap bytes */
    SWAP_BYTES(data[0], data[1]);
    SWAP_BYTES(data[2], data[3]);
    SWAP_BYTES(data[4], data[5]);
}

struct eeprom_driver natsemi_driver = {
    natsemi_read_eeprom, /* read_eeprom */
    natsemi_write_eeprom, /* write_eeprom */
    natsemi_read_byte, /* read_byte */
    natsemi_read_word, /* read_word */
    natsemi_write_byte, /* write_byte */
    natsemi_write_word, /* write_word */
    natsemi_get_size, /* get_size */
    natsemi_update_checksum, /* update_checksum */
    natsemi_load_default, /* load_default */
    natsemi_read_mac, /* read_mac */
    natsemi_write_mac, /* write_mac */
    natsemi_read_wol, /* read_wol */
    natsemi_write_wol, /* write_wol */
    natsemi_read_sopass, /* read_sopass */
    natsemi_write_sopass /* write_sopass */
};

