From 5c105d9f3fd086aff195d3849dcf847d6b0bd927 Mon Sep 17 00:00:00 2001 From: blogic Date: Fri, 5 Oct 2012 10:12:53 +0000 Subject: branch Attitude Adjustment git-svn-id: svn://svn.openwrt.org/openwrt/branches/attitude_adjustment@33625 3c298f89-4303-0410-b956-a3cf2f4a3e73 --- .../arch/ubicom32/mach-common/switch-bcm539x.c | 1195 ++++++++++++++++++++ 1 file changed, 1195 insertions(+) create mode 100644 target/linux/ubicom32/files/arch/ubicom32/mach-common/switch-bcm539x.c (limited to 'target/linux/ubicom32/files/arch/ubicom32/mach-common/switch-bcm539x.c') diff --git a/target/linux/ubicom32/files/arch/ubicom32/mach-common/switch-bcm539x.c b/target/linux/ubicom32/files/arch/ubicom32/mach-common/switch-bcm539x.c new file mode 100644 index 000000000..d9eca381a --- /dev/null +++ b/target/linux/ubicom32/files/arch/ubicom32/mach-common/switch-bcm539x.c @@ -0,0 +1,1195 @@ +/* + * arch/ubicom32/mach-common/switch-bcm539x.c + * BCM539X switch driver, SPI mode + * + * (C) Copyright 2009, Ubicom, Inc. + * + * This file is part of the Ubicom32 Linux Kernel Port. + * + * The Ubicom32 Linux Kernel Port is free software: you can redistribute + * it and/or modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 2 of the + * License, or (at your option) any later version. + * + * The Ubicom32 Linux Kernel Port 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 the Ubicom32 Linux Kernel Port. If not, + * see . + * + * Ubicom32 implementation derived from (with many thanks): + * arch/m68knommu + * arch/blackfin + * arch/parisc + */ + +#include +#include +#include +#include +#include + +#include +#include +#include "switch-core.h" +#include "switch-bcm539x-reg.h" + +#define DRIVER_NAME "bcm539x-spi" +#define DRIVER_VERSION "1.0" + +#undef BCM539X_DEBUG +#define BCM539X_SPI_RETRIES 100 + +struct bcm539x_data { + struct switch_device *switch_dev; + + /* + * Our private data + */ + struct spi_device *spi; + struct switch_core_platform_data *pdata; + + /* + * Last page we accessed + */ + u8_t last_page; + + /* + * 539x Device ID + */ + u8_t device_id; +}; + +/* + * bcm539x_wait_status + * Waits for the specified bit in the status register to be set/cleared. + */ +static int bcm539x_wait_status(struct bcm539x_data *bd, u8_t mask, int set) +{ + u8_t txbuf[2]; + u8_t rxbuf; + int i; + int ret; + + txbuf[0] = BCM539X_CMD_READ; + txbuf[1] = BCM539X_GLOBAL_SPI_STATUS; + for (i = 0; i < BCM539X_SPI_RETRIES; i++) { + ret = spi_write_then_read(bd->spi, txbuf, 2, &rxbuf, 1); + rxbuf &= mask; + if ((set && rxbuf) || (!set && !rxbuf)) { + return 0; + } + udelay(1); + } + + return -EIO; +} + +/* + * bcm539x_set_page + * Sets the register page for access (only if necessary) + */ +static int bcm539x_set_page(struct bcm539x_data *bd, u8_t page) +{ + u8_t txbuf[3]; + + if (page == bd->last_page) { + return 0; + } + + bd->last_page = page; + + txbuf[0] = BCM539X_CMD_WRITE; + txbuf[1] = BCM539X_GLOBAL_PAGE; + txbuf[2] = page; + + return spi_write(bd->spi, txbuf, 3); +} + +/* + * bcm539x_write_bytes + * Writes a number of bytes to a given page and register + */ +static int bcm539x_write_bytes(struct bcm539x_data *bd, u8_t page, + u8_t reg, void *buf, u8_t len) +{ + int ret; + u8_t *txbuf; + + txbuf = kmalloc(2 + len, GFP_KERNEL); + if (!txbuf) { + return -ENOMEM; + } + + /* + * Make sure the chip has finished processing our previous request + */ + ret = bcm539x_wait_status(bd, BCM539X_GLOBAL_SPI_ST_SPIF, 0); + if (ret) { + goto done; + } + + /* + * Set the page + */ + ret = bcm539x_set_page(bd, page); + if (ret) { + goto done; + } + + /* + * Read the data + */ + txbuf[0] = BCM539X_CMD_WRITE; + txbuf[1] = reg; + memcpy(&txbuf[2], buf, len); + +#ifdef BCM539X_DEBUG + { + int i; + printk("write page %02x reg %02x len=%d buf=", page, reg, len); + for (i = 0; i < len + 2; i++) { + printk("%02x ", txbuf[i]); + } + printk("\n"); + } +#endif + + ret = spi_write(bd->spi, txbuf, 2 + len); + +done: + kfree(txbuf); + return ret; +} + +/* + * bcm539x_write_32 + * Writes 32 bits of data to the given page and register + */ +static inline int bcm539x_write_32(struct bcm539x_data *bd, u8_t page, + u8_t reg, u32_t data) +{ + data = cpu_to_le32(data); + return bcm539x_write_bytes(bd, page, reg, &data, 4); +} + +/* + * bcm539x_write_16 + * Writes 16 bits of data to the given page and register + */ +static inline int bcm539x_write_16(struct bcm539x_data *bd, u8_t page, + u8_t reg, u16_t data) +{ + data = cpu_to_le16(data); + return bcm539x_write_bytes(bd, page, reg, &data, 2); +} + +/* + * bcm539x_write_8 + * Writes 8 bits of data to the given page and register + */ +static inline int bcm539x_write_8(struct bcm539x_data *bd, u8_t page, + u8_t reg, u8_t data) +{ + return bcm539x_write_bytes(bd, page, reg, &data, 1); +} + +/* + * bcm539x_read_bytes + * Reads a number of bytes from a given page and register + */ +static int bcm539x_read_bytes(struct bcm539x_data *bd, u8_t page, + u8_t reg, void *buf, u8_t len) +{ + u8_t txbuf[2]; + int ret; + + /* + * (1) Make sure the chip has finished processing our previous request + */ + ret = bcm539x_wait_status(bd, BCM539X_GLOBAL_SPI_ST_SPIF, 0); + if (ret) { + return ret; + } + + /* + * (2) Set the page + */ + ret = bcm539x_set_page(bd, page); + if (ret) { + return ret; + } + + /* + * (3) Kick off the register read + */ + txbuf[0] = BCM539X_CMD_READ; + txbuf[1] = reg; + ret = spi_write_then_read(bd->spi, txbuf, 2, txbuf, 1); + if (ret) { + return ret; + } + + /* + * (4) Wait for RACK + */ + ret = bcm539x_wait_status(bd, BCM539X_GLOBAL_SPI_ST_RACK, 1); + if (ret) { + return ret; + } + + /* + * (5) Read the data + */ + txbuf[0] = BCM539X_CMD_READ; + txbuf[1] = BCM539X_GLOBAL_SPI_DATA0; + + ret = spi_write_then_read(bd->spi, txbuf, 2, buf, len); + +#ifdef BCM539X_DEBUG + { + int i; + printk("read page %02x reg %02x len=%d rxbuf=", + page, reg, len); + for (i = 0; i < len; i++) { + printk("%02x ", ((u8_t *)buf)[i]); + } + printk("\n"); + } +#endif + + return ret; +} + +/* + * bcm539x_read_32 + * Reads an 32 bit number from a given page and register + */ +static int bcm539x_read_32(struct bcm539x_data *bd, u8_t page, + u8_t reg, u32_t *buf) +{ + int ret = bcm539x_read_bytes(bd, page, reg, buf, 4); + *buf = le32_to_cpu(*buf); + return ret; +} + +/* + * bcm539x_read_16 + * Reads an 16 bit number from a given page and register + */ +static int bcm539x_read_16(struct bcm539x_data *bd, u8_t page, + u8_t reg, u16_t *buf) +{ + int ret = bcm539x_read_bytes(bd, page, reg, buf, 2); + *buf = le16_to_cpu(*buf); + return ret; +} + +/* + * bcm539x_read_8 + * Reads an 8 bit number from a given page and register + */ +static int bcm539x_read_8(struct bcm539x_data *bd, u8_t page, + u8_t reg, u8_t *buf) +{ + return bcm539x_read_bytes(bd, page, reg, buf, 1); +} + +/* + * bcm539x_set_mode + */ +static int bcm539x_set_mode(struct bcm539x_data *bd, int state) +{ + u8_t buf; + int ret; + + ret = bcm539x_read_8(bd, PAGE_PORT_TC, REG_CTRL_MODE, &buf); + if (ret) { + return ret; + } + + buf &= ~(1 << 1); + buf |= state ? (1 << 1) : 0; + + ret = bcm539x_write_8(bd, PAGE_PORT_TC, REG_CTRL_MODE, buf); + return ret; +} + +/* + * bcm539x_handle_reset + */ +static int bcm539x_handle_reset(struct switch_device *dev, char *buf, int inst) +{ + struct bcm539x_data *bd = + (struct bcm539x_data *)switch_get_drvdata(dev); + int ret; + + ret = bcm539x_write_8(bd, PAGE_PORT_TC, REG_CTRL_SRST, + (1 << 7) | (1 << 4)); + if (ret) { + return ret; + } + + udelay(20); + + ret = bcm539x_write_8(bd, PAGE_PORT_TC, REG_CTRL_SRST, 0); + return ret; +} + +/* + * bcm539x_handle_vlan_ports_read + */ +static int bcm539x_handle_vlan_ports_read(struct switch_device *dev, + char *buf, int inst) +{ + struct bcm539x_data *bd = + (struct bcm539x_data *)switch_get_drvdata(dev); + int j; + int len = 0; + u8_t rxbuf8; + u32_t rxbuf32; + int ret; + + ret = bcm539x_write_16(bd, PAGE_VTBL, REG_VTBL_INDX_5395, inst); + if (ret) { + return ret; + } + + ret = bcm539x_write_8(bd, PAGE_VTBL, REG_VTBL_ACCESS_5395, + (1 << 7) | 1); + if (ret) { + return ret; + } + + /* + * Wait for completion + */ + for (j = 0; j < BCM539X_SPI_RETRIES; j++) { + ret = bcm539x_read_8(bd, PAGE_VTBL, REG_VTBL_ACCESS_5395, + &rxbuf8); + if (ret) { + return ret; + } + if (!(rxbuf8 & (1 << 7))) { + break; + } + } + + if (j == BCM539X_SPI_RETRIES) { + return -EIO; + } + + /* + * Read the table entry + */ + ret = bcm539x_read_32(bd, PAGE_VTBL, REG_VTBL_ENTRY_5395, &rxbuf32); + if (ret) { + return ret; + } + + for (j = 0; j < 9; j++) { + if (rxbuf32 & (1 << j)) { + u16_t rxbuf16; + len += sprintf(buf + len, "%d", j); + if (rxbuf32 & (1 << (j + 9))) { + buf[len++] = 'u'; + } else { + buf[len++] = 't'; + } + ret = bcm539x_read_16(bd, PAGE_VLAN, + REG_VLAN_PTAG0 + (j << 1), + &rxbuf16); + if (ret) { + return ret; + } + if (rxbuf16 == inst) { + buf[len++] = '*'; + } + buf[len++] = '\t'; + } + } + + len += sprintf(buf + len, "\n"); + buf[len] = '\0'; + + return len; +} + +/* + * bcm539x_handle_vlan_ports_write + */ +static int bcm539x_handle_vlan_ports_write(struct switch_device *dev, + char *buf, int inst) +{ + struct bcm539x_data *bd = + (struct bcm539x_data *)switch_get_drvdata(dev); + int j; + u32_t untag; + u32_t ports; + u32_t def; + + u8_t rxbuf8; + u16_t rxbuf16; + int ret; + + switch_parse_vlan_ports(dev, buf, &untag, &ports, &def); + +#ifdef BCM539X_DEBUG + printk(KERN_DEBUG "'%s' inst=%d untag=%08x ports=%08x def=%08x\n", + buf, inst, untag, ports, def); +#endif + + if (!ports) { + return 0; + } + + /* + * Change default vlan tag + */ + for (j = 0; j < 9; j++) { + if ((untag | def) & (1 << j)) { + ret = bcm539x_write_16(bd, PAGE_VLAN, + REG_VLAN_PTAG0 + (j << 1), + inst); + if (ret) { + return ret; + } + continue; + } + + if (!(dev->port_mask[0] & (1 << j))) { + continue; + } + + /* + * Remove any ports which are not listed anymore as members of + * this vlan + */ + ret = bcm539x_read_16(bd, PAGE_VLAN, + REG_VLAN_PTAG0 + (j << 1), &rxbuf16); + if (ret) { + return ret; + } + if (rxbuf16 == inst) { + ret = bcm539x_write_16(bd, PAGE_VLAN, + REG_VLAN_PTAG0 + (j << 1), 0); + if (ret) { + return ret; + } + } + } + + /* + * Write the VLAN table + */ + ret = bcm539x_write_16(bd, PAGE_VTBL, REG_VTBL_INDX_5395, inst); + if (ret) { + return ret; + } + + ret = bcm539x_write_32(bd, PAGE_VTBL, REG_VTBL_ENTRY_5395, + (untag << 9) | ports); + if (ret) { + return ret; + } + + ret = bcm539x_write_8(bd, PAGE_VTBL, REG_VTBL_ACCESS_5395, + (1 << 7) | 0); + if (ret) { + return ret; + } + + /* + * Wait for completion + */ + for (j = 0; j < BCM539X_SPI_RETRIES; j++) { + ret = bcm539x_read_bytes(bd, PAGE_VTBL, REG_VTBL_ACCESS_5395, + &rxbuf8, 1); + if (ret) { + return ret; + } + if (!(rxbuf8 & (1 << 7))) { + break; + } + } + + return (j < BCM539X_SPI_RETRIES) ? 0 : -EIO; +} + +/* + * Handlers for /vlan/ + */ +static const struct switch_handler bcm539x_switch_handlers_vlan_dir[] = { + { + .name = "ports", + .read = bcm539x_handle_vlan_ports_read, + .write = bcm539x_handle_vlan_ports_write, + }, + { + }, +}; + +/* + * bcm539x_handle_vlan_delete_write + */ +static int bcm539x_handle_vlan_delete_write(struct switch_device *dev, + char *buf, int inst) +{ + struct bcm539x_data *bd = + (struct bcm539x_data *)switch_get_drvdata(dev); + int vid; + u8_t rxbuf8; + u32_t txbuf; + int j; + int ret; + + vid = simple_strtoul(buf, NULL, 0); + if (!vid) { + return -EINVAL; + } + + /* + * Disable this VLAN + * + * Go through the port-based vlan registers and clear the appropriate + * ones out + */ + for (j = 0; j < 9; j++) { + u16_t rxbuf16; + ret = bcm539x_read_16(bd, PAGE_VLAN, REG_VLAN_PTAG0 + (j << 1), + &rxbuf16); + if (ret) { + return ret; + } + if (rxbuf16 == vid) { + txbuf = 0; + ret = bcm539x_write_16(bd, PAGE_VLAN, + REG_VLAN_PTAG0 + (j << 1), + txbuf); + if (ret) { + return ret; + } + } + } + + /* + * Write the VLAN table + */ + txbuf = vid; + ret = bcm539x_write_16(bd, PAGE_VTBL, REG_VTBL_INDX_5395, txbuf); + if (ret) { + return ret; + } + + txbuf = 0; + ret = bcm539x_write_32(bd, PAGE_VTBL, REG_VTBL_ENTRY_5395, txbuf); + if (ret) { + return ret; + } + + txbuf = (1 << 7) | (0); + ret = bcm539x_write_8(bd, PAGE_VTBL, REG_VTBL_ACCESS_5395, txbuf); + if (ret) { + return ret; + } + + /* + * Wait for completion + */ + for (j = 0; j < BCM539X_SPI_RETRIES; j++) { + ret = bcm539x_read_bytes(bd, PAGE_VTBL, REG_VTBL_ACCESS_5395, + &rxbuf8, 1); + if (ret) { + return ret; + } + if (!(rxbuf8 & (1 << 7))) { + break; + } + } + + if (j == BCM539X_SPI_RETRIES) { + return -EIO; + } + + return switch_remove_vlan_dir(dev, vid); +} + +/* + * bcm539x_handle_vlan_create_write + */ +static int bcm539x_handle_vlan_create_write(struct switch_device *dev, + char *buf, int inst) +{ + int vid; + + vid = simple_strtoul(buf, NULL, 0); + if (!vid) { + return -EINVAL; + } + + return switch_create_vlan_dir(dev, vid, + bcm539x_switch_handlers_vlan_dir); +} + +/* + * bcm539x_handle_enable_read + */ +static int bcm539x_handle_enable_read(struct switch_device *dev, + char *buf, int inst) +{ + struct bcm539x_data *bd = + (struct bcm539x_data *)switch_get_drvdata(dev); + u8_t rxbuf; + int ret; + + ret = bcm539x_read_8(bd, PAGE_PORT_TC, REG_CTRL_MODE, &rxbuf); + if (ret) { + return ret; + } + rxbuf = (rxbuf & (1 << 1)) ? 1 : 0; + + return sprintf(buf, "%d\n", rxbuf); +} + +/* + * bcm539x_handle_enable_write + */ +static int bcm539x_handle_enable_write(struct switch_device *dev, + char *buf, int inst) +{ + struct bcm539x_data *bd = + (struct bcm539x_data *)switch_get_drvdata(dev); + + return bcm539x_set_mode(bd, buf[0] == '1'); +} + +/* + * bcm539x_handle_enable_vlan_read + */ +static int bcm539x_handle_enable_vlan_read(struct switch_device *dev, + char *buf, int inst) +{ + struct bcm539x_data *bd = + (struct bcm539x_data *)switch_get_drvdata(dev); + u8_t rxbuf; + int ret; + + ret = bcm539x_read_8(bd, PAGE_VLAN, REG_VLAN_CTRL0, &rxbuf); + if (ret) { + return ret; + } + rxbuf = (rxbuf & (1 << 7)) ? 1 : 0; + + return sprintf(buf, "%d\n", rxbuf); +} + +/* + * bcm539x_handle_enable_vlan_write + */ +static int bcm539x_handle_enable_vlan_write(struct switch_device *dev, + char *buf, int inst) +{ + struct bcm539x_data *bd = + (struct bcm539x_data *)switch_get_drvdata(dev); + int ret; + + /* + * disable 802.1Q VLANs + */ + if (buf[0] != '1') { + ret = bcm539x_write_8(bd, PAGE_VLAN, REG_VLAN_CTRL0, 0); + return ret; + } + + /* + * enable 802.1Q VLANs + * + * Enable 802.1Q | IVL learning + */ + ret = bcm539x_write_8(bd, PAGE_VLAN, REG_VLAN_CTRL0, + (1 << 7) | (3 << 5)); + if (ret) { + return ret; + } + + /* + * RSV multicast fwd | RSV multicast chk + */ + ret = bcm539x_write_8(bd, PAGE_VLAN, REG_VLAN_CTRL1, + (1 << 2) | (1 << 3)); + if (ret) { + return ret; + } +#if 0 + /* + * Drop invalid VID + */ + ret = bcm539x_write_16(bd, PAGE_VLAN, REG_VLAN_CTRL3, 0x00FF); + if (ret) { + return ret; + } +#endif + return 0; +} + +/* + * bcm539x_handle_port_enable_read + */ +static int bcm539x_handle_port_enable_read(struct switch_device *dev, + char *buf, int inst) +{ + return sprintf(buf, "%d\n", 1); +} + +/* + * bcm539x_handle_port_enable_write + */ +static int bcm539x_handle_port_enable_write(struct switch_device *dev, + char *buf, int inst) +{ + /* + * validate port + */ + if (!(dev->port_mask[0] & (1 << inst))) { + return -EIO; + } + + if (buf[0] != '1') { + printk(KERN_WARNING "switch port[%d] disabling is not supported\n", inst); + } + return 0; +} + +/* + * bcm539x_handle_port_state_read + */ +static int bcm539x_handle_port_state_read(struct switch_device *dev, + char *buf, int inst) +{ + struct bcm539x_data *bd = + (struct bcm539x_data *)switch_get_drvdata(dev); + int ret; + u16_t link; + + /* + * validate port + */ + if (!(dev->port_mask[0] & (1 << inst))) { + return -EIO; + } + + /* + * check PHY link state - CPU port (port 8) is always up + */ + ret = bcm539x_read_16(bd, PAGE_STATUS, REG_LINK_STATUS, &link); + if (ret) { + return ret; + } + link |= (1 << 8); + + return sprintf(buf, "%d\n", (link & (1 << inst)) ? 1 : 0); +} + +/* + * bcm539x_handle_port_media_read + */ +static int bcm539x_handle_port_media_read(struct switch_device *dev, + char *buf, int inst) +{ + struct bcm539x_data *bd = + (struct bcm539x_data *)switch_get_drvdata(dev); + int ret; + u16_t link, duplex; + u32_t speed; + + /* + * validate port + */ + if (!(dev->port_mask[0] & (1 << inst))) { + return -EIO; + } + + /* + * check PHY link state first - CPU port (port 8) is always up + */ + ret = bcm539x_read_16(bd, PAGE_STATUS, REG_LINK_STATUS, &link); + if (ret) { + return ret; + } + link |= (1 << 8); + + if (!(link & (1 << inst))) { + return sprintf(buf, "UNKNOWN\n"); + } + + /* + * get link speeda dn duplex - CPU port (port 8) is 1000/full + */ + ret = bcm539x_read_32(bd, PAGE_STATUS, 4, &speed); + if (ret) { + return ret; + } + speed |= (2 << 16); + speed = (speed >> (2 * inst)) & 3; + + ret = bcm539x_read_16(bd, PAGE_STATUS, 8, &duplex); + if (ret) { + return ret; + } + duplex |= (1 << 8); + duplex = (duplex >> inst) & 1; + + return sprintf(buf, "%d%cD\n", + (speed == 0) ? 10 : ((speed == 1) ? 100 : 1000), + duplex ? 'F' : 'H'); +} + +/* + * bcm539x_handle_port_meida_write + */ +static int bcm539x_handle_port_meida_write(struct switch_device *dev, + char *buf, int inst) +{ + struct bcm539x_data *bd = + (struct bcm539x_data *)switch_get_drvdata(dev); + int ret; + u16_t ctrl_word, local_cap, local_giga_cap; + + /* + * validate port (not for CPU port) + */ + if (!(dev->port_mask[0] & (1 << inst) & ~(1 << 8))) { + return -EIO; + } + + /* + * Get the maximum capability from status + * SPI reg[0x00] = PHY[0x0] --- MII control + * SPI reg[0x08] = PHY[0x4] --- MII local capability + * SPI reg[0x12] = PHY[0x9] --- GMII control + */ + ret = bcm539x_read_16(bd, REG_MII_PAGE + inst, (MII_ADVERTISE << 1), &local_cap); + if (ret) { + return ret; + } + ret = bcm539x_read_16(bd, REG_MII_PAGE + inst, (MII_CTRL1000 << 1), &local_giga_cap); + if (ret) { + return ret; + } + + /* Configure to the requested speed */ + if (strncmp(buf, "1000FD", 6) == 0) { + /* speed */ + local_cap &= ~(ADVERTISE_10HALF | ADVERTISE_10FULL); + local_cap &= ~(ADVERTISE_100HALF | ADVERTISE_100FULL); + local_giga_cap |= (ADVERTISE_1000HALF | ADVERTISE_1000FULL); + /* duplex */ + } else if (strncmp(buf, "100FD", 5) == 0) { + /* speed */ + local_cap &= ~(ADVERTISE_10HALF | ADVERTISE_10FULL); + local_cap |= (ADVERTISE_100HALF | ADVERTISE_100FULL); + local_giga_cap &= ~(ADVERTISE_1000HALF | ADVERTISE_1000FULL); + /* duplex */ + local_cap &= ~(ADVERTISE_100HALF); + } else if (strncmp(buf, "100HD", 5) == 0) { + /* speed */ + local_cap &= ~(ADVERTISE_10HALF | ADVERTISE_10FULL); + local_cap |= (ADVERTISE_100HALF | ADVERTISE_100FULL); + local_giga_cap &= ~(ADVERTISE_1000HALF | ADVERTISE_1000FULL); + /* duplex */ + local_cap &= ~(ADVERTISE_100FULL); + } else if (strncmp(buf, "10FD", 4) == 0) { + /* speed */ + local_cap |= (ADVERTISE_10HALF | ADVERTISE_10FULL); + local_cap &= ~(ADVERTISE_100HALF | ADVERTISE_100FULL); + local_giga_cap &= ~(ADVERTISE_1000HALF | ADVERTISE_1000FULL); + /* duplex */ + local_cap &= ~(ADVERTISE_10HALF); + } else if (strncmp(buf, "10HD", 4) == 0) { + /* speed */ + local_cap |= (ADVERTISE_10HALF | ADVERTISE_10FULL); + local_cap &= ~(ADVERTISE_100HALF | ADVERTISE_100FULL); + local_giga_cap &= ~(ADVERTISE_1000HALF | ADVERTISE_1000FULL); + /* duplex */ + local_cap &= ~(ADVERTISE_10FULL); + } else if (strncmp(buf, "AUTO", 4) == 0) { + /* speed */ + local_cap |= (ADVERTISE_10HALF | ADVERTISE_10FULL); + local_cap |= (ADVERTISE_100HALF | ADVERTISE_100FULL); + local_giga_cap |= (ADVERTISE_1000HALF | ADVERTISE_1000FULL); + } else { + return -EINVAL; + } + + /* Active PHY with the requested speed for auto-negotiation */ + ret = bcm539x_write_16(bd, REG_MII_PAGE + inst, (MII_ADVERTISE << 1), local_cap); + if (ret) { + return ret; + } + ret = bcm539x_write_16(bd, REG_MII_PAGE + inst, (MII_CTRL1000 << 1), local_giga_cap); + if (ret) { + return ret; + } + + ret = bcm539x_read_16(bd, REG_MII_PAGE + inst, (MII_BMCR << 1), &ctrl_word); + if (ret) { + return ret; + } + ctrl_word |= (BMCR_ANENABLE | BMCR_ANRESTART); + ret = bcm539x_write_16(bd, REG_MII_PAGE + inst, (MII_BMCR << 1), ctrl_word); + if (ret) { + return ret; + } + + return 0; +} + +/* + * proc_fs entries for this switch + */ +static const struct switch_handler bcm539x_switch_handlers[] = { + { + .name = "enable", + .read = bcm539x_handle_enable_read, + .write = bcm539x_handle_enable_write, + }, + { + .name = "enable_vlan", + .read = bcm539x_handle_enable_vlan_read, + .write = bcm539x_handle_enable_vlan_write, + }, + { + .name = "reset", + .write = bcm539x_handle_reset, + }, + { + }, +}; + +/* + * Handlers for /vlan + */ +static const struct switch_handler bcm539x_switch_handlers_vlan[] = { + { + .name = "delete", + .write = bcm539x_handle_vlan_delete_write, + }, + { + .name = "create", + .write = bcm539x_handle_vlan_create_write, + }, + { + }, +}; + +/* + * Handlers for /port/ + */ +static const struct switch_handler bcm539x_switch_handlers_port[] = { + { + .name = "enable", + .read = bcm539x_handle_port_enable_read, + .write = bcm539x_handle_port_enable_write, + }, + { + .name = "state", + .read = bcm539x_handle_port_state_read, + }, + { + .name = "media", + .read = bcm539x_handle_port_media_read, + .write = bcm539x_handle_port_meida_write, + }, + { + }, +}; + +/* + * bcm539x_probe + */ +static int __devinit bcm539x_probe(struct spi_device *spi) +{ + struct bcm539x_data *bd; + struct switch_core_platform_data *pdata; + struct switch_device *switch_dev = NULL; + int i, ret; + u8_t txbuf[2]; + + pdata = spi->dev.platform_data; + if (!pdata) { + return -EINVAL; + } + + ret = spi_setup(spi); + if (ret < 0) { + return ret; + } + + /* + * Reset the chip if requested + */ + if (pdata->flags & SWITCH_DEV_FLAG_HW_RESET) { + ret = gpio_request(pdata->pin_reset, "switch-bcm539x-reset"); + if (ret) { + printk(KERN_WARNING "Could not request reset\n"); + return -EINVAL; + } + + gpio_direction_output(pdata->pin_reset, 0); + udelay(10); + gpio_set_value(pdata->pin_reset, 1); + udelay(20); + } + + /* + * Allocate our private data structure + */ + bd = kzalloc(sizeof(struct bcm539x_data), GFP_KERNEL); + if (!bd) { + return -ENOMEM; + } + + dev_set_drvdata(&spi->dev, bd); + bd->pdata = pdata; + bd->spi = spi; + bd->last_page = 0xFF; + + /* + * First perform SW reset if needed + */ + if (pdata->flags & SWITCH_DEV_FLAG_SW_RESET) { + txbuf[0] = (1 << 7) | (1 << 4); + ret = bcm539x_write_bytes(bd, PAGE_PORT_TC, + REG_CTRL_SRST, txbuf, 1); + if (ret) { + goto fail; + } + + udelay(20); + + txbuf[0] = 0; + ret = bcm539x_write_bytes(bd, PAGE_PORT_TC, + REG_CTRL_SRST, txbuf, 1); + if (ret) { + goto fail; + } + } + + /* + * See if we can see the chip + */ + for (i = 0; i < 10; i++) { + ret = bcm539x_read_bytes(bd, PAGE_MMR, REG_DEVICE_ID, + &bd->device_id, 1); + if (!ret) { + break; + } + } + if (ret) { + goto fail; + } + + /* + * We only support 5395, 5397, 5398 + */ + if ((bd->device_id != 0x95) && (bd->device_id != 0x97) && + (bd->device_id != 0x98)) { + ret = -ENODEV; + goto fail; + } + + /* + * Override CPU port config: fixed link @1000 with flow control + */ + ret = bcm539x_read_8(bd, PAGE_PORT_TC, REG_CTRL_MIIPO, txbuf); + bcm539x_write_8(bd, PAGE_PORT_TC, REG_CTRL_MIIPO, 0xbb); // Override IMP port config + printk("Broadcom SW CPU port setting: 0x%x -> 0xbb\n", txbuf[0]); + + /* + * Setup the switch driver structure + */ + switch_dev = switch_alloc(); + if (!switch_dev) { + ret = -ENOMEM; + goto fail; + } + switch_dev->name = pdata->name; + + switch_dev->ports = (bd->device_id == 0x98) ? 9 : 6; + switch_dev->port_mask[0] = (bd->device_id == 0x98) ? 0x1FF : 0x11F; + switch_dev->driver_handlers = bcm539x_switch_handlers; + switch_dev->reg_handlers = NULL; + switch_dev->vlan_handlers = bcm539x_switch_handlers_vlan; + switch_dev->port_handlers = bcm539x_switch_handlers_port; + + bd->switch_dev = switch_dev; + switch_set_drvdata(switch_dev, (void *)bd); + + ret = switch_register(bd->switch_dev); + if (ret < 0) { + goto fail; + } + + printk(KERN_INFO "bcm53%02x switch chip initialized\n", bd->device_id); + + return ret; + +fail: + if (switch_dev) { + switch_release(switch_dev); + } + dev_set_drvdata(&spi->dev, NULL); + kfree(bd); + return ret; +} + +static int __attribute__((unused)) bcm539x_remove(struct spi_device *spi) +{ + struct bcm539x_data *bd; + + bd = dev_get_drvdata(&spi->dev); + + if (bd->pdata->flags & SWITCH_DEV_FLAG_HW_RESET) { + gpio_free(bd->pdata->pin_reset); + } + + if (bd->switch_dev) { + switch_unregister(bd->switch_dev); + switch_release(bd->switch_dev); + } + + dev_set_drvdata(&spi->dev, NULL); + + kfree(bd); + + return 0; +} + +static struct spi_driver bcm539x_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, + .probe = bcm539x_probe, + .remove = __devexit_p(bcm539x_remove), +}; + +static int __init bcm539x_init(void) +{ + return spi_register_driver(&bcm539x_driver); +} + +module_init(bcm539x_init); + +static void __exit bcm539x_exit(void) +{ + spi_unregister_driver(&bcm539x_driver); +} +module_exit(bcm539x_exit); + +MODULE_AUTHOR("Pat Tjin"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("bcm539x SPI switch chip driver"); -- cgit v1.2.3