/******************************************************************************
     Copyright (c) 2004, Infineon Technologies.  All rights reserved.

                               No Warranty
   Because the program is licensed free of charge, there is no warranty for
   the program, to the extent permitted by applicable law.  Except when
   otherwise stated in writing the copyright holders and/or other parties
   provide the program "as is" without warranty of any kind, either
   expressed or implied, including, but not limited to, the implied
   warranties of merchantability and fitness for a particular purpose. The
   entire risk as to the quality and performance of the program is with
   you.  should the program prove defective, you assume the cost of all
   necessary servicing, repair or correction.

   In no event unless required by applicable law or agreed to in writing
   will any copyright holder, or any other party who may modify and/or
   redistribute the program as permitted above, be liable to you for
   damages, including any general, special, incidental or consequential
   damages arising out of the use or inability to use the program
   (including but not limited to loss of data or data being rendered
   inaccurate or losses sustained by you or third parties or a failure of
   the program to operate with any other programs), even if such holder or
   other party has been advised of the possibility of such damages.
 ******************************************************************************
   Module      : admmod.c
   Date        : 2004-09-01
   Description : JoeLin
   Remarks:

 *****************************************************************************/


#include <linux/module.h>
#include <linux/string.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <linux/init.h>
#include <linux/ioctl.h>
#include <asm/atomic.h>

#include "adm6996.h"
#include <linux/adm_reg.h>

#include <asm/avalanche/avalanche_map.h>
#include <asm/avalanche/sangam/hw_gpio.h>

#define adm_printf printk

static unsigned int adm_conf[ADM_SW_MAX_PORT_NUM+1] = \
	    {ADM_SW_PORT0_CONF, ADM_SW_PORT1_CONF, ADM_SW_PORT2_CONF, \
	     ADM_SW_PORT3_CONF, ADM_SW_PORT4_CONF, ADM_SW_PORT5_CONF};

static unsigned int adm_bits[8] = {0x1, 0x3, 0x7, 0xf, 0x1f, 0x3f, 0x7f, 0xff};
static unsigned int adm_vlan_port[6] = {0, 2, 4, 6, 7, 8};

#define admi    0
#define admlc   1
#define adml    2

unsigned int avm_cpmac_switch_type;
static unsigned int adm_read_mask;

static struct {
    unsigned int disabled;
    unsigned long state_since;
} adm_switch_port[4];

/*--- #define ADM6996i ---*/
#ifdef ADM6996i
static unsigned int adm_mode=admi;
#endif

/*------------------------------------------------------------------------------------------*\
 * Register cache
\*------------------------------------------------------------------------------------------*/
#define NUMBER_OF_SERIAL_REGISTERS (0x3c + 1)
#define MAX_SERIAL_REGISTER_AGE 200
struct adm_struct adm_serial_register;
static unsigned int *serial_register = (unsigned int *) &adm_serial_register;
static unsigned long serial_register_timestamp[NUMBER_OF_SERIAL_REGISTERS];
static int adm_serial_registers[] = {
    REG_ADM_ID, REG_ADM_STATUS0, REG_ADM_STATUS1, REG_ADM_CABLEBROKENSTATUS,
    REG_ADM_RXPKTCNT0, REG_ADM_RXPKTCNT1, REG_ADM_RXPKTCNT2, REG_ADM_RXPKTCNT3, REG_ADM_RXPKTCNT4, REG_ADM_RXPKTCNT5,
    REG_ADM_RXPKTBYTECNT0, REG_ADM_RXPKTBYTECNT1, REG_ADM_RXPKTBYTECNT2, REG_ADM_RXPKTBYTECNT3, REG_ADM_RXPKTBYTECNT4, REG_ADM_RXPKTBYTECNT5,
    REG_ADM_TXPKTCNT0, REG_ADM_TXPKTCNT1, REG_ADM_TXPKTCNT2, REG_ADM_TXPKTCNT3, REG_ADM_TXPKTCNT4, REG_ADM_TXPKTCNT5,
    REG_ADM_TXPKTBYTECNT0, REG_ADM_TXPKTBYTECNT1, REG_ADM_TXPKTBYTECNT2, REG_ADM_TXPKTBYTECNT3, REG_ADM_TXPKTBYTECNT4, REG_ADM_TXPKTBYTECNT5,
    REG_ADM_COLLISIONCNT0, REG_ADM_COLLISIONCNT1, REG_ADM_COLLISIONCNT2, REG_ADM_COLLISIONCNT3, REG_ADM_COLLISIONCNT4, REG_ADM_COLLISIONCNT5,
    REG_ADM_ERRCNT0, REG_ADM_ERRCNT1, REG_ADM_ERRCNT2, REG_ADM_ERRCNT3, REG_ADM_ERRCNT4, REG_ADM_ERRCNT5,
    REG_ADM_OVERFLOWFLAG0, REG_ADM_OVERFLOWFLAG1, REG_ADM_OVERFLOWFLAG2,
    -1
};



