mirror of
git://projects.qi-hardware.com/openwrt-xburst.git
synced 2025-04-21 12:27:27 +03:00
git-svn-id: svn://svn.openwrt.org/openwrt/trunk@19815 3c298f89-4303-0410-b956-a3cf2f4a3e73
1196 lines
24 KiB
C
1196 lines
24 KiB
C
/*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Ubicom32 implementation derived from (with many thanks):
|
|
* arch/m68knommu
|
|
* arch/blackfin
|
|
* arch/parisc
|
|
*/
|
|
|
|
#include <linux/platform_device.h>
|
|
#include <linux/spi/spi.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/mii.h>
|
|
|
|
#include <asm/switch-dev.h>
|
|
#include <asm/ubicom32-spi-gpio.h>
|
|
#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 <this_driver>/vlan/<vlan_id>
|
|
*/
|
|
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 <this_driver>/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 <this_driver>/port/<port number>
|
|
*/
|
|
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");
|