*------------------------------------------------------------------------------------------*\
* Copyright (C) 2006,2007,2008,2009 AVM GmbH <[email protected]>
*
* 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 version 2 of the License.
*
* 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 <linux/interrupt.h>
#include <linux/timer.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>
#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 <linux/skbuff.h>
#include <asm/atomic.h>
#include <asm/mach_avm.h>
#if defined(CONFIG_AVM_POWER)
#include <linux/avm_power.h>
#endif /*--- #if defined(CONFIG_AVM_POWER) ---*/
#if !defined(CONFIG_NETCHIP_ADM69961)
#define CONFIG_NETCHIP_ADM69961
#endif
#include <asm/mips-boards/prom.h>
#include <linux/avm_event.h>
#include <linux/avm_cpmac.h>
#include <linux/adm_reg.h>
#include "cpmac_if.h"
#include "cpmac_const.h"
#include "cpmac_debug.h"
#include "cpmac_eth.h"
#include "cpphy_const.h"
#include "cpphy_types.h"
#include "cpphy_mdio.h"
#include "cpphy_mgmt.h"
#include "cpphy_if.h"
#include "adm6996.h"
#include "tantos.h"
#include "cpphy_adm6996.h"
#include "cpphy_ar8216.h"
#include <linux/ar_reg.h>
/*------------------------------------------------------------------------------------------*\
* Register cache
\*------------------------------------------------------------------------------------------*/
#define ADM_TANTOS_GET(mdio, addr) tantos_read((mdio), (unsigned short *) &(addr))
#define ADM_TANTOS_PUT(mdio, addr, dat) tantos_write((mdio), (unsigned short *) &(addr), (dat))
#define ADM_TANTOS_SYNC(mdio, addr) tantos_write((mdio), (unsigned short *) &(addr), *((unsigned short *) &(addr)))
#define ADM_TANTOS_PHY_GET(mdio, phy, addr) tantos_phy_access((mdio), TANTOS_MIIAC_READ, (unsigned short *) &(addr), (phy), 0)
#define ADM_TANTOS_PHY_PUT(mdio, phy, addr, data) tantos_phy_access((mdio), TANTOS_MIIAC_WRITE, (unsigned short *) &(addr), (phy), (data))
#define ADM_TANTOS_PHY_SYNC(mdio, phy, addr) tantos_phy_access((mdio), TANTOS_MIIAC_SYNC, (unsigned short *) &(addr), (phy), 0)
#define PRINT_REGISTER(x) DEB_TEST("Reg 0x%3x = 0x%4x\n", x, ADM_GET_EEPROM_REG(mdio, (x)));
#define PRINT_TANTOS_REGISTER(x) DEB_TEST("Reg 0x%3x = 0x%4x\n", x, ADM_TANTOS_GET(mdio, (x)));
#define NUMBER_OF_SERIAL_REGISTERS (0x3c + 1)
/* alle 200ms Register aktualisieren */
#define MAX_SERIAL_REGISTER_AGE (HZ / 4)
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_LC_ID, REG_ADM_LC_STATUS0, REG_ADM_LC_STATUS2,
REG_ADM_LC_RXPKTCNT0, REG_ADM_LC_RXPKTCNT1, REG_ADM_LC_RXPKTCNT2, REG_ADM_LC_RXPKTCNT3, REG_ADM_LC_RXPKTCNT4, REG_ADM_LC_RXPKTCNT5,
REG_ADM_LC_RXPKTBYTECNT0, REG_ADM_LC_RXPKTBYTECNT1, REG_ADM_LC_RXPKTBYTECNT2, REG_ADM_LC_RXPKTBYTECNT3, REG_ADM_LC_RXPKTBYTECNT4, REG_ADM_LC_RXPKTBYTECNT5,
REG_ADM_LC_TXPKTCNT0, REG_ADM_LC_TXPKTCNT1, REG_ADM_LC_TXPKTCNT2, REG_ADM_LC_TXPKTCNT3, REG_ADM_LC_TXPKTCNT4, REG_ADM_LC_TXPKTCNT5,
REG_ADM_LC_TXPKTBYTECNT0, REG_ADM_LC_TXPKTBYTECNT1, REG_ADM_LC_TXPKTBYTECNT2, REG_ADM_LC_TXPKTBYTECNT3, REG_ADM_LC_TXPKTBYTECNT4, REG_ADM_LC_TXPKTBYTECNT5,
REG_ADM_LC_COLLISIONCNT0, REG_ADM_LC_COLLISIONCNT1, REG_ADM_LC_COLLISIONCNT2, REG_ADM_LC_COLLISIONCNT3, REG_ADM_LC_COLLISIONCNT4, REG_ADM_LC_COLLISIONCNT5,
REG_ADM_LC_ERRCNT0, REG_ADM_LC_ERRCNT1, REG_ADM_LC_ERRCNT2, REG_ADM_LC_ERRCNT3, REG_ADM_LC_ERRCNT4, REG_ADM_LC_ERRCNT5,
REG_ADM_LC_OVERFLOWFLAG0, REG_ADM_LC_OVERFLOWFLAG2, REG_ADM_LC_OVERFLOWFLAG4,
-1
};
static const unsigned int adm_port_registers[] = {
REG_ADM_LC_PORT0_CONF, REG_ADM_LC_PORT1_CONF, REG_ADM_LC_PORT2_CONF,
REG_ADM_LC_PORT3_CONF, REG_ADM_LC_PORT4_CONF, REG_ADM_LC_PORT5_CONF
};
#if defined(CONFIG_MIPS_AR7) || defined(CONFIG_MIPS_OHIO)
static const unsigned int adm_port_link_bits[] = {
ADM_STATUS0_PORT0_LINKUP, ADM_STATUS0_PORT1_LINKUP, ADM_STATUS0_PORT2_LINKUP,
ADM_STATUS0_PORT3_LINKUP, ADM_STATUS0_PORT4_LINKUP, 0, 0
};
#endif /*--- #if defined(CONFIG_MIPS_AR7) || defined(CONFIG_MIPS_OHIO) ---*/
static const unsigned int adm_vlan_port_map[] = {
(1 << 0), (1 << 2), (1 << 4), (1 << 6), (1 << 7), (1 << 8)
};
static tantos_switch_struct tantos_switch_memory;
static tantos_switch_struct *tantos_switch = &tantos_switch_memory;
tantos_ports_struct *tantos_ports = (tantos_ports_struct *) &tantos_switch_memory;
static tantos_phy_struct tantos_phy_memory[7];
static tantos_phy_struct *tantos_phys = tantos_phy_memory;
static tantos_counter_struct tantos_counter_memory[7];
#define read_serial_register_timestamp(reg) _read_serial_register_timestamp(reg, __FILE__, __LINE__)
inline static unsigned long _read_serial_register_timestamp(unsigned int reg, char *file, int line) {
if(reg >= sizeof(serial_register_timestamp) / sizeof(serial_register_timestamp[0])) {
panic("[read_serial_register_timestamp] illegal index 0x%x (max=%x) (%s, %u)\n",
reg, sizeof(serial_register_timestamp) / sizeof(serial_register_timestamp[0]),
file, line);
} else {
return serial_register_timestamp[reg];
}
}
#define write_serial_register_timestamp(reg, value) _write_serial_register_timestamp(reg, value, __FILE__, __LINE__)
inline static void _write_serial_register_timestamp(unsigned int reg, unsigned long value, char *file, int line) {
if(reg >= sizeof(serial_register_timestamp) / sizeof(serial_register_timestamp[0])) {
panic("[write_serial_register_timestamp] illegal index 0x%x (max=%x, %s, %d)\n",
reg, sizeof(serial_register_timestamp) / sizeof(serial_register_timestamp[0]),
file, line);
} else {
serial_register_timestamp[reg] = value;
}
}
cpmac_switch_configuration_t switch_configuration[CPMAC_SWITCH_CONF_MAX_PRODUCTS][CPMAC_MODE_MAX_NO] = {
{ /* 7170 */
/* No legal mode */ { 0, 0xff,
{ {"", 0x0}
}
},
/* CPMAC_MODE_NORMAL */ { 1, 0xff,
{ {"eth0", 0x2f}
}
},
/* CPMAC_MODE_ATA */ { 2, 0,
{ {"wan", 0x21},
{"eth0", 0x2e},
}
},
/* CPMAC_MODE_SPLIT */ { 4, 0xff,
{ {"eth0", 0x21},
{"eth1", 0x22},
{"eth2", 0x24},
{"eth3", 0x28}
}
},
/* CPMAC_MODE_SPLIT_ATA */ { 4, 0,
{ {"wan", 0x21},
{"eth1", 0x22},
{"eth2", 0x24},
{"eth3", 0x28},
}
},
/* CPMAC_MODE_ALL_PORTS */ { 1, 0xff,
{ {"eth0", 0x3f}
}
}
}, { /* VINAX5 */
/* No legal mode */ { 0, 0xff,
{ {"", 0x0}
}
},
/* CPMAC_MODE_NORMAL */ { 2, 4,
{ {"wan", 0x30},
{"eth0", 0x2f}
}
},
/* CPMAC_MODE_ATA */ { 2, 0,
{ {"wan", 0x21},
{"eth0", 0x2e}
}
},
/* CPMAC_MODE_SPLIT */ { 5, 4,
{ {"wan", 0x30},
{"eth0", 0x21},
{"eth1", 0x22},
{"eth2", 0x24},
{"eth3", 0x28}
}
},
/* CPMAC_MODE_SPLIT_ATA */ { 4, 0,
{ {"wan", 0x21},
{"eth1", 0x22},
{"eth2", 0x24},
{"eth3", 0x28}
}
},
/* CPMAC_MODE_ALL_PORTS */ { 1, 0xff,
{ {"eth0", 0x3f}
}
}
}, { /* VINAX7 */
/* No legal mode */ { 0, 0xff,
{ {"", 0x0}
}
},
/* CPMAC_MODE_NORMAL */ { 2, 6,
{ {"wan", 0x60},
{"eth0", 0x2f}
}
},
/* CPMAC_MODE_ATA */ { 2, 0,
{ {"wan", 0x21},
{"eth0", 0x2e}
}
},
/* CPMAC_MODE_SPLIT */ { 5, 6,
{ {"wan", 0x60},
{"eth0", 0x21},
{"eth1", 0x22},
{"eth2", 0x24},
{"eth3", 0x28}
}
},
/* CPMAC_MODE_SPLIT_ATA */ { 4, 0,
{ {"wan", 0x21},
{"eth1", 0x22},
{"eth2", 0x24},
{"eth3", 0x28}
}
},
/* CPMAC_MODE_ALL_PORTS */ { 1, 0xff,
{ {"eth0", 0x6f}
}
}
}, { /* ProfiVoIP 2 CPUs */
/* No legal mode */ { 0, 0xff,
{ {"", 0x0}
}
},
/* CPMAC_MODE_NORMAL */ { 2, 0xff,
{ {"eth0", 0x2f},
{"cpu", 0x30}
}
},
/* CPMAC_MODE_ATA */ { 3, 0,
{ {"wan", 0x21},
{"eth0", 0x2e},
{"cpu", 0x30}
}
},
/* CPMAC_MODE_SPLIT */ { 5, 0xff,
{ {"eth0", 0x21},
{"eth1", 0x22},
{"eth2", 0x24},
{"eth3", 0x28},
{"cpu", 0x30}
}
},
/* CPMAC_MODE_SPLIT_ATA */ { 5, 0,
{ {"wan", 0x21},
{"eth1", 0x22},
{"eth2", 0x24},
{"eth3", 0x28},
{"cpu", 0x30}
}
},
/* CPMAC_MODE_ALL_PORTS */ { 1, 0xff,
{ {"eth0", 0x3f}
}
}
}, { /* ProfiVoIP 1 CPU */
/* No legal mode */ { 0, 0xff,
{ {"", 0x0}
}
},
/* CPMAC_MODE_NORMAL */ { 1, 0xff,
{ {"eth0", 0x1f}
}
},
/* CPMAC_MODE_ATA */ { 2, 0,
{ {"wan", 0x11},
{"eth0", 0x1e}
}
},
/* CPMAC_MODE_SPLIT */ { 4, 0xff,
{ {"eth0", 0x11},
{"eth1", 0x12},
{"eth2", 0x14},
{"eth3", 0x18}
}
},
/* CPMAC_MODE_SPLIT_ATA */ { 4, 0,
{ {"wan", 0x11},
{"eth1", 0x12},
{"eth2", 0x14},
{"eth3", 0x18}
}
},
/* CPMAC_MODE_ALL_PORTS */ { 1, 0xff,
{ {"eth0", 0x3f}
}
}
}, { /* AR8216 */
/* No legal mode */ { 0, 0xff,
{ {"", 0x0}
}
},
/* CPMAC_MODE_NORMAL */ { 1, 0xff,
{ {"eth0", 0x1f}
}
},
/* CPMAC_MODE_ATA */ { 2, 1,
{ {"wan", 0x03},
{"eth0", 0x1d},
}
},
/* CPMAC_MODE_SPLIT */ { 4, 0xff,
{ {"eth0", 0x03},
{"eth1", 0x05},
{"eth2", 0x09},
{"eth3", 0x11}
}
},
/* CPMAC_MODE_SPLIT_ATA */ { 4, 1,
{ {"wan", 0x03},
{"eth1", 0x05},
{"eth2", 0x09},
{"eth3", 0x11},
}
},
/* CPMAC_MODE_ALL_PORTS */ { 1, 0xff,
{ {"eth0", 0x3f}
}
}
}
};
/*------------------------------------------------------------------------------------------*\
* initialize GPIO pins. output mode, low
\*------------------------------------------------------------------------------------------*/
# if defined(CONFIG_MIPS_AR7) || defined(CONFIG_MIPS_OHIO)
static void adm_gpio_init(void) {
/*--- set pins GPIO, OUTPUT, LOW ---*/
avm_gpio_ctrl(GPIO_BIT_FSER_CLK, GPIO_PIN, GPIO_OUTPUT_PIN);
avm_gpio_ctrl(GPIO_BIT_MII_DIO, GPIO_PIN, GPIO_OUTPUT_PIN);
avm_gpio_ctrl(GPIO_BIT_MII_DCLK, GPIO_PIN, GPIO_OUTPUT_PIN);
avm_gpio_set_bitmask(GPIO_MASK_MII_DCLK | GPIO_MASK_MII_DIO | GPIO_MASK_FSER_CLK, 0);
}
static void adm_gpio_finit(void) {
avm_gpio_ctrl(GPIO_BIT_FSER_CLK, FUNCTION_PIN, GPIO_OUTPUT_PIN);
avm_gpio_ctrl(GPIO_BIT_MII_DIO, FUNCTION_PIN, GPIO_OUTPUT_PIN);
avm_gpio_ctrl(GPIO_BIT_MII_DCLK, FUNCTION_PIN, GPIO_OUTPUT_PIN);
}
/*------------------------------------------------------------------------------------------*\
* read one bit from mdio port
\*------------------------------------------------------------------------------------------*/
static int adm_mdio_readbit(void) {
return avm_gpio_in_bit(GPIO_BIT_MII_DIO);
}
/*------------------------------------------------------------------------------------------*\
MDIO mode selection
0 -> output
1 -> input
switch input/output mode of GPIO 0
\*------------------------------------------------------------------------------------------*/
static void adm_mdio_mode(int mode) {
avm_gpio_ctrl(GPIO_BIT_MII_DIO, GPIO_PIN, (GPIO_DIR)mode);
}
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static void adm_mdc_hi(void) {
avm_gpio_out_bit(GPIO_BIT_MII_DCLK, 1);
}
static void adm_mdio_hi(void) {
avm_gpio_out_bit(GPIO_BIT_MII_DIO, 1);
}
static void adm_mdcs_hi(void) {
avm_gpio_out_bit(GPIO_BIT_FSER_CLK, 1);
}
static void adm_mdc_lo(void) {
avm_gpio_out_bit(GPIO_BIT_MII_DCLK, 0);
}
static void adm_mdio_lo(void) {
avm_gpio_out_bit(GPIO_BIT_MII_DIO, 0);
}
static void adm_mdcs_lo(void) {
avm_gpio_out_bit(GPIO_BIT_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();
}
/*------------------------------------------------------------------------------------------*\
read registers from ADM6996
32 Bit Mode:
serial registers start at 0x200 (addr bit 9 = 1b)
EEPROM registers -> shifted to 16bits; Serial registers -> 32bits
Autodetect with addr == 0x0: (checks for EEPROM Signature)
\*------------------------------------------------------------------------------------------*/
static unsigned int adm_read_adml(unsigned int addr) {
unsigned int op, dat;
adm_gpio_init();
adm_mdcs_hi();
udelay(ADM_SW_CS_DELAY);
adm_mdcs_lo();
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) */
/*--- adm_write_bits_pulsed(ADM_SW_SMI_START, 2); ---*/
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) */
/*--- adm_write_bits_pulsed(ADM_SW_SMI_READ, 2); ---*/
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 */
/*--- adm_write_bits_pulsed(addr, 10); ---*/
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;
if(!addr) {
/* Autodetect: erstmal nur 16 Bit lesen, weil Mode unbekannt ist */
op = ADM_SW_BIT_MASK_16;
} else {
op = ADM_SW_BIT_MASK_32;
}
while(op) {
dat <<= 1;
if(adm_mdio_readbit())
dat |= 1;
adm_mdc_toggle();
op >>= 1;
}
if(!addr && (dat != 0x4154)) {
/* Autodetect: im 32 Bit Mode die restlichen 16 Bit lesen */
op = ADM_SW_BIT_MASK_16;
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();
adm_mdcs_hi();
/* EEPROM registers */
if(!(addr & ADM_SERIAL_OFFSET))
{
if(addr & 1)
dat >>= 16;
else
dat &= 0xffff;
}
return dat;
}
# endif /*--- #if defined(CONFIG_MIPS_AR7) || defined(CONFIG_MIPS_OHIO) ---*/
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
unsigned int adm_read_32Bit_cached(cpphy_mdio_t *mdio, unsigned int addr) {
unsigned int address, data = 0;
if(!(mdio->Mode & CPPHY_SWITCH_MODE_READ))
return 0;
/* Counter Register lesen */
address = (addr - 0xa0) >> 1;
/*--- if( (mdio->Mode & CPPHY_SWITCH_MODE_16BIT) ---*/ /* No caching for 16 bit hardware mdio access */
/*--- || (unsigned int)(jiffies - read_serial_register_timestamp(address)) > MAX_SERIAL_REGISTER_AGE) { ---*/
/* Invalid cache value of register */
write_serial_register_timestamp(address, jiffies);
if(mdio->Mode & CPPHY_SWITCH_MODE_16BIT) {
data = cpphy_mdio_user_access_read(mdio, addr & 0x1f, addr >> 5) +
(cpphy_mdio_user_access_read(mdio, (addr + 1) & 0x1f, (addr + 1) >> 5) << 16);
}
# if defined(CONFIG_MIPS_AR7) || defined(CONFIG_MIPS_OHIO)
else {
data = adm_read_adml(address + ADM_DEVICE_ADDRESS + ADM_SERIAL_OFFSET);
}
# endif /*--- #if defined(CONFIG_MIPS_AR7) || defined(CONFIG_MIPS_OHIO) ---*/
if(address >= sizeof(adm_serial_register) / sizeof(serial_register[0])) {
panic("write: serial_register[%x] out of index (max 0x%x)\n", address, sizeof(adm_serial_register) / sizeof(serial_register[0]));
}
serial_register[address] = data;
return data;
/*--- } ---*/
if(address >= sizeof(adm_serial_register) / sizeof(serial_register[0])) {
panic("read: serial_register[%x] out of index (max 0x%x)\n", address, sizeof(adm_serial_register) / sizeof(serial_register[0]));
}
return serial_register[address];
}
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
unsigned int adm_read(cpphy_mdio_t *mdio, unsigned int addr) {
if(!(mdio->Mode & CPPHY_SWITCH_MODE_READ)) {
DEB_INFO("adm_read called without being allowed to read!\n");
return 0;
}
if(mdio->Mode & CPPHY_SWITCH_MODE_16BIT) {
return cpphy_mdio_user_access_read(mdio, addr & 0x1f, addr >> 5);
} else {
# if defined(CONFIG_MIPS_AR7) || defined(CONFIG_MIPS_OHIO)
return adm_read_adml(addr + ADM_DEVICE_ADDRESS);
# else /*--- #if defined(CONFIG_MIPS_AR7) || defined(CONFIG_MIPS_OHIO) ---*/
return 0xdead;
# endif /*--- #else ---*/ /*--- #if defined(CONFIG_MIPS_AR7) || defined(CONFIG_MIPS_OHIO) ---*/
}
}
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
unsigned short tantos_read(cpphy_mdio_t *mdio, unsigned short *ptr) {
unsigned int addr = (unsigned int) (ptr - (unsigned short *) tantos_switch);
unsigned short value = adm_read(mdio, addr);
/*--- DEB_TEST("tantos_read: ptr = %p, tantos_switch = %p\n", ptr, tantos_switch); ---*/
/*--- DEB_TEST("tantos_read: addr = %x, value = %x\n", addr, value); ---*/
if(addr > sizeof(tantos_switch_memory) / sizeof(unsigned short))
panic("[tantos_read] addr %x out of range (max %x)\n", addr, sizeof(tantos_switch_memory) / sizeof(unsigned short));
((unsigned short *) tantos_switch)[addr] = value;
return value;
}
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
unsigned int adm_safe_read(cpphy_mdio_t *mdio, unsigned int addr) {
unsigned int count = 0, first, second;
first = adm_read(mdio, addr);
do {
count++;
second = adm_read(mdio, addr);
if(first == second) {
if(count > 1) {
DEB_TEST("Value correct for register %#x on read number %u\n", addr, count);
}
return first;
} else {
DEB_TEST("Addr %#x: %#x != %#x\n", addr, first, second);
}
first = second;
} while(count < 5);
DEB_ERR("Could not read correct value for register %#x\n", addr);
return 0;
}
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
void adm_update_error_status(cpphy_mdio_t *mdio, unsigned int port) {
if(!(mdio->Mode & CPPHY_SWITCH_MODE_READ))
return;
switch(port) {
case 0:
ADM_GET_SERIAL_REG(mdio, REG_ADM_LC_COLLISIONCNT0);
ADM_GET_SERIAL_REG(mdio, REG_ADM_LC_ERRCNT0);
break;
case 1:
ADM_GET_SERIAL_REG(mdio, REG_ADM_LC_COLLISIONCNT1);
ADM_GET_SERIAL_REG(mdio, REG_ADM_LC_ERRCNT1);
break;
case 2:
ADM_GET_SERIAL_REG(mdio, REG_ADM_LC_COLLISIONCNT2);
ADM_GET_SERIAL_REG(mdio, REG_ADM_LC_ERRCNT2);
break;
case 3:
ADM_GET_SERIAL_REG(mdio, REG_ADM_LC_COLLISIONCNT3);
ADM_GET_SERIAL_REG(mdio, REG_ADM_LC_ERRCNT3);
break;
case 4:
ADM_GET_SERIAL_REG(mdio, REG_ADM_LC_COLLISIONCNT4);
ADM_GET_SERIAL_REG(mdio, REG_ADM_LC_ERRCNT4);
break;
case 5:
ADM_GET_SERIAL_REG(mdio, REG_ADM_LC_COLLISIONCNT5);
ADM_GET_SERIAL_REG(mdio, REG_ADM_LC_ERRCNT5);
break;
default:
DEB_ERR("adm_update_error_status, unhandled port %u\n", port);
break;
}
}
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
void adm_update_cache(cpphy_mdio_t *mdio) {
unsigned int i = 0;
if(!(mdio->Mode & CPPHY_SWITCH_MODE_READ))
return;
for(i = 0; adm_serial_registers[i] != -1; i++) {
ADM_GET_SERIAL_REG(mdio, adm_serial_registers[i]);
PRINT_REGISTER(adm_serial_registers[i]);
}
}
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
#if defined(CONFIG_MIPS_AR7) || defined(CONFIG_MIPS_OHIO)
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;
}
#endif /*--- #if defined(CONFIG_MIPS_AR7) || defined(CONFIG_MIPS_OHIO) ---*/
/*------------------------------------------------------------------------------------------*\
* write register to ADM6996 eeprom registers
\*------------------------------------------------------------------------------------------*/
int adm_write(cpphy_mdio_t *mdio, unsigned int addr, unsigned int dat) {
if(!(mdio->Mode & CPPHY_SWITCH_MODE_WRITE)) {
return -1;
}
if(mdio->Mode & CPPHY_SWITCH_MODE_16BIT) {
/*--- DEB_TEST("[adm_write] Write reg %#x: %#x -> %#x\n", addr, cpphy_mdio_user_access_read(mdio, addr & 0x1f, addr >> 5), dat); ---*/
cpphy_mdio_user_access_write(mdio, addr & 0x1f, addr >> 5, dat);
}
# if defined(CONFIG_MIPS_AR7) || defined(CONFIG_MIPS_OHIO)
else {
adm_write_adml(addr + 0x80, dat);
}
# endif /*--- #if defined(CONFIG_MIPS_AR7) || defined(CONFIG_MIPS_OHIO) ---*/
return 0;
}
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
unsigned short tantos_write(cpphy_mdio_t *mdio, unsigned short *ptr, unsigned short value) {
unsigned int addr = (unsigned int) (ptr - (unsigned short *) tantos_switch);
if(addr > sizeof(tantos_switch_memory) / sizeof(unsigned short))
panic("[tantos_write] addr %x out of range (max %x)\n", addr, sizeof(tantos_switch_memory) / sizeof(unsigned short));
((unsigned short *) tantos_switch)[addr] = value;
return adm_write(mdio, addr, value);
}
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static void tantos_wait_for_access_complete(cpphy_mdio_t *mdio) {
for( ;; ) {
ADM_TANTOS_GET(mdio, tantos_switch->MIIAC);
if(tantos_switch->MIIAC.MBUSY == 0)
break;
schedule();
}
}
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static unsigned int tantos_phy_access(cpphy_mdio_t *mdio,
unsigned int method,
unsigned short *regadr,
unsigned int phyadr,
unsigned int data) {
unsigned int value;
unsigned int addr = (unsigned int) (regadr - (unsigned short *) tantos_phys);
if(phyadr >= sizeof(tantos_phy_memory) / sizeof(tantos_phy_memory[0])) {
panic("[tantos_phy_access] illegal phyadr %x (max 0x%x)\n", phyadr, sizeof(tantos_phy_memory) / sizeof(tantos_phy_memory[0]));
}
if(addr >= sizeof(tantos_phy_memory[0]) / sizeof(unsigned short)) {
panic("[tantos_phy_access] illegal addr %x (max 0x%x)\n", addr, sizeof(tantos_phy_memory[0]) / sizeof(unsigned short));
}
if(method == TANTOS_MIIAC_SYNC) {
data = ((unsigned short *) &(tantos_phy_memory[phyadr]))[addr];
method = TANTOS_MIIAC_WRITE;
}
value = data;
down_interruptible(&mdio->semaphore_switch);
tantos_wait_for_access_complete(mdio); /* Wait until UserAccess ready */
tantos_switch->MIIAC.OP = method;
tantos_switch->MIIAC.PHYAD = phyadr;
tantos_switch->MIIAC.REGAD = addr;
if(method == TANTOS_MIIAC_WRITE) {
((unsigned short *) &(tantos_phy_memory[phyadr]))[addr] = data;
ADM_TANTOS_PUT(mdio, tantos_switch->MIIWD, data);
}
ADM_TANTOS_SYNC(mdio, tantos_switch->MIIAC);
tantos_wait_for_access_complete(mdio); /* Wait for Read to complete */
if(method == TANTOS_MIIAC_READ) {
value = ADM_TANTOS_GET(mdio, tantos_switch->MIIRD);
((unsigned short *) &(tantos_phy_memory[phyadr]))[addr] = value;
}
/*--- DEB_TRC("[tantos_phy_access] %s phy %u, reg %#x = %#x\n", ---*/
/*--- (method == TANTOS_MIIAC_WRITE) ? "Writing" : "Reading", ---*/
/*--- phyadr, ---*/
/*--- addr, ---*/
/*--- value); ---*/
up(&mdio->semaphore_switch);
return value;
}
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static void tantos_wait_for_counter_access_complete(cpphy_mdio_t *mdio) {
for( ;; ) {
ADM_TANTOS_GET(mdio, tantos_switch->RCC);
if(tantos_switch->RCC.BAS == 0)
break;
schedule();
}
}
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static unsigned int tantos_counter_access(cpphy_mdio_t *mdio,
unsigned short method,
unsigned short phy,
unsigned short offset) {
unsigned int value = 0;
/*--- unsigned int addr = (unsigned int) (regadr - (unsigned short *) tantos_phys); ---*/
/*--- if(phyadr >= sizeof(tantos_phy_memory) / sizeof(tantos_phy_memory[0])) { ---*/
/*--- panic("[tantos_phy_access] illegal phyadr %x (max 0x%x)\n", phyadr, sizeof(tantos_phy_memory) / sizeof(tantos_phy_memory[0])); ---*/
/*--- } ---*/
/*--- if(addr >= sizeof(tantos_phy_memory[0]) / sizeof(unsigned short)) { ---*/
/*--- panic("[tantos_phy_access] illegal addr %x (max 0x%x)\n", addr, sizeof(tantos_phy_memory[0]) / sizeof(unsigned short)); ---*/
/*--- } ---*/
if(phy >= 7) {
panic("[tantos_counter_access] Illegal PHY number %u\n", phy);
}
if(offset > 27) {
panic("[tantos_counter_access] Illegal offset %u\n", offset);
}
switch(method) {
case TANTOS_COUNTER_READ:
down_interruptible(&mdio->semaphore_switch);
tantos_wait_for_counter_access_complete(mdio);
tantos_switch->RCC.CAC = method;
tantos_switch->RCC.PORTC = phy;
tantos_switch->RCC.OFFSET = offset;
tantos_switch->RCC.BAS = 1;
ADM_TANTOS_SYNC(mdio, tantos_switch->RCC);
tantos_wait_for_counter_access_complete(mdio);
value = ADM_TANTOS_GET(mdio, tantos_switch->RCSL)
+ (ADM_TANTOS_GET(mdio, tantos_switch->RCSH) << 16);
up(&mdio->semaphore_switch);
return value;
break;
case TANTOS_COUNTER_READ_ALL:
down_interruptible(&mdio->semaphore_switch);
tantos_wait_for_counter_access_complete(mdio);
tantos_switch->RCC.CAC = method;
tantos_switch->RCC.PORTC = phy;
tantos_switch->RCC.OFFSET = 0;
tantos_switch->RCC.BAS = 1;
ADM_TANTOS_SYNC(mdio, tantos_switch->RCC);
tantos_counter_memory[phy].RxGoodByte = 0;
tantos_counter_memory[phy].RxBadByte = 0;
tantos_counter_memory[phy].TxGoodByte = 0;
for(offset = 0; offset <= 0x27; offset++) {
tantos_wait_for_counter_access_complete(mdio);
value = ADM_TANTOS_GET(mdio, tantos_switch->RCSL)
+ (ADM_TANTOS_GET(mdio, tantos_switch->RCSH) << 16);
((unsigned int *) &(tantos_counter_memory[phy]))[offset] = value;
}
tantos_counter_memory[phy].RxGoodByte = ((unsigned long long) (((unsigned int *) &(tantos_counter_memory[phy]))[23]) << 32)
+ ((unsigned long long) ((unsigned int *) &(tantos_counter_memory[phy]))[22]);
tantos_counter_memory[phy].RxBadByte = ((unsigned long long) (((unsigned int *) &(tantos_counter_memory[phy]))[25]) << 32)
+ ((unsigned long long) ((unsigned int *) &(tantos_counter_memory[phy]))[24]);
tantos_counter_memory[phy].TxGoodByte = ((unsigned long long) (((unsigned int *) &(tantos_counter_memory[phy]))[27]) << 32)
+ ((unsigned long long) ((unsigned int *) &(tantos_counter_memory[phy]))[26]);
up(&mdio->semaphore_switch);
return 0;
break;
case TANTOS_COUNTER_RENEW_PORT_COUNTERS:
case TANTOS_COUNTER_RENEW_ALL_COUNTERS:
printk("[tantos_counter_access] Method %u not yet implemented\n", method);
return 0;
break;
default:
printk("[tantos_counter_access] Illegal method %u\n", method);
break;
}
return value;
}
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
void adm_prepare_reboot(cpphy_mdio_t *mdio) {
unsigned int i;
adm_vlan_fbox_reset(mdio);
ADM_PUT_EEPROM_REG(mdio, REG_ADM_LC_VLAN_MODE, ADM_GET_EEPROM_REG(mdio, REG_ADM_LC_VLAN_MODE) & 0xffcf);
mdio->cpmac_priv->enable_vlan = 0;
mdio->adm_power.roundrobin = 0;
for(i = 0; i < 5; i++) {
mdio->adm_power.setup.mode[i] = ADM_PHY_POWER_ON;
}
cpphy_mgmt_power(mdio);
}
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
#if 0
#if defined(CONFIG_MIPS_OHIO)
static irqreturn_t adm_interrupt(int irq, void *p_param, struct pt_regs *regs) {
cpphy_mdio_t *mdio = (cpphy_mdio_t *) p_param;
unsigned short status = ADM_GET_EEPROM_REG(mdio, REG_ADM_LC_IS);
DEB_TRC("adm_interrupt: %#x\n", status);
/* Reset time stamp to ensure that the just changed value is requested */
serial_register_timestamp[(REG_ADM_LC_STATUS0 - 0xa0) >> 1] = 0;
return IRQ_HANDLED;
}
#endif /*--- #if defined(CONFIG_MIPS_OHIO) ---*/
#endif /*--- #if 0 ---*/
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
unsigned long switch_dump(cpphy_mdio_t *mdio) {
unsigned int i, phy;
#if defined(CONFIG_MIPS_AR7) || defined(CONFIG_MIPS_OHIO)
DEB_TEST("Registerdump CPMAC SANGAM/OHIO\n");
for(i = 0; i <= 0x28c; i += 4) {
DEB_TEST("CPMAC Register 0x%x = 0x%x\n", i, *((volatile unsigned int *) (mdio->cpmac_priv->owner->base_addr + i)));
}
#elif defined(CONFIG_MIPS_UR8) /*--- #if defined(CONFIG_MIPS_AR7) || defined(CONFIG_MIPS_OHIO) ---*/
/*--- DEB_TEST("Registerdump CPMAC UR8\n"); ---*/
#endif /*--- #elif defined(CONFIG_MIPS_UR8) ---*/
if(mdio->cpmac_switch == AVM_CPMAC_SWITCH_TANTOS) {
if(mdio->switch_dump_value == 1) { /* Register dump */
DEB_TEST("Registerdump Tantos: All values are hexadecimal!\n");
for(i = 0x00; i < 0x20; i++) {
DEB_TEST("Reg %3x = %4x %3x = %4x %3x = %4x %3x = %4x %3x = %4x %3x = %4x %3x = %4x\n",
0x00 + i, ADM_GET_EEPROM_REG(mdio, 0x00 + i),
0x20 + i, ADM_GET_EEPROM_REG(mdio, 0x20 + i),
0x40 + i, ADM_GET_EEPROM_REG(mdio, 0x40 + i),
0x60 + i, ADM_GET_EEPROM_REG(mdio, 0x60 + i),
0x80 + i, ADM_GET_EEPROM_REG(mdio, 0x80 + i),
0xa0 + i, ADM_GET_EEPROM_REG(mdio, 0xa0 + i),
0xc0 + i, ADM_GET_EEPROM_REG(mdio, 0xc0 + i));
}
for(i = 0xe0; i <= 0x122; i++) PRINT_REGISTER(i);
DEB_TEST("Additional register dump:\n");
for(phy = 0; phy < 7; phy++) {
DEB_TEST(" For PHY %u\n", phy);
for(i = 0x00; i < 0x18; i++) {
DEB_TEST(" Reg %3x = %4x\n", i, tantos_phy_access(mdio, TANTOS_MIIAC_READ, ((unsigned short *) tantos_phys) + i, (phy), 0));
}
tantos_counter_access(mdio, TANTOS_COUNTER_READ_ALL, phy, 0);
for(i = 0x00; i <= 0x27; i++) {
DEB_TEST(" Counter %3x = %4x\n", i, ((unsigned int *) &(tantos_counter_memory[phy]))[i]);
}
}
} else if(mdio->switch_dump_value == 2) { /* MAC Hash table */
down_interruptible(&mdio->semaphore_switch);
for(i = 0; i < 4; i++) { /* Search through all four possible FIDs */
DEB_TEST("MAC table entries for FID = %u\n", i);
do { ADM_TANTOS_GET(mdio, tantos_switch->ATS5); } while(tantos_switch->ATS5.BUSY);
tantos_switch->ATC5.AC_CMD = TANTOS_MACTABLE_INITIAL_FIRST;
ADM_TANTOS_SYNC(mdio, tantos_switch->ATC5);
do { ADM_TANTOS_GET(mdio, tantos_switch->ATS5); } while(tantos_switch->ATS5.BUSY);
do {
tantos_switch->ATC3.FID = i;
tantos_switch->ATC5.AC_CMD = TANTOS_MACTABLE_SEARCH_FID;
ADM_TANTOS_SYNC(mdio, tantos_switch->ATC3);
ADM_TANTOS_SYNC(mdio, tantos_switch->ATC5);
do { ADM_TANTOS_GET(mdio, tantos_switch->ATS5); } while( tantos_switch->ATS5.BUSY
|| (tantos_switch->ATS5.RSLT == TANTOS_MACTABLE_RESULT_TEMPORARY_STATE));
if(tantos_switch->ATS5.RSLT == TANTOS_MACTABLE_RESULT_OK) {
ADM_TANTOS_GET(mdio, tantos_switch->ATS4);
if(!tantos_switch->ATS4.OCP) { /* Unoccupied entries are not interesting */
continue;
}
ADM_TANTOS_GET(mdio, tantos_switch->ATS0);
ADM_TANTOS_GET(mdio, tantos_switch->ATS1);
ADM_TANTOS_GET(mdio, tantos_switch->ATS2);
ADM_TANTOS_GET(mdio, tantos_switch->ATS3);
if(tantos_switch->ATS4.INFOTS) {
DEB_TEST(" static : FID %#x, ports %#2x, %04x%04x%04x, no details yet\n",
tantos_switch->ATS3.FIDS,
tantos_switch->ATS3.PMAPS,
tantos_switch->ATS2.ADDR47_32,
tantos_switch->ATS1.ADDR31_16,
tantos_switch->ATS0.ADDR15_0);
} else {
DEB_TEST(" dynamic: FID %#x, ports %#2x, %04x%04x%04x, age %u\n",
tantos_switch->ATS3.FIDS,
tantos_switch->ATS3.PMAPS,
tantos_switch->ATS2.ADDR47_32,
tantos_switch->ATS1.ADDR31_16,
tantos_switch->ATS0.ADDR15_0,
tantos_switch->ATS4.ITATS);
}
}
} while(tantos_switch->ATS5.RSLT == TANTOS_MACTABLE_RESULT_OK);
}
up(&mdio->semaphore_switch);
} else if(mdio->switch_dump_value == 3) { /* Counter dump */
/* TODO */
} else { /* Unknown, probably everything */
for(i = 1; i < 4; i++) {
mdio->switch_dump_value = i;
switch_dump(mdio);
}
}
} else if(mdio->cpmac_switch == AVM_CPMAC_SWITCH_ADM6996) {
DEB_TEST("Registerdump ADM6996: All values are hexadecimal!\n");
for(i = 0x000; i <= 0x09c; i++) PRINT_REGISTER(i);
for(i = 0x0a0; i <= 0x0ad; i++) PRINT_REGISTER(i);
for(i = 0x0b0; i <= 0x0b1; i++) PRINT_REGISTER(i);
for(i = 0x0b4; i <= 0x0bb; i++) PRINT_REGISTER(i);
for(i = 0x0be; i <= 0x0bf; i++) PRINT_REGISTER(i);
for(i = 0x0c2; i <= 0x0c3; i++) PRINT_REGISTER(i);
for(i = 0x0c6; i <= 0x0cd; i++) PRINT_REGISTER(i);
for(i = 0x0d0; i <= 0x0d1; i++) PRINT_REGISTER(i);
for(i = 0x0d4; i <= 0x0d5; i++) PRINT_REGISTER(i);
for(i = 0x0d8; i <= 0x0df; i++) PRINT_REGISTER(i);
for(i = 0x0e2; i <= 0x0e3; i++) PRINT_REGISTER(i);
for(i = 0x0e6; i <= 0x0e7; i++) PRINT_REGISTER(i);
for(i = 0x0ea; i <= 0x0f1; i++) PRINT_REGISTER(i);
for(i = 0x0f4; i <= 0x0f5; i++) PRINT_REGISTER(i);
for(i = 0x0f8; i <= 0x0f9; i++) PRINT_REGISTER(i);
for(i = 0x0fc; i <= 0x103; i++) PRINT_REGISTER(i);
for(i = 0x106; i <= 0x107; i++) PRINT_REGISTER(i);
for(i = 0x10a; i <= 0x10b; i++) PRINT_REGISTER(i);
for(i = 0x10e; i <= 0x119; i++) PRINT_REGISTER(i);
for(i = 0x130; i <= 0x143; i++) PRINT_REGISTER(i);
for(i = 0x200; i <= 0x208; i++) PRINT_REGISTER(i);
for(i = 0x220; i <= 0x228; i++) PRINT_REGISTER(i);
for(i = 0x240; i <= 0x248; i++) PRINT_REGISTER(i);
for(i = 0x260; i <= 0x268; i++) PRINT_REGISTER(i);
for(i = 0x280; i <= 0x288; i++) PRINT_REGISTER(i);
#if 0
down_interruptible(&mdio->semaphore_switch);
for(i = 0; i < 4; i++) { /* Search through all four possible FIDs */
unsigned short ATS5;
DEB_TEST("MAC table entries for FID = %u\n", i);
do { ; } while(ADM_GET_EEPROM_REG(mdio, REG_ADM_LC_ATS5) & ADM_ATS5_BUSY_MASK);
DEB_TEST("Initialize search\n"); /* FIXME */
/* Initialize the MAC table access engine */
ADM_PUT_EEPROM_REG(mdio, REG_ADM_LC_ATC5, TANTOS_MACTABLE_INITIAL_FIRST << ADM_ATC5_AC_CMD_SHIFT);
do { ; } while(ADM_GET_EEPROM_REG(mdio, REG_ADM_LC_ATS5) & ADM_ATS5_BUSY_MASK);
do {
DEB_TEST("Search initialize start\n"); /* FIXME */
/* Enter search criteria and start the search, then wait until it finished */
ADM_PUT_EEPROM_REG(mdio, REG_ADM_LC_ATC3, i << ADM_ATC3_PMAP_SHIFT);
ADM_PUT_EEPROM_REG(mdio, REG_ADM_LC_ATC5, TANTOS_MACTABLE_SEARCH_FID << ADM_ATC5_AC_CMD_SHIFT);
DEB_TEST("Search start\n"); /* FIXME */
do {
ATS5 = ADM_GET_EEPROM_REG(mdio, REG_ADM_LC_ATS5);
} while( (ATS5 & ADM_ATS5_BUSY_MASK)
|| (((ATS5 & ADM_ATS5_RSLT_MASK) >> ADM_ATS5_RSLT_SHIFT) == TANTOS_MACTABLE_RESULT_TEMPORARY_STATE));
DEB_TEST("Result (ATS5 = %#x)('ATS6' = %#x)\n", ATS5, ADM_GET_EEPROM_REG(mdio, REG_ADM_LC_ATS5 + 1)); /* FIXME */
if(((ATS5 & ADM_ATS5_RSLT_MASK) >> ADM_ATS5_RSLT_SHIFT) == TANTOS_MACTABLE_RESULT_OK) {
unsigned short ATS0, ATS1, ATS2, ATS3, ATS4;
ATS4 = ADM_GET_EEPROM_REG(mdio, REG_ADM_LC_ATS4);
if(!(ATS4 & ADM_ATS4_OCP_MASK)) { /* Unoccupied entries are not interesting */
continue;
}
ATS0 = ADM_GET_EEPROM_REG(mdio, REG_ADM_LC_ATS4);
ATS1 = ADM_GET_EEPROM_REG(mdio, REG_ADM_LC_ATS4);
ATS2 = ADM_GET_EEPROM_REG(mdio, REG_ADM_LC_ATS4);
ATS3 = ADM_GET_EEPROM_REG(mdio, REG_ADM_LC_ATS4);
if(ATS4 & ADM_ATS4_INFOTS_MASK) {
DEB_TEST(" static : FID %#x, ports %#2x, %04x%04x%04x, no details yet\n",
(ATS3 & ADM_ATS3_FIDS_MASK) >> ADM_ATS3_FIDS_SHIFT,
(ATS3 & ADM_ATS3_PMAPS_MASK) >> ADM_ATS3_PMAPS_SHIFT,
ATS2, ATS1, ATS0);
} else {
DEB_TEST(" dynamic: FID %#x, ports %#2x, %04x%04x%04x, age %u\n",
(ATS3 & ADM_ATS3_FIDS_MASK) >> ADM_ATS3_FIDS_SHIFT,
(ATS3 & ADM_ATS3_PMAPS_MASK) >> ADM_ATS3_PMAPS_SHIFT,
ATS2, ATS1, ATS0,
(ATS4 & ADM_ATS4_INFOTS_MASK) >> ADM_ATS4_INFOTS_SHIFT);
}
}
} while(((ATS5 & ADM_ATS5_RSLT_MASK) >> ADM_ATS5_RSLT_SHIFT) == TANTOS_MACTABLE_RESULT_OK);
}
up(&mdio->semaphore_switch);
#endif /*--- #if 0 ---*/
} else if(mdio->cpmac_switch == AVM_CPMAC_SWITCH_AR8216) {
display_ath_regs(mdio, ATH_PORT_CONTROL);
display_ath_regs(mdio, ATH_CONTROL);
display_ath_regs(mdio, ATH_MII);
} else {
DEB_TEST("Switch dump requested, but no known switch is connected.\n");
}
mdio->switch_dump_value = 0;
cpphy_mgmt_work_del(mdio, CPMAC_WORK_SWITCH_DUMP);
return 0;
}
/*------------------------------------------------------------------------------------------*\
* default VLAN setting
* port 0~3 as untag port and PVID = 1
* VLAN1: port 0~3 and port 5 (MII)
\*------------------------------------------------------------------------------------------*/
void adm_init(cpphy_mdio_t *mdio) {
unsigned int i;
char *hwrev, *cpu_nr;
for(i = 0; i < NUMBER_OF_SERIAL_REGISTERS; i++) {
serial_register[i] = 0;
write_serial_register_timestamp(i, 0);
}
mdio->Mode = 0 | CPPHY_SWITCH_MODE_WRITE | CPPHY_SWITCH_MODE_READ; /* 32 Bit, read, write */
mdio->mode_pppoa = 0;
if(mdio->is_vinax) {
# if defined(CONFIG_MIPS_AR7) || defined(CONFIG_MIPS_OHIO)
mdio->switch_config = &(switch_configuration[CPMAC_SWITCH_CONF_VINAX5][0]);
# elif defined(CONFIG_MIPS_UR8) /*--- #if defined(CONFIG_MIPS_AR7) || defined(CONFIG_MIPS_OHIO) ---*/
mdio->switch_config = &(switch_configuration[CPMAC_SWITCH_CONF_VINAX7][0]);
# else /*--- #elif defined(CONFIG_MIPS_UR8) ---*/
# warning "There is no switch configuration for the VINAX with this architecture!"
# endif /*--- #else ---*/ /*--- #elif defined(CONFIG_MIPS_UR8) ---*/
} else {
mdio->switch_config = &(switch_configuration[CPMAC_SWITCH_CONF_7170][0]);
}
hwrev = prom_getenv("HWRevision");
mdio->switch_status.cpu_port = 5;
if(hwrev && (strstr("103 ", hwrev) || strstr("104 ", hwrev))) {
char *cmdline = prom_getcmdline();
cpu_nr = strstr( cmdline, "CPU_NR=" );
if(cpu_nr == NULL) {
DEB_WARN("ProfiVoIP: running on first CPU\n");
mdio->switch_config = switch_configuration[CPMAC_SWITCH_CONF_PROFIVOIP2];
mdio->switch_status.cpu_port = 5;
} else {
DEB_WARN("ProfiVoIP: running on second CPU\n");
mdio->Mode = 0; /* No access at all! */
mdio->linked = 1;
return;
}
} else if(hwrev && ( (!(strncmp( "94", hwrev, 2)) && (strlen(hwrev) < 4)) /* First 7170 revision */
|| (!(strncmp( "95", hwrev, 2)) && (strlen(hwrev) < 4)) /* First 7140 revision */
|| (!(strncmp("107", hwrev, 3)) && (strlen(hwrev) < 5)) /* First 7140 Annex A revision */
) ) {
DEB_INFO("switch works in read only 32 bit mode\n");
mdio->Mode &= ~CPPHY_SWITCH_MODE_WRITE;
# if defined(CONFIG_MIPS_AR7) || defined(CONFIG_MIPS_OHIO)
adm_gpio_init();
# endif /*--- #if defined(CONFIG_MIPS_AR7) || defined(CONFIG_MIPS_OHIO) ---*/
adm_check_link(mdio);
return;
}
if(hwrev && ( !(strncmp("79", hwrev, 2)) /* 3070 with ADM6996L */
|| !(strncmp("84", hwrev, 2)) /* 2070 with ADM6996L */
)
) {
DEB_INFO("switch works in read/write 32 bit mode\n");
# if defined(CONFIG_MIPS_AR7) || defined(CONFIG_MIPS_OHIO)
adm_gpio_init();
# endif /*--- #if defined(CONFIG_MIPS_AR7) || defined(CONFIG_MIPS_OHIO) ---*/
} else {
/* "Normal" switch with 16 bit access and hardware mdio */
# if defined(CONFIG_MIPS_AR7) || defined(CONFIG_MIPS_OHIO)
adm_gpio_finit();
# endif /*--- #if defined(CONFIG_MIPS_AR7) || defined(CONFIG_MIPS_OHIO) ---*/
mdio->Mode |= CPPHY_SWITCH_MODE_16BIT;
DEB_INFO("switch is in 16bit Mode\n");
}
/* Check the kind of switch */
init_MUTEX(&mdio->semaphore_switch);
if( (ADM_GET_EEPROM_REG(mdio, REG_ADM_LC_ID2) == 0x0007)
&& (ADM_GET_EEPROM_REG(mdio, REG_ADM_LC_ID) == 0x1022)) {
DEB_INFOTRC("Identified ADM6996LC switch\n");
mdio->cpmac_switch = AVM_CPMAC_SWITCH_ADM6996;
mdio->switch_ports = 6;
} else if( (ADM_GET_EEPROM_REG(mdio, REG_ADM_LC_ID2) == 0x0007)
&& (ADM_GET_EEPROM_REG(mdio, REG_ADM_LC_ID) == 0x1023)) {
DEB_INFOTRC("Identified ADM6996FC switch\n");
mdio->cpmac_switch = AVM_CPMAC_SWITCH_ADM6996;
mdio->switch_ports = 6;
} else if( ( (ADM_TANTOS_GET(mdio, tantos_switch->CI0) == 0x1) /* Tantos 1.2 */
|| (ADM_TANTOS_GET(mdio, tantos_switch->CI0) == 0x2)) /* Tantos 1.3 */
&& (ADM_TANTOS_GET(mdio, tantos_switch->CI1) == 0x2599)) {
DEB_INFOTRC("Identified TANTOS switch family\n");
mdio->cpmac_switch = AVM_CPMAC_SWITCH_TANTOS;
mdio->switch_ports = 7;
ADM_PUT_EEPROM_REG(mdio, 0xa1, 0x4);
ADM_PUT_EEPROM_REG(mdio, 0xf5, 0x777);
for(i = 0; i < 5; i++) {
ADM_TANTOS_PHY_GET(mdio, i, tantos_phys->PHY_CR);
tantos_phy_memory[i].PHY_CR.DISPMG = 1;
ADM_TANTOS_PHY_SYNC(mdio, i, tantos_phys->PHY_CR);
}
ADM_TANTOS_GET(mdio, tantos_switch->MIICR);
*((unsigned short *)&tantos_switch->MIICR) = 0x777; /* MIIs: 100MBits/FD/FC */
ADM_TANTOS_SYNC(mdio, tantos_switch->MIICR);
for(i = 5; i <= 6; i++) {
if((i == 6) && !(mdio->is_vinax))
continue;
ADM_TANTOS_GET(mdio, tantos_ports->port[i].PBC);
tantos_ports->port[i].PBC.FLP = 1; /* Force link up for MIIs */
ADM_TANTOS_SYNC(mdio, tantos_ports->port[i].PBC);
}
#if 0
/* FIXME Test mirror option */
ADM_TANTOS_GET(mdio, tantos_switch->CMH);
DEB_TEST("[adm_init] before CMH = %#x\n", *((unsigned int *) &tantos_switch->CMH));
/*--- tantos_switch->CMH.CPN = 0x5; ---*/ /* CPU port is number 5 */
tantos_switch->CMH.MSA = 0x1; /* Mirror short packets */
tantos_switch->CMH.MPA = 0x1; /* Mirror pause packets */
tantos_switch->CMH.SPN = 0x5; /* Mirror to the CPU port 5 */
ADM_TANTOS_SYNC(mdio, tantos_switch->CMH);
DEB_TEST("[adm_init] after CMH = %#x\n", *((unsigned int *) &tantos_switch->CMH));
ADM_TANTOS_GET(mdio, tantos_ports->port[6].PEC);
tantos_ports->port[6].PEC.IPMO = 0x1; /* Enable receive mirroring */
ADM_TANTOS_SYNC(mdio, tantos_ports->port[6].PEC);
#endif /*--- #if 0 ---*/
} else {
DEB_INFOTRC("Could not identify switch. Assuming ADM6996 family\n");
mdio->cpmac_switch = AVM_CPMAC_SWITCH_ADM6996;
mdio->switch_ports = 6;
}
cpphy_mgmt_power_init(mdio);
/* TODO: Would we benefit? Or is this workqueue variant better anyway? */
# if 0
# if defined(CONFIG_MIPS_OHIO)
/* Check, if we can use the interrupt */
if(hwrev && !(strncmp("94", hwrev, 2)) && (strlen(hwrev) > 2)) {
/* We need write access to configure the switch */
if(mdio->Mode | CPPHY_SWITCH_MODE_WRITE) {
if(request_irq(OHIOINT_EXT_1, adm_interrupt, SA_INTERRUPT | SA_SHIRQ, "ADM6996 Driver", mdio)) {
DEB_ERR("adm_init, failed to register the irq %u\n", OHIOINT_EXT_1);
return;
}
ADM_PUT_EEPROM_REG(mdio, REG_ADM_LC_IE, ADM_IE_PStatIE);
ADM_PUT_EEPROM_REG(mdio, REG_ADM_LC_VLAN_MODE, ADM_GET_EEPROM_REG(mdio, REG_ADM_LC_VLAN_MODE) | 0x0004);
/* Make sure, that all values are zero by reading the register */
ADM_GET_EEPROM_REG(mdio, REG_ADM_LC_IS);
mdio->cpmac_irq = 1;
}
}
# endif /*--- #if defined(CONFIG_MIPS_OHIO) ---*/
# endif /*--- #if 0 ---*/
adm_check_link(mdio);
}
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
unsigned int adm_check_link(cpphy_mdio_t *mdio) {
unsigned int port, adm_status = 0;
if(!(mdio->Mode & CPPHY_SWITCH_MODE_READ))
return 0xff;
if(mdio->cpmac_switch == AVM_CPMAC_SWITCH_TANTOS) {
adm_status = 0;
for(port = 0; port < TANTOS_MAX_PORTS ; port++) {
ADM_TANTOS_GET(mdio, tantos_ports->port[port].PS);
if(tantos_ports->port[port].PS.link) {
adm_status |= 1 << port;
}
}
/* All ports except the CPU port */
mdio->linked = adm_status & (0x05f);
} else {
unsigned int linked = 0;
/* FIXME The event changes should move */
if(mdio->Mode & CPPHY_SWITCH_MODE_16BIT) {
adm_status = ADM_GET_EEPROM_REG(mdio, REG_ADM_LC_STATUS0);
linked |= ((adm_status & ADM_LC_STATUS0_PORT0_LINKUP) ? 1 : 0) << 0;
linked |= ((adm_status & ADM_LC_STATUS0_PORT1_LINKUP) ? 1 : 0) << 1;
mdio->event_data.port[0].link = ((adm_status & ADM_LC_STATUS0_PORT0_LINKUP) ? 1 : 0);
mdio->event_data.port[0].speed100 = ((adm_status & ADM_LC_STATUS0_PORT0_SPEED) ? 1 : 0);
mdio->event_data.port[0].fullduplex = ((adm_status & ADM_LC_STATUS0_PORT0_DUPLEX) ? 1 : 0);
mdio->event_data.port[1].link = ((adm_status & ADM_LC_STATUS0_PORT1_LINKUP) ? 1 : 0);
mdio->event_data.port[1].speed100 = ((adm_status & ADM_LC_STATUS0_PORT1_SPEED) ? 1 : 0);
mdio->event_data.port[1].fullduplex = ((adm_status & ADM_LC_STATUS0_PORT1_DUPLEX) ? 1 : 0);
adm_status = ADM_GET_EEPROM_REG(mdio, REG_ADM_LC_STATUS1);
linked |= ((adm_status & ADM_LC_STATUS1_PORT2_LINKUP) ? 1 : 0) << 2;
linked |= ((adm_status & ADM_LC_STATUS1_PORT3_LINKUP) ? 1 : 0) << 3;
linked |= ((adm_status & ADM_LC_STATUS1_PORT4_LINKUP) ? 1 : 0) << 4;
mdio->event_data.port[2].link = ((adm_status & ADM_LC_STATUS1_PORT2_LINKUP) ? 1 : 0);
mdio->event_data.port[2].speed100 = ((adm_status & ADM_LC_STATUS1_PORT2_SPEED) ? 1 : 0);
mdio->event_data.port[2].fullduplex = ((adm_status & ADM_LC_STATUS1_PORT2_DUPLEX) ? 1 : 0);
mdio->event_data.port[3].link = ((adm_status & ADM_LC_STATUS1_PORT3_LINKUP) ? 1 : 0);
mdio->event_data.port[3].speed100 = ((adm_status & ADM_LC_STATUS1_PORT3_SPEED) ? 1 : 0);
mdio->event_data.port[3].fullduplex = ((adm_status & ADM_LC_STATUS1_PORT3_DUPLEX) ? 1 : 0);
mdio->event_data.port[4].link = ((adm_status & ADM_LC_STATUS1_PORT4_LINKUP) ? 1 : 0);
mdio->event_data.port[4].speed100 = ((adm_status & ADM_LC_STATUS1_PORT4_SPEED) ? 1 : 0);
mdio->event_data.port[4].fullduplex = ((adm_status & ADM_LC_STATUS1_PORT4_DUPLEX) ? 1 : 0);
adm_status = ADM_GET_EEPROM_REG(mdio, REG_ADM_LC_STATUS2);
linked |= ((adm_status & ADM_LC_STATUS2_PORT5_LINKUP) ? 1 : 0) << 5;
mdio->event_data.port[5].link = ((adm_status & ADM_LC_STATUS2_PORT5_LINKUP) ? 1 : 0);
mdio->event_data.port[5].speed100 = ((adm_status & ADM_LC_STATUS2_PORT5_SPEED) ? 1 : 0);
mdio->event_data.port[5].fullduplex = ((adm_status & ADM_LC_STATUS2_PORT5_DUPLEX) ? 1 : 0);
} else {
# if defined(CONFIG_MIPS_AR7) || defined(CONFIG_MIPS_OHIO)
/* Is needed for 2070, 3070, some 7170 */
adm_status = ADM_GET_SERIAL_REG(mdio, REG_ADM_LC_STATUS0);
for(port = 0; port < TANTOS_MAX_PORTS ; port++) {
if(adm_status & adm_port_link_bits[port]) {
linked |= 1 << port;
}
}
mdio->event_data.port[0].link = adm_serial_register.status.port0linkup;
mdio->event_data.port[0].speed100 = adm_serial_register.status.port0speed;
mdio->event_data.port[0].fullduplex = adm_serial_register.status.port0duplex;
mdio->event_data.port[1].link = adm_serial_register.status.port1linkup;
mdio->event_data.port[1].speed100 = adm_serial_register.status.port1speed;
mdio->event_data.port[1].fullduplex = adm_serial_register.status.port1duplex;
mdio->event_data.port[2].link = adm_serial_register.status.port2linkup;
mdio->event_data.port[2].speed100 = adm_serial_register.status.port2speed;
mdio->event_data.port[2].fullduplex = adm_serial_register.status.port2duplex;
mdio->event_data.port[3].link = adm_serial_register.status.port3linkup;
mdio->event_data.port[3].speed100 = adm_serial_register.status.port3speed;
mdio->event_data.port[3].fullduplex = adm_serial_register.status.port3duplex;
mdio->event_data.port[4].link = adm_serial_register.status.port4linkup;
mdio->event_data.port[4].speed100 = adm_serial_register.status.port4speed;
mdio->event_data.port[4].fullduplex = adm_serial_register.status.port4duplex;
# endif /*--- #if defined(CONFIG_MIPS_AR7) || defined(CONFIG_MIPS_OHIO) ---*/
}
if(mdio->linked != linked) {
mdio->linked = linked;
cpmac_main_event_update();
}
mdio->linked = linked;
}
return mdio->linked;
}
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
void adm_switch_port_power(cpphy_mdio_t *mdio, unsigned int port, unsigned int power_on) {
unsigned int value;
assert(port < AVM_CPMAC_MAX_PORTS); /* To ease Klocwork checking */
value = ADM_PHY_CONTROL_DPLX
| ADM_PHY_CONTROL_ANEN
| ADM_PHY_CONTROL_SPEED_LSB
| (power_on ? ADM_PHY_CONTROL_RST : ADM_PHY_CONTROL_PDN);
if(mdio->cpmac_switch == AVM_CPMAC_SWITCH_TANTOS) {
ADM_TANTOS_PHY_PUT(mdio, port, tantos_phys->CR, value);
} else {
ADM_PUT_EEPROM_REG(mdio, REG_ADM_LC_PHY_CONTROL_PORT0 + (0x20 * port), value);
}
}
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static void adm_vlan_reset(cpphy_mdio_t *mdio) {
unsigned int group, port;
mdio->cpmac_priv->enable_vlan = 0;
for(group = 0; group < AVM_CPMAC_MAX_VLAN_GROUPS; group++) {
mdio->switch_status.vlan[group].written = 0;
mdio->switch_status.vlan[group].active = 0;
mdio->switch_status.vlan[group].VFL.Bits.VV = 0;
mdio->switch_status.vlan[group].VFL.Bits.VP = 0;
mdio->switch_status.vlan[group].VFL.Bits.VID = group + 16;
mdio->switch_status.vlan[group].VFH.Bits.FID = 0;
mdio->switch_status.vlan[group].VFH.Bits.TM = 0;
mdio->switch_status.vlan[group].VFH.Bits.M = (mdio->cpmac_switch == AVM_CPMAC_SWITCH_TANTOS) ? 0x7F : 0x3F;
}
for(port = 0; port <= 5; port++) {
mdio->switch_status.port[port].written = 0;
mdio->switch_status.port[port].vid = port + 16;
mdio->switch_status.port[port].keep_tag_outgoing = 0;
mdio->cpmac_priv->map_port_out[port] = port + 16;
}
};
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
unsigned long adm_set_wan_keep_tagging(cpphy_mdio_t *mdio) {
struct avm_new_switch_struct *status = &(mdio->switch_status);
unsigned int port = mdio->cpmac_priv->wanport;
unsigned int RegData;
assert(mdio != NULL);
assert(port < 6); /* The ADM6996xC have a maximum of six ports */
cpphy_mgmt_work_del(mdio, CPMAC_WORK_TOGGLE_VLAN);
if(status->port[port].keep_tag_outgoing == mdio->wan_tagging_enable) {
DEB_WARN("[adm_set_wan_keep_tagging] Already correct setting: %stagged\n",
mdio->wan_tagging_enable ? "" : "un");
cpphy_if_data_from_queues(mdio->cpmac_priv->cppi);
return 0;
}
switch(mdio->cpmac_switch) {
case AVM_CPMAC_SWITCH_TANTOS:
case AVM_CPMAC_SWITCH_NONE:
/* Nothing to be done in those cases */
DEB_ERR("[adm_set_wan_keep_tagging] This should not be called for this hardware!\n");
return 0;
case AVM_CPMAC_SWITCH_ADM6996:
case AVM_CPMAC_SWITCH_ADM6996L:
case AVM_CPMAC_SWITCH_AR8216:
default:
break;
}
tasklet_disable(&mdio->cpmac_priv->tasklet);
status->port[port].keep_tag_outgoing = mdio->wan_tagging_enable;
switch(mdio->cpmac_switch) {
case AVM_CPMAC_SWITCH_ADM6996:
case AVM_CPMAC_SWITCH_ADM6996L:
RegData = ADM_GET_EEPROM_REG(mdio, adm_port_registers[port]);
if(status->port[port].keep_tag_outgoing)
RegData |= ADM_SW_PORT_TAG;
else
RegData &= ~ADM_SW_PORT_TAG;
ADM_PUT_EEPROM_REG(mdio, adm_port_registers[port], RegData);
break;
case AVM_CPMAC_SWITCH_AR8216:
RegData = ar8216_mdio_read32(mdio, (port + 1) * AR8216_PORT0_CONTROL_BASIS + AR8216_PORT_CONTROL_OFFSET);
RegData &= ~(3u << 8); /* Delete VLAN status */
if(!status->port[port].keep_tag_outgoing)
RegData |= ATH_PORT_CTRL_EG_VLAN_MODE(1);
ar8216_mdio_write32(mdio, (port + 1) * AR8216_PORT0_CONTROL_BASIS + AR8216_PORT_CONTROL_OFFSET, RegData);
break;
default:
break;
}
tasklet_enable(&mdio->cpmac_priv->tasklet);
DEB_INFO("Configure wan keep_tagging to %d\n", mdio->wan_tagging_enable);
cpphy_if_data_from_queues(mdio->cpmac_priv->cppi);
return 0;
}
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
unsigned long adm_set_mode_pppoa_work(cpphy_mdio_t *mdio) {
struct avm_new_switch_struct *status = &(mdio->switch_status);
unsigned char wanport = mdio->switch_config[mdio->cpmac_config.cpmac_mode].wanport;
cpphy_mgmt_work_del(mdio, CPMAC_WORK_TOGGLE_PPPOA);
tasklet_disable(&mdio->cpmac_priv->tasklet);
DEB_INFOTRC("[adm_set_mode_pppoa_work] %sabling PPPoA\n", mdio->mode_pppoa ? "En" : "Dis");
/* Asserts, to make Klocwork happy */
assert(status->cpu_port < AVM_CPMAC_MAX_PORTS);
assert(wanport < AVM_CPMAC_MAX_PORTS);
/* Change MAC learning off for PPPoA, on otherwise */
ADM_TANTOS_GET(mdio, tantos_ports->port[status->cpu_port].PEC);
tantos_ports->port[status->cpu_port].PEC.LD = mdio->mode_pppoa;
ADM_TANTOS_SYNC(mdio, tantos_ports->port[status->cpu_port].PEC);
ADM_TANTOS_GET(mdio, tantos_ports->port[wanport].PEC);
tantos_ports->port[wanport].PEC.LD = mdio->mode_pppoa;
ADM_TANTOS_SYNC(mdio, tantos_ports->port[wanport].PEC);
/* Switch flow control off for PPPoA, on otherwise */
ADM_TANTOS_GET(mdio, tantos_ports->port[wanport].PS);
tantos_ports->port[wanport].PS.flowcontrol = mdio->mode_pppoa ? 0 : 1;
ADM_TANTOS_SYNC(mdio, tantos_ports->port[wanport].PS);
tasklet_enable(&mdio->cpmac_priv->tasklet);
return 0;
}
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
cpmac_err_t adm_set_mode_pppoa(cpphy_mdio_t *mdio, unsigned int enable_pppoa) {
if(!mdio->is_vinax || (mdio->cpmac_switch != AVM_CPMAC_SWITCH_TANTOS)) {
DEB_ERR("[adm_set_mode_pppoa] PPPOA mode exists only for the Tantos/VINAX combination!\n");
return CPMAC_ERR_ILL_CONTROL;
}
if(mdio->switch_config[mdio->cpmac_config.cpmac_mode].wanport > AVM_CPMAC_MAX_PORTS) {
DEB_ERR("[adm_set_mode_pppoa] There seems to be no WAN port for PPPoA!?\n");
return CPMAC_ERR_ILL_CONTROL;
}
mdio->mode_pppoa = enable_pppoa ? 1 : 0;
DEB_INFOTRC("[adm_set_mode_pppoa] Enqueue %sabling PPPoA\n", mdio->mode_pppoa ? "en" : "dis");
cpphy_mgmt_work_add(mdio, CPMAC_WORK_TOGGLE_PPPOA, adm_set_mode_pppoa_work, 0);
return CPMAC_ERR_NOERR;
}
/*------------------------------------------------------------------------------------------*\
* Configure VLAN on the switch *
\*------------------------------------------------------------------------------------------*/
static void adm_vlan_config(cpphy_mdio_t *mdio) {
unsigned int vlan_mapping, group, port, RegData;
if(!(mdio->Mode & CPPHY_SWITCH_MODE_WRITE))
return; /*--- CPMAC_ERR_NO_VLAN_POSSIBLE; ---*/
if(mdio->cpmac_switch == AVM_CPMAC_SWITCH_TANTOS) {
for(port = 0; port < mdio->switch_ports; port++) {
ADM_TANTOS_GET(mdio, tantos_ports->port[port].PBVM);
tantos_ports->port[port].PBVM.TBVE = mdio->cpmac_priv->enable_vlan;
ADM_TANTOS_SYNC(mdio, tantos_ports->port[port].PBVM);
}
for(port = 0; port < mdio->switch_ports; port++) {
DEB_INFOTRC("Configure port %u with VLAN group %#x %stagged %s\n",
port,
mdio->switch_status.port[port].vid,
mdio->switch_status.port[port].keep_tag_outgoing ? "" : "un",
mdio->switch_status.port[port].written ? "" : "(new)");
if(mdio->switch_status.port[port].written)
continue;
mdio->switch_status.port[port].written = 1;
/* Set the default VID */
ADM_TANTOS_GET(mdio, tantos_ports->port[port].PDVID);
tantos_ports->port[port].PDVID.PVID = mdio->switch_status.port[port].vid;
ADM_TANTOS_SYNC(mdio, tantos_ports->port[port].PDVID);
}
for(group = 0; group < AVM_CPMAC_MAX_VLAN_GROUPS; group++) {
DEB_INFOTRC("Configure VLAN group %#x (%s): FID %#x VID %#x target 0x%x tagged 0x%x %s\n",
group,
mdio->switch_status.vlan[group].VFL.Bits.VV ? " active" : "inactive",
mdio->switch_status.vlan[group].VFH.Bits.FID,
mdio->switch_status.vlan[group].VFL.Bits.VID,
mdio->switch_status.vlan[group].VFH.Bits.M,
mdio->switch_status.vlan[group].VFH.Bits.TM,
mdio->switch_status.vlan[group].written ? "" : "(new)");
if(mdio->switch_status.vlan[group].written)
continue;
mdio->switch_status.vlan[group].written = 1;
if(group < 8) {
ADM_TANTOS_PUT(mdio, tantos_switch->VF0[group].VFH, mdio->switch_status.vlan[group].VFH.Register);
ADM_TANTOS_PUT(mdio, tantos_switch->VF0[group].VFL, mdio->switch_status.vlan[group].VFL.Register);
} else {
ADM_TANTOS_PUT(mdio, tantos_switch->VF8[group - 8].VFH, mdio->switch_status.vlan[group].VFH.Register);
ADM_TANTOS_PUT(mdio, tantos_switch->VF8[group - 8].VFL, mdio->switch_status.vlan[group].VFL.Register);
}
}
} else {
/*--- Select two bits MAC Clone, when cloning (for VLANs) is enabled ---*/
RegData = ADM_GET_EEPROM_REG(mdio, REG_ADM_LC_MISC_CONF);
RegData |= ADM_CONF_MISC_MCEB;
ADM_PUT_EEPROM_REG(mdio, REG_ADM_LC_MISC_CONF, RegData);
if(mdio->cpmac_priv->enable_vlan == 1) {
/* MAC clone, 802.1q based VLAN */
ADM_PUT_EEPROM_REG(mdio, REG_ADM_LC_VLAN_MODE, ADM_GET_EEPROM_REG(mdio, REG_ADM_LC_VLAN_MODE) | 0x0030);
/* MAC clone, 802.1q based VLAN, address aging timer 1 second */
/*--- ADM_PUT_EEPROM_REG(mdio, REG_ADM_LC_VLAN_MODE, 0xff33); ---*/
} else {
/* MAC clone, Port based by-pass mode */
ADM_PUT_EEPROM_REG(mdio, REG_ADM_LC_VLAN_MODE, ADM_GET_EEPROM_REG(mdio, REG_ADM_LC_VLAN_MODE) & 0xffcf);
}
for(port = 0; port < mdio->switch_ports; port++) {
assert(port < 6); /* The ADM6996xC have a maximum of six ports */
DEB_INFOTRC("Configure port 0x%x with VID 0x%x %stagged %s\n",
port,
mdio->switch_status.port[port].vid,
mdio->switch_status.port[port].keep_tag_outgoing ? "" : "un",
mdio->switch_status.port[port].written ? "" : "(new)");
if(mdio->switch_status.port[port].written)
continue;
mdio->switch_status.port[port].written = 1;
RegData = ADM_GET_EEPROM_REG(mdio, adm_port_registers[port]);
RegData &= ~(ADM_CONF_OUTPUT_PKT_TAG | ADM_CONF_PORT_VLAN_ID);
if(mdio->switch_status.port[port].keep_tag_outgoing)
RegData |= ADM_SW_PORT_TAG;
RegData |= (mdio->switch_status.port[port].vid & 0xf) << ADM_SW_PORT_PVID_SHIFT;
ADM_PUT_EEPROM_REG(mdio, adm_port_registers[port], RegData);
if(port == 5) {
RegData = ADM_GET_EEPROM_REG(mdio, REG_ADM_LC_RACP) & 0xff00;
RegData |= (mdio->switch_status.port[port].vid & 0xff0) >> 4;
ADM_PUT_EEPROM_REG(mdio, REG_ADM_LC_RACP, RegData);
} else if(port == 4) {
RegData = ADM_GET_EEPROM_REG(mdio, REG_ADM_LC_PORT34_PVID) & 0x00ff;
RegData |= (mdio->switch_status.port[port].vid & 0xff0) << 4;
ADM_PUT_EEPROM_REG(mdio, REG_ADM_LC_PORT34_PVID, RegData);
} else {
RegData = ADM_GET_EEPROM_REG(mdio, REG_ADM_LC_PORT0_PVID + port) & 0xff00;
RegData |= (mdio->switch_status.port[port].vid & 0xff0) >> 4;
ADM_PUT_EEPROM_REG(mdio, REG_ADM_LC_PORT0_PVID + port, RegData);
}
}
for(group = 0; group < AVM_CPMAC_MAX_VLAN_GROUPS; group++) {
unsigned short target_register;
DEB_INFOTRC("Configure VLAN group %#x (%s): VID 0x%x target 0x%x %s\n",
group,
mdio->switch_status.vlan[group].active ? " active" : "inactive",
mdio->switch_status.vlan[group].vid,
mdio->switch_status.vlan[group].VFH.Bits.M,
mdio->switch_status.vlan[group].written ? "" : "(new)");
if(mdio->switch_status.vlan[group].written)
continue;
mdio->switch_status.vlan[group].written = 1;
vlan_mapping = mdio->switch_status.vlan[group].VFH.Bits.M;
vlan_mapping |= ADM_GET_TAG_GROUP(mdio->switch_status.vlan[group].vid) << 12;
target_register = REG_ADM_LC_VLAN_FILTER0_LOW + 2 * group;
ADM_PUT_EEPROM_REG(mdio, target_register, vlan_mapping);
DEB_INFOTRC("[adm_new_vlan_config] Writing register %#x = %#x\n", target_register, vlan_mapping);
vlan_mapping = (mdio->switch_status.vlan[group].active << 15) | mdio->switch_status.vlan[group].vid;
target_register = REG_ADM_LC_VLAN_FILTER0_HIGH + 2 * group;
ADM_PUT_EEPROM_REG(mdio, target_register, vlan_mapping);
DEB_INFOTRC("[adm_new_vlan_config] Writing register %#x = %#x\n", target_register, vlan_mapping);
}
}
}
/*------------------------------------------------------------------------------------------*\
* Reset the VLAN settings of the switch *
\*------------------------------------------------------------------------------------------*/
void adm_vlan_fbox_reset(cpphy_mdio_t *mdio) {
/* switch off VLAN */
adm_vlan_reset(mdio);
adm_vlan_config(mdio);
}
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
struct net_device *find_or_register_device(char *name,
cpmac_priv_t *cpmac_priv,
unsigned short VID) {
struct net_device *dev;
int i;
for(i = 0; i < AVM_CPMAC_MAX_DEVS; i++) {
dev = cpmac_priv->olddevs[i];
if(dev && strcmp(dev->name, name) == 0) {
DEB_INFOTRC("Device configuration: Update '%s' with VID 0x%x\n", name, VID);
cpmac_update_device(dev, VID);
cpmac_priv->olddevs[i] = 0;
return dev;
}
}
if(strcmp(name, "wan") != 0) {
DEB_INFOTRC("Device configuration: Connect outgoing '%s' with VID 0x%x\n", name, VID);
return cpmac_register_eth_device(name, cpmac_priv->owner, VID);
}
DEB_INFOTRC("Device configuration: Connect outgoing '%s' with VID 0x%x (WAN)\n", name, VID);
cpmac_priv->wanport_default_vid = VID;
return cpmac_register_wan_device(name, cpmac_priv->owner, VID);
}
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
/* Find unused VLAN ID group */
#define find_unused_vlan_old() \
for(vid_group = 0; vid_group < AVM_CPMAC_MAX_VLAN_GROUPS; vid_group++) { \
if(!(used_vid_mask & (1 << vid_group))) { \
break; \
} \
} \
if(vid_group == AVM_CPMAC_MAX_VLAN_GROUPS) { \
DEB_ERR("Error! I need more VLAN groups!\n"); \
return CPMAC_ERR_NO_VLAN_POSSIBLE; \
} \
used_vid_mask |= 1 << vid_group;
#define adm_get_new_vid() \
/* Do not use VIDs < 2; ensure it by adding 4, that does not change the lower two bits */ \
new_vid = (((vid_group << ADM_6996_TAGSHIFT) + bit_combination) & 0xfff); \
for( ; ; ) { \
new_vid += (1 << 2); \
new_vid &= 0xfff; /* To make Klockworks happy */ \
if(mdio->cpmac_priv->map_in[new_vid].used) \
continue; \
if(((new_vid >> ADM_6996_TAGSHIFT) & 0xf) != vid_group) { \
DEB_ERR("Internal VLAN configuration error! Too many VIDs for this device %u?\n", device); \
} \
DEB_TRC("[adm_get_new_vid] new_vid = %#x\n", new_vid); \
break; \
}
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
void set_map_in(cpphy_mdio_t *mdio,
unsigned short vid,
unsigned short device,
unsigned short port) {
assert(port < AVM_CPMAC_MAX_PORTS);
vid &= 0xfff; /* VID is always 0-4095; this tries to make Klockworks happy */
if(mdio->cpmac_priv->map_in[vid].used) {
if(mdio->cpmac_priv->map_in[vid].port != port) {
DEB_ERR("[set_map_in] Error! VID %#x port (%u - %u) already set and differs!\n",
vid,
mdio->cpmac_priv->map_in[vid].port, port);
} else {
DEB_INFOTRC("[set_map_in] VID %#x Port (%u) already set, but identical.\n",
vid, port);
}
} else {
DEB_INFOTRC("[set_map_in] Setting VID %#x Device (%u), port (%u).\n", vid, device, port);
mdio->cpmac_priv->map_in[vid].port = port;
mdio->cpmac_priv->map_in[vid].used = 1;
}
if(mdio->cpmac_priv->map_in_port_dev[port].used) {
if(mdio->cpmac_priv->map_in_port_dev[port].dev != device) {
DEB_ERR("[set_map_in] Error! Port %u already set to device %u instead of %u.\n",
port, mdio->cpmac_priv->map_in_port_dev[port].dev, device);
} else {
DEB_INFOTRC("[set_map_in] Port %u already set to device %u.\n",
port, mdio->cpmac_priv->map_in_port_dev[port].dev);
}
} else {
DEB_INFOTRC("[set_map_in] Setting port %u to device %u.\n", port, device);
mdio->cpmac_priv->map_in_port_dev[port].dev = device;
mdio->cpmac_priv->map_in_port_dev[port].used = 1;
}
}
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
cpmac_err_t adm_configure_cpmac(cpphy_mdio_t *mdio, struct avm_cpmac_config_struct *config) {
unsigned short used_vid_mask = 0, bit_combination;
unsigned char port, device, vid_group, used_ports = 0, roundrobin;
unsigned int i, create_port_specific_vlan, vid, devices_old;
unsigned short new_vid; /*--- = (1 << (4 + ADM_6996_TAGSHIFT)); ---*/
struct avm_new_switch_struct *status = &(mdio->switch_status);
if(!(mdio->Mode & CPPHY_SWITCH_MODE_WRITE)) {
DEB_ERR("[adm_configure_cpmac] Illegal configuration for this hardware!\n");
return CPMAC_ERR_NO_VLAN_POSSIBLE;
}
/* Check plausibility */
if(config->cpmac_mode >= CPMAC_MODE_MAX_NO) {
DEB_ERR("[adm_configure_cpmac] Unknown cpmac_configuration %u\n", config->cpmac_mode);
return CPMAC_ERR_EXCEEDS_LIMIT;
}
assert(status->cpu_port < AVM_CPMAC_MAX_PORTS);
/* Make sure, that nothing works in parallel */
cpphy_mgmt_work_stop(mdio);
tasklet_disable(&mdio->cpmac_priv->tasklet);
/* Backup device entries */
for(i = 0; i < AVM_CPMAC_MAX_DEVS; i++) {
mdio->cpmac_priv->olddevs[i] = mdio->cpmac_priv->devs[i];
mdio->cpmac_priv->devs[i] = 0;
}
devices_old = mdio->cpmac_priv->devices;
mdio->cpmac_priv->devices = mdio->switch_config[config->cpmac_mode].number_of_devices;
if(mdio->cpmac_priv->devices > AVM_CPMAC_MAX_DEVS) {
mdio->cpmac_priv->devices = devices_old;
DEB_ERR("[adm_configure_cpmac] Too many devices in configuration!\n");
return CPMAC_ERR_EXCEEDS_LIMIT;
}
DEB_INFO("Configure cpmac to mode %u with %u devices:\n",
config->cpmac_mode, mdio->cpmac_priv->devices);
for(i = 0; i < mdio->cpmac_priv->devices; i++) {
DEB_INFO("Configure cpmac device '%s' with target_mask 0x%x\n",
mdio->switch_config[config->cpmac_mode].device[i].name,
mdio->switch_config[config->cpmac_mode].device[i].target_mask);
}
memcpy(&mdio->cpmac_config, config, sizeof(struct avm_cpmac_config_struct));
/* More than four devices might mean problems with port addressing */
if( ( (mdio->cpmac_switch == AVM_CPMAC_SWITCH_ADM6996L)
|| (mdio->cpmac_switch == AVM_CPMAC_SWITCH_ADM6996))
&& (mdio->cpmac_priv->devices > 4)) {
DEB_WARN("[adm_configure_cpmac] Warning: Too many devices (%u). This might cause packet loss!\n",
mdio->cpmac_priv->devices);
}
adm_vlan_reset(mdio);
if(mdio->cpmac_config.cpmac_mode == CPMAC_MODE_ALL_PORTS) {
DEB_INFOTRC("Configure: Disable VLAN\n");
mdio->cpmac_priv->enable_vlan = 0;
} else {
DEB_INFOTRC("Configure: Enable VLAN\n");
mdio->cpmac_priv->enable_vlan = 1;
}
/* Determine which ports are used and therefor need a VLAN ID */
for(device = 0; device < mdio->cpmac_priv->devices; device++) {
used_ports |= mdio->switch_config[config->cpmac_mode].device[device].target_mask;
}
/* Reset VID->Port mapping */
for(vid = 0; vid < 4096; vid++) {
/*--- set_map_in(mdio, vid, 0, status->cpu_port); ---*/
mdio->cpmac_priv->map_in[vid].port = status->cpu_port;
mdio->cpmac_priv->map_in[vid].used = 0;
}
/* Reset port->device mapping */
for(port = 0; port < AVM_CPMAC_MAX_PORTS; port++) {
mdio->cpmac_priv->map_in_port_dev[port].dev = 0;
mdio->cpmac_priv->map_in_port_dev[port].used = 0;
}
/* Reset PPPoA mode */
mdio->mode_pppoa = 0;
if(mdio->cpmac_switch == AVM_CPMAC_SWITCH_TANTOS) {
/* Do we have predefined VLAN IDs? */
if(config->wan_vlan_number) {
for(device = 0; device < mdio->cpmac_priv->devices; device++) {
if(strncmp(mdio->switch_config[config->cpmac_mode].device[device].name, "wan", 3) == 0) {
for(i = 0; i < config->wan_vlan_number; i++) {
vid = config->wan_vlan_id[i];
find_unused_vlan_old(); /* sets vid_group */
/* Make all ports that belong to the same VLAN group receive the data of the port */
status->vlan[vid_group].VFL.Bits.VV = 1;
status->vlan[vid_group].VFL.Bits.VID = vid;
status->vlan[vid_group].VFH.Bits.M = mdio->switch_config[config->cpmac_mode].device[device].target_mask;
status->vlan[vid_group].VFH.Bits.TM = mdio->switch_config[config->cpmac_mode].device[device].target_mask;
/*--- DEB_TEST("-- %u -- VID_group %#x = %#x\n", __LINE__, vid & 0xf, status->vlan[vid & 0xf].VFH.Register); ---*/ /* FIXME */
status->vlan[vid_group].written = 0;
for(port = 0; port < AVM_CPMAC_MAX_PORTS; port++) {
if(mdio->switch_config[config->cpmac_mode].device[device].target_mask & (1 << port)) {
if(port != status->cpu_port) {
set_map_in(mdio, vid, device, port);
}
}
}
}
}
}
}
/* Tag incoming data with individual tags and deliver it to all interested ports */
for(port = 0; port < AVM_CPMAC_MAX_PORTS; port++) {
/* Do not tag incoming data from the CPU port */
if(port == status->cpu_port)
continue;
/* If port is unused, it needs no VLAN tag */
if(!(used_ports & (1 << port))) {
continue;
}
/* Find unused VLAN ID group */
find_unused_vlan_old();
bit_combination = 0; /* Does not really matter for the Tantos */
adm_get_new_vid();
status->port[port].vid = new_vid;
status->port[port].written = 0;
if(port != mdio->switch_config[config->cpmac_mode].wanport) {
status->port[status->cpu_port].vid = status->port[port].vid;
status->port[status->cpu_port].written = 0;
DEB_INFO("Setting VID of CPU-port to %#x\n", status->port[status->cpu_port].vid);
}
DEB_INFOTRC("Configure: Setting VLAN tagging on port %u with tag %#x\n", port, new_vid);
/* Make all ports that belong to the same VLAN group receive the data of the port */
create_port_specific_vlan = 0;
for(device = 0; device < mdio->cpmac_priv->devices; device++) {
if(mdio->switch_config[config->cpmac_mode].device[device].target_mask & (1 << port)) {
set_map_in(mdio, status->port[port].vid, device, port);
status->vlan[vid_group].VFL.Bits.VV = 1;
status->vlan[vid_group].VFL.Bits.VID = new_vid;
status->vlan[vid_group].VFH.Bits.M = mdio->switch_config[config->cpmac_mode].device[device].target_mask;
status->vlan[vid_group].VFH.Bits.TM = 0x1 << status->cpu_port;
status->vlan[vid_group].written = 0;
set_map_in(mdio, status->vlan[vid_group].VFL.Bits.VID, device, port);
if(mdio->switch_config[config->cpmac_mode].device[device].target_mask & (1 << status->cpu_port)) {
create_port_specific_vlan = 1;
}
}
}
/* Create VLANs for port specific outgoing traffic */
if(create_port_specific_vlan == 1) {
find_unused_vlan_old();
bit_combination = 0; /* Does not really matter for the Tantos */
adm_get_new_vid();
status->vlan[vid_group].VFL.Bits.VV = 1;
status->vlan[vid_group].VFL.Bits.VID = new_vid;
status->vlan[vid_group].VFH.Bits.M = (0x1 << status->cpu_port) | (0x1 << port);
status->vlan[vid_group].VFH.Bits.TM = 0x1 << status->cpu_port;
status->vlan[vid_group].written = 0;
/* This VID is not intended for incoming traffic. It is *\
* defined here to make checking for used VIDs possible */
mdio->cpmac_priv->map_in[new_vid].port = port;
mdio->cpmac_priv->map_in[new_vid].used = 1;
mdio->cpmac_priv->map_port_out[port] = status->vlan[vid_group].VFL.Bits.VID;
DEB_INFOTRC("Configure: Setting outgoing VLAN ID for port %u to %#x\n", port, new_vid);
}
}
for(vid_group = 0; vid_group < AVM_CPMAC_MAX_VLAN_GROUPS; vid_group++) {
for(device = 0; device < mdio->cpmac_priv->devices; device++) {
/* Are all target ports in the device target mask? Do they belong together? */
if( ( mdio->switch_config[config->cpmac_mode].device[device].target_mask
& status->vlan[vid_group].VFH.Bits.M)
== status->vlan[vid_group].VFH.Bits.M) {
if(device >= 4) {
DEB_ERR("Attention! More than four devices might produce packet forwarding errors!\n");
}
status->vlan[vid_group].VFH.Bits.FID = device & 0x3;
}
}
}
} else {
/* We need to treat the WAN case special, because of limitations of the *\
* ADM6996 switches: *
* - Four consecutive bits of the VID are used as VID group *
* - The MAC hash table uses only the lower two bits of the VID to *
* distinguish VLANs *
* *
* This code assumes provided VIDs below 256 and at most three of the *
* four two bit combinations for the lower two bits of the VID taken. *
* This range is now expected and summarized in group 0. Further it is *
* expected, that the WAN device connects only the CPU port and the WAN *
\* port. */
int bit_combinations_used[4] = {-1, -1, -1, -1};
if(4 < (mdio->cpmac_priv->devices + (config->wan_vlan_number ? (config->wan_vlan_number - 1) : 0))) {
DEB_WARN("[adm_configure_cpmac] Warning: Too many devices (%u) and/or predefined VLAN IDs (%u). This might cause packet loss!\n",
mdio->cpmac_priv->devices, config->wan_vlan_number);
}
for(device = 0; device < mdio->cpmac_priv->devices; device++) {
if(strncmp(mdio->switch_config[config->cpmac_mode].device[device].name, "wan", 3) == 0) {
bit_combination = 0;
/* There is no need to configure the switch for each provided VID, because *\
* there will be one configured group. The criterion to be considered is *
\* the VID group, which will be 0 for the provided and the default VID. */
for(i = 0; i < config->wan_vlan_number; i++) {
vid = config->wan_vlan_id[i];
set_map_in(mdio, vid, device, mdio->switch_config[config->cpmac_mode].wanport);
bit_combinations_used[vid & 0x3] = (int) device;
bit_combination = vid & 0x3;
}
vid_group = 0; /* Used by adm_get_new_vid */
adm_get_new_vid();
used_vid_mask |= 1 << vid_group;
status->vlan[vid_group].vid = new_vid;
status->vlan[vid_group].VFH.Bits.M = mdio->switch_config[config->cpmac_mode].device[device].target_mask;
status->vlan[vid_group].active = 1;
status->vlan[vid_group].written = 0;
status->port[mdio->switch_config[config->cpmac_mode].wanport].vid = new_vid;
status->port[mdio->switch_config[config->cpmac_mode].wanport].written = 0;
set_map_in(mdio, new_vid, device, mdio->switch_config[config->cpmac_mode].wanport);
DEB_INFOTRC("[adm_configure_cpmac] Create WAN info: group %#x, VID %#x, mask %#x\n",
vid_group,
new_vid,
mdio->switch_config[config->cpmac_mode].device[device].target_mask);
}
}
for(device = 0; device < mdio->cpmac_priv->devices; device++) {
unsigned short device_vid_group;
if(strncmp(mdio->switch_config[config->cpmac_mode].device[device].name, "wan", 3) == 0) {
continue;
}
for(bit_combination = 0; bit_combination < 4; bit_combination++) {
if(bit_combinations_used[bit_combination] != -1)
continue;
break;
}
if(bit_combination == 4) {
DEB_ERR("[adm_configure_cpmac] Out of bit combinations for VIDs! Using 3, expect problems!\n");
bit_combination = 3;
}
bit_combinations_used[bit_combination] = device;
find_unused_vlan_old();
device_vid_group = vid_group;
for(port = 0; port < AVM_CPMAC_MAX_PORTS; port++) {
/* Skip unused ports */
if(!((1 << port) & mdio->switch_config[config->cpmac_mode].device[device].target_mask))
continue;
/* Do not tag incoming data from the CPU port */
if(port == status->cpu_port)
continue;
/* Find unused VLAN ID group */
/* (port + 1), because it needs to be different to the outgoing VID */
vid_group = device_vid_group;
adm_get_new_vid();
status->vlan[vid_group].vid = new_vid;
status->vlan[vid_group].VFH.Bits.M = mdio->switch_config[config->cpmac_mode].device[device].target_mask;
status->vlan[vid_group].active = 1;
status->vlan[vid_group].written = 0;
status->port[port].vid = new_vid;
status->port[port].written = 0;
set_map_in(mdio, new_vid, device, port);
DEB_INFOTRC("Configure: Setting incoming VLAN ID for port %u to %#x, target mask %#x\n",
port,
new_vid,
status->vlan[vid_group].VFH.Bits.M);
/* Set the default VID for the CPU port to that of the last configured incoming port */
/* This is done to enable untagged packets from the CPU to reach the ethernet */
if(port < 4) { /* Only for ethernet ports */
DEB_INFO("Setting VID of CPU-port to %#x\n", new_vid);
status->port[status->cpu_port].vid = new_vid;
status->port[status->cpu_port].written = 0;
}
/* Create VLANs for port specific outgoing traffic */
find_unused_vlan_old();
adm_get_new_vid();
status->vlan[vid_group].vid = new_vid;
status->vlan[vid_group].VFH.Bits.M = (1 << status->cpu_port) | (1 << port);
status->vlan[vid_group].active = 1;
status->vlan[vid_group].written = 0;
mdio->cpmac_priv->map_in[new_vid].port = port;
mdio->cpmac_priv->map_in[new_vid].used = 1;
mdio->cpmac_priv->map_port_out[port] = new_vid;
DEB_INFOTRC("Configure: Setting outgoing VLAN ID for port %u to %#x, target mask %#x\n",
port,
new_vid,
status->vlan[vid_group].VFH.Bits.M);
}
}
}
status->port[status->cpu_port].keep_tag_outgoing = 1;
status->port[status->cpu_port].written = 0;
mdio->cpmac_priv->wanport = mdio->switch_config[config->cpmac_mode].wanport;
mdio->cpmac_priv->wanport_keeptags = 0;
if(config->wan_vlan_number) {
assert(mdio->cpmac_priv->wanport < AVM_CPMAC_MAX_PORTS);
status->port[mdio->cpmac_priv->wanport].keep_tag_outgoing = 1;
mdio->wan_tagging_enable = 1;
mdio->cpmac_priv->wanport_keeptags = 1;
DEB_INFOTRC("[adm_configure_cpmac] Configure: Keep tags on wanport\n");
}
if(mdio->cpmac_switch == AVM_CPMAC_SWITCH_ADM6996) {
/* Configure tag shift */
unsigned short RegData = ADM_GET_EEPROM_REG(mdio, REG_ADM_LC_RACP);
RegData &= ~ADM_CONF_RACP_TAG_SHIFT;
RegData |= (ADM_6996_TAGSHIFT << ADM_CONF_RACP_TAG_SHIFT_SHIFT);
ADM_PUT_EEPROM_REG(mdio, REG_ADM_LC_RACP, RegData);
}
# ifdef CONFIG_IP_MULTICAST_FASTFORWARD
for(vid_group = 0; vid_group < AVM_CPMAC_MAX_VLAN_GROUPS; vid_group++) {
mcfw_portset *setp = &mdio->cpmac_priv->map_vid_portset[vid_group];
mcfw_portset_reset(setp);
for(port = 0; port < 4; port++) {
if(port == mdio->cpmac_priv->wanport)
continue;
if(mdio->cpmac_switch == AVM_CPMAC_SWITCH_TANTOS) {
if((1 << port) & status->vlan[vid_group].VFH.Bits.M)
mcfw_portset_port_add(setp, port);
} else {
if((1 << port) & status->vlan[vid_group].VFH.Bits.M)
mcfw_portset_port_add(setp, port);
}
}
}
# endif
/* Now register the devices with the corresponding VIDs */
for(device = 0; device < mdio->cpmac_priv->devices; device++) {
for(vid_group = 0; vid_group < AVM_CPMAC_MAX_VLAN_GROUPS; vid_group++) {
if(mdio->switch_config[config->cpmac_mode].device[device].target_mask == status->vlan[vid_group].VFH.Bits.M) {
unsigned int predefined_vid = 0;
if(mdio->cpmac_priv->devs[device])
continue;
if(mdio->cpmac_switch == AVM_CPMAC_SWITCH_TANTOS) {
unsigned short vid_group2;
for(vid_group2 = 0; vid_group2 < AVM_CPMAC_MAX_VLAN_GROUPS; vid_group2++) {
if(mdio->switch_config[config->cpmac_mode].device[device].target_mask == status->vlan[vid_group2].VFH.Bits.M) {
vid = status->vlan[vid_group2].VFL.Bits.VID;
}
}
} else {
vid = status->vlan[vid_group].vid;
}
for(i = 0; i < config->wan_vlan_number; i++) {
if(vid == config->wan_vlan_id[i]) {
predefined_vid = 1;
}
}
if(predefined_vid) {
continue; /* Next vid_group */
}
if(vid == status->port[status->cpu_port].vid) {
/* Default VID of CPU port, no tagging needed */
DEB_INFOTRC("[adm_configure_cpmac] No extra tagging needed for device '%s'\n",
mdio->switch_config[config->cpmac_mode].device[device].name);
vid = 0;
}
mdio->cpmac_priv->devs[device] = find_or_register_device(
mdio->switch_config[config->cpmac_mode].device[device].name,
mdio->cpmac_priv,
vid);
assert(mdio->cpmac_priv->devs[device] != NULL);
}
}
/* Calculate mask for LINKUP bits in status register */
mdio->cpmac_priv->dev_ports[device] = mdio->switch_config[config->cpmac_mode].device[device].target_mask;
}
for(i = 0; i < AVM_CPMAC_MAX_DEVS; i++) {
struct net_device *dev = mdio->cpmac_priv->olddevs[i];
if(dev) {
DEB_INFO("Unregister %s\n", dev->name);
mdio->cpmac_priv->olddevs[i] = 0;
cpmac_unregister_device(dev);
}
}
DEB_TRC("Possible unregister done\n");
for(vid_group = 0; vid_group < AVM_CPMAC_MAX_VLAN_GROUPS; vid_group++) {
unsigned short vid;
unsigned char port, devoffset;
struct net_device *dev;
if(!status->vlan[vid_group].active) {
DEB_INFOTRC("Configure: VID group %#x inactive\n", vid_group);
continue;
}
vid = status->vlan[vid_group].VFL.Bits.VID;
assert(vid < 4096); /* To ease Klocwork checking */
port = mdio->cpmac_priv->map_in[vid].port;
assert(port < AVM_CPMAC_MAX_PORTS);
devoffset = mdio->cpmac_priv->map_in_port_dev[port].dev;
assert(devoffset < AVM_CPMAC_MAX_DEVS); /* To ease Klocwork checking */
dev = mdio->cpmac_priv->map_in_port_dev[port].used ? mdio->cpmac_priv->devs[devoffset]
: NULL;
dev = dev; /* Avoid warning, if DEB_TRC is defined to an empty command */
DEB_INFOTRC("Configure: VID group %#x with VID %#x mapped to port %u attached to %s\n",
vid_group,
vid,
mdio->cpmac_priv->map_in[vid].port,
dev ? dev->name : "no device");
}
adm_vlan_config(mdio);
/*--- switch_dump(mdio); ---*/
# if 0
{
unsigned int i;
for(i = 0x01; i < 0x01 + 9; i++) PRINT_REGISTER(i);
for(i = 0x13; i < 0x13 + 16; i++) PRINT_REGISTER(i);
for(i = 0x28; i <= 0x2c; i++) PRINT_REGISTER(i);
for(i = 0x40; i < 0x40 + 32; i++) PRINT_REGISTER(i);
}
# endif /* #if 0 */
/* Special treatment for fifth to seventh port */ /* TODO Is there a cleaner way for this? */
assert(mdio->switch_ports <= AVM_CPMAC_MAX_PORTS);
for(port = 4; port < mdio->switch_ports; port++) {
if(used_ports & (1 << port)) {
mdio->adm_power.setup.mode[port] = ADM_PHY_POWER_ON;
} else {
mdio->adm_power.setup.mode[port] = ADM_PHY_POWER_OFF;
}
}
roundrobin = mdio->adm_power.roundrobin;
mdio->adm_power.roundrobin = 0;
cpphy_mgmt_power(mdio);
adm_switch_port_power_config(mdio, &mdio->adm_power.setup, 1);
mdio->adm_power.roundrobin = roundrobin;
adm_check_link(mdio); /* Make sure that the sub devices have the correct link status */
tasklet_enable(&mdio->cpmac_priv->tasklet);
cpphy_mgmt_work_start(mdio);
mdio->global_power = CPPHY_POWER_GLOBAL_ON;
return CPMAC_ERR_NOERR;
}
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
cpmac_err_t adm_configure_special(cpphy_mdio_t *mdio, cpmac_switch_configuration_t *config) {
memcpy((unsigned char *) &mdio->switch_config[CPMAC_MODE_SPECIAL], config, sizeof(cpmac_switch_configuration_t));
return CPMAC_ERR_NOERR;
}
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
cpmac_err_t adm_get_configured(cpphy_mdio_t *mdio, cpmac_switch_configuration_t *config) {
memcpy(config,
&mdio->switch_config[mdio->cpmac_config.cpmac_mode],
sizeof(cpmac_switch_configuration_t));
return CPMAC_ERR_NOERR;
}
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
cpmac_err_t adm_get_config_cpmac(cpphy_mdio_t *mdio, struct avm_cpmac_config_struct *config) {
memcpy(config, &mdio->cpmac_config, sizeof(struct avm_cpmac_config_struct));
return CPMAC_ERR_NOERR;
}