/*------------------------------------------------------------------------------------------*\
 * GPIO- Config AR7
\*------------------------------------------------------------------------------------------*/
#define EECK        (1 << 27)           /*--- EECLK     ->  MII_DCLK ---*/
#define EDO_EDI     (1 << 26)           /*--- EDO/EDI   ->  MII_DIO ---*/
#define EECS        (1 << 4 )           /*--- EECS      ->  UARTB_TD/IICCKL/FSER_CLK ---*/

struct _hw_gpio *hw_gpio = (struct _hw_gpio *) AVALANCHE_GPIO_BASE;

/*------------------------------------------------------------------------------------------*\
 * initialize GPIO pins.  output mode, low 
\*------------------------------------------------------------------------------------------*/
void adm_gpio_init(void) {

    /*--- set pins GPIO, OUTPUT, LOW ---*/
    hw_gpio->Enable.Register |= (EECK + EDO_EDI + EECS);
    hw_gpio->Direction.Register &= ~(EECK + EDO_EDI + EECS);
    hw_gpio->OutputData.Register &= ~(EECK + EDO_EDI + EECS);
}

/*------------------------------------------------------------------------------------------*\
 * read one bit from mdio port 
\*------------------------------------------------------------------------------------------*/
int adm_mdio_readbit(void) {

    return hw_gpio->InputData.Bits.mii_dio;
}

