/*------------------------------------------------------------------------------------------*\ * Copyright (C) 2006,2007,2008,2009 AVM GmbH * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(CONFIG_AVM_POWER) #include #endif /*--- #if defined(CONFIG_AVM_POWER) ---*/ #if !defined(CONFIG_NETCHIP_ADM69961) #define CONFIG_NETCHIP_ADM69961 #endif #include #include #include #include #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 /*------------------------------------------------------------------------------------------*\ * 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); } } for(port = 0; port < mdio->switch_ports; port++) { if(mdio->cpmac_priv->wanport == port) { DEB_INFOTRC("[%s] Setting default WAN VID to %#x\n", __FUNCTION__, mdio->switch_status.port[port].vid); mdio->cpmac_priv->wanport_default_vid = mdio->switch_status.port[port].vid; } } } /*------------------------------------------------------------------------------------------*\ * 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; unsigned int i, is_wan = (strcmp(name, "wan") == 0) ? 1 : 0; for(i = 0; i < AVM_CPMAC_MAX_DEVS; i++) { dev = cpmac_priv->olddevs[i]; if(dev && strcmp(dev->name, name) == 0) { DEB_INFOTRC("[%s] Update '%s' with VID %#x (%u)\n", __FUNCTION__, name, VID, VID); cpmac_update_device(dev, VID); cpmac_priv->olddevs[i] = 0; return dev; } } if(!is_wan) { DEB_INFOTRC("Device configuration: Connect outgoing '%s' with VID 0x%x\n", name, VID); return cpmac_register_eth_device(name, cpmac_priv->owner, VID); } else { DEB_INFOTRC("Device configuration: Connect outgoing '%s' with VID 0x%x (WAN)\n", name, 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; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ cpmac_err_t adm_add_port_to_wan(cpphy_mdio_t *mdio, unsigned char port, unsigned short vid) { unsigned int group, found = 12345; unsigned char wanport = mdio->switch_config[mdio->cpmac_config.cpmac_mode].wanport; /* Check port */ if(port > 3) { DEB_ERR("[%s] Illegal port %u given\n", __FUNCTION__, port); return CPMAC_ERR_EXCEEDS_LIMIT; } /* This function is only intended for a kind of ATA mode */ if(wanport == 0xff) { DEB_ERR("[%s] No WAN port defined!\n", __FUNCTION__); return CPMAC_ERR_ILL_CONTROL; } /* Find group for this VID */ for(group = 0; group < AVM_CPMAC_MAX_VLAN_GROUPS; group++) { if(!mdio->switch_status.vlan[group].VFL.Bits.VV) continue; /* VID == 0 => Use first VLAN group that includes the WAN port */ if((vid == 0) && (mdio->switch_status.vlan[group].VFH.Bits.M & (1 << wanport))) { vid = mdio->switch_status.vlan[group].VFL.Bits.VID; } if(mdio->switch_status.vlan[group].VFL.Bits.VID != vid) continue; found = group; DEB_TRC("[%s] Found group %u for VID %u\n", __FUNCTION__, group, vid); break; } if(found == 12345) { DEB_ERR("[%s] Unknown VID %u given!\n", __FUNCTION__, vid); return CPMAC_ERR_EXCEEDS_LIMIT; } /* Remove port from generic ethernet VLAN */ for(group = 0; group < AVM_CPMAC_MAX_VLAN_GROUPS; group++) { unsigned int bit, count = 0; /* Only valid groups are interesting */ if(!mdio->switch_status.vlan[group].VFL.Bits.VV) continue; /* Group should include port and exclude wanport */ if( !(mdio->switch_status.vlan[group].VFH.Bits.M & (1 << port)) || (mdio->switch_status.vlan[group].VFH.Bits.M & (1 << wanport))) { continue; } /* Group should include more than two ports */ for(bit = 0; bit < 16; bit++) { if(mdio->switch_status.vlan[group].VFH.Bits.M & (1 << port)) count++; } if(count < 3) continue; /* Remove port from portmasks */ mdio->switch_status.vlan[group].VFH.Bits.M &= ~(1 << port); mdio->switch_status.vlan[group].VFH.Bits.TM &= ~(1 << port); mdio->switch_status.vlan[group].written = 0; DEB_TRC("[%s] VID group %u after port removal: M = %#x, TM = %#x\n", __FUNCTION__, group, mdio->switch_status.vlan[group].VFH.Bits.M, mdio->switch_status.vlan[group].VFH.Bits.TM); } /* Add port to given VID */ mdio->switch_status.vlan[found].VFH.Bits.M |= 1 << port; mdio->switch_status.vlan[found].written = 0; DEB_TRC("[%s] VID group %u after port addition: M = %#x, TM = %#x\n", __FUNCTION__, found, mdio->switch_status.vlan[found].VFH.Bits.M, mdio->switch_status.vlan[found].VFH.Bits.TM); /* Change default VID for the given port */ mdio->switch_status.port[port].written = 0; mdio->switch_status.port[port].vid = vid; mdio->switch_status.port[port].keep_tag_outgoing = 0; DEB_TRC("[%s] Setting default VID %u for port %u\n", __FUNCTION__, vid, port); adm_vlan_config(mdio); return CPMAC_ERR_NOERR; }