/*------------------------------------------------------------------------------------------*\
  MDIO mode selection
  0 -> output
  1 -> input

  switch input/output mode of GPIO 0
\*------------------------------------------------------------------------------------------*/
void adm_mdio_mode(int mode) {

    hw_gpio->Direction.Bits.mii_dio = mode;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
void adm_mdc_hi(void) {
    hw_gpio->OutputData.Bits.mii_dclk = 1;
}

void adm_mdio_hi(void) {
    hw_gpio->OutputData.Bits.mii_dio = 1;
}

void adm_mdcs_hi(void) {
    hw_gpio->OutputData.Bits.fser_clk = 1;
}

void adm_mdc_lo(void) {
    hw_gpio->OutputData.Bits.mii_dclk = 0;
}

void adm_mdio_lo(void) {
    hw_gpio->OutputData.Bits.mii_dio = 0;
}

void adm_mdcs_lo(void) {
    hw_gpio->OutputData.Bits.fser_clk = 0;
}

/*------------------------------------------------------------------------------------------*\
 * mdc pulse
 * 0 -> 1 -> 0
\*------------------------------------------------------------------------------------------*/
static void adm_mdc_pulse(void) {

    adm_mdc_lo();
    udelay(ADM_SW_MDC_DOWN_DELAY);
    adm_mdc_hi();
    udelay(ADM_SW_MDC_UP_DELAY);
    adm_mdc_lo();
}


/*------------------------------------------------------------------------------------------*\
 * mdc toggle
 * 1 -> 0
\*------------------------------------------------------------------------------------------*/
static void adm_mdc_toggle(void) {

    adm_mdc_hi();
    udelay(ADM_SW_MDC_UP_DELAY);
    adm_mdc_lo();
    udelay(ADM_SW_MDC_DOWN_DELAY);
}


/*------------------------------------------------------------------------------------------*\
 * enable eeprom write
 * For ATC 93C66 type EEPROM; accessing ADM6996 internal EEPROM type registers
\*------------------------------------------------------------------------------------------*/
static void adm_eeprom_write_enable(void) {

    unsigned int op;

    adm_mdcs_lo();
    adm_mdc_lo();
    adm_mdio_hi();
    udelay(ADM_SW_CS_DELAY);
    /* enable chip select */
    adm_mdcs_hi();
    udelay(ADM_SW_CS_DELAY);
    /* start bit */
    adm_mdio_hi();
    adm_mdc_pulse();

    /* eeprom write enable */
    op = ADM_SW_BIT_MASK_4;
    while (op) {
        if (op & ADM_SW_EEPROM_WRITE_ENABLE)
            adm_mdio_hi();
        else
            adm_mdio_lo();

        adm_mdc_pulse();
        op >>= 1;
    }

    op = ADM_SW_BIT_MASK_1 << (EEPROM_TYPE - 3);
    while (op) {
        adm_mdio_lo();
        adm_mdc_pulse();
        op >>= 1;
    }

    /* disable chip select */
    adm_mdcs_lo();
    udelay(ADM_SW_CS_DELAY);
    adm_mdc_pulse();
}


/*------------------------------------------------------------------------------------------*\
 * disable eeprom write
\*------------------------------------------------------------------------------------------*/
static void adm_eeprom_write_disable(void) {

    unsigned int op;

    adm_mdcs_lo();
    adm_mdc_lo();
    adm_mdio_hi();
    udelay(ADM_SW_CS_DELAY);
    /* enable chip select */
    adm_mdcs_hi();
    udelay(ADM_SW_CS_DELAY);

    /* start bit */
    adm_mdio_hi();
    adm_mdc_pulse();
    /* eeprom write disable */
    op = ADM_SW_BIT_MASK_4;
    while (op) {
        if (op & ADM_SW_EEPROM_WRITE_DISABLE)
            adm_mdio_hi();
        else
            adm_mdio_lo();

        adm_mdc_pulse();
        op >>= 1;
    }

    op = ADM_SW_BIT_MASK_1 << (EEPROM_TYPE - 3);
    while (op) {
        adm_mdio_lo();

        adm_mdc_pulse();
        op >>= 1;
    }
    /* disable chip select */
    adm_mdcs_lo();
    udelay(ADM_SW_CS_DELAY);
    adm_mdc_pulse();
}


/*------------------------------------------------------------------------------------------*\
  --- adm ---
  read registers from ADM6996
  serial registers start at 0x200 (addr bit 9 = 1b)
  EEPROM registers -> 16bits; Serial registers -> 32bits
\*------------------------------------------------------------------------------------------*/
static unsigned int adm_read_adml(unsigned int addr) {

    unsigned int op, dat;

    adm_gpio_init();

    adm_mdc_lo();
    adm_mdio_lo();

    udelay(ADM_SW_CS_DELAY);

    /* preamble, 32 bit 1 */
    adm_mdio_hi();
    op = ADM_SW_BIT_MASK_32;
    while (op) {
        adm_mdc_pulse();
        op >>= 1;
    }

    /* command start (01b) */
    op = ADM_SW_BIT_MASK_2;
    while (op) {
        if (op & ADM_SW_SMI_START)
            adm_mdio_hi();
        else
            adm_mdio_lo();

        adm_mdc_pulse();
        op >>= 1;
    }

    /* read command (10b) */
    op = ADM_SW_BIT_MASK_2;
    while (op) {
        if (op & ADM_SW_SMI_READ)
            adm_mdio_hi();
        else
            adm_mdio_lo();

        adm_mdc_pulse();
        op >>= 1;
    }

    /* send address A9 ~ A0 */
    op = ADM_SW_BIT_MASK_10;
    while (op) {
        if (op & addr)
            adm_mdio_hi();
        else
            adm_mdio_lo();

        adm_mdc_pulse();
        op >>= 1;
    }

    /* set MDIO pin to input mode */
    adm_mdio_mode(ADM_SW_MDIO_INPUT);

    /* turnaround bits */
    op = ADM_SW_BIT_MASK_2;
    adm_mdio_hi();
    while (op) {
        adm_mdc_pulse();
        op >>= 1;
    }

    udelay(ADM_SW_MDC_DOWN_DELAY);

    /* start read data */
    dat = 0;
    op = adm_read_mask;
    while (op) {
        dat <<= 1;
        if (adm_mdio_readbit()) dat |= 1;
        adm_mdc_toggle();

        op >>= 1;
    }

    /* set MDIO to output mode */
    adm_mdio_mode(ADM_SW_MDIO_OUTPUT);

    /* dummy clock */
    op = ADM_SW_BIT_MASK_4;
    adm_mdio_lo();
    while(op) {
        adm_mdc_pulse();
        op >>= 1;
    }

    adm_mdc_lo();
    adm_mdio_lo();

    return dat;
}


/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
unsigned int adm_read(unsigned int addr, unsigned int serial) {

    unsigned int address = addr;

    if(avm_cpmac_switch_type != AVM_CPMAC_SWITCH_ADM6996LC) {
        address |= 0x100; /* Device address */
    }

    if(serial) {
        /* Serielles Register lesen */
        if(   (serial_register_timestamp[addr] + MAX_SERIAL_REGISTER_AGE < jiffies) 
           || (serial_register_timestamp[addr] > jiffies)) {
            /* Register-Cache ungueltig */
            if(avm_cpmac_switch_type == AVM_CPMAC_SWITCH_ADM6996LC) {
                serial_register[addr] =  adm_read_adml((address * 2 + 0xa0)) | (adm_read_adml(address * 2 + 0xa1) << 16);
            } else {
                serial_register[addr] =  adm_read_adml(address | 0x200);
            }
            serial_register_timestamp[addr] = jiffies;
        }
        return serial_register[addr];
    } else {
        unsigned int dat = adm_read_adml(address);

        if(avm_cpmac_switch_type == AVM_CPMAC_SWITCH_ADM6996L) {
            if (addr % 2)
                return (dat >> 16);
            else
                return (dat & 0xffff);
        } else
            return dat;
    }
}


/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
void adm_update_cache(void) {
    unsigned int i = 0;

    for(i = 0; adm_serial_registers[i] != -1; i++) {
        ADM_GET_SERIAL_REG(adm_serial_registers[i]);
    }
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
void adm_invalidate_register_cache(unsigned int addr) {
    serial_register_timestamp[addr & 0x7F] += MAX_SERIAL_REGISTER_AGE + 1;
}


/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static int adm_write_admlc(unsigned int addr, unsigned int dat) {

    unsigned int op;

    adm_gpio_init();

    adm_mdc_lo();
    adm_mdio_lo();

    udelay(ADM_SW_CS_DELAY);

    /* preamble, 32 bit 1 */
    adm_mdio_hi();
    op = ADM_SW_BIT_MASK_32;
    while (op) {
        adm_mdc_pulse();
        op >>= 1;
    }

    /* command start (01b) */
    op = ADM_SW_BIT_MASK_2;
    while (op) {
        if (op & ADM_SW_SMI_START)
            adm_mdio_hi();
        else
            adm_mdio_lo();

        adm_mdc_pulse();
        op >>= 1;
    }

    /* write command (01b) */
    op = ADM_SW_BIT_MASK_2;
    while (op) {
        if (op & ADM_SW_SMI_WRITE)
            adm_mdio_hi();
        else
            adm_mdio_lo();

        adm_mdc_pulse();
        op >>= 1;
    }

    /* send address A9 ~ A0 */
    op = ADM_SW_BIT_MASK_10;
    while (op) {
        if (op & addr)
            adm_mdio_hi();
        else
            adm_mdio_lo();

        adm_mdc_pulse();
        op >>= 1;
    }

    /* set MDIO pin to output mode */
    adm_mdio_mode(ADM_SW_MDIO_OUTPUT);
  
    /* turnaround bits */
    op = ADM_SW_BIT_MASK_2;
    adm_mdio_hi();
    while (op) {
        adm_mdc_pulse();
        op >>= 1;
    }

    /* start write data */
    op = ADM_SW_BIT_MASK_16;
    while (op) {
        if (op & dat)
            adm_mdio_hi();
        else
            adm_mdio_lo();

        adm_mdc_pulse();
        op >>= 1;
    }

    /* dummy clock */
    adm_mdio_hi();
    adm_mdc_pulse();
    return 0;
}


/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static int adm_write_adml(unsigned int addr, unsigned int dat) {

    unsigned int op;

    adm_gpio_init();

    /* enable write */
    adm_eeprom_write_enable();

    /* chip select */
    adm_mdcs_hi();
    udelay(ADM_SW_CS_DELAY);

    /* issue write command */
    /* start bit */
    adm_mdio_hi();
    adm_mdc_pulse();

    /* EEPROM write command */
    op = ADM_SW_BIT_MASK_2;
    while (op) {
        if (op & ADM_SW_EEPROM_WRITE)
            adm_mdio_hi();
        else
            adm_mdio_lo();

        adm_mdc_pulse();
        op >>= 1;
    }

    /* send address A7 ~ A0 */
    op = ADM_SW_BIT_MASK_1 << (EEPROM_TYPE - 1);

    while (op) {
        if (op & addr)
            adm_mdio_hi();
        else
            adm_mdio_lo();

        adm_mdc_toggle();
        op >>= 1;
    }

    /* start write data */
    op = ADM_SW_BIT_MASK_16;
    while (op) {
        if (op & dat)
            adm_mdio_hi();
        else
            adm_mdio_lo();

        adm_mdc_toggle();
        op >>= 1;
    }

    /* disable cs & wait 1 clock */
    adm_mdcs_lo();
    udelay(ADM_SW_CS_DELAY);
    adm_mdc_toggle();

    adm_eeprom_write_disable();

    return 0;
}


/*------------------------------------------------------------------------------------------*\
 * write register to ADM6996 eeprom registers
\*------------------------------------------------------------------------------------------*/
int adm_write(unsigned int addr, unsigned int dat) {

    switch(avm_cpmac_switch_type) {
        case AVM_CPMAC_SWITCH_ADM6996L:
            return adm_write_adml(addr + 0x80, dat);
        case AVM_CPMAC_SWITCH_ADM6996LC:
            return adm_write_admlc(addr, dat);
        case AVM_CPMAC_SWITCH_ADM6996LC_BROKEN:
        case AVM_CPMAC_SWITCH_NONE:
        default:
            return 1; /* Did not write! */
    }

	return 2;
}

/*
  do switch PHY reset
*/
int adm_reset(void) {

    /* reset PHY */
    return adm_write(ADM_SW_PHY_RESET, 0);
}

/*
  check port status
*/
int adm_check_port_status(int port) {

    unsigned int val;

    if ((port < 0) || (port > ADM_SW_MAX_PORT_NUM)) {
        adm_printf("error on port number (%d)!!\n", port);
        return -1;
    }

    val = adm_read(adm_conf[port], 0);
    if (adm_conf[port]%2) val >>= 16;
    /* only 16bits are effective */
    val &= 0xFFFF;

    adm_printf("Port %d status (%.8x): \n", port, val);

    if (val & ADM_SW_PORT_FLOWCTL)
        adm_printf("\t802.3x flow control supported!\n");
    else
        adm_printf("\t802.3x flow control not supported!\n");

    if (val & ADM_SW_PORT_AN)
        adm_printf("\tAuto negotiation ON!\n");
    else
        adm_printf("\tAuto negotiation OFF!\n");

    if (val & ADM_SW_PORT_100M)
        adm_printf("\tLink at 100M!\n");
    else
        adm_printf("\tLink at 10M!\n");

    if (val & ADM_SW_PORT_FULL)
        adm_printf("\tFull duplex!\n");
    else
        adm_printf("\tHalf duplex!\n");

    if (val & ADM_SW_PORT_DISABLE)
        adm_printf("\tPort disabled!\n");
    else
        adm_printf("\tPort enabled!\n");

    if (val & ADM_SW_PORT_TOS)
        adm_printf("\tTOS enabled!\n");
    else
        adm_printf("\tTOS disabled!\n");

    if (val & ADM_SW_PORT_PPRI)
        adm_printf("\tPort priority first!\n");
    else
        adm_printf("\tVLAN or TOS priority first!\n");

    if (val & ADM_SW_PORT_MDIX)
        adm_printf("\tAuto MDIX!\n");
    else
        adm_printf("\tNo auto MDIX\n");

    adm_printf("\tPVID: %d\n", \
  	    ((val >> ADM_SW_PORT_PVID_SHIFT)&adm_bits[ADM_SW_PORT_PVID_BITS]));

    return 0;
}

/*------------------------------------------------------------------------------------------*\
 * initialize a VLAN
 * clear all VLAN bits
\*------------------------------------------------------------------------------------------*/
int adm_vlan_init(int vlanid) {

    return adm_write(ADM_SW_VLAN0_CONF + vlanid, 0);
}

/*------------------------------------------------------------------------------------------*\
 * add a port to certain vlan
\*------------------------------------------------------------------------------------------*/
int adm_vlan_add(int port, int vlanid) {

    int reg = 0;

    if ((port < 0) || (port > ADM_SW_MAX_PORT_NUM) || (vlanid < 0) || (vlanid > ADM_SW_MAX_VLAN_NUM)) {
        adm_printf("Port number or VLAN number ERROR!!\n");
        return -1;
    }
    reg = adm_read(ADM_SW_VLAN0_CONF + vlanid, 0);
    reg |= (1 << adm_vlan_port[port]);
    return adm_write(ADM_SW_VLAN0_CONF + vlanid, reg);
}

/*------------------------------------------------------------------------------------------*\
 * delete a given port from certain vlan
\*------------------------------------------------------------------------------------------*/
int adm_vlan_del(int port, int vlanid) {

    unsigned int reg = 0;

    if ((port < 0) || (port > ADM_SW_MAX_PORT_NUM) || (vlanid < 0) || (vlanid > ADM_SW_MAX_VLAN_NUM)) {
        adm_printf("Port number or VLAN number ERROR!!\n");
        return -1;
    }
    reg = adm_read(ADM_SW_VLAN0_CONF + vlanid, 0);
    reg &= ~(1 << adm_vlan_port[port]);
    return adm_write(ADM_SW_VLAN0_CONF + vlanid, reg);
}

/*------------------------------------------------------------------------------------------*\
 * default VLAN setting
 * port 0~3 as untag port and PVID = 1
 * VLAN1: port 0~3 and port 5 (MII)
\*------------------------------------------------------------------------------------------*/
void adm_init(unsigned int switch_type) {

    unsigned int i;

    for(i = 0; i <= 3 ; i++)
        adm_switch_port[i].disabled = 1;

    avm_cpmac_switch_type = switch_type;
    if(   (avm_cpmac_switch_type == AVM_CPMAC_CONF_OHIO_ADM6996L) 
       || (avm_cpmac_switch_type == AVM_CPMAC_CONF_SANGAM_ADM6996L))
        avm_cpmac_switch_type = AVM_CPMAC_SWITCH_ADM6996L;

    if(avm_cpmac_switch_type == AVM_CPMAC_SWITCH_ADM6996LC) {
        adm_read_mask = ADM_SW_BIT_MASK_16;
    } else {
        adm_read_mask = ADM_SW_BIT_MASK_32;
    }

    for(i = 0; i < NUMBER_OF_SERIAL_REGISTERS; i++) {
        serial_register[i] = 0;
        serial_register_timestamp[i] = 0;
    }
    adm_vlan_fbox_reset();
    printk(KERN_ERR "CPMAC ADM: ID serial 0x%x  EEPROM 0x%x\n", ADM_GET_SERIAL_REG(0), ADM_GET_EEPROM_REG(0));
}

/*------------------------------------------------------------------------------------------*\
 * Reset the VLAN settings of the switch                                                    *
\*------------------------------------------------------------------------------------------*/
void adm_vlan_fbox_reset(void) {
    unsigned int RegData, group;

    if(avm_cpmac_switch_type == AVM_CPMAC_SWITCH_ADM6996LC_BROKEN)
        return;

    /* VLAN abschalten */
    ADM_PUT_EEPROM_REG(ADM_SW_VLAN_MODE, 0xff00);
    for(group = 0; group < 16; group++)
        ADM_PUT_EEPROM_REG(ADM_SW_VLAN0_CONF + group, 0xFFFF);
    
    /* Alle aktiven Portsder VLAN-Gruppe 0 zuordnen, kein Tagging aktiv */
    RegData =   ADM_SW_PORT_FLOWCTL | ADM_SW_PORT_AN | ADM_SW_PORT_100M | ADM_SW_PORT_FULL | ADM_SW_PORT_MDIX
              | (0 << ADM_SW_PORT_PVID_SHIFT);
    ADM_PUT_EEPROM_REG(ADM_SW_PORT5_CONF, RegData);
    ADM_PUT_EEPROM_REG(ADM_SW_PORT0_CONF, RegData);
    ADM_PUT_EEPROM_REG(ADM_SW_PORT1_CONF, RegData);
    ADM_PUT_EEPROM_REG(ADM_SW_PORT2_CONF, RegData);
    ADM_PUT_EEPROM_REG(ADM_SW_PORT3_CONF, RegData);

    /* Port 4 ist nicht verbunden, also abschalten */
    RegData =   ADM_SW_PORT_FLOWCTL | ADM_SW_PORT_AN | ADM_SW_PORT_100M | ADM_SW_PORT_FULL | ADM_SW_PORT_MDIX
              | (0 << ADM_SW_PORT_PVID_SHIFT)
              | ADM_SW_PORT_DISABLE;
    ADM_PUT_EEPROM_REG(ADM_SW_PORT4_CONF, RegData);
}

/*------------------------------------------------------------------------------------------*\
 * Configure VLAN on the switch                                                             *
 *                                                                                          *
 * Argument: config - Configuration structure                                               *
 *                                                                                          *
 * Return: 0 - everything okay, negative value otherwise                                    *
\*------------------------------------------------------------------------------------------*/
int adm_vlan_fbox_config(struct avm_switch_struct *config) {
    unsigned int RegData, vlan_mapping, group, port;

    if(avm_cpmac_switch_type == AVM_CPMAC_SWITCH_ADM6996LC_BROKEN)
        return -1;
    
    /* VLAN reseten */
    adm_vlan_fbox_reset();

    if(config->groups == 0) /* Reset VLAN only */
        return 0;
    
    /* MAC clone, 802.1q based VLAN */
    ADM_PUT_EEPROM_REG(ADM_SW_VLAN_MODE, 0xff30);
    
    /* Keep tags for outgoing packets from internal port */
    RegData =   ADM_SW_PORT_FLOWCTL | ADM_SW_PORT_AN | ADM_SW_PORT_100M 
              | ADM_SW_PORT_FULL | ADM_SW_PORT_MDIX | ADM_SW_PORT_TAG;
    ADM_PUT_EEPROM_REG(ADM_SW_PORT5_CONF, RegData);

    for(group = 0; group < config->groups; group++) {
        vlan_mapping = 0x7f << 9;
        for(port = 0; port < 5; port++) {
            if(config->vlan[group].source_mask & (1 << port)) {
                RegData =   ADM_SW_PORT_FLOWCTL | ADM_SW_PORT_AN | ADM_SW_PORT_100M 
                          | ADM_SW_PORT_FULL | ADM_SW_PORT_MDIX
                          | (config->vlan[group].id << ADM_SW_PORT_PVID_SHIFT);
                switch(port) {
                    case 0: /* Keep tags for outgoing packets from internal port */
                        RegData |= ADM_SW_PORT_TAG;
                        ADM_PUT_EEPROM_REG(ADM_SW_PORT5_CONF, RegData); break;
                    case 1: ADM_PUT_EEPROM_REG(ADM_SW_PORT0_CONF, RegData); break;
                    case 2: ADM_PUT_EEPROM_REG(ADM_SW_PORT1_CONF, RegData); break;
                    case 3: ADM_PUT_EEPROM_REG(ADM_SW_PORT2_CONF, RegData); break;
                    case 4: ADM_PUT_EEPROM_REG(ADM_SW_PORT3_CONF, RegData); break;
                }
            }

            if(config->vlan[group].target_mask & (1 << port)) {
                switch(port) {
                    case 0: vlan_mapping |= 1 << 8; break; /* Internal port */
                    case 1: vlan_mapping |= 1 << 0; break; /* External port 1 */
                    case 2: vlan_mapping |= 1 << 2; break; /* External port 1 */
                    case 3: vlan_mapping |= 1 << 4; break; /* External port 1 */
                    case 4: vlan_mapping |= 1 << 6; break; /* External port 1 */
                    default: 
#                       if !defined(CONFIG_NO_PRINTK)
                        printk(KERN_ERR "CPMAC ADM: Illegal port?\n");
#                       endif /*--- #if !defined(CONFIG_NO_PRINTK) ---*/
                        break;
                }
            }
        }
        ADM_PUT_EEPROM_REG(ADM_SW_VLAN0_CONF + config->vlan[group].id, vlan_mapping);
    }

    return 0;
}


/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
unsigned int adm_check_link(void) {
    unsigned int i, status, linked, change = 0;
   
    status = ADM_GET_SERIAL_REG(REG_ADM_STATUS0);
    linked = status & (  ADM_STATUS0_PORT0_LINKUP | ADM_STATUS0_PORT1_LINKUP 
                       | ADM_STATUS0_PORT2_LINKUP | ADM_STATUS0_PORT3_LINKUP);

#if 0
    /* Scheint nicht mit mehreren Ports zu funktionieren! */

    /* Powerdown-Support fuer den ADM6996L. Nicht fuer den ADM6996LC geeignet! */
    if(avm_cpmac_switch_type == AVM_CPMAC_SWITCH_ADM6996L) {
        for(i = 0; i <= 3; i++) {
            adm_switch_port[i].state_since++;
            if(adm_switch_port[i].disabled) {
                if(adm_switch_port[i].state_since > 400) {
                    adm_switch_port[i].disabled = 0;
                    change = 1;
                }
            } else {
                /* Statusberechnung funktioniert nur fuer die ersten vier Ports! */
                if(status & (ADM_STATUS_LINKUP << (8 * i)))
                    continue;
                else if(adm_switch_port[i].state_since > 110) {
                    adm_switch_port[i].disabled = 1;
                    change = 1;
                }
            }
            if(change) {
                unsigned int reg, RegData;

                adm_switch_port[i].state_since = 0;
                /*--- printk(KERN_ERR "Switch port %u disable %u\n", i, adm_switch_port[i].disabled); ---*/
                switch(i) {
                    case 0: reg = ADM_SW_PORT0_CONF; break;
                    case 1: reg = ADM_SW_PORT1_CONF; break;
                    case 2: reg = ADM_SW_PORT2_CONF; break;
                    case 3: reg = ADM_SW_PORT3_CONF; break;
                }
                RegData = ADM_GET_EEPROM_REG(reg);
                if(adm_switch_port[i].disabled)
                    RegData |= ADM_SW_PORT_DISABLE;
                else
                    RegData &= ~ADM_SW_PORT_DISABLE;
                adm_invalidate_register_cache(REG_ADM_STATUS0);
                ADM_PUT_EEPROM_REG(reg, RegData);
            }
            change = 0;
        }
    }
#endif /*--- #if 0 ---*/

    return linked;
}

