/branches/gl-inet/target/linux/generic/files/drivers/net/phy/adm6996.c |
@@ -0,0 +1,1241 @@ |
/* |
* ADM6996 switch driver |
* |
* swconfig interface based on ar8216.c |
* |
* Copyright (c) 2008 Felix Fietkau <nbd@nbd.name> |
* VLAN support Copyright (c) 2010, 2011 Peter Lebbing <peter@digitalbrains.com> |
* Copyright (c) 2013 Hauke Mehrtens <hauke@hauke-m.de> |
* Copyright (c) 2014 Matti Laakso <malaakso@elisanet.fi> |
* |
* This program is free software; you can redistribute it and/or modify it |
* under the terms of the GNU General Public License v2 as published by the |
* Free Software Foundation |
*/ |
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
|
/*#define DEBUG 1*/ |
#include <linux/kernel.h> |
#include <linux/string.h> |
#include <linux/errno.h> |
#include <linux/unistd.h> |
#include <linux/slab.h> |
#include <linux/interrupt.h> |
#include <linux/init.h> |
#include <linux/delay.h> |
#include <linux/gpio.h> |
#include <linux/netdevice.h> |
#include <linux/etherdevice.h> |
#include <linux/skbuff.h> |
#include <linux/spinlock.h> |
#include <linux/mm.h> |
#include <linux/module.h> |
#include <linux/mii.h> |
#include <linux/platform_device.h> |
#include <linux/platform_data/adm6996-gpio.h> |
#include <linux/ethtool.h> |
#include <linux/phy.h> |
#include <linux/switch.h> |
|
#include <asm/io.h> |
#include <asm/irq.h> |
#include <asm/uaccess.h> |
#include "adm6996.h" |
|
MODULE_DESCRIPTION("Infineon ADM6996 Switch"); |
MODULE_AUTHOR("Felix Fietkau, Peter Lebbing <peter@digitalbrains.com>"); |
MODULE_LICENSE("GPL"); |
|
static const char * const adm6996_model_name[] = |
{ |
NULL, |
"ADM6996FC", |
"ADM6996M", |
"ADM6996L" |
}; |
|
struct adm6996_mib_desc { |
unsigned int offset; |
const char *name; |
}; |
|
struct adm6996_priv { |
struct switch_dev dev; |
void *priv; |
|
u8 eecs; |
u8 eesk; |
u8 eedi; |
|
enum adm6996_model model; |
|
bool enable_vlan; |
bool vlan_enabled; /* Current hardware state */ |
|
#ifdef DEBUG |
u16 addr; /* Debugging: register address to operate on */ |
#endif |
|
u16 pvid[ADM_NUM_PORTS]; /* Primary VLAN ID */ |
u8 tagged_ports; |
|
u16 vlan_id[ADM_NUM_VLANS]; |
u8 vlan_table[ADM_NUM_VLANS]; /* bitmap, 1 = port is member */ |
u8 vlan_tagged[ADM_NUM_VLANS]; /* bitmap, 1 = tagged member */ |
|
struct mutex mib_lock; |
char buf[2048]; |
|
struct mutex reg_mutex; |
|
/* use abstraction for regops, we want to add gpio support in the future */ |
u16 (*read)(struct adm6996_priv *priv, enum admreg reg); |
void (*write)(struct adm6996_priv *priv, enum admreg reg, u16 val); |
}; |
|
#define to_adm(_dev) container_of(_dev, struct adm6996_priv, dev) |
#define phy_to_adm(_phy) ((struct adm6996_priv *) (_phy)->priv) |
|
#define MIB_DESC(_o, _n) \ |
{ \ |
.offset = (_o), \ |
.name = (_n), \ |
} |
|
static const struct adm6996_mib_desc adm6996_mibs[] = { |
MIB_DESC(ADM_CL0, "RxPacket"), |
MIB_DESC(ADM_CL6, "RxByte"), |
MIB_DESC(ADM_CL12, "TxPacket"), |
MIB_DESC(ADM_CL18, "TxByte"), |
MIB_DESC(ADM_CL24, "Collision"), |
MIB_DESC(ADM_CL30, "Error"), |
}; |
|
#define ADM6996_MIB_RXB_ID 1 |
#define ADM6996_MIB_TXB_ID 3 |
|
static inline u16 |
r16(struct adm6996_priv *priv, enum admreg reg) |
{ |
return priv->read(priv, reg); |
} |
|
static inline void |
w16(struct adm6996_priv *priv, enum admreg reg, u16 val) |
{ |
priv->write(priv, reg, val); |
} |
|
/* Minimum timing constants */ |
#define EECK_EDGE_TIME 3 /* 3us - max(adm 2.5us, 93c 1us) */ |
#define EEDI_SETUP_TIME 1 /* 1us - max(adm 10ns, 93c 400ns) */ |
#define EECS_SETUP_TIME 1 /* 1us - max(adm no, 93c 200ns) */ |
|
static void adm6996_gpio_write(struct adm6996_priv *priv, int cs, char *buf, unsigned int bits) |
{ |
int i, len = (bits + 7) / 8; |
u8 mask; |
|
gpio_set_value(priv->eecs, cs); |
udelay(EECK_EDGE_TIME); |
|
/* Byte assemble from MSB to LSB */ |
for (i = 0; i < len; i++) { |
/* Bit bang from MSB to LSB */ |
for (mask = 0x80; mask && bits > 0; mask >>= 1, bits --) { |
/* Clock low */ |
gpio_set_value(priv->eesk, 0); |
udelay(EECK_EDGE_TIME); |
|
/* Output on rising edge */ |
gpio_set_value(priv->eedi, (mask & buf[i])); |
udelay(EEDI_SETUP_TIME); |
|
/* Clock high */ |
gpio_set_value(priv->eesk, 1); |
udelay(EECK_EDGE_TIME); |
} |
} |
|
/* Clock low */ |
gpio_set_value(priv->eesk, 0); |
udelay(EECK_EDGE_TIME); |
|
if (cs) |
gpio_set_value(priv->eecs, 0); |
} |
|
static void adm6996_gpio_read(struct adm6996_priv *priv, int cs, char *buf, unsigned int bits) |
{ |
int i, len = (bits + 7) / 8; |
u8 mask; |
|
gpio_set_value(priv->eecs, cs); |
udelay(EECK_EDGE_TIME); |
|
/* Byte assemble from MSB to LSB */ |
for (i = 0; i < len; i++) { |
u8 byte; |
|
/* Bit bang from MSB to LSB */ |
for (mask = 0x80, byte = 0; mask && bits > 0; mask >>= 1, bits --) { |
u8 gp; |
|
/* Clock low */ |
gpio_set_value(priv->eesk, 0); |
udelay(EECK_EDGE_TIME); |
|
/* Input on rising edge */ |
gp = gpio_get_value(priv->eedi); |
if (gp) |
byte |= mask; |
|
/* Clock high */ |
gpio_set_value(priv->eesk, 1); |
udelay(EECK_EDGE_TIME); |
} |
|
*buf++ = byte; |
} |
|
/* Clock low */ |
gpio_set_value(priv->eesk, 0); |
udelay(EECK_EDGE_TIME); |
|
if (cs) |
gpio_set_value(priv->eecs, 0); |
} |
|
/* Advance clock(s) */ |
static void adm6996_gpio_adclk(struct adm6996_priv *priv, int clocks) |
{ |
int i; |
for (i = 0; i < clocks; i++) { |
/* Clock high */ |
gpio_set_value(priv->eesk, 1); |
udelay(EECK_EDGE_TIME); |
|
/* Clock low */ |
gpio_set_value(priv->eesk, 0); |
udelay(EECK_EDGE_TIME); |
} |
} |
|
static u16 |
adm6996_read_gpio_reg(struct adm6996_priv *priv, enum admreg reg) |
{ |
/* cmd: 01 10 T DD R RRRRRR */ |
u8 bits[6] = { |
0xFF, 0xFF, 0xFF, 0xFF, |
(0x06 << 4) | ((0 & 0x01) << 3 | (reg&64)>>6), |
((reg&63)<<2) |
}; |
|
u8 rbits[4]; |
|
/* Enable GPIO outputs with all pins to 0 */ |
gpio_direction_output(priv->eecs, 0); |
gpio_direction_output(priv->eesk, 0); |
gpio_direction_output(priv->eedi, 0); |
|
adm6996_gpio_write(priv, 0, bits, 46); |
gpio_direction_input(priv->eedi); |
adm6996_gpio_adclk(priv, 2); |
adm6996_gpio_read(priv, 0, rbits, 32); |
|
/* Extra clock(s) required per datasheet */ |
adm6996_gpio_adclk(priv, 2); |
|
/* Disable GPIO outputs */ |
gpio_direction_input(priv->eecs); |
gpio_direction_input(priv->eesk); |
|
/* EEPROM has 16-bit registers, but pumps out two registers in one request */ |
return (reg & 0x01 ? (rbits[0]<<8) | rbits[1] : (rbits[2]<<8) | (rbits[3])); |
} |
|
/* Write chip configuration register */ |
/* Follow 93c66 timing and chip's min EEPROM timing requirement */ |
static void |
adm6996_write_gpio_reg(struct adm6996_priv *priv, enum admreg reg, u16 val) |
{ |
/* cmd(27bits): sb(1) + opc(01) + addr(bbbbbbbb) + data(bbbbbbbbbbbbbbbb) */ |
u8 bits[4] = { |
(0x05 << 5) | (reg >> 3), |
(reg << 5) | (u8)(val >> 11), |
(u8)(val >> 3), |
(u8)(val << 5) |
}; |
|
/* Enable GPIO outputs with all pins to 0 */ |
gpio_direction_output(priv->eecs, 0); |
gpio_direction_output(priv->eesk, 0); |
gpio_direction_output(priv->eedi, 0); |
|
/* Write cmd. Total 27 bits */ |
adm6996_gpio_write(priv, 1, bits, 27); |
|
/* Extra clock(s) required per datasheet */ |
adm6996_gpio_adclk(priv, 2); |
|
/* Disable GPIO outputs */ |
gpio_direction_input(priv->eecs); |
gpio_direction_input(priv->eesk); |
gpio_direction_input(priv->eedi); |
} |
|
static u16 |
adm6996_read_mii_reg(struct adm6996_priv *priv, enum admreg reg) |
{ |
struct phy_device *phydev = priv->priv; |
struct mii_bus *bus = phydev->mdio.bus; |
|
return bus->read(bus, PHYADDR(reg)); |
} |
|
static void |
adm6996_write_mii_reg(struct adm6996_priv *priv, enum admreg reg, u16 val) |
{ |
struct phy_device *phydev = priv->priv; |
struct mii_bus *bus = phydev->mdio.bus; |
|
bus->write(bus, PHYADDR(reg), val); |
} |
|
static int |
adm6996_set_enable_vlan(struct switch_dev *dev, const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct adm6996_priv *priv = to_adm(dev); |
|
if (val->value.i > 1) |
return -EINVAL; |
|
priv->enable_vlan = val->value.i; |
|
return 0; |
}; |
|
static int |
adm6996_get_enable_vlan(struct switch_dev *dev, const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct adm6996_priv *priv = to_adm(dev); |
|
val->value.i = priv->enable_vlan; |
|
return 0; |
}; |
|
#ifdef DEBUG |
|
static int |
adm6996_set_addr(struct switch_dev *dev, const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct adm6996_priv *priv = to_adm(dev); |
|
if (val->value.i > 1023) |
return -EINVAL; |
|
priv->addr = val->value.i; |
|
return 0; |
}; |
|
static int |
adm6996_get_addr(struct switch_dev *dev, const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct adm6996_priv *priv = to_adm(dev); |
|
val->value.i = priv->addr; |
|
return 0; |
}; |
|
static int |
adm6996_set_data(struct switch_dev *dev, const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct adm6996_priv *priv = to_adm(dev); |
|
if (val->value.i > 65535) |
return -EINVAL; |
|
w16(priv, priv->addr, val->value.i); |
|
return 0; |
}; |
|
static int |
adm6996_get_data(struct switch_dev *dev, const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct adm6996_priv *priv = to_adm(dev); |
|
val->value.i = r16(priv, priv->addr); |
|
return 0; |
}; |
|
#endif /* def DEBUG */ |
|
static int |
adm6996_set_pvid(struct switch_dev *dev, int port, int vlan) |
{ |
struct adm6996_priv *priv = to_adm(dev); |
|
pr_devel("set_pvid port %d vlan %d\n", port, vlan); |
|
if (vlan > ADM_VLAN_MAX_ID) |
return -EINVAL; |
|
priv->pvid[port] = vlan; |
|
return 0; |
} |
|
static int |
adm6996_get_pvid(struct switch_dev *dev, int port, int *vlan) |
{ |
struct adm6996_priv *priv = to_adm(dev); |
|
pr_devel("get_pvid port %d\n", port); |
*vlan = priv->pvid[port]; |
|
return 0; |
} |
|
static int |
adm6996_set_vid(struct switch_dev *dev, const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct adm6996_priv *priv = to_adm(dev); |
|
pr_devel("set_vid port %d vid %d\n", val->port_vlan, val->value.i); |
|
if (val->value.i > ADM_VLAN_MAX_ID) |
return -EINVAL; |
|
priv->vlan_id[val->port_vlan] = val->value.i; |
|
return 0; |
}; |
|
static int |
adm6996_get_vid(struct switch_dev *dev, const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct adm6996_priv *priv = to_adm(dev); |
|
pr_devel("get_vid port %d\n", val->port_vlan); |
|
val->value.i = priv->vlan_id[val->port_vlan]; |
|
return 0; |
}; |
|
static int |
adm6996_get_ports(struct switch_dev *dev, struct switch_val *val) |
{ |
struct adm6996_priv *priv = to_adm(dev); |
u8 ports = priv->vlan_table[val->port_vlan]; |
u8 tagged = priv->vlan_tagged[val->port_vlan]; |
int i; |
|
pr_devel("get_ports port_vlan %d\n", val->port_vlan); |
|
val->len = 0; |
|
for (i = 0; i < ADM_NUM_PORTS; i++) { |
struct switch_port *p; |
|
if (!(ports & (1 << i))) |
continue; |
|
p = &val->value.ports[val->len++]; |
p->id = i; |
if (tagged & (1 << i)) |
p->flags = (1 << SWITCH_PORT_FLAG_TAGGED); |
else |
p->flags = 0; |
} |
|
return 0; |
}; |
|
static int |
adm6996_set_ports(struct switch_dev *dev, struct switch_val *val) |
{ |
struct adm6996_priv *priv = to_adm(dev); |
u8 *ports = &priv->vlan_table[val->port_vlan]; |
u8 *tagged = &priv->vlan_tagged[val->port_vlan]; |
int i; |
|
pr_devel("set_ports port_vlan %d ports", val->port_vlan); |
|
*ports = 0; |
*tagged = 0; |
|
for (i = 0; i < val->len; i++) { |
struct switch_port *p = &val->value.ports[i]; |
|
#ifdef DEBUG |
pr_cont(" %d%s", p->id, |
((p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) ? "T" : |
"")); |
#endif |
|
if (p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) { |
*tagged |= (1 << p->id); |
priv->tagged_ports |= (1 << p->id); |
} |
|
*ports |= (1 << p->id); |
} |
|
#ifdef DEBUG |
pr_cont("\n"); |
#endif |
|
return 0; |
}; |
|
/* |
* Precondition: reg_mutex must be held |
*/ |
static void |
adm6996_enable_vlan(struct adm6996_priv *priv) |
{ |
u16 reg; |
|
reg = r16(priv, ADM_OTBE_P2_PVID); |
reg &= ~(ADM_OTBE_MASK); |
w16(priv, ADM_OTBE_P2_PVID, reg); |
reg = r16(priv, ADM_IFNTE); |
reg &= ~(ADM_IFNTE_MASK); |
w16(priv, ADM_IFNTE, reg); |
reg = r16(priv, ADM_VID_CHECK); |
reg |= ADM_VID_CHECK_MASK; |
w16(priv, ADM_VID_CHECK, reg); |
reg = r16(priv, ADM_SYSC0); |
reg |= ADM_NTTE; |
reg &= ~(ADM_RVID1); |
w16(priv, ADM_SYSC0, reg); |
reg = r16(priv, ADM_SYSC3); |
reg |= ADM_TBV; |
w16(priv, ADM_SYSC3, reg); |
} |
|
static void |
adm6996_enable_vlan_6996l(struct adm6996_priv *priv) |
{ |
u16 reg; |
|
reg = r16(priv, ADM_SYSC3); |
reg |= ADM_TBV; |
reg |= ADM_MAC_CLONE; |
w16(priv, ADM_SYSC3, reg); |
} |
|
/* |
* Disable VLANs |
* |
* Sets VLAN mapping for port-based VLAN with all ports connected to |
* eachother (this is also the power-on default). |
* |
* Precondition: reg_mutex must be held |
*/ |
static void |
adm6996_disable_vlan(struct adm6996_priv *priv) |
{ |
u16 reg; |
int i; |
|
for (i = 0; i < ADM_NUM_VLANS; i++) { |
reg = ADM_VLAN_FILT_MEMBER_MASK; |
w16(priv, ADM_VLAN_FILT_L(i), reg); |
reg = ADM_VLAN_FILT_VALID | ADM_VLAN_FILT_VID(1); |
w16(priv, ADM_VLAN_FILT_H(i), reg); |
} |
|
reg = r16(priv, ADM_OTBE_P2_PVID); |
reg |= ADM_OTBE_MASK; |
w16(priv, ADM_OTBE_P2_PVID, reg); |
reg = r16(priv, ADM_IFNTE); |
reg |= ADM_IFNTE_MASK; |
w16(priv, ADM_IFNTE, reg); |
reg = r16(priv, ADM_VID_CHECK); |
reg &= ~(ADM_VID_CHECK_MASK); |
w16(priv, ADM_VID_CHECK, reg); |
reg = r16(priv, ADM_SYSC0); |
reg &= ~(ADM_NTTE); |
reg |= ADM_RVID1; |
w16(priv, ADM_SYSC0, reg); |
reg = r16(priv, ADM_SYSC3); |
reg &= ~(ADM_TBV); |
w16(priv, ADM_SYSC3, reg); |
} |
|
/* |
* Disable VLANs |
* |
* Sets VLAN mapping for port-based VLAN with all ports connected to |
* eachother (this is also the power-on default). |
* |
* Precondition: reg_mutex must be held |
*/ |
static void |
adm6996_disable_vlan_6996l(struct adm6996_priv *priv) |
{ |
u16 reg; |
int i; |
|
for (i = 0; i < ADM_NUM_VLANS; i++) { |
w16(priv, ADM_VLAN_MAP(i), 0); |
} |
|
reg = r16(priv, ADM_SYSC3); |
reg &= ~(ADM_TBV); |
reg &= ~(ADM_MAC_CLONE); |
w16(priv, ADM_SYSC3, reg); |
} |
|
/* |
* Precondition: reg_mutex must be held |
*/ |
static void |
adm6996_apply_port_pvids(struct adm6996_priv *priv) |
{ |
u16 reg; |
int i; |
|
for (i = 0; i < ADM_NUM_PORTS; i++) { |
reg = r16(priv, adm_portcfg[i]); |
reg &= ~(ADM_PORTCFG_PVID_MASK); |
reg |= ADM_PORTCFG_PVID(priv->pvid[i]); |
if (priv->model == ADM6996L) { |
if (priv->tagged_ports & (1 << i)) |
reg |= (1 << 4); |
else |
reg &= ~(1 << 4); |
} |
w16(priv, adm_portcfg[i], reg); |
} |
|
w16(priv, ADM_P0_PVID, ADM_P0_PVID_VAL(priv->pvid[0])); |
w16(priv, ADM_P1_PVID, ADM_P1_PVID_VAL(priv->pvid[1])); |
reg = r16(priv, ADM_OTBE_P2_PVID); |
reg &= ~(ADM_P2_PVID_MASK); |
reg |= ADM_P2_PVID_VAL(priv->pvid[2]); |
w16(priv, ADM_OTBE_P2_PVID, reg); |
reg = ADM_P3_PVID_VAL(priv->pvid[3]); |
reg |= ADM_P4_PVID_VAL(priv->pvid[4]); |
w16(priv, ADM_P3_P4_PVID, reg); |
reg = r16(priv, ADM_P5_PVID); |
reg &= ~(ADM_P2_PVID_MASK); |
reg |= ADM_P5_PVID_VAL(priv->pvid[5]); |
w16(priv, ADM_P5_PVID, reg); |
} |
|
/* |
* Precondition: reg_mutex must be held |
*/ |
static void |
adm6996_apply_vlan_filters(struct adm6996_priv *priv) |
{ |
u8 ports, tagged; |
u16 vid, reg; |
int i; |
|
for (i = 0; i < ADM_NUM_VLANS; i++) { |
vid = priv->vlan_id[i]; |
ports = priv->vlan_table[i]; |
tagged = priv->vlan_tagged[i]; |
|
if (ports == 0) { |
/* Disable VLAN entry */ |
w16(priv, ADM_VLAN_FILT_H(i), 0); |
w16(priv, ADM_VLAN_FILT_L(i), 0); |
continue; |
} |
|
reg = ADM_VLAN_FILT_MEMBER(ports); |
reg |= ADM_VLAN_FILT_TAGGED(tagged); |
w16(priv, ADM_VLAN_FILT_L(i), reg); |
reg = ADM_VLAN_FILT_VALID | ADM_VLAN_FILT_VID(vid); |
w16(priv, ADM_VLAN_FILT_H(i), reg); |
} |
} |
|
static void |
adm6996_apply_vlan_filters_6996l(struct adm6996_priv *priv) |
{ |
u8 ports; |
u16 reg; |
int i; |
|
for (i = 0; i < ADM_NUM_VLANS; i++) { |
ports = priv->vlan_table[i]; |
|
if (ports == 0) { |
/* Disable VLAN entry */ |
w16(priv, ADM_VLAN_MAP(i), 0); |
continue; |
} else { |
reg = ADM_VLAN_FILT(ports); |
w16(priv, ADM_VLAN_MAP(i), reg); |
} |
} |
} |
|
static int |
adm6996_hw_apply(struct switch_dev *dev) |
{ |
struct adm6996_priv *priv = to_adm(dev); |
|
pr_devel("hw_apply\n"); |
|
mutex_lock(&priv->reg_mutex); |
|
if (!priv->enable_vlan) { |
if (priv->vlan_enabled) { |
if (priv->model == ADM6996L) |
adm6996_disable_vlan_6996l(priv); |
else |
adm6996_disable_vlan(priv); |
priv->vlan_enabled = 0; |
} |
goto out; |
} |
|
if (!priv->vlan_enabled) { |
if (priv->model == ADM6996L) |
adm6996_enable_vlan_6996l(priv); |
else |
adm6996_enable_vlan(priv); |
priv->vlan_enabled = 1; |
} |
|
adm6996_apply_port_pvids(priv); |
if (priv->model == ADM6996L) |
adm6996_apply_vlan_filters_6996l(priv); |
else |
adm6996_apply_vlan_filters(priv); |
|
out: |
mutex_unlock(&priv->reg_mutex); |
|
return 0; |
} |
|
/* |
* Reset the switch |
* |
* The ADM6996 can't do a software-initiated reset, so we just initialise the |
* registers we support in this driver. |
* |
* Precondition: reg_mutex must be held |
*/ |
static void |
adm6996_perform_reset (struct adm6996_priv *priv) |
{ |
int i; |
|
/* initialize port and vlan settings */ |
for (i = 0; i < ADM_NUM_PORTS - 1; i++) { |
w16(priv, adm_portcfg[i], ADM_PORTCFG_INIT | |
ADM_PORTCFG_PVID(0)); |
} |
w16(priv, adm_portcfg[5], ADM_PORTCFG_CPU); |
|
if (priv->model == ADM6996M || priv->model == ADM6996FC) { |
/* reset all PHY ports */ |
for (i = 0; i < ADM_PHY_PORTS; i++) { |
w16(priv, ADM_PHY_PORT(i), ADM_PHYCFG_INIT); |
} |
} |
|
priv->enable_vlan = 0; |
priv->vlan_enabled = 0; |
|
for (i = 0; i < ADM_NUM_PORTS; i++) { |
priv->pvid[i] = 0; |
} |
|
for (i = 0; i < ADM_NUM_VLANS; i++) { |
priv->vlan_id[i] = i; |
priv->vlan_table[i] = 0; |
priv->vlan_tagged[i] = 0; |
} |
|
if (priv->model == ADM6996M) { |
/* Clear VLAN priority map so prio's are unused */ |
w16 (priv, ADM_VLAN_PRIOMAP, 0); |
|
adm6996_disable_vlan(priv); |
adm6996_apply_port_pvids(priv); |
} else if (priv->model == ADM6996L) { |
/* Clear VLAN priority map so prio's are unused */ |
w16 (priv, ADM_VLAN_PRIOMAP, 0); |
|
adm6996_disable_vlan_6996l(priv); |
adm6996_apply_port_pvids(priv); |
} |
} |
|
static int |
adm6996_reset_switch(struct switch_dev *dev) |
{ |
struct adm6996_priv *priv = to_adm(dev); |
|
pr_devel("reset\n"); |
|
mutex_lock(&priv->reg_mutex); |
adm6996_perform_reset (priv); |
mutex_unlock(&priv->reg_mutex); |
return 0; |
} |
|
static int |
adm6996_get_port_link(struct switch_dev *dev, int port, |
struct switch_port_link *link) |
{ |
struct adm6996_priv *priv = to_adm(dev); |
|
u16 reg = 0; |
|
if (port >= ADM_NUM_PORTS) |
return -EINVAL; |
|
switch (port) { |
case 0: |
reg = r16(priv, ADM_PS0); |
break; |
case 1: |
reg = r16(priv, ADM_PS0); |
reg = reg >> 8; |
break; |
case 2: |
reg = r16(priv, ADM_PS1); |
break; |
case 3: |
reg = r16(priv, ADM_PS1); |
reg = reg >> 8; |
break; |
case 4: |
reg = r16(priv, ADM_PS1); |
reg = reg >> 12; |
break; |
case 5: |
reg = r16(priv, ADM_PS2); |
/* Bits 0, 1, 3 and 4. */ |
reg = (reg & 3) | ((reg & 24) >> 1); |
break; |
default: |
return -EINVAL; |
} |
|
link->link = reg & ADM_PS_LS; |
if (!link->link) |
return 0; |
link->aneg = true; |
link->duplex = reg & ADM_PS_DS; |
link->tx_flow = reg & ADM_PS_FCS; |
link->rx_flow = reg & ADM_PS_FCS; |
if (reg & ADM_PS_SS) |
link->speed = SWITCH_PORT_SPEED_100; |
else |
link->speed = SWITCH_PORT_SPEED_10; |
|
return 0; |
} |
|
static int |
adm6996_sw_get_port_mib(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct adm6996_priv *priv = to_adm(dev); |
int port; |
char *buf = priv->buf; |
int i, len = 0; |
u32 reg = 0; |
|
port = val->port_vlan; |
if (port >= ADM_NUM_PORTS) |
return -EINVAL; |
|
mutex_lock(&priv->mib_lock); |
|
len += snprintf(buf + len, sizeof(priv->buf) - len, |
"Port %d MIB counters\n", |
port); |
|
for (i = 0; i < ARRAY_SIZE(adm6996_mibs); i++) { |
reg = r16(priv, adm6996_mibs[i].offset + ADM_OFFSET_PORT(port)); |
reg += r16(priv, adm6996_mibs[i].offset + ADM_OFFSET_PORT(port) + 1) << 16; |
len += snprintf(buf + len, sizeof(priv->buf) - len, |
"%-12s: %u\n", |
adm6996_mibs[i].name, |
reg); |
} |
|
mutex_unlock(&priv->mib_lock); |
|
val->value.s = buf; |
val->len = len; |
|
return 0; |
} |
|
static int |
adm6996_get_port_stats(struct switch_dev *dev, int port, |
struct switch_port_stats *stats) |
{ |
struct adm6996_priv *priv = to_adm(dev); |
int id; |
u32 reg = 0; |
|
if (port >= ADM_NUM_PORTS) |
return -EINVAL; |
|
mutex_lock(&priv->mib_lock); |
|
id = ADM6996_MIB_TXB_ID; |
reg = r16(priv, adm6996_mibs[id].offset + ADM_OFFSET_PORT(port)); |
reg += r16(priv, adm6996_mibs[id].offset + ADM_OFFSET_PORT(port) + 1) << 16; |
stats->tx_bytes = reg; |
|
id = ADM6996_MIB_RXB_ID; |
reg = r16(priv, adm6996_mibs[id].offset + ADM_OFFSET_PORT(port)); |
reg += r16(priv, adm6996_mibs[id].offset + ADM_OFFSET_PORT(port) + 1) << 16; |
stats->rx_bytes = reg; |
|
mutex_unlock(&priv->mib_lock); |
|
return 0; |
} |
|
static struct switch_attr adm6996_globals[] = { |
{ |
.type = SWITCH_TYPE_INT, |
.name = "enable_vlan", |
.description = "Enable VLANs", |
.set = adm6996_set_enable_vlan, |
.get = adm6996_get_enable_vlan, |
}, |
#ifdef DEBUG |
{ |
.type = SWITCH_TYPE_INT, |
.name = "addr", |
.description = |
"Direct register access: set register address (0 - 1023)", |
.set = adm6996_set_addr, |
.get = adm6996_get_addr, |
}, |
{ |
.type = SWITCH_TYPE_INT, |
.name = "data", |
.description = |
"Direct register access: read/write to register (0 - 65535)", |
.set = adm6996_set_data, |
.get = adm6996_get_data, |
}, |
#endif /* def DEBUG */ |
}; |
|
static struct switch_attr adm6996_port[] = { |
{ |
.type = SWITCH_TYPE_STRING, |
.name = "mib", |
.description = "Get port's MIB counters", |
.set = NULL, |
.get = adm6996_sw_get_port_mib, |
}, |
}; |
|
static struct switch_attr adm6996_vlan[] = { |
{ |
.type = SWITCH_TYPE_INT, |
.name = "vid", |
.description = "VLAN ID", |
.set = adm6996_set_vid, |
.get = adm6996_get_vid, |
}, |
}; |
|
static struct switch_dev_ops adm6996_ops = { |
.attr_global = { |
.attr = adm6996_globals, |
.n_attr = ARRAY_SIZE(adm6996_globals), |
}, |
.attr_port = { |
.attr = adm6996_port, |
.n_attr = ARRAY_SIZE(adm6996_port), |
}, |
.attr_vlan = { |
.attr = adm6996_vlan, |
.n_attr = ARRAY_SIZE(adm6996_vlan), |
}, |
.get_port_pvid = adm6996_get_pvid, |
.set_port_pvid = adm6996_set_pvid, |
.get_vlan_ports = adm6996_get_ports, |
.set_vlan_ports = adm6996_set_ports, |
.apply_config = adm6996_hw_apply, |
.reset_switch = adm6996_reset_switch, |
.get_port_link = adm6996_get_port_link, |
.get_port_stats = adm6996_get_port_stats, |
}; |
|
static int adm6996_switch_init(struct adm6996_priv *priv, const char *alias, struct net_device *netdev) |
{ |
struct switch_dev *swdev; |
u16 test, old; |
|
if (!priv->model) { |
/* Detect type of chip */ |
old = r16(priv, ADM_VID_CHECK); |
test = old ^ (1 << 12); |
w16(priv, ADM_VID_CHECK, test); |
test ^= r16(priv, ADM_VID_CHECK); |
if (test & (1 << 12)) { |
/* |
* Bit 12 of this register is read-only. |
* This is the FC model. |
*/ |
priv->model = ADM6996FC; |
} else { |
/* Bit 12 is read-write. This is the M model. */ |
priv->model = ADM6996M; |
w16(priv, ADM_VID_CHECK, old); |
} |
} |
|
swdev = &priv->dev; |
swdev->name = (adm6996_model_name[priv->model]); |
swdev->cpu_port = ADM_CPU_PORT; |
swdev->ports = ADM_NUM_PORTS; |
swdev->vlans = ADM_NUM_VLANS; |
swdev->ops = &adm6996_ops; |
swdev->alias = alias; |
|
/* The ADM6996L connected through GPIOs does not support any switch |
status calls */ |
if (priv->model == ADM6996L) { |
adm6996_ops.attr_port.n_attr = 0; |
adm6996_ops.get_port_link = NULL; |
} |
|
pr_info ("%s: %s model PHY found.\n", alias, swdev->name); |
|
mutex_lock(&priv->reg_mutex); |
adm6996_perform_reset (priv); |
mutex_unlock(&priv->reg_mutex); |
|
if (priv->model == ADM6996M || priv->model == ADM6996L) { |
return register_switch(swdev, netdev); |
} |
|
return -ENODEV; |
} |
|
static int adm6996_config_init(struct phy_device *pdev) |
{ |
struct adm6996_priv *priv; |
int ret; |
|
pdev->supported = ADVERTISED_100baseT_Full; |
pdev->advertising = ADVERTISED_100baseT_Full; |
|
if (pdev->mdio.addr != 0) { |
pr_info ("%s: PHY overlaps ADM6996, providing fixed PHY 0x%x.\n" |
, pdev->attached_dev->name, pdev->mdio.addr); |
return 0; |
} |
|
priv = devm_kzalloc(&pdev->mdio.dev, sizeof(struct adm6996_priv), GFP_KERNEL); |
if (!priv) |
return -ENOMEM; |
|
mutex_init(&priv->reg_mutex); |
mutex_init(&priv->mib_lock); |
priv->priv = pdev; |
priv->read = adm6996_read_mii_reg; |
priv->write = adm6996_write_mii_reg; |
|
ret = adm6996_switch_init(priv, pdev->attached_dev->name, pdev->attached_dev); |
if (ret < 0) |
return ret; |
|
pdev->priv = priv; |
|
return 0; |
} |
|
/* |
* Warning: phydev->priv is NULL if phydev->mdio.addr != 0 |
*/ |
static int adm6996_read_status(struct phy_device *phydev) |
{ |
phydev->speed = SPEED_100; |
phydev->duplex = DUPLEX_FULL; |
phydev->link = 1; |
|
phydev->state = PHY_RUNNING; |
netif_carrier_on(phydev->attached_dev); |
phydev->adjust_link(phydev->attached_dev); |
|
return 0; |
} |
|
/* |
* Warning: phydev->priv is NULL if phydev->mdio.addr != 0 |
*/ |
static int adm6996_config_aneg(struct phy_device *phydev) |
{ |
return 0; |
} |
|
static int adm6996_fixup(struct phy_device *dev) |
{ |
struct mii_bus *bus = dev->mdio.bus; |
u16 reg; |
|
/* Our custom registers are at PHY addresses 0-10. Claim those. */ |
if (dev->mdio.addr > 10) |
return 0; |
|
/* look for the switch on the bus */ |
reg = bus->read(bus, PHYADDR(ADM_SIG0)) & ADM_SIG0_MASK; |
if (reg != ADM_SIG0_VAL) |
return 0; |
|
reg = bus->read(bus, PHYADDR(ADM_SIG1)) & ADM_SIG1_MASK; |
if (reg != ADM_SIG1_VAL) |
return 0; |
|
dev->phy_id = (ADM_SIG0_VAL << 16) | ADM_SIG1_VAL; |
|
return 0; |
} |
|
static int adm6996_probe(struct phy_device *pdev) |
{ |
return 0; |
} |
|
static void adm6996_remove(struct phy_device *pdev) |
{ |
struct adm6996_priv *priv = phy_to_adm(pdev); |
|
if (priv && (priv->model == ADM6996M || priv->model == ADM6996L)) |
unregister_switch(&priv->dev); |
} |
|
static int adm6996_soft_reset(struct phy_device *phydev) |
{ |
/* we don't need an extra reset */ |
return 0; |
} |
|
static struct phy_driver adm6996_phy_driver = { |
.name = "Infineon ADM6996", |
.phy_id = (ADM_SIG0_VAL << 16) | ADM_SIG1_VAL, |
.phy_id_mask = 0xffffffff, |
.features = PHY_BASIC_FEATURES, |
.probe = adm6996_probe, |
.remove = adm6996_remove, |
.config_init = &adm6996_config_init, |
.config_aneg = &adm6996_config_aneg, |
.read_status = &adm6996_read_status, |
.soft_reset = adm6996_soft_reset, |
}; |
|
static int adm6996_gpio_probe(struct platform_device *pdev) |
{ |
struct adm6996_gpio_platform_data *pdata = pdev->dev.platform_data; |
struct adm6996_priv *priv; |
int ret; |
|
if (!pdata) |
return -EINVAL; |
|
priv = devm_kzalloc(&pdev->dev, sizeof(struct adm6996_priv), GFP_KERNEL); |
if (!priv) |
return -ENOMEM; |
|
mutex_init(&priv->reg_mutex); |
mutex_init(&priv->mib_lock); |
|
priv->eecs = pdata->eecs; |
priv->eedi = pdata->eedi; |
priv->eesk = pdata->eesk; |
|
priv->model = pdata->model; |
priv->read = adm6996_read_gpio_reg; |
priv->write = adm6996_write_gpio_reg; |
|
ret = devm_gpio_request(&pdev->dev, priv->eecs, "adm_eecs"); |
if (ret) |
return ret; |
ret = devm_gpio_request(&pdev->dev, priv->eedi, "adm_eedi"); |
if (ret) |
return ret; |
ret = devm_gpio_request(&pdev->dev, priv->eesk, "adm_eesk"); |
if (ret) |
return ret; |
|
ret = adm6996_switch_init(priv, dev_name(&pdev->dev), NULL); |
if (ret < 0) |
return ret; |
|
platform_set_drvdata(pdev, priv); |
|
return 0; |
} |
|
static int adm6996_gpio_remove(struct platform_device *pdev) |
{ |
struct adm6996_priv *priv = platform_get_drvdata(pdev); |
|
if (priv && (priv->model == ADM6996M || priv->model == ADM6996L)) |
unregister_switch(&priv->dev); |
|
return 0; |
} |
|
static struct platform_driver adm6996_gpio_driver = { |
.probe = adm6996_gpio_probe, |
.remove = adm6996_gpio_remove, |
.driver = { |
.name = "adm6996_gpio", |
}, |
}; |
|
static int __init adm6996_init(void) |
{ |
int err; |
|
phy_register_fixup_for_id(PHY_ANY_ID, adm6996_fixup); |
err = phy_driver_register(&adm6996_phy_driver, THIS_MODULE); |
if (err) |
return err; |
|
err = platform_driver_register(&adm6996_gpio_driver); |
if (err) |
phy_driver_unregister(&adm6996_phy_driver); |
|
return err; |
} |
|
static void __exit adm6996_exit(void) |
{ |
platform_driver_unregister(&adm6996_gpio_driver); |
phy_driver_unregister(&adm6996_phy_driver); |
} |
|
module_init(adm6996_init); |
module_exit(adm6996_exit); |
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/ar8216.c |
@@ -0,0 +1,2313 @@ |
/* |
* ar8216.c: AR8216 switch driver |
* |
* Copyright (C) 2009 Felix Fietkau <nbd@nbd.name> |
* Copyright (C) 2011-2012 Gabor Juhos <juhosg@openwrt.org> |
* |
* 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; either version 2 |
* of the License, or (at your option) any later version. |
* |
* 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. |
*/ |
|
#include <linux/if.h> |
#include <linux/module.h> |
#include <linux/init.h> |
#include <linux/list.h> |
#include <linux/if_ether.h> |
#include <linux/skbuff.h> |
#include <linux/netdevice.h> |
#include <linux/netlink.h> |
#include <linux/bitops.h> |
#include <net/genetlink.h> |
#include <linux/switch.h> |
#include <linux/delay.h> |
#include <linux/phy.h> |
#include <linux/netdevice.h> |
#include <linux/etherdevice.h> |
#include <linux/lockdep.h> |
#include <linux/ar8216_platform.h> |
#include <linux/workqueue.h> |
#include <linux/version.h> |
|
#include "ar8216.h" |
|
extern const struct ar8xxx_chip ar8327_chip; |
extern const struct ar8xxx_chip ar8337_chip; |
|
#define AR8XXX_MIB_WORK_DELAY 2000 /* msecs */ |
|
#define MIB_DESC(_s , _o, _n) \ |
{ \ |
.size = (_s), \ |
.offset = (_o), \ |
.name = (_n), \ |
} |
|
static const struct ar8xxx_mib_desc ar8216_mibs[] = { |
MIB_DESC(1, AR8216_STATS_RXBROAD, "RxBroad"), |
MIB_DESC(1, AR8216_STATS_RXPAUSE, "RxPause"), |
MIB_DESC(1, AR8216_STATS_RXMULTI, "RxMulti"), |
MIB_DESC(1, AR8216_STATS_RXFCSERR, "RxFcsErr"), |
MIB_DESC(1, AR8216_STATS_RXALIGNERR, "RxAlignErr"), |
MIB_DESC(1, AR8216_STATS_RXRUNT, "RxRunt"), |
MIB_DESC(1, AR8216_STATS_RXFRAGMENT, "RxFragment"), |
MIB_DESC(1, AR8216_STATS_RX64BYTE, "Rx64Byte"), |
MIB_DESC(1, AR8216_STATS_RX128BYTE, "Rx128Byte"), |
MIB_DESC(1, AR8216_STATS_RX256BYTE, "Rx256Byte"), |
MIB_DESC(1, AR8216_STATS_RX512BYTE, "Rx512Byte"), |
MIB_DESC(1, AR8216_STATS_RX1024BYTE, "Rx1024Byte"), |
MIB_DESC(1, AR8216_STATS_RXMAXBYTE, "RxMaxByte"), |
MIB_DESC(1, AR8216_STATS_RXTOOLONG, "RxTooLong"), |
MIB_DESC(2, AR8216_STATS_RXGOODBYTE, "RxGoodByte"), |
MIB_DESC(2, AR8216_STATS_RXBADBYTE, "RxBadByte"), |
MIB_DESC(1, AR8216_STATS_RXOVERFLOW, "RxOverFlow"), |
MIB_DESC(1, AR8216_STATS_FILTERED, "Filtered"), |
MIB_DESC(1, AR8216_STATS_TXBROAD, "TxBroad"), |
MIB_DESC(1, AR8216_STATS_TXPAUSE, "TxPause"), |
MIB_DESC(1, AR8216_STATS_TXMULTI, "TxMulti"), |
MIB_DESC(1, AR8216_STATS_TXUNDERRUN, "TxUnderRun"), |
MIB_DESC(1, AR8216_STATS_TX64BYTE, "Tx64Byte"), |
MIB_DESC(1, AR8216_STATS_TX128BYTE, "Tx128Byte"), |
MIB_DESC(1, AR8216_STATS_TX256BYTE, "Tx256Byte"), |
MIB_DESC(1, AR8216_STATS_TX512BYTE, "Tx512Byte"), |
MIB_DESC(1, AR8216_STATS_TX1024BYTE, "Tx1024Byte"), |
MIB_DESC(1, AR8216_STATS_TXMAXBYTE, "TxMaxByte"), |
MIB_DESC(1, AR8216_STATS_TXOVERSIZE, "TxOverSize"), |
MIB_DESC(2, AR8216_STATS_TXBYTE, "TxByte"), |
MIB_DESC(1, AR8216_STATS_TXCOLLISION, "TxCollision"), |
MIB_DESC(1, AR8216_STATS_TXABORTCOL, "TxAbortCol"), |
MIB_DESC(1, AR8216_STATS_TXMULTICOL, "TxMultiCol"), |
MIB_DESC(1, AR8216_STATS_TXSINGLECOL, "TxSingleCol"), |
MIB_DESC(1, AR8216_STATS_TXEXCDEFER, "TxExcDefer"), |
MIB_DESC(1, AR8216_STATS_TXDEFER, "TxDefer"), |
MIB_DESC(1, AR8216_STATS_TXLATECOL, "TxLateCol"), |
}; |
|
const struct ar8xxx_mib_desc ar8236_mibs[39] = { |
MIB_DESC(1, AR8236_STATS_RXBROAD, "RxBroad"), |
MIB_DESC(1, AR8236_STATS_RXPAUSE, "RxPause"), |
MIB_DESC(1, AR8236_STATS_RXMULTI, "RxMulti"), |
MIB_DESC(1, AR8236_STATS_RXFCSERR, "RxFcsErr"), |
MIB_DESC(1, AR8236_STATS_RXALIGNERR, "RxAlignErr"), |
MIB_DESC(1, AR8236_STATS_RXRUNT, "RxRunt"), |
MIB_DESC(1, AR8236_STATS_RXFRAGMENT, "RxFragment"), |
MIB_DESC(1, AR8236_STATS_RX64BYTE, "Rx64Byte"), |
MIB_DESC(1, AR8236_STATS_RX128BYTE, "Rx128Byte"), |
MIB_DESC(1, AR8236_STATS_RX256BYTE, "Rx256Byte"), |
MIB_DESC(1, AR8236_STATS_RX512BYTE, "Rx512Byte"), |
MIB_DESC(1, AR8236_STATS_RX1024BYTE, "Rx1024Byte"), |
MIB_DESC(1, AR8236_STATS_RX1518BYTE, "Rx1518Byte"), |
MIB_DESC(1, AR8236_STATS_RXMAXBYTE, "RxMaxByte"), |
MIB_DESC(1, AR8236_STATS_RXTOOLONG, "RxTooLong"), |
MIB_DESC(2, AR8236_STATS_RXGOODBYTE, "RxGoodByte"), |
MIB_DESC(2, AR8236_STATS_RXBADBYTE, "RxBadByte"), |
MIB_DESC(1, AR8236_STATS_RXOVERFLOW, "RxOverFlow"), |
MIB_DESC(1, AR8236_STATS_FILTERED, "Filtered"), |
MIB_DESC(1, AR8236_STATS_TXBROAD, "TxBroad"), |
MIB_DESC(1, AR8236_STATS_TXPAUSE, "TxPause"), |
MIB_DESC(1, AR8236_STATS_TXMULTI, "TxMulti"), |
MIB_DESC(1, AR8236_STATS_TXUNDERRUN, "TxUnderRun"), |
MIB_DESC(1, AR8236_STATS_TX64BYTE, "Tx64Byte"), |
MIB_DESC(1, AR8236_STATS_TX128BYTE, "Tx128Byte"), |
MIB_DESC(1, AR8236_STATS_TX256BYTE, "Tx256Byte"), |
MIB_DESC(1, AR8236_STATS_TX512BYTE, "Tx512Byte"), |
MIB_DESC(1, AR8236_STATS_TX1024BYTE, "Tx1024Byte"), |
MIB_DESC(1, AR8236_STATS_TX1518BYTE, "Tx1518Byte"), |
MIB_DESC(1, AR8236_STATS_TXMAXBYTE, "TxMaxByte"), |
MIB_DESC(1, AR8236_STATS_TXOVERSIZE, "TxOverSize"), |
MIB_DESC(2, AR8236_STATS_TXBYTE, "TxByte"), |
MIB_DESC(1, AR8236_STATS_TXCOLLISION, "TxCollision"), |
MIB_DESC(1, AR8236_STATS_TXABORTCOL, "TxAbortCol"), |
MIB_DESC(1, AR8236_STATS_TXMULTICOL, "TxMultiCol"), |
MIB_DESC(1, AR8236_STATS_TXSINGLECOL, "TxSingleCol"), |
MIB_DESC(1, AR8236_STATS_TXEXCDEFER, "TxExcDefer"), |
MIB_DESC(1, AR8236_STATS_TXDEFER, "TxDefer"), |
MIB_DESC(1, AR8236_STATS_TXLATECOL, "TxLateCol"), |
}; |
|
static DEFINE_MUTEX(ar8xxx_dev_list_lock); |
static LIST_HEAD(ar8xxx_dev_list); |
|
/* inspired by phy_poll_reset in drivers/net/phy/phy_device.c */ |
static int |
ar8xxx_phy_poll_reset(struct mii_bus *bus) |
{ |
unsigned int sleep_msecs = 20; |
int ret, elapsed, i; |
|
for (elapsed = sleep_msecs; elapsed <= 600; |
elapsed += sleep_msecs) { |
msleep(sleep_msecs); |
for (i = 0; i < AR8XXX_NUM_PHYS; i++) { |
ret = mdiobus_read(bus, i, MII_BMCR); |
if (ret < 0) |
return ret; |
if (ret & BMCR_RESET) |
break; |
if (i == AR8XXX_NUM_PHYS - 1) { |
usleep_range(1000, 2000); |
return 0; |
} |
} |
} |
return -ETIMEDOUT; |
} |
|
static int |
ar8xxx_phy_check_aneg(struct phy_device *phydev) |
{ |
int ret; |
|
if (phydev->autoneg != AUTONEG_ENABLE) |
return 0; |
/* |
* BMCR_ANENABLE might have been cleared |
* by phy_init_hw in certain kernel versions |
* therefore check for it |
*/ |
ret = phy_read(phydev, MII_BMCR); |
if (ret < 0) |
return ret; |
if (ret & BMCR_ANENABLE) |
return 0; |
|
dev_info(&phydev->mdio.dev, "ANEG disabled, re-enabling ...\n"); |
ret |= BMCR_ANENABLE | BMCR_ANRESTART; |
return phy_write(phydev, MII_BMCR, ret); |
} |
|
void |
ar8xxx_phy_init(struct ar8xxx_priv *priv) |
{ |
int i; |
struct mii_bus *bus; |
|
bus = priv->mii_bus; |
for (i = 0; i < AR8XXX_NUM_PHYS; i++) { |
if (priv->chip->phy_fixup) |
priv->chip->phy_fixup(priv, i); |
|
/* initialize the port itself */ |
mdiobus_write(bus, i, MII_ADVERTISE, |
ADVERTISE_ALL | ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM); |
if (ar8xxx_has_gige(priv)) |
mdiobus_write(bus, i, MII_CTRL1000, ADVERTISE_1000FULL); |
mdiobus_write(bus, i, MII_BMCR, BMCR_RESET | BMCR_ANENABLE); |
} |
|
ar8xxx_phy_poll_reset(bus); |
} |
|
u32 |
ar8xxx_mii_read32(struct ar8xxx_priv *priv, int phy_id, int regnum) |
{ |
struct mii_bus *bus = priv->mii_bus; |
u16 lo, hi; |
|
lo = bus->read(bus, phy_id, regnum); |
hi = bus->read(bus, phy_id, regnum + 1); |
|
return (hi << 16) | lo; |
} |
|
void |
ar8xxx_mii_write32(struct ar8xxx_priv *priv, int phy_id, int regnum, u32 val) |
{ |
struct mii_bus *bus = priv->mii_bus; |
u16 lo, hi; |
|
lo = val & 0xffff; |
hi = (u16) (val >> 16); |
|
if (priv->chip->mii_lo_first) |
{ |
bus->write(bus, phy_id, regnum, lo); |
bus->write(bus, phy_id, regnum + 1, hi); |
} else { |
bus->write(bus, phy_id, regnum + 1, hi); |
bus->write(bus, phy_id, regnum, lo); |
} |
} |
|
u32 |
ar8xxx_read(struct ar8xxx_priv *priv, int reg) |
{ |
struct mii_bus *bus = priv->mii_bus; |
u16 r1, r2, page; |
u32 val; |
|
split_addr((u32) reg, &r1, &r2, &page); |
|
mutex_lock(&bus->mdio_lock); |
|
bus->write(bus, 0x18, 0, page); |
wait_for_page_switch(); |
val = ar8xxx_mii_read32(priv, 0x10 | r2, r1); |
|
mutex_unlock(&bus->mdio_lock); |
|
return val; |
} |
|
void |
ar8xxx_write(struct ar8xxx_priv *priv, int reg, u32 val) |
{ |
struct mii_bus *bus = priv->mii_bus; |
u16 r1, r2, page; |
|
split_addr((u32) reg, &r1, &r2, &page); |
|
mutex_lock(&bus->mdio_lock); |
|
bus->write(bus, 0x18, 0, page); |
wait_for_page_switch(); |
ar8xxx_mii_write32(priv, 0x10 | r2, r1, val); |
|
mutex_unlock(&bus->mdio_lock); |
} |
|
u32 |
ar8xxx_rmw(struct ar8xxx_priv *priv, int reg, u32 mask, u32 val) |
{ |
struct mii_bus *bus = priv->mii_bus; |
u16 r1, r2, page; |
u32 ret; |
|
split_addr((u32) reg, &r1, &r2, &page); |
|
mutex_lock(&bus->mdio_lock); |
|
bus->write(bus, 0x18, 0, page); |
wait_for_page_switch(); |
|
ret = ar8xxx_mii_read32(priv, 0x10 | r2, r1); |
ret &= ~mask; |
ret |= val; |
ar8xxx_mii_write32(priv, 0x10 | r2, r1, ret); |
|
mutex_unlock(&bus->mdio_lock); |
|
return ret; |
} |
|
void |
ar8xxx_phy_dbg_write(struct ar8xxx_priv *priv, int phy_addr, |
u16 dbg_addr, u16 dbg_data) |
{ |
struct mii_bus *bus = priv->mii_bus; |
|
mutex_lock(&bus->mdio_lock); |
bus->write(bus, phy_addr, MII_ATH_DBG_ADDR, dbg_addr); |
bus->write(bus, phy_addr, MII_ATH_DBG_DATA, dbg_data); |
mutex_unlock(&bus->mdio_lock); |
} |
|
static inline void |
ar8xxx_phy_mmd_prep(struct mii_bus *bus, int phy_addr, u16 addr, u16 reg) |
{ |
bus->write(bus, phy_addr, MII_ATH_MMD_ADDR, addr); |
bus->write(bus, phy_addr, MII_ATH_MMD_DATA, reg); |
bus->write(bus, phy_addr, MII_ATH_MMD_ADDR, addr | 0x4000); |
} |
|
void |
ar8xxx_phy_mmd_write(struct ar8xxx_priv *priv, int phy_addr, u16 addr, u16 reg, u16 data) |
{ |
struct mii_bus *bus = priv->mii_bus; |
|
mutex_lock(&bus->mdio_lock); |
ar8xxx_phy_mmd_prep(bus, phy_addr, addr, reg); |
bus->write(bus, phy_addr, MII_ATH_MMD_DATA, data); |
mutex_unlock(&bus->mdio_lock); |
} |
|
u16 |
ar8xxx_phy_mmd_read(struct ar8xxx_priv *priv, int phy_addr, u16 addr, u16 reg) |
{ |
struct mii_bus *bus = priv->mii_bus; |
u16 data; |
|
mutex_lock(&bus->mdio_lock); |
ar8xxx_phy_mmd_prep(bus, phy_addr, addr, reg); |
data = bus->read(bus, phy_addr, MII_ATH_MMD_DATA); |
mutex_unlock(&bus->mdio_lock); |
|
return data; |
} |
|
static int |
ar8xxx_reg_wait(struct ar8xxx_priv *priv, u32 reg, u32 mask, u32 val, |
unsigned timeout) |
{ |
int i; |
|
for (i = 0; i < timeout; i++) { |
u32 t; |
|
t = ar8xxx_read(priv, reg); |
if ((t & mask) == val) |
return 0; |
|
usleep_range(1000, 2000); |
cond_resched(); |
} |
|
return -ETIMEDOUT; |
} |
|
static int |
ar8xxx_mib_op(struct ar8xxx_priv *priv, u32 op) |
{ |
unsigned mib_func = priv->chip->mib_func; |
int ret; |
|
lockdep_assert_held(&priv->mib_lock); |
|
/* Capture the hardware statistics for all ports */ |
ar8xxx_rmw(priv, mib_func, AR8216_MIB_FUNC, (op << AR8216_MIB_FUNC_S)); |
|
/* Wait for the capturing to complete. */ |
ret = ar8xxx_reg_wait(priv, mib_func, AR8216_MIB_BUSY, 0, 10); |
if (ret) |
goto out; |
|
ret = 0; |
|
out: |
return ret; |
} |
|
static int |
ar8xxx_mib_capture(struct ar8xxx_priv *priv) |
{ |
return ar8xxx_mib_op(priv, AR8216_MIB_FUNC_CAPTURE); |
} |
|
static int |
ar8xxx_mib_flush(struct ar8xxx_priv *priv) |
{ |
return ar8xxx_mib_op(priv, AR8216_MIB_FUNC_FLUSH); |
} |
|
static void |
ar8xxx_mib_fetch_port_stat(struct ar8xxx_priv *priv, int port, bool flush) |
{ |
unsigned int base; |
u64 *mib_stats; |
int i; |
|
WARN_ON(port >= priv->dev.ports); |
|
lockdep_assert_held(&priv->mib_lock); |
|
base = priv->chip->reg_port_stats_start + |
priv->chip->reg_port_stats_length * port; |
|
mib_stats = &priv->mib_stats[port * priv->chip->num_mibs]; |
for (i = 0; i < priv->chip->num_mibs; i++) { |
const struct ar8xxx_mib_desc *mib; |
u64 t; |
|
mib = &priv->chip->mib_decs[i]; |
t = ar8xxx_read(priv, base + mib->offset); |
if (mib->size == 2) { |
u64 hi; |
|
hi = ar8xxx_read(priv, base + mib->offset + 4); |
t |= hi << 32; |
} |
|
if (flush) |
mib_stats[i] = 0; |
else |
mib_stats[i] += t; |
cond_resched(); |
} |
} |
|
static void |
ar8216_read_port_link(struct ar8xxx_priv *priv, int port, |
struct switch_port_link *link) |
{ |
u32 status; |
u32 speed; |
|
memset(link, '\0', sizeof(*link)); |
|
status = priv->chip->read_port_status(priv, port); |
|
link->aneg = !!(status & AR8216_PORT_STATUS_LINK_AUTO); |
if (link->aneg) { |
link->link = !!(status & AR8216_PORT_STATUS_LINK_UP); |
} else { |
link->link = true; |
|
if (priv->get_port_link) { |
int err; |
|
err = priv->get_port_link(port); |
if (err >= 0) |
link->link = !!err; |
} |
} |
|
if (!link->link) |
return; |
|
link->duplex = !!(status & AR8216_PORT_STATUS_DUPLEX); |
link->tx_flow = !!(status & AR8216_PORT_STATUS_TXFLOW); |
link->rx_flow = !!(status & AR8216_PORT_STATUS_RXFLOW); |
|
if (link->aneg && link->duplex && priv->chip->read_port_eee_status) |
link->eee = priv->chip->read_port_eee_status(priv, port); |
|
speed = (status & AR8216_PORT_STATUS_SPEED) >> |
AR8216_PORT_STATUS_SPEED_S; |
|
switch (speed) { |
case AR8216_PORT_SPEED_10M: |
link->speed = SWITCH_PORT_SPEED_10; |
break; |
case AR8216_PORT_SPEED_100M: |
link->speed = SWITCH_PORT_SPEED_100; |
break; |
case AR8216_PORT_SPEED_1000M: |
link->speed = SWITCH_PORT_SPEED_1000; |
break; |
default: |
link->speed = SWITCH_PORT_SPEED_UNKNOWN; |
break; |
} |
} |
|
static struct sk_buff * |
ar8216_mangle_tx(struct net_device *dev, struct sk_buff *skb) |
{ |
struct ar8xxx_priv *priv = dev->phy_ptr; |
unsigned char *buf; |
|
if (unlikely(!priv)) |
goto error; |
|
if (!priv->vlan) |
goto send; |
|
if (unlikely(skb_headroom(skb) < 2)) { |
if (pskb_expand_head(skb, 2, 0, GFP_ATOMIC) < 0) |
goto error; |
} |
|
buf = skb_push(skb, 2); |
buf[0] = 0x10; |
buf[1] = 0x80; |
|
send: |
return skb; |
|
error: |
dev_kfree_skb_any(skb); |
return NULL; |
} |
|
static void |
ar8216_mangle_rx(struct net_device *dev, struct sk_buff *skb) |
{ |
struct ar8xxx_priv *priv; |
unsigned char *buf; |
int port, vlan; |
|
priv = dev->phy_ptr; |
if (!priv) |
return; |
|
/* don't strip the header if vlan mode is disabled */ |
if (!priv->vlan) |
return; |
|
/* strip header, get vlan id */ |
buf = skb->data; |
skb_pull(skb, 2); |
|
/* check for vlan header presence */ |
if ((buf[12 + 2] != 0x81) || (buf[13 + 2] != 0x00)) |
return; |
|
port = buf[0] & 0x7; |
|
/* no need to fix up packets coming from a tagged source */ |
if (priv->vlan_tagged & (1 << port)) |
return; |
|
/* lookup port vid from local table, the switch passes an invalid vlan id */ |
vlan = priv->vlan_id[priv->pvid[port]]; |
|
buf[14 + 2] &= 0xf0; |
buf[14 + 2] |= vlan >> 8; |
buf[15 + 2] = vlan & 0xff; |
} |
|
int |
ar8216_wait_bit(struct ar8xxx_priv *priv, int reg, u32 mask, u32 val) |
{ |
int timeout = 20; |
u32 t = 0; |
|
while (1) { |
t = ar8xxx_read(priv, reg); |
if ((t & mask) == val) |
return 0; |
|
if (timeout-- <= 0) |
break; |
|
udelay(10); |
cond_resched(); |
} |
|
pr_err("ar8216: timeout on reg %08x: %08x & %08x != %08x\n", |
(unsigned int) reg, t, mask, val); |
return -ETIMEDOUT; |
} |
|
static void |
ar8216_vtu_op(struct ar8xxx_priv *priv, u32 op, u32 val) |
{ |
if (ar8216_wait_bit(priv, AR8216_REG_VTU, AR8216_VTU_ACTIVE, 0)) |
return; |
if ((op & AR8216_VTU_OP) == AR8216_VTU_OP_LOAD) { |
val &= AR8216_VTUDATA_MEMBER; |
val |= AR8216_VTUDATA_VALID; |
ar8xxx_write(priv, AR8216_REG_VTU_DATA, val); |
} |
op |= AR8216_VTU_ACTIVE; |
ar8xxx_write(priv, AR8216_REG_VTU, op); |
} |
|
static void |
ar8216_vtu_flush(struct ar8xxx_priv *priv) |
{ |
ar8216_vtu_op(priv, AR8216_VTU_OP_FLUSH, 0); |
} |
|
static void |
ar8216_vtu_load_vlan(struct ar8xxx_priv *priv, u32 vid, u32 port_mask) |
{ |
u32 op; |
|
op = AR8216_VTU_OP_LOAD | (vid << AR8216_VTU_VID_S); |
ar8216_vtu_op(priv, op, port_mask); |
} |
|
static int |
ar8216_atu_flush(struct ar8xxx_priv *priv) |
{ |
int ret; |
|
ret = ar8216_wait_bit(priv, AR8216_REG_ATU_FUNC0, AR8216_ATU_ACTIVE, 0); |
if (!ret) |
ar8xxx_write(priv, AR8216_REG_ATU_FUNC0, AR8216_ATU_OP_FLUSH | |
AR8216_ATU_ACTIVE); |
|
return ret; |
} |
|
static int |
ar8216_atu_flush_port(struct ar8xxx_priv *priv, int port) |
{ |
u32 t; |
int ret; |
|
ret = ar8216_wait_bit(priv, AR8216_REG_ATU_FUNC0, AR8216_ATU_ACTIVE, 0); |
if (!ret) { |
t = (port << AR8216_ATU_PORT_NUM_S) | AR8216_ATU_OP_FLUSH_PORT; |
t |= AR8216_ATU_ACTIVE; |
ar8xxx_write(priv, AR8216_REG_ATU_FUNC0, t); |
} |
|
return ret; |
} |
|
static u32 |
ar8216_read_port_status(struct ar8xxx_priv *priv, int port) |
{ |
return ar8xxx_read(priv, AR8216_REG_PORT_STATUS(port)); |
} |
|
static void |
ar8216_setup_port(struct ar8xxx_priv *priv, int port, u32 members) |
{ |
u32 header; |
u32 egress, ingress; |
u32 pvid; |
|
if (priv->vlan) { |
pvid = priv->vlan_id[priv->pvid[port]]; |
if (priv->vlan_tagged & (1 << port)) |
egress = AR8216_OUT_ADD_VLAN; |
else |
egress = AR8216_OUT_STRIP_VLAN; |
ingress = AR8216_IN_SECURE; |
} else { |
pvid = port; |
egress = AR8216_OUT_KEEP; |
ingress = AR8216_IN_PORT_ONLY; |
} |
|
if (chip_is_ar8216(priv) && priv->vlan && port == AR8216_PORT_CPU) |
header = AR8216_PORT_CTRL_HEADER; |
else |
header = 0; |
|
ar8xxx_rmw(priv, AR8216_REG_PORT_CTRL(port), |
AR8216_PORT_CTRL_LEARN | AR8216_PORT_CTRL_VLAN_MODE | |
AR8216_PORT_CTRL_SINGLE_VLAN | AR8216_PORT_CTRL_STATE | |
AR8216_PORT_CTRL_HEADER | AR8216_PORT_CTRL_LEARN_LOCK, |
AR8216_PORT_CTRL_LEARN | header | |
(egress << AR8216_PORT_CTRL_VLAN_MODE_S) | |
(AR8216_PORT_STATE_FORWARD << AR8216_PORT_CTRL_STATE_S)); |
|
ar8xxx_rmw(priv, AR8216_REG_PORT_VLAN(port), |
AR8216_PORT_VLAN_DEST_PORTS | AR8216_PORT_VLAN_MODE | |
AR8216_PORT_VLAN_DEFAULT_ID, |
(members << AR8216_PORT_VLAN_DEST_PORTS_S) | |
(ingress << AR8216_PORT_VLAN_MODE_S) | |
(pvid << AR8216_PORT_VLAN_DEFAULT_ID_S)); |
} |
|
static int |
ar8216_hw_init(struct ar8xxx_priv *priv) |
{ |
if (priv->initialized) |
return 0; |
|
ar8xxx_phy_init(priv); |
|
priv->initialized = true; |
return 0; |
} |
|
static void |
ar8216_init_globals(struct ar8xxx_priv *priv) |
{ |
/* standard atheros magic */ |
ar8xxx_write(priv, 0x38, 0xc000050e); |
|
ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CTRL, |
AR8216_GCTRL_MTU, 1518 + 8 + 2); |
} |
|
static void |
ar8216_init_port(struct ar8xxx_priv *priv, int port) |
{ |
/* Enable port learning and tx */ |
ar8xxx_write(priv, AR8216_REG_PORT_CTRL(port), |
AR8216_PORT_CTRL_LEARN | |
(4 << AR8216_PORT_CTRL_STATE_S)); |
|
ar8xxx_write(priv, AR8216_REG_PORT_VLAN(port), 0); |
|
if (port == AR8216_PORT_CPU) { |
ar8xxx_write(priv, AR8216_REG_PORT_STATUS(port), |
AR8216_PORT_STATUS_LINK_UP | |
(ar8xxx_has_gige(priv) ? |
AR8216_PORT_SPEED_1000M : AR8216_PORT_SPEED_100M) | |
AR8216_PORT_STATUS_TXMAC | |
AR8216_PORT_STATUS_RXMAC | |
(chip_is_ar8316(priv) ? AR8216_PORT_STATUS_RXFLOW : 0) | |
(chip_is_ar8316(priv) ? AR8216_PORT_STATUS_TXFLOW : 0) | |
AR8216_PORT_STATUS_DUPLEX); |
} else { |
ar8xxx_write(priv, AR8216_REG_PORT_STATUS(port), |
AR8216_PORT_STATUS_LINK_AUTO); |
} |
} |
|
static void |
ar8216_wait_atu_ready(struct ar8xxx_priv *priv, u16 r2, u16 r1) |
{ |
int timeout = 20; |
|
while (ar8xxx_mii_read32(priv, r2, r1) & AR8216_ATU_ACTIVE && --timeout) { |
udelay(10); |
cond_resched(); |
} |
|
if (!timeout) |
pr_err("ar8216: timeout waiting for atu to become ready\n"); |
} |
|
static void ar8216_get_arl_entry(struct ar8xxx_priv *priv, |
struct arl_entry *a, u32 *status, enum arl_op op) |
{ |
struct mii_bus *bus = priv->mii_bus; |
u16 r2, page; |
u16 r1_func0, r1_func1, r1_func2; |
u32 t, val0, val1, val2; |
int i; |
|
split_addr(AR8216_REG_ATU_FUNC0, &r1_func0, &r2, &page); |
r2 |= 0x10; |
|
r1_func1 = (AR8216_REG_ATU_FUNC1 >> 1) & 0x1e; |
r1_func2 = (AR8216_REG_ATU_FUNC2 >> 1) & 0x1e; |
|
switch (op) { |
case AR8XXX_ARL_INITIALIZE: |
/* all ATU registers are on the same page |
* therefore set page only once |
*/ |
bus->write(bus, 0x18, 0, page); |
wait_for_page_switch(); |
|
ar8216_wait_atu_ready(priv, r2, r1_func0); |
|
ar8xxx_mii_write32(priv, r2, r1_func0, AR8216_ATU_OP_GET_NEXT); |
ar8xxx_mii_write32(priv, r2, r1_func1, 0); |
ar8xxx_mii_write32(priv, r2, r1_func2, 0); |
break; |
case AR8XXX_ARL_GET_NEXT: |
t = ar8xxx_mii_read32(priv, r2, r1_func0); |
t |= AR8216_ATU_ACTIVE; |
ar8xxx_mii_write32(priv, r2, r1_func0, t); |
ar8216_wait_atu_ready(priv, r2, r1_func0); |
|
val0 = ar8xxx_mii_read32(priv, r2, r1_func0); |
val1 = ar8xxx_mii_read32(priv, r2, r1_func1); |
val2 = ar8xxx_mii_read32(priv, r2, r1_func2); |
|
*status = (val2 & AR8216_ATU_STATUS) >> AR8216_ATU_STATUS_S; |
if (!*status) |
break; |
|
i = 0; |
t = AR8216_ATU_PORT0; |
while (!(val2 & t) && ++i < priv->dev.ports) |
t <<= 1; |
|
a->port = i; |
a->mac[0] = (val0 & AR8216_ATU_ADDR5) >> AR8216_ATU_ADDR5_S; |
a->mac[1] = (val0 & AR8216_ATU_ADDR4) >> AR8216_ATU_ADDR4_S; |
a->mac[2] = (val1 & AR8216_ATU_ADDR3) >> AR8216_ATU_ADDR3_S; |
a->mac[3] = (val1 & AR8216_ATU_ADDR2) >> AR8216_ATU_ADDR2_S; |
a->mac[4] = (val1 & AR8216_ATU_ADDR1) >> AR8216_ATU_ADDR1_S; |
a->mac[5] = (val1 & AR8216_ATU_ADDR0) >> AR8216_ATU_ADDR0_S; |
break; |
} |
} |
|
static void |
ar8236_setup_port(struct ar8xxx_priv *priv, int port, u32 members) |
{ |
u32 egress, ingress; |
u32 pvid; |
|
if (priv->vlan) { |
pvid = priv->vlan_id[priv->pvid[port]]; |
if (priv->vlan_tagged & (1 << port)) |
egress = AR8216_OUT_ADD_VLAN; |
else |
egress = AR8216_OUT_STRIP_VLAN; |
ingress = AR8216_IN_SECURE; |
} else { |
pvid = port; |
egress = AR8216_OUT_KEEP; |
ingress = AR8216_IN_PORT_ONLY; |
} |
|
ar8xxx_rmw(priv, AR8216_REG_PORT_CTRL(port), |
AR8216_PORT_CTRL_LEARN | AR8216_PORT_CTRL_VLAN_MODE | |
AR8216_PORT_CTRL_SINGLE_VLAN | AR8216_PORT_CTRL_STATE | |
AR8216_PORT_CTRL_HEADER | AR8216_PORT_CTRL_LEARN_LOCK, |
AR8216_PORT_CTRL_LEARN | |
(egress << AR8216_PORT_CTRL_VLAN_MODE_S) | |
(AR8216_PORT_STATE_FORWARD << AR8216_PORT_CTRL_STATE_S)); |
|
ar8xxx_rmw(priv, AR8236_REG_PORT_VLAN(port), |
AR8236_PORT_VLAN_DEFAULT_ID, |
(pvid << AR8236_PORT_VLAN_DEFAULT_ID_S)); |
|
ar8xxx_rmw(priv, AR8236_REG_PORT_VLAN2(port), |
AR8236_PORT_VLAN2_VLAN_MODE | |
AR8236_PORT_VLAN2_MEMBER, |
(ingress << AR8236_PORT_VLAN2_VLAN_MODE_S) | |
(members << AR8236_PORT_VLAN2_MEMBER_S)); |
} |
|
static void |
ar8236_init_globals(struct ar8xxx_priv *priv) |
{ |
/* enable jumbo frames */ |
ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CTRL, |
AR8316_GCTRL_MTU, 9018 + 8 + 2); |
|
/* enable cpu port to receive arp frames */ |
ar8xxx_reg_set(priv, AR8216_REG_ATU_CTRL, |
AR8236_ATU_CTRL_RES); |
|
/* enable cpu port to receive multicast and broadcast frames */ |
ar8xxx_reg_set(priv, AR8216_REG_FLOOD_MASK, |
AR8236_FM_CPU_BROADCAST_EN | AR8236_FM_CPU_BCAST_FWD_EN); |
|
/* Enable MIB counters */ |
ar8xxx_rmw(priv, AR8216_REG_MIB_FUNC, AR8216_MIB_FUNC | AR8236_MIB_EN, |
(AR8216_MIB_FUNC_NO_OP << AR8216_MIB_FUNC_S) | |
AR8236_MIB_EN); |
} |
|
static int |
ar8316_hw_init(struct ar8xxx_priv *priv) |
{ |
u32 val, newval; |
|
val = ar8xxx_read(priv, AR8316_REG_POSTRIP); |
|
if (priv->phy->interface == PHY_INTERFACE_MODE_RGMII) { |
if (priv->port4_phy) { |
/* value taken from Ubiquiti RouterStation Pro */ |
newval = 0x81461bea; |
pr_info("ar8316: Using port 4 as PHY\n"); |
} else { |
newval = 0x01261be2; |
pr_info("ar8316: Using port 4 as switch port\n"); |
} |
} else if (priv->phy->interface == PHY_INTERFACE_MODE_GMII) { |
/* value taken from AVM Fritz!Box 7390 sources */ |
newval = 0x010e5b71; |
} else { |
/* no known value for phy interface */ |
pr_err("ar8316: unsupported mii mode: %d.\n", |
priv->phy->interface); |
return -EINVAL; |
} |
|
if (val == newval) |
goto out; |
|
ar8xxx_write(priv, AR8316_REG_POSTRIP, newval); |
|
if (priv->port4_phy && |
priv->phy->interface == PHY_INTERFACE_MODE_RGMII) { |
/* work around for phy4 rgmii mode */ |
ar8xxx_phy_dbg_write(priv, 4, 0x12, 0x480c); |
/* rx delay */ |
ar8xxx_phy_dbg_write(priv, 4, 0x0, 0x824e); |
/* tx delay */ |
ar8xxx_phy_dbg_write(priv, 4, 0x5, 0x3d47); |
msleep(1000); |
} |
|
ar8xxx_phy_init(priv); |
|
out: |
priv->initialized = true; |
return 0; |
} |
|
static void |
ar8316_init_globals(struct ar8xxx_priv *priv) |
{ |
/* standard atheros magic */ |
ar8xxx_write(priv, 0x38, 0xc000050e); |
|
/* enable cpu port to receive multicast and broadcast frames */ |
ar8xxx_write(priv, AR8216_REG_FLOOD_MASK, 0x003f003f); |
|
/* enable jumbo frames */ |
ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CTRL, |
AR8316_GCTRL_MTU, 9018 + 8 + 2); |
|
/* Enable MIB counters */ |
ar8xxx_rmw(priv, AR8216_REG_MIB_FUNC, AR8216_MIB_FUNC | AR8236_MIB_EN, |
(AR8216_MIB_FUNC_NO_OP << AR8216_MIB_FUNC_S) | |
AR8236_MIB_EN); |
} |
|
int |
ar8xxx_sw_set_vlan(struct switch_dev *dev, const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
priv->vlan = !!val->value.i; |
return 0; |
} |
|
int |
ar8xxx_sw_get_vlan(struct switch_dev *dev, const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
val->value.i = priv->vlan; |
return 0; |
} |
|
|
int |
ar8xxx_sw_set_pvid(struct switch_dev *dev, int port, int vlan) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
|
/* make sure no invalid PVIDs get set */ |
|
if (vlan < 0 || vlan >= dev->vlans || |
port < 0 || port >= AR8X16_MAX_PORTS) |
return -EINVAL; |
|
priv->pvid[port] = vlan; |
return 0; |
} |
|
int |
ar8xxx_sw_get_pvid(struct switch_dev *dev, int port, int *vlan) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
|
if (port < 0 || port >= AR8X16_MAX_PORTS) |
return -EINVAL; |
|
*vlan = priv->pvid[port]; |
return 0; |
} |
|
static int |
ar8xxx_sw_set_vid(struct switch_dev *dev, const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
|
if (val->port_vlan >= AR8X16_MAX_VLANS) |
return -EINVAL; |
|
priv->vlan_id[val->port_vlan] = val->value.i; |
return 0; |
} |
|
static int |
ar8xxx_sw_get_vid(struct switch_dev *dev, const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
val->value.i = priv->vlan_id[val->port_vlan]; |
return 0; |
} |
|
int |
ar8xxx_sw_get_port_link(struct switch_dev *dev, int port, |
struct switch_port_link *link) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
|
ar8216_read_port_link(priv, port, link); |
return 0; |
} |
|
static int |
ar8xxx_sw_get_ports(struct switch_dev *dev, struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
u8 ports; |
int i; |
|
if (val->port_vlan >= AR8X16_MAX_VLANS) |
return -EINVAL; |
|
ports = priv->vlan_table[val->port_vlan]; |
val->len = 0; |
for (i = 0; i < dev->ports; i++) { |
struct switch_port *p; |
|
if (!(ports & (1 << i))) |
continue; |
|
p = &val->value.ports[val->len++]; |
p->id = i; |
if (priv->vlan_tagged & (1 << i)) |
p->flags = (1 << SWITCH_PORT_FLAG_TAGGED); |
else |
p->flags = 0; |
} |
return 0; |
} |
|
static int |
ar8xxx_sw_set_ports(struct switch_dev *dev, struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
u8 *vt = &priv->vlan_table[val->port_vlan]; |
int i, j; |
|
*vt = 0; |
for (i = 0; i < val->len; i++) { |
struct switch_port *p = &val->value.ports[i]; |
|
if (p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) { |
priv->vlan_tagged |= (1 << p->id); |
} else { |
priv->vlan_tagged &= ~(1 << p->id); |
priv->pvid[p->id] = val->port_vlan; |
|
/* make sure that an untagged port does not |
* appear in other vlans */ |
for (j = 0; j < AR8X16_MAX_VLANS; j++) { |
if (j == val->port_vlan) |
continue; |
priv->vlan_table[j] &= ~(1 << p->id); |
} |
} |
|
*vt |= 1 << p->id; |
} |
return 0; |
} |
|
static void |
ar8216_set_mirror_regs(struct ar8xxx_priv *priv) |
{ |
int port; |
|
/* reset all mirror registers */ |
ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CPUPORT, |
AR8216_GLOBAL_CPUPORT_MIRROR_PORT, |
(0xF << AR8216_GLOBAL_CPUPORT_MIRROR_PORT_S)); |
for (port = 0; port < AR8216_NUM_PORTS; port++) { |
ar8xxx_reg_clear(priv, AR8216_REG_PORT_CTRL(port), |
AR8216_PORT_CTRL_MIRROR_RX); |
|
ar8xxx_reg_clear(priv, AR8216_REG_PORT_CTRL(port), |
AR8216_PORT_CTRL_MIRROR_TX); |
} |
|
/* now enable mirroring if necessary */ |
if (priv->source_port >= AR8216_NUM_PORTS || |
priv->monitor_port >= AR8216_NUM_PORTS || |
priv->source_port == priv->monitor_port) { |
return; |
} |
|
ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CPUPORT, |
AR8216_GLOBAL_CPUPORT_MIRROR_PORT, |
(priv->monitor_port << AR8216_GLOBAL_CPUPORT_MIRROR_PORT_S)); |
|
if (priv->mirror_rx) |
ar8xxx_reg_set(priv, AR8216_REG_PORT_CTRL(priv->source_port), |
AR8216_PORT_CTRL_MIRROR_RX); |
|
if (priv->mirror_tx) |
ar8xxx_reg_set(priv, AR8216_REG_PORT_CTRL(priv->source_port), |
AR8216_PORT_CTRL_MIRROR_TX); |
} |
|
static inline u32 |
ar8xxx_age_time_val(int age_time) |
{ |
return (age_time + AR8XXX_REG_ARL_CTRL_AGE_TIME_SECS / 2) / |
AR8XXX_REG_ARL_CTRL_AGE_TIME_SECS; |
} |
|
static inline void |
ar8xxx_set_age_time(struct ar8xxx_priv *priv, int reg) |
{ |
u32 age_time = ar8xxx_age_time_val(priv->arl_age_time); |
ar8xxx_rmw(priv, reg, AR8216_ATU_CTRL_AGE_TIME, age_time << AR8216_ATU_CTRL_AGE_TIME_S); |
} |
|
int |
ar8xxx_sw_hw_apply(struct switch_dev *dev) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
const struct ar8xxx_chip *chip = priv->chip; |
u8 portmask[AR8X16_MAX_PORTS]; |
int i, j; |
|
mutex_lock(&priv->reg_mutex); |
/* flush all vlan translation unit entries */ |
priv->chip->vtu_flush(priv); |
|
memset(portmask, 0, sizeof(portmask)); |
if (!priv->init) { |
/* calculate the port destination masks and load vlans |
* into the vlan translation unit */ |
for (j = 0; j < AR8X16_MAX_VLANS; j++) { |
u8 vp = priv->vlan_table[j]; |
|
if (!vp) |
continue; |
|
for (i = 0; i < dev->ports; i++) { |
u8 mask = (1 << i); |
if (vp & mask) |
portmask[i] |= vp & ~mask; |
} |
|
chip->vtu_load_vlan(priv, priv->vlan_id[j], |
priv->vlan_table[j]); |
} |
} else { |
/* vlan disabled: |
* isolate all ports, but connect them to the cpu port */ |
for (i = 0; i < dev->ports; i++) { |
if (i == AR8216_PORT_CPU) |
continue; |
|
portmask[i] = 1 << AR8216_PORT_CPU; |
portmask[AR8216_PORT_CPU] |= (1 << i); |
} |
} |
|
/* update the port destination mask registers and tag settings */ |
for (i = 0; i < dev->ports; i++) { |
chip->setup_port(priv, i, portmask[i]); |
} |
|
chip->set_mirror_regs(priv); |
|
/* set age time */ |
if (chip->reg_arl_ctrl) |
ar8xxx_set_age_time(priv, chip->reg_arl_ctrl); |
|
mutex_unlock(&priv->reg_mutex); |
return 0; |
} |
|
int |
ar8xxx_sw_reset_switch(struct switch_dev *dev) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
const struct ar8xxx_chip *chip = priv->chip; |
int i; |
|
mutex_lock(&priv->reg_mutex); |
memset(&priv->vlan, 0, sizeof(struct ar8xxx_priv) - |
offsetof(struct ar8xxx_priv, vlan)); |
|
for (i = 0; i < AR8X16_MAX_VLANS; i++) |
priv->vlan_id[i] = i; |
|
/* Configure all ports */ |
for (i = 0; i < dev->ports; i++) |
chip->init_port(priv, i); |
|
priv->mirror_rx = false; |
priv->mirror_tx = false; |
priv->source_port = 0; |
priv->monitor_port = 0; |
priv->arl_age_time = AR8XXX_DEFAULT_ARL_AGE_TIME; |
|
chip->init_globals(priv); |
chip->atu_flush(priv); |
|
mutex_unlock(&priv->reg_mutex); |
|
return chip->sw_hw_apply(dev); |
} |
|
int |
ar8xxx_sw_set_reset_mibs(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
unsigned int len; |
int ret; |
|
if (!ar8xxx_has_mib_counters(priv)) |
return -EOPNOTSUPP; |
|
mutex_lock(&priv->mib_lock); |
|
len = priv->dev.ports * priv->chip->num_mibs * |
sizeof(*priv->mib_stats); |
memset(priv->mib_stats, '\0', len); |
ret = ar8xxx_mib_flush(priv); |
if (ret) |
goto unlock; |
|
ret = 0; |
|
unlock: |
mutex_unlock(&priv->mib_lock); |
return ret; |
} |
|
int |
ar8xxx_sw_set_mirror_rx_enable(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
|
mutex_lock(&priv->reg_mutex); |
priv->mirror_rx = !!val->value.i; |
priv->chip->set_mirror_regs(priv); |
mutex_unlock(&priv->reg_mutex); |
|
return 0; |
} |
|
int |
ar8xxx_sw_get_mirror_rx_enable(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
val->value.i = priv->mirror_rx; |
return 0; |
} |
|
int |
ar8xxx_sw_set_mirror_tx_enable(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
|
mutex_lock(&priv->reg_mutex); |
priv->mirror_tx = !!val->value.i; |
priv->chip->set_mirror_regs(priv); |
mutex_unlock(&priv->reg_mutex); |
|
return 0; |
} |
|
int |
ar8xxx_sw_get_mirror_tx_enable(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
val->value.i = priv->mirror_tx; |
return 0; |
} |
|
int |
ar8xxx_sw_set_mirror_monitor_port(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
|
mutex_lock(&priv->reg_mutex); |
priv->monitor_port = val->value.i; |
priv->chip->set_mirror_regs(priv); |
mutex_unlock(&priv->reg_mutex); |
|
return 0; |
} |
|
int |
ar8xxx_sw_get_mirror_monitor_port(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
val->value.i = priv->monitor_port; |
return 0; |
} |
|
int |
ar8xxx_sw_set_mirror_source_port(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
|
mutex_lock(&priv->reg_mutex); |
priv->source_port = val->value.i; |
priv->chip->set_mirror_regs(priv); |
mutex_unlock(&priv->reg_mutex); |
|
return 0; |
} |
|
int |
ar8xxx_sw_get_mirror_source_port(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
val->value.i = priv->source_port; |
return 0; |
} |
|
int |
ar8xxx_sw_set_port_reset_mib(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
int port; |
int ret; |
|
if (!ar8xxx_has_mib_counters(priv)) |
return -EOPNOTSUPP; |
|
port = val->port_vlan; |
if (port >= dev->ports) |
return -EINVAL; |
|
mutex_lock(&priv->mib_lock); |
ret = ar8xxx_mib_capture(priv); |
if (ret) |
goto unlock; |
|
ar8xxx_mib_fetch_port_stat(priv, port, true); |
|
ret = 0; |
|
unlock: |
mutex_unlock(&priv->mib_lock); |
return ret; |
} |
|
static void |
ar8xxx_byte_to_str(char *buf, int len, u64 byte) |
{ |
unsigned long b; |
const char *unit; |
|
if (byte >= 0x40000000) { /* 1 GiB */ |
b = byte * 10 / 0x40000000; |
unit = "GiB"; |
} else if (byte >= 0x100000) { /* 1 MiB */ |
b = byte * 10 / 0x100000; |
unit = "MiB"; |
} else if (byte >= 0x400) { /* 1 KiB */ |
b = byte * 10 / 0x400; |
unit = "KiB"; |
} else { |
b = byte; |
unit = "Byte"; |
} |
if (strcmp(unit, "Byte")) |
snprintf(buf, len, "%lu.%lu %s", b / 10, b % 10, unit); |
else |
snprintf(buf, len, "%lu %s", b, unit); |
} |
|
int |
ar8xxx_sw_get_port_mib(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
const struct ar8xxx_chip *chip = priv->chip; |
u64 *mib_stats, mib_data; |
unsigned int port; |
int ret; |
char *buf = priv->buf; |
char buf1[64]; |
const char *mib_name; |
int i, len = 0; |
bool mib_stats_empty = true; |
|
if (!ar8xxx_has_mib_counters(priv)) |
return -EOPNOTSUPP; |
|
port = val->port_vlan; |
if (port >= dev->ports) |
return -EINVAL; |
|
mutex_lock(&priv->mib_lock); |
ret = ar8xxx_mib_capture(priv); |
if (ret) |
goto unlock; |
|
ar8xxx_mib_fetch_port_stat(priv, port, false); |
|
len += snprintf(buf + len, sizeof(priv->buf) - len, |
"MIB counters\n"); |
|
mib_stats = &priv->mib_stats[port * chip->num_mibs]; |
for (i = 0; i < chip->num_mibs; i++) { |
mib_name = chip->mib_decs[i].name; |
mib_data = mib_stats[i]; |
len += snprintf(buf + len, sizeof(priv->buf) - len, |
"%-12s: %llu\n", mib_name, mib_data); |
if ((!strcmp(mib_name, "TxByte") || |
!strcmp(mib_name, "RxGoodByte")) && |
mib_data >= 1024) { |
ar8xxx_byte_to_str(buf1, sizeof(buf1), mib_data); |
--len; /* discard newline at the end of buf */ |
len += snprintf(buf + len, sizeof(priv->buf) - len, |
" (%s)\n", buf1); |
} |
if (mib_stats_empty && mib_data) |
mib_stats_empty = false; |
} |
|
if (mib_stats_empty) |
len = snprintf(buf, sizeof(priv->buf), "No MIB data"); |
|
val->value.s = buf; |
val->len = len; |
|
ret = 0; |
|
unlock: |
mutex_unlock(&priv->mib_lock); |
return ret; |
} |
|
int |
ar8xxx_sw_set_arl_age_time(struct switch_dev *dev, const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
int age_time = val->value.i; |
u32 age_time_val; |
|
if (age_time < 0) |
return -EINVAL; |
|
age_time_val = ar8xxx_age_time_val(age_time); |
if (age_time_val == 0 || age_time_val > 0xffff) |
return -EINVAL; |
|
priv->arl_age_time = age_time; |
return 0; |
} |
|
int |
ar8xxx_sw_get_arl_age_time(struct switch_dev *dev, const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
val->value.i = priv->arl_age_time; |
return 0; |
} |
|
int |
ar8xxx_sw_get_arl_table(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
struct mii_bus *bus = priv->mii_bus; |
const struct ar8xxx_chip *chip = priv->chip; |
char *buf = priv->arl_buf; |
int i, j, k, len = 0; |
struct arl_entry *a, *a1; |
u32 status; |
|
if (!chip->get_arl_entry) |
return -EOPNOTSUPP; |
|
mutex_lock(&priv->reg_mutex); |
mutex_lock(&bus->mdio_lock); |
|
chip->get_arl_entry(priv, NULL, NULL, AR8XXX_ARL_INITIALIZE); |
|
for(i = 0; i < AR8XXX_NUM_ARL_RECORDS; ++i) { |
a = &priv->arl_table[i]; |
duplicate: |
chip->get_arl_entry(priv, a, &status, AR8XXX_ARL_GET_NEXT); |
|
if (!status) |
break; |
|
/* avoid duplicates |
* ARL table can include multiple valid entries |
* per MAC, just with differing status codes |
*/ |
for (j = 0; j < i; ++j) { |
a1 = &priv->arl_table[j]; |
if (a->port == a1->port && !memcmp(a->mac, a1->mac, sizeof(a->mac))) |
goto duplicate; |
} |
} |
|
mutex_unlock(&bus->mdio_lock); |
|
len += snprintf(buf + len, sizeof(priv->arl_buf) - len, |
"address resolution table\n"); |
|
if (i == AR8XXX_NUM_ARL_RECORDS) |
len += snprintf(buf + len, sizeof(priv->arl_buf) - len, |
"Too many entries found, displaying the first %d only!\n", |
AR8XXX_NUM_ARL_RECORDS); |
|
for (j = 0; j < priv->dev.ports; ++j) { |
for (k = 0; k < i; ++k) { |
a = &priv->arl_table[k]; |
if (a->port != j) |
continue; |
len += snprintf(buf + len, sizeof(priv->arl_buf) - len, |
"Port %d: MAC %02x:%02x:%02x:%02x:%02x:%02x\n", |
j, |
a->mac[5], a->mac[4], a->mac[3], |
a->mac[2], a->mac[1], a->mac[0]); |
} |
} |
|
val->value.s = buf; |
val->len = len; |
|
mutex_unlock(&priv->reg_mutex); |
|
return 0; |
} |
|
int |
ar8xxx_sw_set_flush_arl_table(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
int ret; |
|
mutex_lock(&priv->reg_mutex); |
ret = priv->chip->atu_flush(priv); |
mutex_unlock(&priv->reg_mutex); |
|
return ret; |
} |
|
int |
ar8xxx_sw_set_flush_port_arl_table(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
int port, ret; |
|
port = val->port_vlan; |
if (port >= dev->ports) |
return -EINVAL; |
|
mutex_lock(&priv->reg_mutex); |
ret = priv->chip->atu_flush_port(priv, port); |
mutex_unlock(&priv->reg_mutex); |
|
return ret; |
} |
|
static const struct switch_attr ar8xxx_sw_attr_globals[] = { |
{ |
.type = SWITCH_TYPE_INT, |
.name = "enable_vlan", |
.description = "Enable VLAN mode", |
.set = ar8xxx_sw_set_vlan, |
.get = ar8xxx_sw_get_vlan, |
.max = 1 |
}, |
{ |
.type = SWITCH_TYPE_NOVAL, |
.name = "reset_mibs", |
.description = "Reset all MIB counters", |
.set = ar8xxx_sw_set_reset_mibs, |
}, |
{ |
.type = SWITCH_TYPE_INT, |
.name = "enable_mirror_rx", |
.description = "Enable mirroring of RX packets", |
.set = ar8xxx_sw_set_mirror_rx_enable, |
.get = ar8xxx_sw_get_mirror_rx_enable, |
.max = 1 |
}, |
{ |
.type = SWITCH_TYPE_INT, |
.name = "enable_mirror_tx", |
.description = "Enable mirroring of TX packets", |
.set = ar8xxx_sw_set_mirror_tx_enable, |
.get = ar8xxx_sw_get_mirror_tx_enable, |
.max = 1 |
}, |
{ |
.type = SWITCH_TYPE_INT, |
.name = "mirror_monitor_port", |
.description = "Mirror monitor port", |
.set = ar8xxx_sw_set_mirror_monitor_port, |
.get = ar8xxx_sw_get_mirror_monitor_port, |
.max = AR8216_NUM_PORTS - 1 |
}, |
{ |
.type = SWITCH_TYPE_INT, |
.name = "mirror_source_port", |
.description = "Mirror source port", |
.set = ar8xxx_sw_set_mirror_source_port, |
.get = ar8xxx_sw_get_mirror_source_port, |
.max = AR8216_NUM_PORTS - 1 |
}, |
{ |
.type = SWITCH_TYPE_STRING, |
.name = "arl_table", |
.description = "Get ARL table", |
.set = NULL, |
.get = ar8xxx_sw_get_arl_table, |
}, |
{ |
.type = SWITCH_TYPE_NOVAL, |
.name = "flush_arl_table", |
.description = "Flush ARL table", |
.set = ar8xxx_sw_set_flush_arl_table, |
}, |
}; |
|
const struct switch_attr ar8xxx_sw_attr_port[] = { |
{ |
.type = SWITCH_TYPE_NOVAL, |
.name = "reset_mib", |
.description = "Reset single port MIB counters", |
.set = ar8xxx_sw_set_port_reset_mib, |
}, |
{ |
.type = SWITCH_TYPE_STRING, |
.name = "mib", |
.description = "Get port's MIB counters", |
.set = NULL, |
.get = ar8xxx_sw_get_port_mib, |
}, |
{ |
.type = SWITCH_TYPE_NOVAL, |
.name = "flush_arl_table", |
.description = "Flush port's ARL table entries", |
.set = ar8xxx_sw_set_flush_port_arl_table, |
}, |
}; |
|
const struct switch_attr ar8xxx_sw_attr_vlan[1] = { |
{ |
.type = SWITCH_TYPE_INT, |
.name = "vid", |
.description = "VLAN ID (0-4094)", |
.set = ar8xxx_sw_set_vid, |
.get = ar8xxx_sw_get_vid, |
.max = 4094, |
}, |
}; |
|
static const struct switch_dev_ops ar8xxx_sw_ops = { |
.attr_global = { |
.attr = ar8xxx_sw_attr_globals, |
.n_attr = ARRAY_SIZE(ar8xxx_sw_attr_globals), |
}, |
.attr_port = { |
.attr = ar8xxx_sw_attr_port, |
.n_attr = ARRAY_SIZE(ar8xxx_sw_attr_port), |
}, |
.attr_vlan = { |
.attr = ar8xxx_sw_attr_vlan, |
.n_attr = ARRAY_SIZE(ar8xxx_sw_attr_vlan), |
}, |
.get_port_pvid = ar8xxx_sw_get_pvid, |
.set_port_pvid = ar8xxx_sw_set_pvid, |
.get_vlan_ports = ar8xxx_sw_get_ports, |
.set_vlan_ports = ar8xxx_sw_set_ports, |
.apply_config = ar8xxx_sw_hw_apply, |
.reset_switch = ar8xxx_sw_reset_switch, |
.get_port_link = ar8xxx_sw_get_port_link, |
/* The following op is disabled as it hogs the CPU and degrades performance. |
An implementation has been attempted in 4d8a66d but reading MIB data is slow |
on ar8xxx switches. |
|
The high CPU load has been traced down to the ar8xxx_reg_wait() call in |
ar8xxx_mib_op(), which has to usleep_range() till the MIB busy flag set by |
the request to update the MIB counter is cleared. */ |
#if 0 |
.get_port_stats = ar8xxx_sw_get_port_stats, |
#endif |
}; |
|
static const struct ar8xxx_chip ar8216_chip = { |
.caps = AR8XXX_CAP_MIB_COUNTERS, |
|
.reg_port_stats_start = 0x19000, |
.reg_port_stats_length = 0xa0, |
.reg_arl_ctrl = AR8216_REG_ATU_CTRL, |
|
.name = "Atheros AR8216", |
.ports = AR8216_NUM_PORTS, |
.vlans = AR8216_NUM_VLANS, |
.swops = &ar8xxx_sw_ops, |
|
.hw_init = ar8216_hw_init, |
.init_globals = ar8216_init_globals, |
.init_port = ar8216_init_port, |
.setup_port = ar8216_setup_port, |
.read_port_status = ar8216_read_port_status, |
.atu_flush = ar8216_atu_flush, |
.atu_flush_port = ar8216_atu_flush_port, |
.vtu_flush = ar8216_vtu_flush, |
.vtu_load_vlan = ar8216_vtu_load_vlan, |
.set_mirror_regs = ar8216_set_mirror_regs, |
.get_arl_entry = ar8216_get_arl_entry, |
.sw_hw_apply = ar8xxx_sw_hw_apply, |
|
.num_mibs = ARRAY_SIZE(ar8216_mibs), |
.mib_decs = ar8216_mibs, |
.mib_func = AR8216_REG_MIB_FUNC |
}; |
|
static const struct ar8xxx_chip ar8236_chip = { |
.caps = AR8XXX_CAP_MIB_COUNTERS, |
|
.reg_port_stats_start = 0x20000, |
.reg_port_stats_length = 0x100, |
.reg_arl_ctrl = AR8216_REG_ATU_CTRL, |
|
.name = "Atheros AR8236", |
.ports = AR8216_NUM_PORTS, |
.vlans = AR8216_NUM_VLANS, |
.swops = &ar8xxx_sw_ops, |
|
.hw_init = ar8216_hw_init, |
.init_globals = ar8236_init_globals, |
.init_port = ar8216_init_port, |
.setup_port = ar8236_setup_port, |
.read_port_status = ar8216_read_port_status, |
.atu_flush = ar8216_atu_flush, |
.atu_flush_port = ar8216_atu_flush_port, |
.vtu_flush = ar8216_vtu_flush, |
.vtu_load_vlan = ar8216_vtu_load_vlan, |
.set_mirror_regs = ar8216_set_mirror_regs, |
.get_arl_entry = ar8216_get_arl_entry, |
.sw_hw_apply = ar8xxx_sw_hw_apply, |
|
.num_mibs = ARRAY_SIZE(ar8236_mibs), |
.mib_decs = ar8236_mibs, |
.mib_func = AR8216_REG_MIB_FUNC |
}; |
|
static const struct ar8xxx_chip ar8316_chip = { |
.caps = AR8XXX_CAP_GIGE | AR8XXX_CAP_MIB_COUNTERS, |
|
.reg_port_stats_start = 0x20000, |
.reg_port_stats_length = 0x100, |
.reg_arl_ctrl = AR8216_REG_ATU_CTRL, |
|
.name = "Atheros AR8316", |
.ports = AR8216_NUM_PORTS, |
.vlans = AR8X16_MAX_VLANS, |
.swops = &ar8xxx_sw_ops, |
|
.hw_init = ar8316_hw_init, |
.init_globals = ar8316_init_globals, |
.init_port = ar8216_init_port, |
.setup_port = ar8216_setup_port, |
.read_port_status = ar8216_read_port_status, |
.atu_flush = ar8216_atu_flush, |
.atu_flush_port = ar8216_atu_flush_port, |
.vtu_flush = ar8216_vtu_flush, |
.vtu_load_vlan = ar8216_vtu_load_vlan, |
.set_mirror_regs = ar8216_set_mirror_regs, |
.get_arl_entry = ar8216_get_arl_entry, |
.sw_hw_apply = ar8xxx_sw_hw_apply, |
|
.num_mibs = ARRAY_SIZE(ar8236_mibs), |
.mib_decs = ar8236_mibs, |
.mib_func = AR8216_REG_MIB_FUNC |
}; |
|
static int |
ar8xxx_id_chip(struct ar8xxx_priv *priv) |
{ |
u32 val; |
u16 id; |
int i; |
|
val = ar8xxx_read(priv, AR8216_REG_CTRL); |
if (val == ~0) |
return -ENODEV; |
|
id = val & (AR8216_CTRL_REVISION | AR8216_CTRL_VERSION); |
for (i = 0; i < AR8X16_PROBE_RETRIES; i++) { |
u16 t; |
|
val = ar8xxx_read(priv, AR8216_REG_CTRL); |
if (val == ~0) |
return -ENODEV; |
|
t = val & (AR8216_CTRL_REVISION | AR8216_CTRL_VERSION); |
if (t != id) |
return -ENODEV; |
} |
|
priv->chip_ver = (id & AR8216_CTRL_VERSION) >> AR8216_CTRL_VERSION_S; |
priv->chip_rev = (id & AR8216_CTRL_REVISION); |
|
switch (priv->chip_ver) { |
case AR8XXX_VER_AR8216: |
priv->chip = &ar8216_chip; |
break; |
case AR8XXX_VER_AR8236: |
priv->chip = &ar8236_chip; |
break; |
case AR8XXX_VER_AR8316: |
priv->chip = &ar8316_chip; |
break; |
case AR8XXX_VER_AR8327: |
priv->chip = &ar8327_chip; |
break; |
case AR8XXX_VER_AR8337: |
priv->chip = &ar8337_chip; |
break; |
default: |
pr_err("ar8216: Unknown Atheros device [ver=%d, rev=%d]\n", |
priv->chip_ver, priv->chip_rev); |
|
return -ENODEV; |
} |
|
return 0; |
} |
|
static void |
ar8xxx_mib_work_func(struct work_struct *work) |
{ |
struct ar8xxx_priv *priv; |
int err; |
|
priv = container_of(work, struct ar8xxx_priv, mib_work.work); |
|
mutex_lock(&priv->mib_lock); |
|
err = ar8xxx_mib_capture(priv); |
if (err) |
goto next_port; |
|
ar8xxx_mib_fetch_port_stat(priv, priv->mib_next_port, false); |
|
next_port: |
priv->mib_next_port++; |
if (priv->mib_next_port >= priv->dev.ports) |
priv->mib_next_port = 0; |
|
mutex_unlock(&priv->mib_lock); |
schedule_delayed_work(&priv->mib_work, |
msecs_to_jiffies(AR8XXX_MIB_WORK_DELAY)); |
} |
|
static int |
ar8xxx_mib_init(struct ar8xxx_priv *priv) |
{ |
unsigned int len; |
|
if (!ar8xxx_has_mib_counters(priv)) |
return 0; |
|
BUG_ON(!priv->chip->mib_decs || !priv->chip->num_mibs); |
|
len = priv->dev.ports * priv->chip->num_mibs * |
sizeof(*priv->mib_stats); |
priv->mib_stats = kzalloc(len, GFP_KERNEL); |
|
if (!priv->mib_stats) |
return -ENOMEM; |
|
return 0; |
} |
|
static void |
ar8xxx_mib_start(struct ar8xxx_priv *priv) |
{ |
if (!ar8xxx_has_mib_counters(priv)) |
return; |
|
schedule_delayed_work(&priv->mib_work, |
msecs_to_jiffies(AR8XXX_MIB_WORK_DELAY)); |
} |
|
static void |
ar8xxx_mib_stop(struct ar8xxx_priv *priv) |
{ |
if (!ar8xxx_has_mib_counters(priv)) |
return; |
|
cancel_delayed_work_sync(&priv->mib_work); |
} |
|
static struct ar8xxx_priv * |
ar8xxx_create(void) |
{ |
struct ar8xxx_priv *priv; |
|
priv = kzalloc(sizeof(struct ar8xxx_priv), GFP_KERNEL); |
if (priv == NULL) |
return NULL; |
|
mutex_init(&priv->reg_mutex); |
mutex_init(&priv->mib_lock); |
INIT_DELAYED_WORK(&priv->mib_work, ar8xxx_mib_work_func); |
|
return priv; |
} |
|
static void |
ar8xxx_free(struct ar8xxx_priv *priv) |
{ |
if (priv->chip && priv->chip->cleanup) |
priv->chip->cleanup(priv); |
|
kfree(priv->chip_data); |
kfree(priv->mib_stats); |
kfree(priv); |
} |
|
static int |
ar8xxx_probe_switch(struct ar8xxx_priv *priv) |
{ |
const struct ar8xxx_chip *chip; |
struct switch_dev *swdev; |
int ret; |
|
ret = ar8xxx_id_chip(priv); |
if (ret) |
return ret; |
|
chip = priv->chip; |
|
swdev = &priv->dev; |
swdev->cpu_port = AR8216_PORT_CPU; |
swdev->name = chip->name; |
swdev->vlans = chip->vlans; |
swdev->ports = chip->ports; |
swdev->ops = chip->swops; |
|
ret = ar8xxx_mib_init(priv); |
if (ret) |
return ret; |
|
return 0; |
} |
|
static int |
ar8xxx_start(struct ar8xxx_priv *priv) |
{ |
int ret; |
|
priv->init = true; |
|
ret = priv->chip->hw_init(priv); |
if (ret) |
return ret; |
|
ret = ar8xxx_sw_reset_switch(&priv->dev); |
if (ret) |
return ret; |
|
priv->init = false; |
|
ar8xxx_mib_start(priv); |
|
return 0; |
} |
|
static int |
ar8xxx_phy_config_init(struct phy_device *phydev) |
{ |
struct ar8xxx_priv *priv = phydev->priv; |
struct net_device *dev = phydev->attached_dev; |
int ret; |
|
if (WARN_ON(!priv)) |
return -ENODEV; |
|
if (priv->chip->config_at_probe) |
return ar8xxx_phy_check_aneg(phydev); |
|
priv->phy = phydev; |
|
if (phydev->mdio.addr != 0) { |
if (chip_is_ar8316(priv)) { |
/* switch device has been initialized, reinit */ |
priv->dev.ports = (AR8216_NUM_PORTS - 1); |
priv->initialized = false; |
priv->port4_phy = true; |
ar8316_hw_init(priv); |
return 0; |
} |
|
return 0; |
} |
|
ret = ar8xxx_start(priv); |
if (ret) |
return ret; |
|
/* VID fixup only needed on ar8216 */ |
if (chip_is_ar8216(priv)) { |
dev->phy_ptr = priv; |
dev->priv_flags |= IFF_NO_IP_ALIGN; |
dev->eth_mangle_rx = ar8216_mangle_rx; |
dev->eth_mangle_tx = ar8216_mangle_tx; |
} |
|
return 0; |
} |
|
static bool |
ar8xxx_check_link_states(struct ar8xxx_priv *priv) |
{ |
bool link_new, changed = false; |
u32 status; |
int i; |
|
mutex_lock(&priv->reg_mutex); |
|
for (i = 0; i < priv->dev.ports; i++) { |
status = priv->chip->read_port_status(priv, i); |
link_new = !!(status & AR8216_PORT_STATUS_LINK_UP); |
if (link_new == priv->link_up[i]) |
continue; |
|
priv->link_up[i] = link_new; |
changed = true; |
/* flush ARL entries for this port if it went down*/ |
if (!link_new) |
priv->chip->atu_flush_port(priv, i); |
dev_info(&priv->phy->mdio.dev, "Port %d is %s\n", |
i, link_new ? "up" : "down"); |
} |
|
mutex_unlock(&priv->reg_mutex); |
|
return changed; |
} |
|
static int |
ar8xxx_phy_read_status(struct phy_device *phydev) |
{ |
struct ar8xxx_priv *priv = phydev->priv; |
struct switch_port_link link; |
|
/* check for switch port link changes */ |
if (phydev->state == PHY_CHANGELINK) |
ar8xxx_check_link_states(priv); |
|
if (phydev->mdio.addr != 0) |
return genphy_read_status(phydev); |
|
ar8216_read_port_link(priv, phydev->mdio.addr, &link); |
phydev->link = !!link.link; |
if (!phydev->link) |
return 0; |
|
switch (link.speed) { |
case SWITCH_PORT_SPEED_10: |
phydev->speed = SPEED_10; |
break; |
case SWITCH_PORT_SPEED_100: |
phydev->speed = SPEED_100; |
break; |
case SWITCH_PORT_SPEED_1000: |
phydev->speed = SPEED_1000; |
break; |
default: |
phydev->speed = 0; |
} |
phydev->duplex = link.duplex ? DUPLEX_FULL : DUPLEX_HALF; |
|
phydev->state = PHY_RUNNING; |
netif_carrier_on(phydev->attached_dev); |
phydev->adjust_link(phydev->attached_dev); |
|
return 0; |
} |
|
static int |
ar8xxx_phy_config_aneg(struct phy_device *phydev) |
{ |
if (phydev->mdio.addr == 0) |
return 0; |
|
return genphy_config_aneg(phydev); |
} |
|
static const u32 ar8xxx_phy_ids[] = { |
0x004dd033, |
0x004dd034, /* AR8327 */ |
0x004dd036, /* AR8337 */ |
0x004dd041, |
0x004dd042, |
0x004dd043, /* AR8236 */ |
}; |
|
static bool |
ar8xxx_phy_match(u32 phy_id) |
{ |
int i; |
|
for (i = 0; i < ARRAY_SIZE(ar8xxx_phy_ids); i++) |
if (phy_id == ar8xxx_phy_ids[i]) |
return true; |
|
return false; |
} |
|
static bool |
ar8xxx_is_possible(struct mii_bus *bus) |
{ |
unsigned int i, found_phys = 0; |
|
for (i = 0; i < 5; i++) { |
u32 phy_id; |
|
phy_id = mdiobus_read(bus, i, MII_PHYSID1) << 16; |
phy_id |= mdiobus_read(bus, i, MII_PHYSID2); |
if (ar8xxx_phy_match(phy_id)) { |
found_phys++; |
} else if (phy_id) { |
pr_debug("ar8xxx: unknown PHY at %s:%02x id:%08x\n", |
dev_name(&bus->dev), i, phy_id); |
} |
} |
return !!found_phys; |
} |
|
static int |
ar8xxx_phy_probe(struct phy_device *phydev) |
{ |
struct ar8xxx_priv *priv; |
struct switch_dev *swdev; |
int ret; |
|
/* skip PHYs at unused adresses */ |
if (phydev->mdio.addr != 0 && phydev->mdio.addr != 4) |
return -ENODEV; |
|
if (!ar8xxx_is_possible(phydev->mdio.bus)) |
return -ENODEV; |
|
mutex_lock(&ar8xxx_dev_list_lock); |
list_for_each_entry(priv, &ar8xxx_dev_list, list) |
if (priv->mii_bus == phydev->mdio.bus) |
goto found; |
|
priv = ar8xxx_create(); |
if (priv == NULL) { |
ret = -ENOMEM; |
goto unlock; |
} |
|
priv->mii_bus = phydev->mdio.bus; |
|
ret = ar8xxx_probe_switch(priv); |
if (ret) |
goto free_priv; |
|
swdev = &priv->dev; |
swdev->alias = dev_name(&priv->mii_bus->dev); |
ret = register_switch(swdev, NULL); |
if (ret) |
goto free_priv; |
|
pr_info("%s: %s rev. %u switch registered on %s\n", |
swdev->devname, swdev->name, priv->chip_rev, |
dev_name(&priv->mii_bus->dev)); |
|
list_add(&priv->list, &ar8xxx_dev_list); |
|
found: |
priv->use_count++; |
|
if (phydev->mdio.addr == 0) { |
if (ar8xxx_has_gige(priv)) { |
phydev->supported = SUPPORTED_1000baseT_Full; |
phydev->advertising = ADVERTISED_1000baseT_Full; |
} else { |
phydev->supported = SUPPORTED_100baseT_Full; |
phydev->advertising = ADVERTISED_100baseT_Full; |
} |
|
if (priv->chip->config_at_probe) { |
priv->phy = phydev; |
|
ret = ar8xxx_start(priv); |
if (ret) |
goto err_unregister_switch; |
} |
} else { |
if (ar8xxx_has_gige(priv)) { |
phydev->supported |= SUPPORTED_1000baseT_Full; |
phydev->advertising |= ADVERTISED_1000baseT_Full; |
} |
} |
|
phydev->priv = priv; |
|
mutex_unlock(&ar8xxx_dev_list_lock); |
|
return 0; |
|
err_unregister_switch: |
if (--priv->use_count) |
goto unlock; |
|
unregister_switch(&priv->dev); |
|
free_priv: |
ar8xxx_free(priv); |
unlock: |
mutex_unlock(&ar8xxx_dev_list_lock); |
return ret; |
} |
|
static void |
ar8xxx_phy_detach(struct phy_device *phydev) |
{ |
struct net_device *dev = phydev->attached_dev; |
|
if (!dev) |
return; |
|
dev->phy_ptr = NULL; |
dev->priv_flags &= ~IFF_NO_IP_ALIGN; |
dev->eth_mangle_rx = NULL; |
dev->eth_mangle_tx = NULL; |
} |
|
static void |
ar8xxx_phy_remove(struct phy_device *phydev) |
{ |
struct ar8xxx_priv *priv = phydev->priv; |
|
if (WARN_ON(!priv)) |
return; |
|
phydev->priv = NULL; |
|
mutex_lock(&ar8xxx_dev_list_lock); |
|
if (--priv->use_count > 0) { |
mutex_unlock(&ar8xxx_dev_list_lock); |
return; |
} |
|
list_del(&priv->list); |
mutex_unlock(&ar8xxx_dev_list_lock); |
|
unregister_switch(&priv->dev); |
ar8xxx_mib_stop(priv); |
ar8xxx_free(priv); |
} |
|
static int |
ar8xxx_phy_soft_reset(struct phy_device *phydev) |
{ |
/* we don't need an extra reset */ |
return 0; |
} |
|
static struct phy_driver ar8xxx_phy_driver[] = { |
{ |
.phy_id = 0x004d0000, |
.name = "Atheros AR8216/AR8236/AR8316", |
.phy_id_mask = 0xffff0000, |
.features = PHY_BASIC_FEATURES, |
.probe = ar8xxx_phy_probe, |
.remove = ar8xxx_phy_remove, |
.detach = ar8xxx_phy_detach, |
.config_init = ar8xxx_phy_config_init, |
.config_aneg = ar8xxx_phy_config_aneg, |
.read_status = ar8xxx_phy_read_status, |
.soft_reset = ar8xxx_phy_soft_reset, |
} |
}; |
|
module_phy_driver(ar8xxx_phy_driver); |
MODULE_LICENSE("GPL"); |
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/ar8216.h |
@@ -0,0 +1,644 @@ |
/* |
* ar8216.h: AR8216 switch driver |
* |
* Copyright (C) 2009 Felix Fietkau <nbd@nbd.name> |
* |
* 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; either version 2 |
* of the License, or (at your option) any later version. |
* |
* 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. |
*/ |
|
#ifndef __AR8216_H |
#define __AR8216_H |
|
#define BITS(_s, _n) (((1UL << (_n)) - 1) << _s) |
|
#define AR8XXX_CAP_GIGE BIT(0) |
#define AR8XXX_CAP_MIB_COUNTERS BIT(1) |
|
#define AR8XXX_NUM_PHYS 5 |
#define AR8216_PORT_CPU 0 |
#define AR8216_NUM_PORTS 6 |
#define AR8216_NUM_VLANS 16 |
#define AR8316_NUM_VLANS 4096 |
|
/* size of the vlan table */ |
#define AR8X16_MAX_VLANS 128 |
#define AR8X16_PROBE_RETRIES 10 |
#define AR8X16_MAX_PORTS 8 |
|
#define AR8XXX_REG_ARL_CTRL_AGE_TIME_SECS 7 |
#define AR8XXX_DEFAULT_ARL_AGE_TIME 300 |
|
/* Atheros specific MII registers */ |
#define MII_ATH_MMD_ADDR 0x0d |
#define MII_ATH_MMD_DATA 0x0e |
#define MII_ATH_DBG_ADDR 0x1d |
#define MII_ATH_DBG_DATA 0x1e |
|
#define AR8216_REG_CTRL 0x0000 |
#define AR8216_CTRL_REVISION BITS(0, 8) |
#define AR8216_CTRL_REVISION_S 0 |
#define AR8216_CTRL_VERSION BITS(8, 8) |
#define AR8216_CTRL_VERSION_S 8 |
#define AR8216_CTRL_RESET BIT(31) |
|
#define AR8216_REG_FLOOD_MASK 0x002C |
#define AR8216_FM_UNI_DEST_PORTS BITS(0, 6) |
#define AR8216_FM_MULTI_DEST_PORTS BITS(16, 6) |
#define AR8236_FM_CPU_BROADCAST_EN BIT(26) |
#define AR8236_FM_CPU_BCAST_FWD_EN BIT(25) |
|
#define AR8216_REG_GLOBAL_CTRL 0x0030 |
#define AR8216_GCTRL_MTU BITS(0, 11) |
#define AR8236_GCTRL_MTU BITS(0, 14) |
#define AR8316_GCTRL_MTU BITS(0, 14) |
|
#define AR8216_REG_VTU 0x0040 |
#define AR8216_VTU_OP BITS(0, 3) |
#define AR8216_VTU_OP_NOOP 0x0 |
#define AR8216_VTU_OP_FLUSH 0x1 |
#define AR8216_VTU_OP_LOAD 0x2 |
#define AR8216_VTU_OP_PURGE 0x3 |
#define AR8216_VTU_OP_REMOVE_PORT 0x4 |
#define AR8216_VTU_ACTIVE BIT(3) |
#define AR8216_VTU_FULL BIT(4) |
#define AR8216_VTU_PORT BITS(8, 4) |
#define AR8216_VTU_PORT_S 8 |
#define AR8216_VTU_VID BITS(16, 12) |
#define AR8216_VTU_VID_S 16 |
#define AR8216_VTU_PRIO BITS(28, 3) |
#define AR8216_VTU_PRIO_S 28 |
#define AR8216_VTU_PRIO_EN BIT(31) |
|
#define AR8216_REG_VTU_DATA 0x0044 |
#define AR8216_VTUDATA_MEMBER BITS(0, 10) |
#define AR8236_VTUDATA_MEMBER BITS(0, 7) |
#define AR8216_VTUDATA_VALID BIT(11) |
|
#define AR8216_REG_ATU_FUNC0 0x0050 |
#define AR8216_ATU_OP BITS(0, 3) |
#define AR8216_ATU_OP_NOOP 0x0 |
#define AR8216_ATU_OP_FLUSH 0x1 |
#define AR8216_ATU_OP_LOAD 0x2 |
#define AR8216_ATU_OP_PURGE 0x3 |
#define AR8216_ATU_OP_FLUSH_UNLOCKED 0x4 |
#define AR8216_ATU_OP_FLUSH_PORT 0x5 |
#define AR8216_ATU_OP_GET_NEXT 0x6 |
#define AR8216_ATU_ACTIVE BIT(3) |
#define AR8216_ATU_PORT_NUM BITS(8, 4) |
#define AR8216_ATU_PORT_NUM_S 8 |
#define AR8216_ATU_FULL_VIO BIT(12) |
#define AR8216_ATU_ADDR5 BITS(16, 8) |
#define AR8216_ATU_ADDR5_S 16 |
#define AR8216_ATU_ADDR4 BITS(24, 8) |
#define AR8216_ATU_ADDR4_S 24 |
|
#define AR8216_REG_ATU_FUNC1 0x0054 |
#define AR8216_ATU_ADDR3 BITS(0, 8) |
#define AR8216_ATU_ADDR3_S 0 |
#define AR8216_ATU_ADDR2 BITS(8, 8) |
#define AR8216_ATU_ADDR2_S 8 |
#define AR8216_ATU_ADDR1 BITS(16, 8) |
#define AR8216_ATU_ADDR1_S 16 |
#define AR8216_ATU_ADDR0 BITS(24, 8) |
#define AR8216_ATU_ADDR0_S 24 |
|
#define AR8216_REG_ATU_FUNC2 0x0058 |
#define AR8216_ATU_PORTS BITS(0, 6) |
#define AR8216_ATU_PORT0 BIT(0) |
#define AR8216_ATU_PORT1 BIT(1) |
#define AR8216_ATU_PORT2 BIT(2) |
#define AR8216_ATU_PORT3 BIT(3) |
#define AR8216_ATU_PORT4 BIT(4) |
#define AR8216_ATU_PORT5 BIT(5) |
#define AR8216_ATU_STATUS BITS(16, 4) |
#define AR8216_ATU_STATUS_S 16 |
|
#define AR8216_REG_ATU_CTRL 0x005C |
#define AR8216_ATU_CTRL_AGE_EN BIT(17) |
#define AR8216_ATU_CTRL_AGE_TIME BITS(0, 16) |
#define AR8216_ATU_CTRL_AGE_TIME_S 0 |
#define AR8236_ATU_CTRL_RES BIT(20) |
|
#define AR8216_REG_MIB_FUNC 0x0080 |
#define AR8216_MIB_TIMER BITS(0, 16) |
#define AR8216_MIB_AT_HALF_EN BIT(16) |
#define AR8216_MIB_BUSY BIT(17) |
#define AR8216_MIB_FUNC BITS(24, 3) |
#define AR8216_MIB_FUNC_S 24 |
#define AR8216_MIB_FUNC_NO_OP 0x0 |
#define AR8216_MIB_FUNC_FLUSH 0x1 |
#define AR8216_MIB_FUNC_CAPTURE 0x3 |
#define AR8236_MIB_EN BIT(30) |
|
#define AR8216_REG_GLOBAL_CPUPORT 0x0078 |
#define AR8216_GLOBAL_CPUPORT_MIRROR_PORT BITS(4, 4) |
#define AR8216_GLOBAL_CPUPORT_MIRROR_PORT_S 4 |
|
#define AR8216_PORT_OFFSET(_i) (0x0100 * (_i + 1)) |
#define AR8216_REG_PORT_STATUS(_i) (AR8216_PORT_OFFSET(_i) + 0x0000) |
#define AR8216_PORT_STATUS_SPEED BITS(0,2) |
#define AR8216_PORT_STATUS_SPEED_S 0 |
#define AR8216_PORT_STATUS_TXMAC BIT(2) |
#define AR8216_PORT_STATUS_RXMAC BIT(3) |
#define AR8216_PORT_STATUS_TXFLOW BIT(4) |
#define AR8216_PORT_STATUS_RXFLOW BIT(5) |
#define AR8216_PORT_STATUS_DUPLEX BIT(6) |
#define AR8216_PORT_STATUS_LINK_UP BIT(8) |
#define AR8216_PORT_STATUS_LINK_AUTO BIT(9) |
#define AR8216_PORT_STATUS_LINK_PAUSE BIT(10) |
#define AR8216_PORT_STATUS_FLOW_CONTROL BIT(12) |
|
#define AR8216_REG_PORT_CTRL(_i) (AR8216_PORT_OFFSET(_i) + 0x0004) |
|
/* port forwarding state */ |
#define AR8216_PORT_CTRL_STATE BITS(0, 3) |
#define AR8216_PORT_CTRL_STATE_S 0 |
|
#define AR8216_PORT_CTRL_LEARN_LOCK BIT(7) |
|
/* egress 802.1q mode */ |
#define AR8216_PORT_CTRL_VLAN_MODE BITS(8, 2) |
#define AR8216_PORT_CTRL_VLAN_MODE_S 8 |
|
#define AR8216_PORT_CTRL_IGMP_SNOOP BIT(10) |
#define AR8216_PORT_CTRL_HEADER BIT(11) |
#define AR8216_PORT_CTRL_MAC_LOOP BIT(12) |
#define AR8216_PORT_CTRL_SINGLE_VLAN BIT(13) |
#define AR8216_PORT_CTRL_LEARN BIT(14) |
#define AR8216_PORT_CTRL_MIRROR_TX BIT(16) |
#define AR8216_PORT_CTRL_MIRROR_RX BIT(17) |
|
#define AR8216_REG_PORT_VLAN(_i) (AR8216_PORT_OFFSET(_i) + 0x0008) |
|
#define AR8216_PORT_VLAN_DEFAULT_ID BITS(0, 12) |
#define AR8216_PORT_VLAN_DEFAULT_ID_S 0 |
|
#define AR8216_PORT_VLAN_DEST_PORTS BITS(16, 9) |
#define AR8216_PORT_VLAN_DEST_PORTS_S 16 |
|
/* bit0 added to the priority field of egress frames */ |
#define AR8216_PORT_VLAN_TX_PRIO BIT(27) |
|
/* port default priority */ |
#define AR8216_PORT_VLAN_PRIORITY BITS(28, 2) |
#define AR8216_PORT_VLAN_PRIORITY_S 28 |
|
/* ingress 802.1q mode */ |
#define AR8216_PORT_VLAN_MODE BITS(30, 2) |
#define AR8216_PORT_VLAN_MODE_S 30 |
|
#define AR8216_REG_PORT_RATE(_i) (AR8216_PORT_OFFSET(_i) + 0x000c) |
#define AR8216_REG_PORT_PRIO(_i) (AR8216_PORT_OFFSET(_i) + 0x0010) |
|
#define AR8216_STATS_RXBROAD 0x00 |
#define AR8216_STATS_RXPAUSE 0x04 |
#define AR8216_STATS_RXMULTI 0x08 |
#define AR8216_STATS_RXFCSERR 0x0c |
#define AR8216_STATS_RXALIGNERR 0x10 |
#define AR8216_STATS_RXRUNT 0x14 |
#define AR8216_STATS_RXFRAGMENT 0x18 |
#define AR8216_STATS_RX64BYTE 0x1c |
#define AR8216_STATS_RX128BYTE 0x20 |
#define AR8216_STATS_RX256BYTE 0x24 |
#define AR8216_STATS_RX512BYTE 0x28 |
#define AR8216_STATS_RX1024BYTE 0x2c |
#define AR8216_STATS_RXMAXBYTE 0x30 |
#define AR8216_STATS_RXTOOLONG 0x34 |
#define AR8216_STATS_RXGOODBYTE 0x38 |
#define AR8216_STATS_RXBADBYTE 0x40 |
#define AR8216_STATS_RXOVERFLOW 0x48 |
#define AR8216_STATS_FILTERED 0x4c |
#define AR8216_STATS_TXBROAD 0x50 |
#define AR8216_STATS_TXPAUSE 0x54 |
#define AR8216_STATS_TXMULTI 0x58 |
#define AR8216_STATS_TXUNDERRUN 0x5c |
#define AR8216_STATS_TX64BYTE 0x60 |
#define AR8216_STATS_TX128BYTE 0x64 |
#define AR8216_STATS_TX256BYTE 0x68 |
#define AR8216_STATS_TX512BYTE 0x6c |
#define AR8216_STATS_TX1024BYTE 0x70 |
#define AR8216_STATS_TXMAXBYTE 0x74 |
#define AR8216_STATS_TXOVERSIZE 0x78 |
#define AR8216_STATS_TXBYTE 0x7c |
#define AR8216_STATS_TXCOLLISION 0x84 |
#define AR8216_STATS_TXABORTCOL 0x88 |
#define AR8216_STATS_TXMULTICOL 0x8c |
#define AR8216_STATS_TXSINGLECOL 0x90 |
#define AR8216_STATS_TXEXCDEFER 0x94 |
#define AR8216_STATS_TXDEFER 0x98 |
#define AR8216_STATS_TXLATECOL 0x9c |
|
#define AR8236_REG_PORT_VLAN(_i) (AR8216_PORT_OFFSET((_i)) + 0x0008) |
#define AR8236_PORT_VLAN_DEFAULT_ID BITS(16, 12) |
#define AR8236_PORT_VLAN_DEFAULT_ID_S 16 |
#define AR8236_PORT_VLAN_PRIORITY BITS(29, 3) |
#define AR8236_PORT_VLAN_PRIORITY_S 28 |
|
#define AR8236_REG_PORT_VLAN2(_i) (AR8216_PORT_OFFSET((_i)) + 0x000c) |
#define AR8236_PORT_VLAN2_MEMBER BITS(16, 7) |
#define AR8236_PORT_VLAN2_MEMBER_S 16 |
#define AR8236_PORT_VLAN2_TX_PRIO BIT(23) |
#define AR8236_PORT_VLAN2_VLAN_MODE BITS(30, 2) |
#define AR8236_PORT_VLAN2_VLAN_MODE_S 30 |
|
#define AR8236_STATS_RXBROAD 0x00 |
#define AR8236_STATS_RXPAUSE 0x04 |
#define AR8236_STATS_RXMULTI 0x08 |
#define AR8236_STATS_RXFCSERR 0x0c |
#define AR8236_STATS_RXALIGNERR 0x10 |
#define AR8236_STATS_RXRUNT 0x14 |
#define AR8236_STATS_RXFRAGMENT 0x18 |
#define AR8236_STATS_RX64BYTE 0x1c |
#define AR8236_STATS_RX128BYTE 0x20 |
#define AR8236_STATS_RX256BYTE 0x24 |
#define AR8236_STATS_RX512BYTE 0x28 |
#define AR8236_STATS_RX1024BYTE 0x2c |
#define AR8236_STATS_RX1518BYTE 0x30 |
#define AR8236_STATS_RXMAXBYTE 0x34 |
#define AR8236_STATS_RXTOOLONG 0x38 |
#define AR8236_STATS_RXGOODBYTE 0x3c |
#define AR8236_STATS_RXBADBYTE 0x44 |
#define AR8236_STATS_RXOVERFLOW 0x4c |
#define AR8236_STATS_FILTERED 0x50 |
#define AR8236_STATS_TXBROAD 0x54 |
#define AR8236_STATS_TXPAUSE 0x58 |
#define AR8236_STATS_TXMULTI 0x5c |
#define AR8236_STATS_TXUNDERRUN 0x60 |
#define AR8236_STATS_TX64BYTE 0x64 |
#define AR8236_STATS_TX128BYTE 0x68 |
#define AR8236_STATS_TX256BYTE 0x6c |
#define AR8236_STATS_TX512BYTE 0x70 |
#define AR8236_STATS_TX1024BYTE 0x74 |
#define AR8236_STATS_TX1518BYTE 0x78 |
#define AR8236_STATS_TXMAXBYTE 0x7c |
#define AR8236_STATS_TXOVERSIZE 0x80 |
#define AR8236_STATS_TXBYTE 0x84 |
#define AR8236_STATS_TXCOLLISION 0x8c |
#define AR8236_STATS_TXABORTCOL 0x90 |
#define AR8236_STATS_TXMULTICOL 0x94 |
#define AR8236_STATS_TXSINGLECOL 0x98 |
#define AR8236_STATS_TXEXCDEFER 0x9c |
#define AR8236_STATS_TXDEFER 0xa0 |
#define AR8236_STATS_TXLATECOL 0xa4 |
|
#define AR8316_REG_POSTRIP 0x0008 |
#define AR8316_POSTRIP_MAC0_GMII_EN BIT(0) |
#define AR8316_POSTRIP_MAC0_RGMII_EN BIT(1) |
#define AR8316_POSTRIP_PHY4_GMII_EN BIT(2) |
#define AR8316_POSTRIP_PHY4_RGMII_EN BIT(3) |
#define AR8316_POSTRIP_MAC0_MAC_MODE BIT(4) |
#define AR8316_POSTRIP_RTL_MODE BIT(5) |
#define AR8316_POSTRIP_RGMII_RXCLK_DELAY_EN BIT(6) |
#define AR8316_POSTRIP_RGMII_TXCLK_DELAY_EN BIT(7) |
#define AR8316_POSTRIP_SERDES_EN BIT(8) |
#define AR8316_POSTRIP_SEL_ANA_RST BIT(9) |
#define AR8316_POSTRIP_GATE_25M_EN BIT(10) |
#define AR8316_POSTRIP_SEL_CLK25M BIT(11) |
#define AR8316_POSTRIP_HIB_PULSE_HW BIT(12) |
#define AR8316_POSTRIP_DBG_MODE_I BIT(13) |
#define AR8316_POSTRIP_MAC5_MAC_MODE BIT(14) |
#define AR8316_POSTRIP_MAC5_PHY_MODE BIT(15) |
#define AR8316_POSTRIP_POWER_DOWN_HW BIT(16) |
#define AR8316_POSTRIP_LPW_STATE_EN BIT(17) |
#define AR8316_POSTRIP_MAN_EN BIT(18) |
#define AR8316_POSTRIP_PHY_PLL_ON BIT(19) |
#define AR8316_POSTRIP_LPW_EXIT BIT(20) |
#define AR8316_POSTRIP_TXDELAY_S0 BIT(21) |
#define AR8316_POSTRIP_TXDELAY_S1 BIT(22) |
#define AR8316_POSTRIP_RXDELAY_S0 BIT(23) |
#define AR8316_POSTRIP_LED_OPEN_EN BIT(24) |
#define AR8316_POSTRIP_SPI_EN BIT(25) |
#define AR8316_POSTRIP_RXDELAY_S1 BIT(26) |
#define AR8316_POSTRIP_POWER_ON_SEL BIT(31) |
|
/* port speed */ |
enum { |
AR8216_PORT_SPEED_10M = 0, |
AR8216_PORT_SPEED_100M = 1, |
AR8216_PORT_SPEED_1000M = 2, |
AR8216_PORT_SPEED_ERR = 3, |
}; |
|
/* ingress 802.1q mode */ |
enum { |
AR8216_IN_PORT_ONLY = 0, |
AR8216_IN_PORT_FALLBACK = 1, |
AR8216_IN_VLAN_ONLY = 2, |
AR8216_IN_SECURE = 3 |
}; |
|
/* egress 802.1q mode */ |
enum { |
AR8216_OUT_KEEP = 0, |
AR8216_OUT_STRIP_VLAN = 1, |
AR8216_OUT_ADD_VLAN = 2 |
}; |
|
/* port forwarding state */ |
enum { |
AR8216_PORT_STATE_DISABLED = 0, |
AR8216_PORT_STATE_BLOCK = 1, |
AR8216_PORT_STATE_LISTEN = 2, |
AR8216_PORT_STATE_LEARN = 3, |
AR8216_PORT_STATE_FORWARD = 4 |
}; |
|
enum { |
AR8XXX_VER_AR8216 = 0x01, |
AR8XXX_VER_AR8236 = 0x03, |
AR8XXX_VER_AR8316 = 0x10, |
AR8XXX_VER_AR8327 = 0x12, |
AR8XXX_VER_AR8337 = 0x13, |
}; |
|
#define AR8XXX_NUM_ARL_RECORDS 100 |
|
enum arl_op { |
AR8XXX_ARL_INITIALIZE, |
AR8XXX_ARL_GET_NEXT |
}; |
|
struct arl_entry { |
u8 port; |
u8 mac[6]; |
}; |
|
struct ar8xxx_priv; |
|
struct ar8xxx_mib_desc { |
unsigned int size; |
unsigned int offset; |
const char *name; |
}; |
|
struct ar8xxx_chip { |
unsigned long caps; |
bool config_at_probe; |
bool mii_lo_first; |
|
/* parameters to calculate REG_PORT_STATS_BASE */ |
unsigned reg_port_stats_start; |
unsigned reg_port_stats_length; |
|
unsigned reg_arl_ctrl; |
|
int (*hw_init)(struct ar8xxx_priv *priv); |
void (*cleanup)(struct ar8xxx_priv *priv); |
|
const char *name; |
int vlans; |
int ports; |
const struct switch_dev_ops *swops; |
|
void (*init_globals)(struct ar8xxx_priv *priv); |
void (*init_port)(struct ar8xxx_priv *priv, int port); |
void (*setup_port)(struct ar8xxx_priv *priv, int port, u32 members); |
u32 (*read_port_status)(struct ar8xxx_priv *priv, int port); |
u32 (*read_port_eee_status)(struct ar8xxx_priv *priv, int port); |
int (*atu_flush)(struct ar8xxx_priv *priv); |
int (*atu_flush_port)(struct ar8xxx_priv *priv, int port); |
void (*vtu_flush)(struct ar8xxx_priv *priv); |
void (*vtu_load_vlan)(struct ar8xxx_priv *priv, u32 vid, u32 port_mask); |
void (*phy_fixup)(struct ar8xxx_priv *priv, int phy); |
void (*set_mirror_regs)(struct ar8xxx_priv *priv); |
void (*get_arl_entry)(struct ar8xxx_priv *priv, struct arl_entry *a, |
u32 *status, enum arl_op op); |
int (*sw_hw_apply)(struct switch_dev *dev); |
|
const struct ar8xxx_mib_desc *mib_decs; |
unsigned num_mibs; |
unsigned mib_func; |
}; |
|
struct ar8xxx_priv { |
struct switch_dev dev; |
struct mii_bus *mii_bus; |
struct phy_device *phy; |
|
int (*get_port_link)(unsigned port); |
|
const struct net_device_ops *ndo_old; |
struct net_device_ops ndo; |
struct mutex reg_mutex; |
u8 chip_ver; |
u8 chip_rev; |
const struct ar8xxx_chip *chip; |
void *chip_data; |
bool initialized; |
bool port4_phy; |
char buf[2048]; |
struct arl_entry arl_table[AR8XXX_NUM_ARL_RECORDS]; |
char arl_buf[AR8XXX_NUM_ARL_RECORDS * 32 + 256]; |
bool link_up[AR8X16_MAX_PORTS]; |
|
bool init; |
|
struct mutex mib_lock; |
struct delayed_work mib_work; |
int mib_next_port; |
u64 *mib_stats; |
|
struct list_head list; |
unsigned int use_count; |
|
/* all fields below are cleared on reset */ |
bool vlan; |
u16 vlan_id[AR8X16_MAX_VLANS]; |
u8 vlan_table[AR8X16_MAX_VLANS]; |
u8 vlan_tagged; |
u16 pvid[AR8X16_MAX_PORTS]; |
int arl_age_time; |
|
/* mirroring */ |
bool mirror_rx; |
bool mirror_tx; |
int source_port; |
int monitor_port; |
u8 port_vlan_prio[AR8X16_MAX_PORTS]; |
}; |
|
u32 |
ar8xxx_mii_read32(struct ar8xxx_priv *priv, int phy_id, int regnum); |
void |
ar8xxx_mii_write32(struct ar8xxx_priv *priv, int phy_id, int regnum, u32 val); |
u32 |
ar8xxx_read(struct ar8xxx_priv *priv, int reg); |
void |
ar8xxx_write(struct ar8xxx_priv *priv, int reg, u32 val); |
u32 |
ar8xxx_rmw(struct ar8xxx_priv *priv, int reg, u32 mask, u32 val); |
|
void |
ar8xxx_phy_dbg_write(struct ar8xxx_priv *priv, int phy_addr, |
u16 dbg_addr, u16 dbg_data); |
void |
ar8xxx_phy_mmd_write(struct ar8xxx_priv *priv, int phy_addr, u16 addr, u16 reg, u16 data); |
u16 |
ar8xxx_phy_mmd_read(struct ar8xxx_priv *priv, int phy_addr, u16 addr, u16 reg); |
void |
ar8xxx_phy_init(struct ar8xxx_priv *priv); |
int |
ar8xxx_sw_set_vlan(struct switch_dev *dev, const struct switch_attr *attr, |
struct switch_val *val); |
int |
ar8xxx_sw_get_vlan(struct switch_dev *dev, const struct switch_attr *attr, |
struct switch_val *val); |
int |
ar8xxx_sw_set_reset_mibs(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val); |
int |
ar8xxx_sw_set_mirror_rx_enable(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val); |
int |
ar8xxx_sw_get_mirror_rx_enable(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val); |
int |
ar8xxx_sw_set_mirror_tx_enable(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val); |
int |
ar8xxx_sw_get_mirror_tx_enable(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val); |
int |
ar8xxx_sw_set_mirror_monitor_port(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val); |
int |
ar8xxx_sw_get_mirror_monitor_port(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val); |
int |
ar8xxx_sw_set_mirror_source_port(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val); |
int |
ar8xxx_sw_get_mirror_source_port(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val); |
int |
ar8xxx_sw_set_pvid(struct switch_dev *dev, int port, int vlan); |
int |
ar8xxx_sw_get_pvid(struct switch_dev *dev, int port, int *vlan); |
int |
ar8xxx_sw_hw_apply(struct switch_dev *dev); |
int |
ar8xxx_sw_reset_switch(struct switch_dev *dev); |
int |
ar8xxx_sw_get_port_link(struct switch_dev *dev, int port, |
struct switch_port_link *link); |
int |
ar8xxx_sw_set_port_reset_mib(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val); |
int |
ar8xxx_sw_get_port_mib(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val); |
int |
ar8xxx_sw_get_arl_age_time(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val); |
int |
ar8xxx_sw_set_arl_age_time(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val); |
int |
ar8xxx_sw_get_arl_table(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val); |
int |
ar8xxx_sw_set_flush_arl_table(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val); |
int |
ar8xxx_sw_set_flush_port_arl_table(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val); |
int |
ar8216_wait_bit(struct ar8xxx_priv *priv, int reg, u32 mask, u32 val); |
|
static inline struct ar8xxx_priv * |
swdev_to_ar8xxx(struct switch_dev *swdev) |
{ |
return container_of(swdev, struct ar8xxx_priv, dev); |
} |
|
static inline bool ar8xxx_has_gige(struct ar8xxx_priv *priv) |
{ |
return priv->chip->caps & AR8XXX_CAP_GIGE; |
} |
|
static inline bool ar8xxx_has_mib_counters(struct ar8xxx_priv *priv) |
{ |
return priv->chip->caps & AR8XXX_CAP_MIB_COUNTERS; |
} |
|
static inline bool chip_is_ar8216(struct ar8xxx_priv *priv) |
{ |
return priv->chip_ver == AR8XXX_VER_AR8216; |
} |
|
static inline bool chip_is_ar8236(struct ar8xxx_priv *priv) |
{ |
return priv->chip_ver == AR8XXX_VER_AR8236; |
} |
|
static inline bool chip_is_ar8316(struct ar8xxx_priv *priv) |
{ |
return priv->chip_ver == AR8XXX_VER_AR8316; |
} |
|
static inline bool chip_is_ar8327(struct ar8xxx_priv *priv) |
{ |
return priv->chip_ver == AR8XXX_VER_AR8327; |
} |
|
static inline bool chip_is_ar8337(struct ar8xxx_priv *priv) |
{ |
return priv->chip_ver == AR8XXX_VER_AR8337; |
} |
|
static inline void |
ar8xxx_reg_set(struct ar8xxx_priv *priv, int reg, u32 val) |
{ |
ar8xxx_rmw(priv, reg, 0, val); |
} |
|
static inline void |
ar8xxx_reg_clear(struct ar8xxx_priv *priv, int reg, u32 val) |
{ |
ar8xxx_rmw(priv, reg, val, 0); |
} |
|
static inline void |
split_addr(u32 regaddr, u16 *r1, u16 *r2, u16 *page) |
{ |
regaddr >>= 1; |
*r1 = regaddr & 0x1e; |
|
regaddr >>= 5; |
*r2 = regaddr & 0x7; |
|
regaddr >>= 3; |
*page = regaddr & 0x1ff; |
} |
|
static inline void |
wait_for_page_switch(void) |
{ |
udelay(5); |
} |
|
#endif |
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/ar8327.c |
@@ -0,0 +1,1543 @@ |
/* |
* ar8327.c: AR8216 switch driver |
* |
* Copyright (C) 2009 Felix Fietkau <nbd@nbd.name> |
* Copyright (C) 2011-2012 Gabor Juhos <juhosg@openwrt.org> |
* |
* 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; either version 2 |
* of the License, or (at your option) any later version. |
* |
* 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. |
*/ |
|
#include <linux/list.h> |
#include <linux/bitops.h> |
#include <linux/switch.h> |
#include <linux/delay.h> |
#include <linux/phy.h> |
#include <linux/lockdep.h> |
#include <linux/ar8216_platform.h> |
#include <linux/workqueue.h> |
#include <linux/of_device.h> |
#include <linux/leds.h> |
#include <linux/mdio.h> |
|
#include "ar8216.h" |
#include "ar8327.h" |
|
extern const struct ar8xxx_mib_desc ar8236_mibs[39]; |
extern const struct switch_attr ar8xxx_sw_attr_vlan[1]; |
|
static u32 |
ar8327_get_pad_cfg(struct ar8327_pad_cfg *cfg) |
{ |
u32 t; |
|
if (!cfg) |
return 0; |
|
t = 0; |
switch (cfg->mode) { |
case AR8327_PAD_NC: |
break; |
|
case AR8327_PAD_MAC2MAC_MII: |
t = AR8327_PAD_MAC_MII_EN; |
if (cfg->rxclk_sel) |
t |= AR8327_PAD_MAC_MII_RXCLK_SEL; |
if (cfg->txclk_sel) |
t |= AR8327_PAD_MAC_MII_TXCLK_SEL; |
break; |
|
case AR8327_PAD_MAC2MAC_GMII: |
t = AR8327_PAD_MAC_GMII_EN; |
if (cfg->rxclk_sel) |
t |= AR8327_PAD_MAC_GMII_RXCLK_SEL; |
if (cfg->txclk_sel) |
t |= AR8327_PAD_MAC_GMII_TXCLK_SEL; |
break; |
|
case AR8327_PAD_MAC_SGMII: |
t = AR8327_PAD_SGMII_EN; |
|
/* |
* WAR for the QUalcomm Atheros AP136 board. |
* It seems that RGMII TX/RX delay settings needs to be |
* applied for SGMII mode as well, The ethernet is not |
* reliable without this. |
*/ |
t |= cfg->txclk_delay_sel << AR8327_PAD_RGMII_TXCLK_DELAY_SEL_S; |
t |= cfg->rxclk_delay_sel << AR8327_PAD_RGMII_RXCLK_DELAY_SEL_S; |
if (cfg->rxclk_delay_en) |
t |= AR8327_PAD_RGMII_RXCLK_DELAY_EN; |
if (cfg->txclk_delay_en) |
t |= AR8327_PAD_RGMII_TXCLK_DELAY_EN; |
|
if (cfg->sgmii_delay_en) |
t |= AR8327_PAD_SGMII_DELAY_EN; |
|
break; |
|
case AR8327_PAD_MAC2PHY_MII: |
t = AR8327_PAD_PHY_MII_EN; |
if (cfg->rxclk_sel) |
t |= AR8327_PAD_PHY_MII_RXCLK_SEL; |
if (cfg->txclk_sel) |
t |= AR8327_PAD_PHY_MII_TXCLK_SEL; |
break; |
|
case AR8327_PAD_MAC2PHY_GMII: |
t = AR8327_PAD_PHY_GMII_EN; |
if (cfg->pipe_rxclk_sel) |
t |= AR8327_PAD_PHY_GMII_PIPE_RXCLK_SEL; |
if (cfg->rxclk_sel) |
t |= AR8327_PAD_PHY_GMII_RXCLK_SEL; |
if (cfg->txclk_sel) |
t |= AR8327_PAD_PHY_GMII_TXCLK_SEL; |
break; |
|
case AR8327_PAD_MAC_RGMII: |
t = AR8327_PAD_RGMII_EN; |
t |= cfg->txclk_delay_sel << AR8327_PAD_RGMII_TXCLK_DELAY_SEL_S; |
t |= cfg->rxclk_delay_sel << AR8327_PAD_RGMII_RXCLK_DELAY_SEL_S; |
if (cfg->rxclk_delay_en) |
t |= AR8327_PAD_RGMII_RXCLK_DELAY_EN; |
if (cfg->txclk_delay_en) |
t |= AR8327_PAD_RGMII_TXCLK_DELAY_EN; |
break; |
|
case AR8327_PAD_PHY_GMII: |
t = AR8327_PAD_PHYX_GMII_EN; |
break; |
|
case AR8327_PAD_PHY_RGMII: |
t = AR8327_PAD_PHYX_RGMII_EN; |
break; |
|
case AR8327_PAD_PHY_MII: |
t = AR8327_PAD_PHYX_MII_EN; |
break; |
} |
|
return t; |
} |
|
static void |
ar8327_phy_fixup(struct ar8xxx_priv *priv, int phy) |
{ |
switch (priv->chip_rev) { |
case 1: |
/* For 100M waveform */ |
ar8xxx_phy_dbg_write(priv, phy, 0, 0x02ea); |
/* Turn on Gigabit clock */ |
ar8xxx_phy_dbg_write(priv, phy, 0x3d, 0x68a0); |
break; |
|
case 2: |
ar8xxx_phy_mmd_write(priv, phy, 0x7, 0x3c, 0x0); |
/* fallthrough */ |
case 4: |
ar8xxx_phy_mmd_write(priv, phy, 0x3, 0x800d, 0x803f); |
ar8xxx_phy_dbg_write(priv, phy, 0x3d, 0x6860); |
ar8xxx_phy_dbg_write(priv, phy, 0x5, 0x2c46); |
ar8xxx_phy_dbg_write(priv, phy, 0x3c, 0x6000); |
break; |
} |
} |
|
static u32 |
ar8327_get_port_init_status(struct ar8327_port_cfg *cfg) |
{ |
u32 t; |
|
if (!cfg->force_link) |
return AR8216_PORT_STATUS_LINK_AUTO; |
|
t = AR8216_PORT_STATUS_TXMAC | AR8216_PORT_STATUS_RXMAC; |
t |= cfg->duplex ? AR8216_PORT_STATUS_DUPLEX : 0; |
t |= cfg->rxpause ? AR8216_PORT_STATUS_RXFLOW : 0; |
t |= cfg->txpause ? AR8216_PORT_STATUS_TXFLOW : 0; |
|
switch (cfg->speed) { |
case AR8327_PORT_SPEED_10: |
t |= AR8216_PORT_SPEED_10M; |
break; |
case AR8327_PORT_SPEED_100: |
t |= AR8216_PORT_SPEED_100M; |
break; |
case AR8327_PORT_SPEED_1000: |
t |= AR8216_PORT_SPEED_1000M; |
break; |
} |
|
return t; |
} |
|
#define AR8327_LED_ENTRY(_num, _reg, _shift) \ |
[_num] = { .reg = (_reg), .shift = (_shift) } |
|
static const struct ar8327_led_entry |
ar8327_led_map[AR8327_NUM_LEDS] = { |
AR8327_LED_ENTRY(AR8327_LED_PHY0_0, 0, 14), |
AR8327_LED_ENTRY(AR8327_LED_PHY0_1, 1, 14), |
AR8327_LED_ENTRY(AR8327_LED_PHY0_2, 2, 14), |
|
AR8327_LED_ENTRY(AR8327_LED_PHY1_0, 3, 8), |
AR8327_LED_ENTRY(AR8327_LED_PHY1_1, 3, 10), |
AR8327_LED_ENTRY(AR8327_LED_PHY1_2, 3, 12), |
|
AR8327_LED_ENTRY(AR8327_LED_PHY2_0, 3, 14), |
AR8327_LED_ENTRY(AR8327_LED_PHY2_1, 3, 16), |
AR8327_LED_ENTRY(AR8327_LED_PHY2_2, 3, 18), |
|
AR8327_LED_ENTRY(AR8327_LED_PHY3_0, 3, 20), |
AR8327_LED_ENTRY(AR8327_LED_PHY3_1, 3, 22), |
AR8327_LED_ENTRY(AR8327_LED_PHY3_2, 3, 24), |
|
AR8327_LED_ENTRY(AR8327_LED_PHY4_0, 0, 30), |
AR8327_LED_ENTRY(AR8327_LED_PHY4_1, 1, 30), |
AR8327_LED_ENTRY(AR8327_LED_PHY4_2, 2, 30), |
}; |
|
static void |
ar8327_set_led_pattern(struct ar8xxx_priv *priv, unsigned int led_num, |
enum ar8327_led_pattern pattern) |
{ |
const struct ar8327_led_entry *entry; |
|
entry = &ar8327_led_map[led_num]; |
ar8xxx_rmw(priv, AR8327_REG_LED_CTRL(entry->reg), |
(3 << entry->shift), pattern << entry->shift); |
} |
|
static void |
ar8327_led_work_func(struct work_struct *work) |
{ |
struct ar8327_led *aled; |
u8 pattern; |
|
aled = container_of(work, struct ar8327_led, led_work); |
|
pattern = aled->pattern; |
|
ar8327_set_led_pattern(aled->sw_priv, aled->led_num, |
pattern); |
} |
|
static void |
ar8327_led_schedule_change(struct ar8327_led *aled, u8 pattern) |
{ |
if (aled->pattern == pattern) |
return; |
|
aled->pattern = pattern; |
schedule_work(&aled->led_work); |
} |
|
static inline struct ar8327_led * |
led_cdev_to_ar8327_led(struct led_classdev *led_cdev) |
{ |
return container_of(led_cdev, struct ar8327_led, cdev); |
} |
|
static int |
ar8327_led_blink_set(struct led_classdev *led_cdev, |
unsigned long *delay_on, |
unsigned long *delay_off) |
{ |
struct ar8327_led *aled = led_cdev_to_ar8327_led(led_cdev); |
|
if (*delay_on == 0 && *delay_off == 0) { |
*delay_on = 125; |
*delay_off = 125; |
} |
|
if (*delay_on != 125 || *delay_off != 125) { |
/* |
* The hardware only supports blinking at 4Hz. Fall back |
* to software implementation in other cases. |
*/ |
return -EINVAL; |
} |
|
spin_lock(&aled->lock); |
|
aled->enable_hw_mode = false; |
ar8327_led_schedule_change(aled, AR8327_LED_PATTERN_BLINK); |
|
spin_unlock(&aled->lock); |
|
return 0; |
} |
|
static void |
ar8327_led_set_brightness(struct led_classdev *led_cdev, |
enum led_brightness brightness) |
{ |
struct ar8327_led *aled = led_cdev_to_ar8327_led(led_cdev); |
u8 pattern; |
bool active; |
|
active = (brightness != LED_OFF); |
active ^= aled->active_low; |
|
pattern = (active) ? AR8327_LED_PATTERN_ON : |
AR8327_LED_PATTERN_OFF; |
|
spin_lock(&aled->lock); |
|
aled->enable_hw_mode = false; |
ar8327_led_schedule_change(aled, pattern); |
|
spin_unlock(&aled->lock); |
} |
|
static ssize_t |
ar8327_led_enable_hw_mode_show(struct device *dev, |
struct device_attribute *attr, |
char *buf) |
{ |
struct led_classdev *led_cdev = dev_get_drvdata(dev); |
struct ar8327_led *aled = led_cdev_to_ar8327_led(led_cdev); |
ssize_t ret = 0; |
|
ret += scnprintf(buf, PAGE_SIZE, "%d\n", aled->enable_hw_mode); |
|
return ret; |
} |
|
static ssize_t |
ar8327_led_enable_hw_mode_store(struct device *dev, |
struct device_attribute *attr, |
const char *buf, |
size_t size) |
{ |
struct led_classdev *led_cdev = dev_get_drvdata(dev); |
struct ar8327_led *aled = led_cdev_to_ar8327_led(led_cdev); |
u8 pattern; |
u8 value; |
int ret; |
|
ret = kstrtou8(buf, 10, &value); |
if (ret < 0) |
return -EINVAL; |
|
spin_lock(&aled->lock); |
|
aled->enable_hw_mode = !!value; |
if (aled->enable_hw_mode) |
pattern = AR8327_LED_PATTERN_RULE; |
else |
pattern = AR8327_LED_PATTERN_OFF; |
|
ar8327_led_schedule_change(aled, pattern); |
|
spin_unlock(&aled->lock); |
|
return size; |
} |
|
static DEVICE_ATTR(enable_hw_mode, S_IRUGO | S_IWUSR, |
ar8327_led_enable_hw_mode_show, |
ar8327_led_enable_hw_mode_store); |
|
static int |
ar8327_led_register(struct ar8327_led *aled) |
{ |
int ret; |
|
ret = led_classdev_register(NULL, &aled->cdev); |
if (ret < 0) |
return ret; |
|
if (aled->mode == AR8327_LED_MODE_HW) { |
ret = device_create_file(aled->cdev.dev, |
&dev_attr_enable_hw_mode); |
if (ret) |
goto err_unregister; |
} |
|
return 0; |
|
err_unregister: |
led_classdev_unregister(&aled->cdev); |
return ret; |
} |
|
static void |
ar8327_led_unregister(struct ar8327_led *aled) |
{ |
if (aled->mode == AR8327_LED_MODE_HW) |
device_remove_file(aled->cdev.dev, &dev_attr_enable_hw_mode); |
|
led_classdev_unregister(&aled->cdev); |
cancel_work_sync(&aled->led_work); |
} |
|
static int |
ar8327_led_create(struct ar8xxx_priv *priv, |
const struct ar8327_led_info *led_info) |
{ |
struct ar8327_data *data = priv->chip_data; |
struct ar8327_led *aled; |
int ret; |
|
if (!IS_ENABLED(CONFIG_AR8216_PHY_LEDS)) |
return 0; |
|
if (!led_info->name) |
return -EINVAL; |
|
if (led_info->led_num >= AR8327_NUM_LEDS) |
return -EINVAL; |
|
aled = kzalloc(sizeof(*aled) + strlen(led_info->name) + 1, |
GFP_KERNEL); |
if (!aled) |
return -ENOMEM; |
|
aled->sw_priv = priv; |
aled->led_num = led_info->led_num; |
aled->active_low = led_info->active_low; |
aled->mode = led_info->mode; |
|
if (aled->mode == AR8327_LED_MODE_HW) |
aled->enable_hw_mode = true; |
|
aled->name = (char *)(aled + 1); |
strcpy(aled->name, led_info->name); |
|
aled->cdev.name = aled->name; |
aled->cdev.brightness_set = ar8327_led_set_brightness; |
aled->cdev.blink_set = ar8327_led_blink_set; |
aled->cdev.default_trigger = led_info->default_trigger; |
|
spin_lock_init(&aled->lock); |
mutex_init(&aled->mutex); |
INIT_WORK(&aled->led_work, ar8327_led_work_func); |
|
ret = ar8327_led_register(aled); |
if (ret) |
goto err_free; |
|
data->leds[data->num_leds++] = aled; |
|
return 0; |
|
err_free: |
kfree(aled); |
return ret; |
} |
|
static void |
ar8327_led_destroy(struct ar8327_led *aled) |
{ |
ar8327_led_unregister(aled); |
kfree(aled); |
} |
|
static void |
ar8327_leds_init(struct ar8xxx_priv *priv) |
{ |
struct ar8327_data *data = priv->chip_data; |
unsigned i; |
|
if (!IS_ENABLED(CONFIG_AR8216_PHY_LEDS)) |
return; |
|
for (i = 0; i < data->num_leds; i++) { |
struct ar8327_led *aled; |
|
aled = data->leds[i]; |
|
if (aled->enable_hw_mode) |
aled->pattern = AR8327_LED_PATTERN_RULE; |
else |
aled->pattern = AR8327_LED_PATTERN_OFF; |
|
ar8327_set_led_pattern(priv, aled->led_num, aled->pattern); |
} |
} |
|
static void |
ar8327_leds_cleanup(struct ar8xxx_priv *priv) |
{ |
struct ar8327_data *data = priv->chip_data; |
unsigned i; |
|
if (!IS_ENABLED(CONFIG_AR8216_PHY_LEDS)) |
return; |
|
for (i = 0; i < data->num_leds; i++) { |
struct ar8327_led *aled; |
|
aled = data->leds[i]; |
ar8327_led_destroy(aled); |
} |
|
kfree(data->leds); |
} |
|
static int |
ar8327_hw_config_pdata(struct ar8xxx_priv *priv, |
struct ar8327_platform_data *pdata) |
{ |
struct ar8327_led_cfg *led_cfg; |
struct ar8327_data *data = priv->chip_data; |
u32 pos, new_pos; |
u32 t; |
|
if (!pdata) |
return -EINVAL; |
|
priv->get_port_link = pdata->get_port_link; |
|
data->port0_status = ar8327_get_port_init_status(&pdata->port0_cfg); |
data->port6_status = ar8327_get_port_init_status(&pdata->port6_cfg); |
|
t = ar8327_get_pad_cfg(pdata->pad0_cfg); |
if (chip_is_ar8337(priv) && !pdata->pad0_cfg->mac06_exchange_dis) |
t |= AR8337_PAD_MAC06_EXCHANGE_EN; |
ar8xxx_write(priv, AR8327_REG_PAD0_MODE, t); |
|
t = ar8327_get_pad_cfg(pdata->pad5_cfg); |
if (chip_is_ar8337(priv)) { |
/* |
* Workaround: RGMII RX delay setting needs to be |
* always specified for AR8337 to avoid port 5 |
* RX hang on high traffic / flood conditions |
*/ |
t |= AR8327_PAD_RGMII_RXCLK_DELAY_EN; |
} |
ar8xxx_write(priv, AR8327_REG_PAD5_MODE, t); |
t = ar8327_get_pad_cfg(pdata->pad6_cfg); |
ar8xxx_write(priv, AR8327_REG_PAD6_MODE, t); |
|
pos = ar8xxx_read(priv, AR8327_REG_POWER_ON_STRIP); |
new_pos = pos; |
|
led_cfg = pdata->led_cfg; |
if (led_cfg) { |
if (led_cfg->open_drain) |
new_pos |= AR8327_POWER_ON_STRIP_LED_OPEN_EN; |
else |
new_pos &= ~AR8327_POWER_ON_STRIP_LED_OPEN_EN; |
|
ar8xxx_write(priv, AR8327_REG_LED_CTRL0, led_cfg->led_ctrl0); |
ar8xxx_write(priv, AR8327_REG_LED_CTRL1, led_cfg->led_ctrl1); |
ar8xxx_write(priv, AR8327_REG_LED_CTRL2, led_cfg->led_ctrl2); |
ar8xxx_write(priv, AR8327_REG_LED_CTRL3, led_cfg->led_ctrl3); |
|
if (new_pos != pos) |
new_pos |= AR8327_POWER_ON_STRIP_POWER_ON_SEL; |
} |
|
if (pdata->sgmii_cfg) { |
t = pdata->sgmii_cfg->sgmii_ctrl; |
if (priv->chip_rev == 1) |
t |= AR8327_SGMII_CTRL_EN_PLL | |
AR8327_SGMII_CTRL_EN_RX | |
AR8327_SGMII_CTRL_EN_TX; |
else |
t &= ~(AR8327_SGMII_CTRL_EN_PLL | |
AR8327_SGMII_CTRL_EN_RX | |
AR8327_SGMII_CTRL_EN_TX); |
|
ar8xxx_write(priv, AR8327_REG_SGMII_CTRL, t); |
|
if (pdata->sgmii_cfg->serdes_aen) |
new_pos &= ~AR8327_POWER_ON_STRIP_SERDES_AEN; |
else |
new_pos |= AR8327_POWER_ON_STRIP_SERDES_AEN; |
} |
|
ar8xxx_write(priv, AR8327_REG_POWER_ON_STRIP, new_pos); |
|
if (pdata->leds && pdata->num_leds) { |
int i; |
|
data->leds = kzalloc(pdata->num_leds * sizeof(void *), |
GFP_KERNEL); |
if (!data->leds) |
return -ENOMEM; |
|
for (i = 0; i < pdata->num_leds; i++) |
ar8327_led_create(priv, &pdata->leds[i]); |
} |
|
return 0; |
} |
|
#ifdef CONFIG_OF |
static int |
ar8327_hw_config_of(struct ar8xxx_priv *priv, struct device_node *np) |
{ |
struct ar8327_data *data = priv->chip_data; |
const __be32 *paddr; |
int len; |
int i; |
|
paddr = of_get_property(np, "qca,ar8327-initvals", &len); |
if (!paddr || len < (2 * sizeof(*paddr))) |
return -EINVAL; |
|
len /= sizeof(*paddr); |
|
for (i = 0; i < len - 1; i += 2) { |
u32 reg; |
u32 val; |
|
reg = be32_to_cpup(paddr + i); |
val = be32_to_cpup(paddr + i + 1); |
|
switch (reg) { |
case AR8327_REG_PORT_STATUS(0): |
data->port0_status = val; |
break; |
case AR8327_REG_PORT_STATUS(6): |
data->port6_status = val; |
break; |
default: |
ar8xxx_write(priv, reg, val); |
break; |
} |
} |
|
return 0; |
} |
#else |
static inline int |
ar8327_hw_config_of(struct ar8xxx_priv *priv, struct device_node *np) |
{ |
return -EINVAL; |
} |
#endif |
|
static int |
ar8327_hw_init(struct ar8xxx_priv *priv) |
{ |
int ret; |
|
priv->chip_data = kzalloc(sizeof(struct ar8327_data), GFP_KERNEL); |
if (!priv->chip_data) |
return -ENOMEM; |
|
if (priv->phy->mdio.dev.of_node) |
ret = ar8327_hw_config_of(priv, priv->phy->mdio.dev.of_node); |
else |
ret = ar8327_hw_config_pdata(priv, |
priv->phy->mdio.dev.platform_data); |
|
if (ret) |
return ret; |
|
ar8327_leds_init(priv); |
|
ar8xxx_phy_init(priv); |
|
return 0; |
} |
|
static void |
ar8327_cleanup(struct ar8xxx_priv *priv) |
{ |
ar8327_leds_cleanup(priv); |
} |
|
static void |
ar8327_init_globals(struct ar8xxx_priv *priv) |
{ |
struct ar8327_data *data = priv->chip_data; |
u32 t; |
int i; |
|
/* enable CPU port and disable mirror port */ |
t = AR8327_FWD_CTRL0_CPU_PORT_EN | |
AR8327_FWD_CTRL0_MIRROR_PORT; |
ar8xxx_write(priv, AR8327_REG_FWD_CTRL0, t); |
|
/* forward multicast and broadcast frames to CPU */ |
t = (AR8327_PORTS_ALL << AR8327_FWD_CTRL1_UC_FLOOD_S) | |
(AR8327_PORTS_ALL << AR8327_FWD_CTRL1_MC_FLOOD_S) | |
(AR8327_PORTS_ALL << AR8327_FWD_CTRL1_BC_FLOOD_S); |
ar8xxx_write(priv, AR8327_REG_FWD_CTRL1, t); |
|
/* enable jumbo frames */ |
ar8xxx_rmw(priv, AR8327_REG_MAX_FRAME_SIZE, |
AR8327_MAX_FRAME_SIZE_MTU, 9018 + 8 + 2); |
|
/* Enable MIB counters */ |
ar8xxx_reg_set(priv, AR8327_REG_MODULE_EN, |
AR8327_MODULE_EN_MIB); |
|
/* Disable EEE on all phy's due to stability issues */ |
for (i = 0; i < AR8XXX_NUM_PHYS; i++) |
data->eee[i] = false; |
|
if (chip_is_ar8337(priv)) { |
/* Update HOL registers with values suggested by QCA switch team */ |
for (i = 0; i < AR8327_NUM_PORTS; i++) { |
if (i == AR8216_PORT_CPU || i == 5 || i == 6) { |
t = 0x3 << AR8327_PORT_HOL_CTRL0_EG_PRI0_BUF_S; |
t |= 0x4 << AR8327_PORT_HOL_CTRL0_EG_PRI1_BUF_S; |
t |= 0x4 << AR8327_PORT_HOL_CTRL0_EG_PRI2_BUF_S; |
t |= 0x4 << AR8327_PORT_HOL_CTRL0_EG_PRI3_BUF_S; |
t |= 0x6 << AR8327_PORT_HOL_CTRL0_EG_PRI4_BUF_S; |
t |= 0x8 << AR8327_PORT_HOL_CTRL0_EG_PRI5_BUF_S; |
t |= 0x1e << AR8327_PORT_HOL_CTRL0_EG_PORT_BUF_S; |
} else { |
t = 0x3 << AR8327_PORT_HOL_CTRL0_EG_PRI0_BUF_S; |
t |= 0x4 << AR8327_PORT_HOL_CTRL0_EG_PRI1_BUF_S; |
t |= 0x6 << AR8327_PORT_HOL_CTRL0_EG_PRI2_BUF_S; |
t |= 0x8 << AR8327_PORT_HOL_CTRL0_EG_PRI3_BUF_S; |
t |= 0x19 << AR8327_PORT_HOL_CTRL0_EG_PORT_BUF_S; |
} |
ar8xxx_write(priv, AR8327_REG_PORT_HOL_CTRL0(i), t); |
|
t = 0x6 << AR8327_PORT_HOL_CTRL1_ING_BUF_S; |
t |= AR8327_PORT_HOL_CTRL1_EG_PRI_BUF_EN; |
t |= AR8327_PORT_HOL_CTRL1_EG_PORT_BUF_EN; |
t |= AR8327_PORT_HOL_CTRL1_WRED_EN; |
ar8xxx_rmw(priv, AR8327_REG_PORT_HOL_CTRL1(i), |
AR8327_PORT_HOL_CTRL1_ING_BUF | |
AR8327_PORT_HOL_CTRL1_EG_PRI_BUF_EN | |
AR8327_PORT_HOL_CTRL1_EG_PORT_BUF_EN | |
AR8327_PORT_HOL_CTRL1_WRED_EN, |
t); |
} |
} |
} |
|
static void |
ar8327_init_port(struct ar8xxx_priv *priv, int port) |
{ |
struct ar8327_data *data = priv->chip_data; |
u32 t; |
|
if (port == AR8216_PORT_CPU) |
t = data->port0_status; |
else if (port == 6) |
t = data->port6_status; |
else |
t = AR8216_PORT_STATUS_LINK_AUTO; |
|
if (port != AR8216_PORT_CPU && port != 6) { |
/*hw limitation:if configure mac when there is traffic, |
port MAC may work abnormal. Need disable lan&wan mac at fisrt*/ |
ar8xxx_write(priv, AR8327_REG_PORT_STATUS(port), 0); |
msleep(100); |
t |= AR8216_PORT_STATUS_FLOW_CONTROL; |
ar8xxx_write(priv, AR8327_REG_PORT_STATUS(port), t); |
} else { |
ar8xxx_write(priv, AR8327_REG_PORT_STATUS(port), t); |
} |
|
ar8xxx_write(priv, AR8327_REG_PORT_HEADER(port), 0); |
|
ar8xxx_write(priv, AR8327_REG_PORT_VLAN0(port), 0); |
|
t = AR8327_PORT_VLAN1_OUT_MODE_UNTOUCH << AR8327_PORT_VLAN1_OUT_MODE_S; |
ar8xxx_write(priv, AR8327_REG_PORT_VLAN1(port), t); |
|
t = AR8327_PORT_LOOKUP_LEARN; |
t |= AR8216_PORT_STATE_FORWARD << AR8327_PORT_LOOKUP_STATE_S; |
ar8xxx_write(priv, AR8327_REG_PORT_LOOKUP(port), t); |
} |
|
static u32 |
ar8327_read_port_status(struct ar8xxx_priv *priv, int port) |
{ |
u32 t; |
|
t = ar8xxx_read(priv, AR8327_REG_PORT_STATUS(port)); |
/* map the flow control autoneg result bits to the flow control bits |
* used in forced mode to allow ar8216_read_port_link detect |
* flow control properly if autoneg is used |
*/ |
if (t & AR8216_PORT_STATUS_LINK_UP && |
t & AR8216_PORT_STATUS_LINK_AUTO) { |
t &= ~(AR8216_PORT_STATUS_TXFLOW | AR8216_PORT_STATUS_RXFLOW); |
if (t & AR8327_PORT_STATUS_TXFLOW_AUTO) |
t |= AR8216_PORT_STATUS_TXFLOW; |
if (t & AR8327_PORT_STATUS_RXFLOW_AUTO) |
t |= AR8216_PORT_STATUS_RXFLOW; |
} |
|
return t; |
} |
|
static u32 |
ar8327_read_port_eee_status(struct ar8xxx_priv *priv, int port) |
{ |
int phy; |
u16 t; |
|
if (port >= priv->dev.ports) |
return 0; |
|
if (port == 0 || port == 6) |
return 0; |
|
phy = port - 1; |
|
/* EEE Ability Auto-negotiation Result */ |
t = ar8xxx_phy_mmd_read(priv, phy, 0x7, 0x8000); |
|
return mmd_eee_adv_to_ethtool_adv_t(t); |
} |
|
static int |
ar8327_atu_flush(struct ar8xxx_priv *priv) |
{ |
int ret; |
|
ret = ar8216_wait_bit(priv, AR8327_REG_ATU_FUNC, |
AR8327_ATU_FUNC_BUSY, 0); |
if (!ret) |
ar8xxx_write(priv, AR8327_REG_ATU_FUNC, |
AR8327_ATU_FUNC_OP_FLUSH | |
AR8327_ATU_FUNC_BUSY); |
|
return ret; |
} |
|
static int |
ar8327_atu_flush_port(struct ar8xxx_priv *priv, int port) |
{ |
u32 t; |
int ret; |
|
ret = ar8216_wait_bit(priv, AR8327_REG_ATU_FUNC, |
AR8327_ATU_FUNC_BUSY, 0); |
if (!ret) { |
t = (port << AR8327_ATU_PORT_NUM_S); |
t |= AR8327_ATU_FUNC_OP_FLUSH_PORT; |
t |= AR8327_ATU_FUNC_BUSY; |
ar8xxx_write(priv, AR8327_REG_ATU_FUNC, t); |
} |
|
return ret; |
} |
|
static int |
ar8327_get_port_igmp(struct ar8xxx_priv *priv, int port) |
{ |
u32 fwd_ctrl, frame_ack; |
|
fwd_ctrl = (BIT(port) << AR8327_FWD_CTRL1_IGMP_S); |
frame_ack = ((AR8327_FRAME_ACK_CTRL_IGMP_MLD | |
AR8327_FRAME_ACK_CTRL_IGMP_JOIN | |
AR8327_FRAME_ACK_CTRL_IGMP_LEAVE) << |
AR8327_FRAME_ACK_CTRL_S(port)); |
|
return (ar8xxx_read(priv, AR8327_REG_FWD_CTRL1) & |
fwd_ctrl) == fwd_ctrl && |
(ar8xxx_read(priv, AR8327_REG_FRAME_ACK_CTRL(port)) & |
frame_ack) == frame_ack; |
} |
|
static void |
ar8327_set_port_igmp(struct ar8xxx_priv *priv, int port, int enable) |
{ |
int reg_frame_ack = AR8327_REG_FRAME_ACK_CTRL(port); |
u32 val_frame_ack = (AR8327_FRAME_ACK_CTRL_IGMP_MLD | |
AR8327_FRAME_ACK_CTRL_IGMP_JOIN | |
AR8327_FRAME_ACK_CTRL_IGMP_LEAVE) << |
AR8327_FRAME_ACK_CTRL_S(port); |
|
if (enable) { |
ar8xxx_rmw(priv, AR8327_REG_FWD_CTRL1, |
BIT(port) << AR8327_FWD_CTRL1_MC_FLOOD_S, |
BIT(port) << AR8327_FWD_CTRL1_IGMP_S); |
ar8xxx_reg_set(priv, reg_frame_ack, val_frame_ack); |
} else { |
ar8xxx_rmw(priv, AR8327_REG_FWD_CTRL1, |
BIT(port) << AR8327_FWD_CTRL1_IGMP_S, |
BIT(port) << AR8327_FWD_CTRL1_MC_FLOOD_S); |
ar8xxx_reg_clear(priv, reg_frame_ack, val_frame_ack); |
} |
} |
|
static void |
ar8327_vtu_op(struct ar8xxx_priv *priv, u32 op, u32 val) |
{ |
if (ar8216_wait_bit(priv, AR8327_REG_VTU_FUNC1, |
AR8327_VTU_FUNC1_BUSY, 0)) |
return; |
|
if ((op & AR8327_VTU_FUNC1_OP) == AR8327_VTU_FUNC1_OP_LOAD) |
ar8xxx_write(priv, AR8327_REG_VTU_FUNC0, val); |
|
op |= AR8327_VTU_FUNC1_BUSY; |
ar8xxx_write(priv, AR8327_REG_VTU_FUNC1, op); |
} |
|
static void |
ar8327_vtu_flush(struct ar8xxx_priv *priv) |
{ |
ar8327_vtu_op(priv, AR8327_VTU_FUNC1_OP_FLUSH, 0); |
} |
|
static void |
ar8327_vtu_load_vlan(struct ar8xxx_priv *priv, u32 vid, u32 port_mask) |
{ |
u32 op; |
u32 val; |
int i; |
|
op = AR8327_VTU_FUNC1_OP_LOAD | (vid << AR8327_VTU_FUNC1_VID_S); |
val = AR8327_VTU_FUNC0_VALID | AR8327_VTU_FUNC0_IVL; |
for (i = 0; i < AR8327_NUM_PORTS; i++) { |
u32 mode; |
|
if ((port_mask & BIT(i)) == 0) |
mode = AR8327_VTU_FUNC0_EG_MODE_NOT; |
else if (priv->vlan == 0) |
mode = AR8327_VTU_FUNC0_EG_MODE_KEEP; |
else if ((priv->vlan_tagged & BIT(i)) || (priv->vlan_id[priv->pvid[i]] != vid)) |
mode = AR8327_VTU_FUNC0_EG_MODE_TAG; |
else |
mode = AR8327_VTU_FUNC0_EG_MODE_UNTAG; |
|
val |= mode << AR8327_VTU_FUNC0_EG_MODE_S(i); |
} |
ar8327_vtu_op(priv, op, val); |
} |
|
static void |
ar8327_setup_port(struct ar8xxx_priv *priv, int port, u32 members) |
{ |
u32 t; |
u32 egress, ingress; |
u32 pvid = priv->vlan_id[priv->pvid[port]]; |
|
if (priv->vlan) { |
egress = AR8327_PORT_VLAN1_OUT_MODE_UNMOD; |
ingress = AR8216_IN_SECURE; |
} else { |
egress = AR8327_PORT_VLAN1_OUT_MODE_UNTOUCH; |
ingress = AR8216_IN_PORT_ONLY; |
} |
|
t = pvid << AR8327_PORT_VLAN0_DEF_SVID_S; |
t |= pvid << AR8327_PORT_VLAN0_DEF_CVID_S; |
if (priv->vlan && priv->port_vlan_prio[port]) { |
u32 prio = priv->port_vlan_prio[port]; |
|
t |= prio << AR8327_PORT_VLAN0_DEF_SPRI_S; |
t |= prio << AR8327_PORT_VLAN0_DEF_CPRI_S; |
} |
ar8xxx_write(priv, AR8327_REG_PORT_VLAN0(port), t); |
|
t = AR8327_PORT_VLAN1_PORT_VLAN_PROP; |
t |= egress << AR8327_PORT_VLAN1_OUT_MODE_S; |
if (priv->vlan && priv->port_vlan_prio[port]) |
t |= AR8327_PORT_VLAN1_VLAN_PRI_PROP; |
|
ar8xxx_write(priv, AR8327_REG_PORT_VLAN1(port), t); |
|
t = members; |
t |= AR8327_PORT_LOOKUP_LEARN; |
t |= ingress << AR8327_PORT_LOOKUP_IN_MODE_S; |
t |= AR8216_PORT_STATE_FORWARD << AR8327_PORT_LOOKUP_STATE_S; |
ar8xxx_write(priv, AR8327_REG_PORT_LOOKUP(port), t); |
} |
|
static int |
ar8327_sw_get_ports(struct switch_dev *dev, struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
u8 ports = priv->vlan_table[val->port_vlan]; |
int i; |
|
val->len = 0; |
for (i = 0; i < dev->ports; i++) { |
struct switch_port *p; |
|
if (!(ports & (1 << i))) |
continue; |
|
p = &val->value.ports[val->len++]; |
p->id = i; |
if ((priv->vlan_tagged & (1 << i)) || (priv->pvid[i] != val->port_vlan)) |
p->flags = (1 << SWITCH_PORT_FLAG_TAGGED); |
else |
p->flags = 0; |
} |
return 0; |
} |
|
static int |
ar8327_sw_set_ports(struct switch_dev *dev, struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
u8 *vt = &priv->vlan_table[val->port_vlan]; |
int i; |
|
*vt = 0; |
for (i = 0; i < val->len; i++) { |
struct switch_port *p = &val->value.ports[i]; |
|
if (p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) { |
if (val->port_vlan == priv->pvid[p->id]) { |
priv->vlan_tagged |= (1 << p->id); |
} |
} else { |
priv->vlan_tagged &= ~(1 << p->id); |
priv->pvid[p->id] = val->port_vlan; |
} |
|
*vt |= 1 << p->id; |
} |
return 0; |
} |
|
static void |
ar8327_set_mirror_regs(struct ar8xxx_priv *priv) |
{ |
int port; |
|
/* reset all mirror registers */ |
ar8xxx_rmw(priv, AR8327_REG_FWD_CTRL0, |
AR8327_FWD_CTRL0_MIRROR_PORT, |
(0xF << AR8327_FWD_CTRL0_MIRROR_PORT_S)); |
for (port = 0; port < AR8327_NUM_PORTS; port++) { |
ar8xxx_reg_clear(priv, AR8327_REG_PORT_LOOKUP(port), |
AR8327_PORT_LOOKUP_ING_MIRROR_EN); |
|
ar8xxx_reg_clear(priv, AR8327_REG_PORT_HOL_CTRL1(port), |
AR8327_PORT_HOL_CTRL1_EG_MIRROR_EN); |
} |
|
/* now enable mirroring if necessary */ |
if (priv->source_port >= AR8327_NUM_PORTS || |
priv->monitor_port >= AR8327_NUM_PORTS || |
priv->source_port == priv->monitor_port) { |
return; |
} |
|
ar8xxx_rmw(priv, AR8327_REG_FWD_CTRL0, |
AR8327_FWD_CTRL0_MIRROR_PORT, |
(priv->monitor_port << AR8327_FWD_CTRL0_MIRROR_PORT_S)); |
|
if (priv->mirror_rx) |
ar8xxx_reg_set(priv, AR8327_REG_PORT_LOOKUP(priv->source_port), |
AR8327_PORT_LOOKUP_ING_MIRROR_EN); |
|
if (priv->mirror_tx) |
ar8xxx_reg_set(priv, AR8327_REG_PORT_HOL_CTRL1(priv->source_port), |
AR8327_PORT_HOL_CTRL1_EG_MIRROR_EN); |
} |
|
static int |
ar8327_sw_set_eee(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
struct ar8327_data *data = priv->chip_data; |
int port = val->port_vlan; |
int phy; |
|
if (port >= dev->ports) |
return -EINVAL; |
if (port == 0 || port == 6) |
return -EOPNOTSUPP; |
|
phy = port - 1; |
|
data->eee[phy] = !!(val->value.i); |
|
return 0; |
} |
|
static int |
ar8327_sw_get_eee(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
const struct ar8327_data *data = priv->chip_data; |
int port = val->port_vlan; |
int phy; |
|
if (port >= dev->ports) |
return -EINVAL; |
if (port == 0 || port == 6) |
return -EOPNOTSUPP; |
|
phy = port - 1; |
|
val->value.i = data->eee[phy]; |
|
return 0; |
} |
|
static void |
ar8327_wait_atu_ready(struct ar8xxx_priv *priv, u16 r2, u16 r1) |
{ |
int timeout = 20; |
|
while (ar8xxx_mii_read32(priv, r2, r1) & AR8327_ATU_FUNC_BUSY && --timeout) { |
udelay(10); |
cond_resched(); |
} |
|
if (!timeout) |
pr_err("ar8327: timeout waiting for atu to become ready\n"); |
} |
|
static void ar8327_get_arl_entry(struct ar8xxx_priv *priv, |
struct arl_entry *a, u32 *status, enum arl_op op) |
{ |
struct mii_bus *bus = priv->mii_bus; |
u16 r2, page; |
u16 r1_data0, r1_data1, r1_data2, r1_func; |
u32 t, val0, val1, val2; |
int i; |
|
split_addr(AR8327_REG_ATU_DATA0, &r1_data0, &r2, &page); |
r2 |= 0x10; |
|
r1_data1 = (AR8327_REG_ATU_DATA1 >> 1) & 0x1e; |
r1_data2 = (AR8327_REG_ATU_DATA2 >> 1) & 0x1e; |
r1_func = (AR8327_REG_ATU_FUNC >> 1) & 0x1e; |
|
switch (op) { |
case AR8XXX_ARL_INITIALIZE: |
/* all ATU registers are on the same page |
* therefore set page only once |
*/ |
bus->write(bus, 0x18, 0, page); |
wait_for_page_switch(); |
|
ar8327_wait_atu_ready(priv, r2, r1_func); |
|
ar8xxx_mii_write32(priv, r2, r1_data0, 0); |
ar8xxx_mii_write32(priv, r2, r1_data1, 0); |
ar8xxx_mii_write32(priv, r2, r1_data2, 0); |
break; |
case AR8XXX_ARL_GET_NEXT: |
ar8xxx_mii_write32(priv, r2, r1_func, |
AR8327_ATU_FUNC_OP_GET_NEXT | |
AR8327_ATU_FUNC_BUSY); |
ar8327_wait_atu_ready(priv, r2, r1_func); |
|
val0 = ar8xxx_mii_read32(priv, r2, r1_data0); |
val1 = ar8xxx_mii_read32(priv, r2, r1_data1); |
val2 = ar8xxx_mii_read32(priv, r2, r1_data2); |
|
*status = val2 & AR8327_ATU_STATUS; |
if (!*status) |
break; |
|
i = 0; |
t = AR8327_ATU_PORT0; |
while (!(val1 & t) && ++i < AR8327_NUM_PORTS) |
t <<= 1; |
|
a->port = i; |
a->mac[0] = (val0 & AR8327_ATU_ADDR0) >> AR8327_ATU_ADDR0_S; |
a->mac[1] = (val0 & AR8327_ATU_ADDR1) >> AR8327_ATU_ADDR1_S; |
a->mac[2] = (val0 & AR8327_ATU_ADDR2) >> AR8327_ATU_ADDR2_S; |
a->mac[3] = (val0 & AR8327_ATU_ADDR3) >> AR8327_ATU_ADDR3_S; |
a->mac[4] = (val1 & AR8327_ATU_ADDR4) >> AR8327_ATU_ADDR4_S; |
a->mac[5] = (val1 & AR8327_ATU_ADDR5) >> AR8327_ATU_ADDR5_S; |
break; |
} |
} |
|
static int |
ar8327_sw_hw_apply(struct switch_dev *dev) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
const struct ar8327_data *data = priv->chip_data; |
int ret, i; |
|
ret = ar8xxx_sw_hw_apply(dev); |
if (ret) |
return ret; |
|
for (i=0; i < AR8XXX_NUM_PHYS; i++) { |
if (data->eee[i]) |
ar8xxx_reg_clear(priv, AR8327_REG_EEE_CTRL, |
AR8327_EEE_CTRL_DISABLE_PHY(i)); |
else |
ar8xxx_reg_set(priv, AR8327_REG_EEE_CTRL, |
AR8327_EEE_CTRL_DISABLE_PHY(i)); |
} |
|
return 0; |
} |
|
int |
ar8327_sw_get_port_igmp_snooping(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
int port = val->port_vlan; |
|
if (port >= dev->ports) |
return -EINVAL; |
|
mutex_lock(&priv->reg_mutex); |
val->value.i = ar8327_get_port_igmp(priv, port); |
mutex_unlock(&priv->reg_mutex); |
|
return 0; |
} |
|
int |
ar8327_sw_set_port_igmp_snooping(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
int port = val->port_vlan; |
|
if (port >= dev->ports) |
return -EINVAL; |
|
mutex_lock(&priv->reg_mutex); |
ar8327_set_port_igmp(priv, port, val->value.i); |
mutex_unlock(&priv->reg_mutex); |
|
return 0; |
} |
|
int |
ar8327_sw_get_igmp_snooping(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
int port; |
|
for (port = 0; port < dev->ports; port++) { |
val->port_vlan = port; |
if (ar8327_sw_get_port_igmp_snooping(dev, attr, val) || |
!val->value.i) |
break; |
} |
|
return 0; |
} |
|
int |
ar8327_sw_set_igmp_snooping(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
int port; |
|
for (port = 0; port < dev->ports; port++) { |
val->port_vlan = port; |
if (ar8327_sw_set_port_igmp_snooping(dev, attr, val)) |
break; |
} |
|
return 0; |
} |
|
int |
ar8327_sw_get_igmp_v3(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
u32 val_reg; |
|
mutex_lock(&priv->reg_mutex); |
val_reg = ar8xxx_read(priv, AR8327_REG_FRAME_ACK_CTRL1); |
val->value.i = ((val_reg & AR8327_FRAME_ACK_CTRL_IGMP_V3_EN) != 0); |
mutex_unlock(&priv->reg_mutex); |
|
return 0; |
} |
|
int |
ar8327_sw_set_igmp_v3(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
|
mutex_lock(&priv->reg_mutex); |
if (val->value.i) |
ar8xxx_reg_set(priv, AR8327_REG_FRAME_ACK_CTRL1, |
AR8327_FRAME_ACK_CTRL_IGMP_V3_EN); |
else |
ar8xxx_reg_clear(priv, AR8327_REG_FRAME_ACK_CTRL1, |
AR8327_FRAME_ACK_CTRL_IGMP_V3_EN); |
mutex_unlock(&priv->reg_mutex); |
|
return 0; |
} |
|
static int |
ar8327_sw_set_port_vlan_prio(struct switch_dev *dev, const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
int port = val->port_vlan; |
|
if (port >= dev->ports) |
return -EINVAL; |
if (port == 0 || port == 6) |
return -EOPNOTSUPP; |
if (val->value.i < 0 || val->value.i > 7) |
return -EINVAL; |
|
priv->port_vlan_prio[port] = val->value.i; |
|
return 0; |
} |
|
static int |
ar8327_sw_get_port_vlan_prio(struct switch_dev *dev, const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
int port = val->port_vlan; |
|
val->value.i = priv->port_vlan_prio[port]; |
|
return 0; |
} |
|
static const struct switch_attr ar8327_sw_attr_globals[] = { |
{ |
.type = SWITCH_TYPE_INT, |
.name = "enable_vlan", |
.description = "Enable VLAN mode", |
.set = ar8xxx_sw_set_vlan, |
.get = ar8xxx_sw_get_vlan, |
.max = 1 |
}, |
{ |
.type = SWITCH_TYPE_NOVAL, |
.name = "reset_mibs", |
.description = "Reset all MIB counters", |
.set = ar8xxx_sw_set_reset_mibs, |
}, |
{ |
.type = SWITCH_TYPE_INT, |
.name = "enable_mirror_rx", |
.description = "Enable mirroring of RX packets", |
.set = ar8xxx_sw_set_mirror_rx_enable, |
.get = ar8xxx_sw_get_mirror_rx_enable, |
.max = 1 |
}, |
{ |
.type = SWITCH_TYPE_INT, |
.name = "enable_mirror_tx", |
.description = "Enable mirroring of TX packets", |
.set = ar8xxx_sw_set_mirror_tx_enable, |
.get = ar8xxx_sw_get_mirror_tx_enable, |
.max = 1 |
}, |
{ |
.type = SWITCH_TYPE_INT, |
.name = "mirror_monitor_port", |
.description = "Mirror monitor port", |
.set = ar8xxx_sw_set_mirror_monitor_port, |
.get = ar8xxx_sw_get_mirror_monitor_port, |
.max = AR8327_NUM_PORTS - 1 |
}, |
{ |
.type = SWITCH_TYPE_INT, |
.name = "mirror_source_port", |
.description = "Mirror source port", |
.set = ar8xxx_sw_set_mirror_source_port, |
.get = ar8xxx_sw_get_mirror_source_port, |
.max = AR8327_NUM_PORTS - 1 |
}, |
{ |
.type = SWITCH_TYPE_INT, |
.name = "arl_age_time", |
.description = "ARL age time (secs)", |
.set = ar8xxx_sw_set_arl_age_time, |
.get = ar8xxx_sw_get_arl_age_time, |
}, |
{ |
.type = SWITCH_TYPE_STRING, |
.name = "arl_table", |
.description = "Get ARL table", |
.set = NULL, |
.get = ar8xxx_sw_get_arl_table, |
}, |
{ |
.type = SWITCH_TYPE_NOVAL, |
.name = "flush_arl_table", |
.description = "Flush ARL table", |
.set = ar8xxx_sw_set_flush_arl_table, |
}, |
{ |
.type = SWITCH_TYPE_INT, |
.name = "igmp_snooping", |
.description = "Enable IGMP Snooping", |
.set = ar8327_sw_set_igmp_snooping, |
.get = ar8327_sw_get_igmp_snooping, |
.max = 1 |
}, |
{ |
.type = SWITCH_TYPE_INT, |
.name = "igmp_v3", |
.description = "Enable IGMPv3 support", |
.set = ar8327_sw_set_igmp_v3, |
.get = ar8327_sw_get_igmp_v3, |
.max = 1 |
}, |
}; |
|
static const struct switch_attr ar8327_sw_attr_port[] = { |
{ |
.type = SWITCH_TYPE_NOVAL, |
.name = "reset_mib", |
.description = "Reset single port MIB counters", |
.set = ar8xxx_sw_set_port_reset_mib, |
}, |
{ |
.type = SWITCH_TYPE_STRING, |
.name = "mib", |
.description = "Get port's MIB counters", |
.set = NULL, |
.get = ar8xxx_sw_get_port_mib, |
}, |
{ |
.type = SWITCH_TYPE_INT, |
.name = "enable_eee", |
.description = "Enable EEE PHY sleep mode", |
.set = ar8327_sw_set_eee, |
.get = ar8327_sw_get_eee, |
.max = 1, |
}, |
{ |
.type = SWITCH_TYPE_NOVAL, |
.name = "flush_arl_table", |
.description = "Flush port's ARL table entries", |
.set = ar8xxx_sw_set_flush_port_arl_table, |
}, |
{ |
.type = SWITCH_TYPE_INT, |
.name = "igmp_snooping", |
.description = "Enable port's IGMP Snooping", |
.set = ar8327_sw_set_port_igmp_snooping, |
.get = ar8327_sw_get_port_igmp_snooping, |
.max = 1 |
}, |
{ |
.type = SWITCH_TYPE_INT, |
.name = "vlan_prio", |
.description = "Port VLAN default priority (VLAN PCP) (0-7)", |
.set = ar8327_sw_set_port_vlan_prio, |
.get = ar8327_sw_get_port_vlan_prio, |
.max = 7, |
}, |
}; |
|
static const struct switch_dev_ops ar8327_sw_ops = { |
.attr_global = { |
.attr = ar8327_sw_attr_globals, |
.n_attr = ARRAY_SIZE(ar8327_sw_attr_globals), |
}, |
.attr_port = { |
.attr = ar8327_sw_attr_port, |
.n_attr = ARRAY_SIZE(ar8327_sw_attr_port), |
}, |
.attr_vlan = { |
.attr = ar8xxx_sw_attr_vlan, |
.n_attr = ARRAY_SIZE(ar8xxx_sw_attr_vlan), |
}, |
.get_port_pvid = ar8xxx_sw_get_pvid, |
.set_port_pvid = ar8xxx_sw_set_pvid, |
.get_vlan_ports = ar8327_sw_get_ports, |
.set_vlan_ports = ar8327_sw_set_ports, |
.apply_config = ar8327_sw_hw_apply, |
.reset_switch = ar8xxx_sw_reset_switch, |
.get_port_link = ar8xxx_sw_get_port_link, |
/* The following op is disabled as it hogs the CPU and degrades performance. |
An implementation has been attempted in 4d8a66d but reading MIB data is slow |
on ar8xxx switches. |
|
The high CPU load has been traced down to the ar8xxx_reg_wait() call in |
ar8xxx_mib_op(), which has to usleep_range() till the MIB busy flag set by |
the request to update the MIB counter is cleared. */ |
#if 0 |
.get_port_stats = ar8xxx_sw_get_port_stats, |
#endif |
}; |
|
const struct ar8xxx_chip ar8327_chip = { |
.caps = AR8XXX_CAP_GIGE | AR8XXX_CAP_MIB_COUNTERS, |
.config_at_probe = true, |
.mii_lo_first = true, |
|
.name = "Atheros AR8327", |
.ports = AR8327_NUM_PORTS, |
.vlans = AR8X16_MAX_VLANS, |
.swops = &ar8327_sw_ops, |
|
.reg_port_stats_start = 0x1000, |
.reg_port_stats_length = 0x100, |
.reg_arl_ctrl = AR8327_REG_ARL_CTRL, |
|
.hw_init = ar8327_hw_init, |
.cleanup = ar8327_cleanup, |
.init_globals = ar8327_init_globals, |
.init_port = ar8327_init_port, |
.setup_port = ar8327_setup_port, |
.read_port_status = ar8327_read_port_status, |
.read_port_eee_status = ar8327_read_port_eee_status, |
.atu_flush = ar8327_atu_flush, |
.atu_flush_port = ar8327_atu_flush_port, |
.vtu_flush = ar8327_vtu_flush, |
.vtu_load_vlan = ar8327_vtu_load_vlan, |
.set_mirror_regs = ar8327_set_mirror_regs, |
.get_arl_entry = ar8327_get_arl_entry, |
.sw_hw_apply = ar8327_sw_hw_apply, |
|
.num_mibs = ARRAY_SIZE(ar8236_mibs), |
.mib_decs = ar8236_mibs, |
.mib_func = AR8327_REG_MIB_FUNC |
}; |
|
const struct ar8xxx_chip ar8337_chip = { |
.caps = AR8XXX_CAP_GIGE | AR8XXX_CAP_MIB_COUNTERS, |
.config_at_probe = true, |
.mii_lo_first = true, |
|
.name = "Atheros AR8337", |
.ports = AR8327_NUM_PORTS, |
.vlans = AR8X16_MAX_VLANS, |
.swops = &ar8327_sw_ops, |
|
.reg_port_stats_start = 0x1000, |
.reg_port_stats_length = 0x100, |
.reg_arl_ctrl = AR8327_REG_ARL_CTRL, |
|
.hw_init = ar8327_hw_init, |
.cleanup = ar8327_cleanup, |
.init_globals = ar8327_init_globals, |
.init_port = ar8327_init_port, |
.setup_port = ar8327_setup_port, |
.read_port_status = ar8327_read_port_status, |
.read_port_eee_status = ar8327_read_port_eee_status, |
.atu_flush = ar8327_atu_flush, |
.atu_flush_port = ar8327_atu_flush_port, |
.vtu_flush = ar8327_vtu_flush, |
.vtu_load_vlan = ar8327_vtu_load_vlan, |
.phy_fixup = ar8327_phy_fixup, |
.set_mirror_regs = ar8327_set_mirror_regs, |
.get_arl_entry = ar8327_get_arl_entry, |
.sw_hw_apply = ar8327_sw_hw_apply, |
|
.num_mibs = ARRAY_SIZE(ar8236_mibs), |
.mib_decs = ar8236_mibs, |
.mib_func = AR8327_REG_MIB_FUNC |
}; |
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/b53/b53_common.c |
@@ -0,0 +1,1722 @@ |
/* |
* B53 switch driver main logic |
* |
* Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org> |
* |
* Permission to use, copy, modify, and/or distribute this software for any |
* purpose with or without fee is hereby granted, provided that the above |
* copyright notice and this permission notice appear in all copies. |
* |
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
*/ |
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
|
#include <linux/delay.h> |
#include <linux/export.h> |
#include <linux/gpio.h> |
#include <linux/kernel.h> |
#include <linux/module.h> |
#include <linux/switch.h> |
#include <linux/phy.h> |
#include <linux/of.h> |
#include <linux/of_net.h> |
#include <linux/platform_data/b53.h> |
|
#include "b53_regs.h" |
#include "b53_priv.h" |
|
/* buffer size needed for displaying all MIBs with max'd values */ |
#define B53_BUF_SIZE 1188 |
|
struct b53_mib_desc { |
u8 size; |
u8 offset; |
const char *name; |
}; |
|
/* BCM5365 MIB counters */ |
static const struct b53_mib_desc b53_mibs_65[] = { |
{ 8, 0x00, "TxOctets" }, |
{ 4, 0x08, "TxDropPkts" }, |
{ 4, 0x10, "TxBroadcastPkts" }, |
{ 4, 0x14, "TxMulticastPkts" }, |
{ 4, 0x18, "TxUnicastPkts" }, |
{ 4, 0x1c, "TxCollisions" }, |
{ 4, 0x20, "TxSingleCollision" }, |
{ 4, 0x24, "TxMultipleCollision" }, |
{ 4, 0x28, "TxDeferredTransmit" }, |
{ 4, 0x2c, "TxLateCollision" }, |
{ 4, 0x30, "TxExcessiveCollision" }, |
{ 4, 0x38, "TxPausePkts" }, |
{ 8, 0x44, "RxOctets" }, |
{ 4, 0x4c, "RxUndersizePkts" }, |
{ 4, 0x50, "RxPausePkts" }, |
{ 4, 0x54, "Pkts64Octets" }, |
{ 4, 0x58, "Pkts65to127Octets" }, |
{ 4, 0x5c, "Pkts128to255Octets" }, |
{ 4, 0x60, "Pkts256to511Octets" }, |
{ 4, 0x64, "Pkts512to1023Octets" }, |
{ 4, 0x68, "Pkts1024to1522Octets" }, |
{ 4, 0x6c, "RxOversizePkts" }, |
{ 4, 0x70, "RxJabbers" }, |
{ 4, 0x74, "RxAlignmentErrors" }, |
{ 4, 0x78, "RxFCSErrors" }, |
{ 8, 0x7c, "RxGoodOctets" }, |
{ 4, 0x84, "RxDropPkts" }, |
{ 4, 0x88, "RxUnicastPkts" }, |
{ 4, 0x8c, "RxMulticastPkts" }, |
{ 4, 0x90, "RxBroadcastPkts" }, |
{ 4, 0x94, "RxSAChanges" }, |
{ 4, 0x98, "RxFragments" }, |
{ }, |
}; |
|
#define B63XX_MIB_TXB_ID 0 /* TxOctets */ |
#define B63XX_MIB_RXB_ID 14 /* RxOctets */ |
|
/* BCM63xx MIB counters */ |
static const struct b53_mib_desc b53_mibs_63xx[] = { |
{ 8, 0x00, "TxOctets" }, |
{ 4, 0x08, "TxDropPkts" }, |
{ 4, 0x0c, "TxQoSPkts" }, |
{ 4, 0x10, "TxBroadcastPkts" }, |
{ 4, 0x14, "TxMulticastPkts" }, |
{ 4, 0x18, "TxUnicastPkts" }, |
{ 4, 0x1c, "TxCollisions" }, |
{ 4, 0x20, "TxSingleCollision" }, |
{ 4, 0x24, "TxMultipleCollision" }, |
{ 4, 0x28, "TxDeferredTransmit" }, |
{ 4, 0x2c, "TxLateCollision" }, |
{ 4, 0x30, "TxExcessiveCollision" }, |
{ 4, 0x38, "TxPausePkts" }, |
{ 8, 0x3c, "TxQoSOctets" }, |
{ 8, 0x44, "RxOctets" }, |
{ 4, 0x4c, "RxUndersizePkts" }, |
{ 4, 0x50, "RxPausePkts" }, |
{ 4, 0x54, "Pkts64Octets" }, |
{ 4, 0x58, "Pkts65to127Octets" }, |
{ 4, 0x5c, "Pkts128to255Octets" }, |
{ 4, 0x60, "Pkts256to511Octets" }, |
{ 4, 0x64, "Pkts512to1023Octets" }, |
{ 4, 0x68, "Pkts1024to1522Octets" }, |
{ 4, 0x6c, "RxOversizePkts" }, |
{ 4, 0x70, "RxJabbers" }, |
{ 4, 0x74, "RxAlignmentErrors" }, |
{ 4, 0x78, "RxFCSErrors" }, |
{ 8, 0x7c, "RxGoodOctets" }, |
{ 4, 0x84, "RxDropPkts" }, |
{ 4, 0x88, "RxUnicastPkts" }, |
{ 4, 0x8c, "RxMulticastPkts" }, |
{ 4, 0x90, "RxBroadcastPkts" }, |
{ 4, 0x94, "RxSAChanges" }, |
{ 4, 0x98, "RxFragments" }, |
{ 4, 0xa0, "RxSymbolErrors" }, |
{ 4, 0xa4, "RxQoSPkts" }, |
{ 8, 0xa8, "RxQoSOctets" }, |
{ 4, 0xb0, "Pkts1523to2047Octets" }, |
{ 4, 0xb4, "Pkts2048to4095Octets" }, |
{ 4, 0xb8, "Pkts4096to8191Octets" }, |
{ 4, 0xbc, "Pkts8192to9728Octets" }, |
{ 4, 0xc0, "RxDiscarded" }, |
{ } |
}; |
|
#define B53XX_MIB_TXB_ID 0 /* TxOctets */ |
#define B53XX_MIB_RXB_ID 12 /* RxOctets */ |
|
/* MIB counters */ |
static const struct b53_mib_desc b53_mibs[] = { |
{ 8, 0x00, "TxOctets" }, |
{ 4, 0x08, "TxDropPkts" }, |
{ 4, 0x10, "TxBroadcastPkts" }, |
{ 4, 0x14, "TxMulticastPkts" }, |
{ 4, 0x18, "TxUnicastPkts" }, |
{ 4, 0x1c, "TxCollisions" }, |
{ 4, 0x20, "TxSingleCollision" }, |
{ 4, 0x24, "TxMultipleCollision" }, |
{ 4, 0x28, "TxDeferredTransmit" }, |
{ 4, 0x2c, "TxLateCollision" }, |
{ 4, 0x30, "TxExcessiveCollision" }, |
{ 4, 0x38, "TxPausePkts" }, |
{ 8, 0x50, "RxOctets" }, |
{ 4, 0x58, "RxUndersizePkts" }, |
{ 4, 0x5c, "RxPausePkts" }, |
{ 4, 0x60, "Pkts64Octets" }, |
{ 4, 0x64, "Pkts65to127Octets" }, |
{ 4, 0x68, "Pkts128to255Octets" }, |
{ 4, 0x6c, "Pkts256to511Octets" }, |
{ 4, 0x70, "Pkts512to1023Octets" }, |
{ 4, 0x74, "Pkts1024to1522Octets" }, |
{ 4, 0x78, "RxOversizePkts" }, |
{ 4, 0x7c, "RxJabbers" }, |
{ 4, 0x80, "RxAlignmentErrors" }, |
{ 4, 0x84, "RxFCSErrors" }, |
{ 8, 0x88, "RxGoodOctets" }, |
{ 4, 0x90, "RxDropPkts" }, |
{ 4, 0x94, "RxUnicastPkts" }, |
{ 4, 0x98, "RxMulticastPkts" }, |
{ 4, 0x9c, "RxBroadcastPkts" }, |
{ 4, 0xa0, "RxSAChanges" }, |
{ 4, 0xa4, "RxFragments" }, |
{ 4, 0xa8, "RxJumboPkts" }, |
{ 4, 0xac, "RxSymbolErrors" }, |
{ 4, 0xc0, "RxDiscarded" }, |
{ } |
}; |
|
static int b53_do_vlan_op(struct b53_device *dev, u8 op) |
{ |
unsigned int i; |
|
b53_write8(dev, B53_ARLIO_PAGE, dev->vta_regs[0], VTA_START_CMD | op); |
|
for (i = 0; i < 10; i++) { |
u8 vta; |
|
b53_read8(dev, B53_ARLIO_PAGE, dev->vta_regs[0], &vta); |
if (!(vta & VTA_START_CMD)) |
return 0; |
|
usleep_range(100, 200); |
} |
|
return -EIO; |
} |
|
static void b53_set_vlan_entry(struct b53_device *dev, u16 vid, u16 members, |
u16 untag) |
{ |
if (is5325(dev)) { |
u32 entry = 0; |
|
if (members) { |
entry = ((untag & VA_UNTAG_MASK_25) << VA_UNTAG_S_25) | |
members; |
if (dev->core_rev >= 3) |
entry |= VA_VALID_25_R4 | vid << VA_VID_HIGH_S; |
else |
entry |= VA_VALID_25; |
} |
|
b53_write32(dev, B53_VLAN_PAGE, B53_VLAN_WRITE_25, entry); |
b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_TABLE_ACCESS_25, vid | |
VTA_RW_STATE_WR | VTA_RW_OP_EN); |
} else if (is5365(dev)) { |
u16 entry = 0; |
|
if (members) |
entry = ((untag & VA_UNTAG_MASK_65) << VA_UNTAG_S_65) | |
members | VA_VALID_65; |
|
b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_WRITE_65, entry); |
b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_TABLE_ACCESS_65, vid | |
VTA_RW_STATE_WR | VTA_RW_OP_EN); |
} else { |
b53_write16(dev, B53_ARLIO_PAGE, dev->vta_regs[1], vid); |
b53_write32(dev, B53_ARLIO_PAGE, dev->vta_regs[2], |
(untag << VTE_UNTAG_S) | members); |
|
b53_do_vlan_op(dev, VTA_CMD_WRITE); |
} |
} |
|
void b53_set_forwarding(struct b53_device *dev, int enable) |
{ |
u8 mgmt; |
|
b53_read8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, &mgmt); |
|
if (enable) |
mgmt |= SM_SW_FWD_EN; |
else |
mgmt &= ~SM_SW_FWD_EN; |
|
b53_write8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, mgmt); |
} |
|
static void b53_enable_vlan(struct b53_device *dev, int enable) |
{ |
u8 mgmt, vc0, vc1, vc4 = 0, vc5; |
|
b53_read8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, &mgmt); |
b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL0, &vc0); |
b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL1, &vc1); |
|
if (is5325(dev) || is5365(dev)) { |
b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_25, &vc4); |
b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5_25, &vc5); |
} else if (is63xx(dev)) { |
b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_63XX, &vc4); |
b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5_63XX, &vc5); |
} else { |
b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4, &vc4); |
b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5, &vc5); |
} |
|
mgmt &= ~SM_SW_FWD_MODE; |
|
if (enable) { |
vc0 |= VC0_VLAN_EN | VC0_VID_CHK_EN | VC0_VID_HASH_VID; |
vc1 |= VC1_RX_MCST_UNTAG_EN | VC1_RX_MCST_FWD_EN; |
vc4 &= ~VC4_ING_VID_CHECK_MASK; |
vc4 |= VC4_ING_VID_VIO_DROP << VC4_ING_VID_CHECK_S; |
vc5 |= VC5_DROP_VTABLE_MISS; |
|
if (is5325(dev)) |
vc0 &= ~VC0_RESERVED_1; |
|
if (is5325(dev) || is5365(dev)) |
vc1 |= VC1_RX_MCST_TAG_EN; |
|
if (!is5325(dev) && !is5365(dev)) { |
if (dev->allow_vid_4095) |
vc5 |= VC5_VID_FFF_EN; |
else |
vc5 &= ~VC5_VID_FFF_EN; |
} |
} else { |
vc0 &= ~(VC0_VLAN_EN | VC0_VID_CHK_EN | VC0_VID_HASH_VID); |
vc1 &= ~(VC1_RX_MCST_UNTAG_EN | VC1_RX_MCST_FWD_EN); |
vc4 &= ~VC4_ING_VID_CHECK_MASK; |
vc5 &= ~VC5_DROP_VTABLE_MISS; |
|
if (is5325(dev) || is5365(dev)) |
vc4 |= VC4_ING_VID_VIO_FWD << VC4_ING_VID_CHECK_S; |
else |
vc4 |= VC4_ING_VID_VIO_TO_IMP << VC4_ING_VID_CHECK_S; |
|
if (is5325(dev) || is5365(dev)) |
vc1 &= ~VC1_RX_MCST_TAG_EN; |
|
if (!is5325(dev) && !is5365(dev)) |
vc5 &= ~VC5_VID_FFF_EN; |
} |
|
b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL0, vc0); |
b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL1, vc1); |
|
if (is5325(dev) || is5365(dev)) { |
/* enable the high 8 bit vid check on 5325 */ |
if (is5325(dev) && enable) |
b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL3, |
VC3_HIGH_8BIT_EN); |
else |
b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL3, 0); |
|
b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_25, vc4); |
b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5_25, vc5); |
} else if (is63xx(dev)) { |
b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_CTRL3_63XX, 0); |
b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_63XX, vc4); |
b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5_63XX, vc5); |
} else { |
b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_CTRL3, 0); |
b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4, vc4); |
b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5, vc5); |
} |
|
b53_write8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, mgmt); |
} |
|
static int b53_set_jumbo(struct b53_device *dev, int enable, int allow_10_100) |
{ |
u32 port_mask = 0; |
u16 max_size = JMS_MIN_SIZE; |
|
if (is5325(dev) || is5365(dev)) |
return -EINVAL; |
|
if (enable) { |
port_mask = dev->enabled_ports; |
max_size = JMS_MAX_SIZE; |
if (allow_10_100) |
port_mask |= JPM_10_100_JUMBO_EN; |
} |
|
b53_write32(dev, B53_JUMBO_PAGE, dev->jumbo_pm_reg, port_mask); |
return b53_write16(dev, B53_JUMBO_PAGE, dev->jumbo_size_reg, max_size); |
} |
|
static int b53_flush_arl(struct b53_device *dev) |
{ |
unsigned int i; |
|
b53_write8(dev, B53_CTRL_PAGE, B53_FAST_AGE_CTRL, |
FAST_AGE_DONE | FAST_AGE_DYNAMIC | FAST_AGE_STATIC); |
|
for (i = 0; i < 10; i++) { |
u8 fast_age_ctrl; |
|
b53_read8(dev, B53_CTRL_PAGE, B53_FAST_AGE_CTRL, |
&fast_age_ctrl); |
|
if (!(fast_age_ctrl & FAST_AGE_DONE)) |
return 0; |
|
mdelay(1); |
} |
|
pr_warn("time out while flushing ARL\n"); |
|
return -EINVAL; |
} |
|
static void b53_enable_ports(struct b53_device *dev) |
{ |
unsigned i; |
|
b53_for_each_port(dev, i) { |
u8 port_ctrl; |
u16 pvlan_mask; |
|
/* |
* prevent leaking packets between wan and lan in unmanaged |
* mode through port vlans. |
*/ |
if (dev->enable_vlan || is_cpu_port(dev, i)) |
pvlan_mask = 0x1ff; |
else if (is531x5(dev) || is5301x(dev)) |
/* BCM53115 may use a different port as cpu port */ |
pvlan_mask = BIT(dev->sw_dev.cpu_port); |
else |
pvlan_mask = BIT(B53_CPU_PORT); |
|
/* BCM5325 CPU port is at 8 */ |
if ((is5325(dev) || is5365(dev)) && i == B53_CPU_PORT_25) |
i = B53_CPU_PORT; |
|
if (dev->chip_id == BCM5398_DEVICE_ID && (i == 6 || i == 7)) |
/* disable unused ports 6 & 7 */ |
port_ctrl = PORT_CTRL_RX_DISABLE | PORT_CTRL_TX_DISABLE; |
else if (i == B53_CPU_PORT) |
port_ctrl = PORT_CTRL_RX_BCST_EN | |
PORT_CTRL_RX_MCST_EN | |
PORT_CTRL_RX_UCST_EN; |
else |
port_ctrl = 0; |
|
b53_write16(dev, B53_PVLAN_PAGE, B53_PVLAN_PORT_MASK(i), |
pvlan_mask); |
|
/* port state is handled by bcm63xx_enet driver */ |
if (!is63xx(dev) && !(is5301x(dev) && i == 6)) |
b53_write8(dev, B53_CTRL_PAGE, B53_PORT_CTRL(i), |
port_ctrl); |
} |
} |
|
static void b53_enable_mib(struct b53_device *dev) |
{ |
u8 gc; |
|
b53_read8(dev, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, &gc); |
|
gc &= ~(GC_RESET_MIB | GC_MIB_AC_EN); |
|
b53_write8(dev, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, gc); |
} |
|
static int b53_apply(struct b53_device *dev) |
{ |
int i; |
|
/* clear all vlan entries */ |
if (is5325(dev) || is5365(dev)) { |
for (i = 1; i < dev->sw_dev.vlans; i++) |
b53_set_vlan_entry(dev, i, 0, 0); |
} else { |
b53_do_vlan_op(dev, VTA_CMD_CLEAR); |
} |
|
b53_enable_vlan(dev, dev->enable_vlan); |
|
/* fill VLAN table */ |
if (dev->enable_vlan) { |
for (i = 0; i < dev->sw_dev.vlans; i++) { |
struct b53_vlan *vlan = &dev->vlans[i]; |
|
if (!vlan->members) |
continue; |
|
b53_set_vlan_entry(dev, i, vlan->members, vlan->untag); |
} |
|
b53_for_each_port(dev, i) |
b53_write16(dev, B53_VLAN_PAGE, |
B53_VLAN_PORT_DEF_TAG(i), |
dev->ports[i].pvid); |
} else { |
b53_for_each_port(dev, i) |
b53_write16(dev, B53_VLAN_PAGE, |
B53_VLAN_PORT_DEF_TAG(i), 1); |
|
} |
|
b53_enable_ports(dev); |
|
if (!is5325(dev) && !is5365(dev)) |
b53_set_jumbo(dev, dev->enable_jumbo, 1); |
|
return 0; |
} |
|
static void b53_switch_reset_gpio(struct b53_device *dev) |
{ |
int gpio = dev->reset_gpio; |
|
if (gpio < 0) |
return; |
|
/* |
* Reset sequence: RESET low(50ms)->high(20ms) |
*/ |
gpio_set_value(gpio, 0); |
mdelay(50); |
|
gpio_set_value(gpio, 1); |
mdelay(20); |
|
dev->current_page = 0xff; |
} |
|
static int b53_configure_ports_of(struct b53_device *dev) |
{ |
struct device_node *dn, *pn; |
u32 port_num; |
|
dn = of_get_child_by_name(dev_of_node(dev->dev), "ports"); |
|
for_each_available_child_of_node(dn, pn) { |
struct device_node *fixed_link; |
|
if (of_property_read_u32(pn, "reg", &port_num)) |
continue; |
|
if (port_num > B53_CPU_PORT) |
continue; |
|
fixed_link = of_get_child_by_name(pn, "fixed-link"); |
if (fixed_link) { |
u32 spd; |
u8 po = GMII_PO_LINK; |
int mode = of_get_phy_mode(pn); |
|
if (!of_property_read_u32(fixed_link, "speed", &spd)) { |
switch (spd) { |
case 10: |
po |= GMII_PO_SPEED_10M; |
break; |
case 100: |
po |= GMII_PO_SPEED_100M; |
break; |
case 2000: |
if (is_imp_port(dev, port_num)) |
po |= PORT_OVERRIDE_SPEED_2000M; |
else |
po |= GMII_PO_SPEED_2000M; |
/* fall through */ |
case 1000: |
po |= GMII_PO_SPEED_1000M; |
break; |
} |
} |
|
if (of_property_read_bool(fixed_link, "full-duplex")) |
po |= PORT_OVERRIDE_FULL_DUPLEX; |
if (of_property_read_bool(fixed_link, "pause")) |
po |= GMII_PO_RX_FLOW; |
if (of_property_read_bool(fixed_link, "asym-pause")) |
po |= GMII_PO_TX_FLOW; |
|
if (is_imp_port(dev, port_num)) { |
po |= PORT_OVERRIDE_EN; |
|
if (is5325(dev) && |
mode == PHY_INTERFACE_MODE_REVMII) |
po |= PORT_OVERRIDE_RV_MII_25; |
|
b53_write8(dev, B53_CTRL_PAGE, |
B53_PORT_OVERRIDE_CTRL, po); |
|
if (is5325(dev) && |
mode == PHY_INTERFACE_MODE_REVMII) { |
b53_read8(dev, B53_CTRL_PAGE, |
B53_PORT_OVERRIDE_CTRL, &po); |
if (!(po & PORT_OVERRIDE_RV_MII_25)) |
pr_err("Failed to enable reverse MII mode\n"); |
return -EINVAL; |
} |
} else { |
po |= GMII_PO_EN; |
b53_write8(dev, B53_CTRL_PAGE, |
B53_GMII_PORT_OVERRIDE_CTRL(port_num), |
po); |
} |
} |
} |
|
return 0; |
} |
|
static int b53_configure_ports(struct b53_device *dev) |
{ |
u8 cpu_port = dev->sw_dev.cpu_port; |
|
/* configure MII port if necessary */ |
if (is5325(dev)) { |
u8 mii_port_override; |
|
b53_read8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, |
&mii_port_override); |
/* reverse mii needs to be enabled */ |
if (!(mii_port_override & PORT_OVERRIDE_RV_MII_25)) { |
b53_write8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, |
mii_port_override | PORT_OVERRIDE_RV_MII_25); |
b53_read8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, |
&mii_port_override); |
|
if (!(mii_port_override & PORT_OVERRIDE_RV_MII_25)) { |
pr_err("Failed to enable reverse MII mode\n"); |
return -EINVAL; |
} |
} |
} else if (is531x5(dev) && cpu_port == B53_CPU_PORT) { |
u8 mii_port_override; |
|
b53_read8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, |
&mii_port_override); |
b53_write8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, |
mii_port_override | PORT_OVERRIDE_EN | |
PORT_OVERRIDE_LINK); |
|
/* BCM47189 has another interface connected to the port 5 */ |
if (dev->enabled_ports & BIT(5)) { |
u8 po_reg = B53_GMII_PORT_OVERRIDE_CTRL(5); |
u8 gmii_po; |
|
b53_read8(dev, B53_CTRL_PAGE, po_reg, &gmii_po); |
gmii_po |= GMII_PO_LINK | |
GMII_PO_RX_FLOW | |
GMII_PO_TX_FLOW | |
GMII_PO_EN; |
b53_write8(dev, B53_CTRL_PAGE, po_reg, gmii_po); |
} |
} else if (is5301x(dev)) { |
if (cpu_port == 8) { |
u8 mii_port_override; |
|
b53_read8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, |
&mii_port_override); |
mii_port_override |= PORT_OVERRIDE_LINK | |
PORT_OVERRIDE_RX_FLOW | |
PORT_OVERRIDE_TX_FLOW | |
PORT_OVERRIDE_SPEED_2000M | |
PORT_OVERRIDE_EN; |
b53_write8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, |
mii_port_override); |
|
/* TODO: Ports 5 & 7 require some extra handling */ |
} else { |
u8 po_reg = B53_GMII_PORT_OVERRIDE_CTRL(cpu_port); |
u8 gmii_po; |
|
b53_read8(dev, B53_CTRL_PAGE, po_reg, &gmii_po); |
gmii_po |= GMII_PO_LINK | |
GMII_PO_RX_FLOW | |
GMII_PO_TX_FLOW | |
GMII_PO_EN | |
GMII_PO_SPEED_2000M; |
b53_write8(dev, B53_CTRL_PAGE, po_reg, gmii_po); |
} |
} |
|
return 0; |
} |
|
static int b53_switch_reset(struct b53_device *dev) |
{ |
int ret = 0; |
u8 mgmt; |
|
b53_switch_reset_gpio(dev); |
|
if (is539x(dev)) { |
b53_write8(dev, B53_CTRL_PAGE, B53_SOFTRESET, 0x83); |
b53_write8(dev, B53_CTRL_PAGE, B53_SOFTRESET, 0x00); |
} |
|
b53_read8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, &mgmt); |
|
if (!(mgmt & SM_SW_FWD_EN)) { |
mgmt &= ~SM_SW_FWD_MODE; |
mgmt |= SM_SW_FWD_EN; |
|
b53_write8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, mgmt); |
b53_read8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, &mgmt); |
|
if (!(mgmt & SM_SW_FWD_EN)) { |
pr_err("Failed to enable switch!\n"); |
return -EINVAL; |
} |
} |
|
/* enable all ports */ |
b53_enable_ports(dev); |
|
if (dev->dev->of_node) |
ret = b53_configure_ports_of(dev); |
else |
ret = b53_configure_ports(dev); |
|
if (ret) |
return ret; |
|
b53_enable_mib(dev); |
|
return b53_flush_arl(dev); |
} |
|
/* |
* Swconfig glue functions |
*/ |
|
static int b53_global_get_vlan_enable(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct b53_device *priv = sw_to_b53(dev); |
|
val->value.i = priv->enable_vlan; |
|
return 0; |
} |
|
static int b53_global_set_vlan_enable(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct b53_device *priv = sw_to_b53(dev); |
|
priv->enable_vlan = val->value.i; |
|
return 0; |
} |
|
static int b53_global_get_jumbo_enable(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct b53_device *priv = sw_to_b53(dev); |
|
val->value.i = priv->enable_jumbo; |
|
return 0; |
} |
|
static int b53_global_set_jumbo_enable(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct b53_device *priv = sw_to_b53(dev); |
|
priv->enable_jumbo = val->value.i; |
|
return 0; |
} |
|
static int b53_global_get_4095_enable(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct b53_device *priv = sw_to_b53(dev); |
|
val->value.i = priv->allow_vid_4095; |
|
return 0; |
} |
|
static int b53_global_set_4095_enable(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct b53_device *priv = sw_to_b53(dev); |
|
priv->allow_vid_4095 = val->value.i; |
|
return 0; |
} |
|
static int b53_global_get_ports(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct b53_device *priv = sw_to_b53(dev); |
|
val->len = snprintf(priv->buf, B53_BUF_SIZE, "0x%04x", |
priv->enabled_ports); |
val->value.s = priv->buf; |
|
return 0; |
} |
|
static int b53_port_get_pvid(struct switch_dev *dev, int port, int *val) |
{ |
struct b53_device *priv = sw_to_b53(dev); |
|
*val = priv->ports[port].pvid; |
|
return 0; |
} |
|
static int b53_port_set_pvid(struct switch_dev *dev, int port, int val) |
{ |
struct b53_device *priv = sw_to_b53(dev); |
|
if (val > 15 && is5325(priv)) |
return -EINVAL; |
if (val == 4095 && !priv->allow_vid_4095) |
return -EINVAL; |
|
priv->ports[port].pvid = val; |
|
return 0; |
} |
|
static int b53_vlan_get_ports(struct switch_dev *dev, struct switch_val *val) |
{ |
struct b53_device *priv = sw_to_b53(dev); |
struct switch_port *port = &val->value.ports[0]; |
struct b53_vlan *vlan = &priv->vlans[val->port_vlan]; |
int i; |
|
val->len = 0; |
|
if (!vlan->members) |
return 0; |
|
for (i = 0; i < dev->ports; i++) { |
if (!(vlan->members & BIT(i))) |
continue; |
|
|
if (!(vlan->untag & BIT(i))) |
port->flags = BIT(SWITCH_PORT_FLAG_TAGGED); |
else |
port->flags = 0; |
|
port->id = i; |
val->len++; |
port++; |
} |
|
return 0; |
} |
|
static int b53_vlan_set_ports(struct switch_dev *dev, struct switch_val *val) |
{ |
struct b53_device *priv = sw_to_b53(dev); |
struct switch_port *port; |
struct b53_vlan *vlan = &priv->vlans[val->port_vlan]; |
int i; |
|
/* only BCM5325 and BCM5365 supports VID 0 */ |
if (val->port_vlan == 0 && !is5325(priv) && !is5365(priv)) |
return -EINVAL; |
|
/* VLAN 4095 needs special handling */ |
if (val->port_vlan == 4095 && !priv->allow_vid_4095) |
return -EINVAL; |
|
port = &val->value.ports[0]; |
vlan->members = 0; |
vlan->untag = 0; |
for (i = 0; i < val->len; i++, port++) { |
vlan->members |= BIT(port->id); |
|
if (!(port->flags & BIT(SWITCH_PORT_FLAG_TAGGED))) { |
vlan->untag |= BIT(port->id); |
priv->ports[port->id].pvid = val->port_vlan; |
}; |
} |
|
/* ignore disabled ports */ |
vlan->members &= priv->enabled_ports; |
vlan->untag &= priv->enabled_ports; |
|
return 0; |
} |
|
static int b53_port_get_link(struct switch_dev *dev, int port, |
struct switch_port_link *link) |
{ |
struct b53_device *priv = sw_to_b53(dev); |
|
if (is_cpu_port(priv, port)) { |
link->link = 1; |
link->duplex = 1; |
link->speed = is5325(priv) || is5365(priv) ? |
SWITCH_PORT_SPEED_100 : SWITCH_PORT_SPEED_1000; |
link->aneg = 0; |
} else if (priv->enabled_ports & BIT(port)) { |
u32 speed; |
u16 lnk, duplex; |
|
b53_read16(priv, B53_STAT_PAGE, B53_LINK_STAT, &lnk); |
b53_read16(priv, B53_STAT_PAGE, priv->duplex_reg, &duplex); |
|
lnk = (lnk >> port) & 1; |
duplex = (duplex >> port) & 1; |
|
if (is5325(priv) || is5365(priv)) { |
u16 tmp; |
|
b53_read16(priv, B53_STAT_PAGE, B53_SPEED_STAT, &tmp); |
speed = SPEED_PORT_FE(tmp, port); |
} else { |
b53_read32(priv, B53_STAT_PAGE, B53_SPEED_STAT, &speed); |
speed = SPEED_PORT_GE(speed, port); |
} |
|
link->link = lnk; |
if (lnk) { |
link->duplex = duplex; |
switch (speed) { |
case SPEED_STAT_10M: |
link->speed = SWITCH_PORT_SPEED_10; |
break; |
case SPEED_STAT_100M: |
link->speed = SWITCH_PORT_SPEED_100; |
break; |
case SPEED_STAT_1000M: |
link->speed = SWITCH_PORT_SPEED_1000; |
break; |
} |
} |
|
link->aneg = 1; |
} else { |
link->link = 0; |
} |
|
return 0; |
|
} |
|
static int b53_port_set_link(struct switch_dev *sw_dev, int port, |
struct switch_port_link *link) |
{ |
struct b53_device *dev = sw_to_b53(sw_dev); |
|
/* |
* TODO: BCM63XX requires special handling as it can have external phys |
* and ports might be GE or only FE |
*/ |
if (is63xx(dev)) |
return -ENOTSUPP; |
|
if (port == sw_dev->cpu_port) |
return -EINVAL; |
|
if (!(BIT(port) & dev->enabled_ports)) |
return -EINVAL; |
|
if (link->speed == SWITCH_PORT_SPEED_1000 && |
(is5325(dev) || is5365(dev))) |
return -EINVAL; |
|
if (link->speed == SWITCH_PORT_SPEED_1000 && !link->duplex) |
return -EINVAL; |
|
return switch_generic_set_link(sw_dev, port, link); |
} |
|
static int b53_phy_read16(struct switch_dev *dev, int addr, u8 reg, u16 *value) |
{ |
struct b53_device *priv = sw_to_b53(dev); |
|
if (priv->ops->phy_read16) |
return priv->ops->phy_read16(priv, addr, reg, value); |
|
return b53_read16(priv, B53_PORT_MII_PAGE(addr), reg, value); |
} |
|
static int b53_phy_write16(struct switch_dev *dev, int addr, u8 reg, u16 value) |
{ |
struct b53_device *priv = sw_to_b53(dev); |
|
if (priv->ops->phy_write16) |
return priv->ops->phy_write16(priv, addr, reg, value); |
|
return b53_write16(priv, B53_PORT_MII_PAGE(addr), reg, value); |
} |
|
static int b53_global_reset_switch(struct switch_dev *dev) |
{ |
struct b53_device *priv = sw_to_b53(dev); |
|
/* reset vlans */ |
priv->enable_vlan = 0; |
priv->enable_jumbo = 0; |
priv->allow_vid_4095 = 0; |
|
memset(priv->vlans, 0, sizeof(*priv->vlans) * dev->vlans); |
memset(priv->ports, 0, sizeof(*priv->ports) * dev->ports); |
|
return b53_switch_reset(priv); |
} |
|
static int b53_global_apply_config(struct switch_dev *dev) |
{ |
struct b53_device *priv = sw_to_b53(dev); |
|
/* disable switching */ |
b53_set_forwarding(priv, 0); |
|
b53_apply(priv); |
|
/* enable switching */ |
b53_set_forwarding(priv, 1); |
|
return 0; |
} |
|
|
static int b53_global_reset_mib(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct b53_device *priv = sw_to_b53(dev); |
u8 gc; |
|
b53_read8(priv, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, &gc); |
|
b53_write8(priv, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, gc | GC_RESET_MIB); |
mdelay(1); |
b53_write8(priv, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, gc & ~GC_RESET_MIB); |
mdelay(1); |
|
return 0; |
} |
|
static int b53_port_get_mib(struct switch_dev *sw_dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct b53_device *dev = sw_to_b53(sw_dev); |
const struct b53_mib_desc *mibs; |
int port = val->port_vlan; |
int len = 0; |
|
if (!(BIT(port) & dev->enabled_ports)) |
return -1; |
|
if (is5365(dev)) { |
if (port == 5) |
port = 8; |
|
mibs = b53_mibs_65; |
} else if (is63xx(dev)) { |
mibs = b53_mibs_63xx; |
} else { |
mibs = b53_mibs; |
} |
|
dev->buf[0] = 0; |
|
for (; mibs->size > 0; mibs++) { |
u64 val; |
|
if (mibs->size == 8) { |
b53_read64(dev, B53_MIB_PAGE(port), mibs->offset, &val); |
} else { |
u32 val32; |
|
b53_read32(dev, B53_MIB_PAGE(port), mibs->offset, |
&val32); |
val = val32; |
} |
|
len += snprintf(dev->buf + len, B53_BUF_SIZE - len, |
"%-20s: %llu\n", mibs->name, val); |
} |
|
val->len = len; |
val->value.s = dev->buf; |
|
return 0; |
} |
|
static int b53_port_get_stats(struct switch_dev *sw_dev, int port, |
struct switch_port_stats *stats) |
{ |
struct b53_device *dev = sw_to_b53(sw_dev); |
const struct b53_mib_desc *mibs; |
int txb_id, rxb_id; |
u64 rxb, txb; |
|
if (!(BIT(port) & dev->enabled_ports)) |
return -EINVAL; |
|
txb_id = B53XX_MIB_TXB_ID; |
rxb_id = B53XX_MIB_RXB_ID; |
|
if (is5365(dev)) { |
if (port == 5) |
port = 8; |
|
mibs = b53_mibs_65; |
} else if (is63xx(dev)) { |
mibs = b53_mibs_63xx; |
txb_id = B63XX_MIB_TXB_ID; |
rxb_id = B63XX_MIB_RXB_ID; |
} else { |
mibs = b53_mibs; |
} |
|
dev->buf[0] = 0; |
|
if (mibs->size == 8) { |
b53_read64(dev, B53_MIB_PAGE(port), mibs[txb_id].offset, &txb); |
b53_read64(dev, B53_MIB_PAGE(port), mibs[rxb_id].offset, &rxb); |
} else { |
u32 val32; |
|
b53_read32(dev, B53_MIB_PAGE(port), mibs[txb_id].offset, &val32); |
txb = val32; |
|
b53_read32(dev, B53_MIB_PAGE(port), mibs[rxb_id].offset, &val32); |
rxb = val32; |
} |
|
stats->tx_bytes = txb; |
stats->rx_bytes = rxb; |
|
return 0; |
} |
|
static struct switch_attr b53_global_ops_25[] = { |
{ |
.type = SWITCH_TYPE_INT, |
.name = "enable_vlan", |
.description = "Enable VLAN mode", |
.set = b53_global_set_vlan_enable, |
.get = b53_global_get_vlan_enable, |
.max = 1, |
}, |
{ |
.type = SWITCH_TYPE_STRING, |
.name = "ports", |
.description = "Available ports (as bitmask)", |
.get = b53_global_get_ports, |
}, |
}; |
|
static struct switch_attr b53_global_ops_65[] = { |
{ |
.type = SWITCH_TYPE_INT, |
.name = "enable_vlan", |
.description = "Enable VLAN mode", |
.set = b53_global_set_vlan_enable, |
.get = b53_global_get_vlan_enable, |
.max = 1, |
}, |
{ |
.type = SWITCH_TYPE_STRING, |
.name = "ports", |
.description = "Available ports (as bitmask)", |
.get = b53_global_get_ports, |
}, |
{ |
.type = SWITCH_TYPE_INT, |
.name = "reset_mib", |
.description = "Reset MIB counters", |
.set = b53_global_reset_mib, |
}, |
}; |
|
static struct switch_attr b53_global_ops[] = { |
{ |
.type = SWITCH_TYPE_INT, |
.name = "enable_vlan", |
.description = "Enable VLAN mode", |
.set = b53_global_set_vlan_enable, |
.get = b53_global_get_vlan_enable, |
.max = 1, |
}, |
{ |
.type = SWITCH_TYPE_STRING, |
.name = "ports", |
.description = "Available Ports (as bitmask)", |
.get = b53_global_get_ports, |
}, |
{ |
.type = SWITCH_TYPE_INT, |
.name = "reset_mib", |
.description = "Reset MIB counters", |
.set = b53_global_reset_mib, |
}, |
{ |
.type = SWITCH_TYPE_INT, |
.name = "enable_jumbo", |
.description = "Enable Jumbo Frames", |
.set = b53_global_set_jumbo_enable, |
.get = b53_global_get_jumbo_enable, |
.max = 1, |
}, |
{ |
.type = SWITCH_TYPE_INT, |
.name = "allow_vid_4095", |
.description = "Allow VID 4095", |
.set = b53_global_set_4095_enable, |
.get = b53_global_get_4095_enable, |
.max = 1, |
}, |
}; |
|
static struct switch_attr b53_port_ops[] = { |
{ |
.type = SWITCH_TYPE_STRING, |
.name = "mib", |
.description = "Get port's MIB counters", |
.get = b53_port_get_mib, |
}, |
}; |
|
static struct switch_attr b53_no_ops[] = { |
}; |
|
static const struct switch_dev_ops b53_switch_ops_25 = { |
.attr_global = { |
.attr = b53_global_ops_25, |
.n_attr = ARRAY_SIZE(b53_global_ops_25), |
}, |
.attr_port = { |
.attr = b53_no_ops, |
.n_attr = ARRAY_SIZE(b53_no_ops), |
}, |
.attr_vlan = { |
.attr = b53_no_ops, |
.n_attr = ARRAY_SIZE(b53_no_ops), |
}, |
|
.get_vlan_ports = b53_vlan_get_ports, |
.set_vlan_ports = b53_vlan_set_ports, |
.get_port_pvid = b53_port_get_pvid, |
.set_port_pvid = b53_port_set_pvid, |
.apply_config = b53_global_apply_config, |
.reset_switch = b53_global_reset_switch, |
.get_port_link = b53_port_get_link, |
.set_port_link = b53_port_set_link, |
.get_port_stats = b53_port_get_stats, |
.phy_read16 = b53_phy_read16, |
.phy_write16 = b53_phy_write16, |
}; |
|
static const struct switch_dev_ops b53_switch_ops_65 = { |
.attr_global = { |
.attr = b53_global_ops_65, |
.n_attr = ARRAY_SIZE(b53_global_ops_65), |
}, |
.attr_port = { |
.attr = b53_port_ops, |
.n_attr = ARRAY_SIZE(b53_port_ops), |
}, |
.attr_vlan = { |
.attr = b53_no_ops, |
.n_attr = ARRAY_SIZE(b53_no_ops), |
}, |
|
.get_vlan_ports = b53_vlan_get_ports, |
.set_vlan_ports = b53_vlan_set_ports, |
.get_port_pvid = b53_port_get_pvid, |
.set_port_pvid = b53_port_set_pvid, |
.apply_config = b53_global_apply_config, |
.reset_switch = b53_global_reset_switch, |
.get_port_link = b53_port_get_link, |
.set_port_link = b53_port_set_link, |
.get_port_stats = b53_port_get_stats, |
.phy_read16 = b53_phy_read16, |
.phy_write16 = b53_phy_write16, |
}; |
|
static const struct switch_dev_ops b53_switch_ops = { |
.attr_global = { |
.attr = b53_global_ops, |
.n_attr = ARRAY_SIZE(b53_global_ops), |
}, |
.attr_port = { |
.attr = b53_port_ops, |
.n_attr = ARRAY_SIZE(b53_port_ops), |
}, |
.attr_vlan = { |
.attr = b53_no_ops, |
.n_attr = ARRAY_SIZE(b53_no_ops), |
}, |
|
.get_vlan_ports = b53_vlan_get_ports, |
.set_vlan_ports = b53_vlan_set_ports, |
.get_port_pvid = b53_port_get_pvid, |
.set_port_pvid = b53_port_set_pvid, |
.apply_config = b53_global_apply_config, |
.reset_switch = b53_global_reset_switch, |
.get_port_link = b53_port_get_link, |
.set_port_link = b53_port_set_link, |
.get_port_stats = b53_port_get_stats, |
.phy_read16 = b53_phy_read16, |
.phy_write16 = b53_phy_write16, |
}; |
|
struct b53_chip_data { |
u32 chip_id; |
const char *dev_name; |
const char *alias; |
u16 vlans; |
u16 enabled_ports; |
u8 cpu_port; |
u8 vta_regs[3]; |
u8 duplex_reg; |
u8 jumbo_pm_reg; |
u8 jumbo_size_reg; |
const struct switch_dev_ops *sw_ops; |
}; |
|
#define B53_VTA_REGS \ |
{ B53_VT_ACCESS, B53_VT_INDEX, B53_VT_ENTRY } |
#define B53_VTA_REGS_9798 \ |
{ B53_VT_ACCESS_9798, B53_VT_INDEX_9798, B53_VT_ENTRY_9798 } |
#define B53_VTA_REGS_63XX \ |
{ B53_VT_ACCESS_63XX, B53_VT_INDEX_63XX, B53_VT_ENTRY_63XX } |
|
static const struct b53_chip_data b53_switch_chips[] = { |
{ |
.chip_id = BCM5325_DEVICE_ID, |
.dev_name = "BCM5325", |
.alias = "bcm5325", |
.vlans = 16, |
.enabled_ports = 0x1f, |
.cpu_port = B53_CPU_PORT_25, |
.duplex_reg = B53_DUPLEX_STAT_FE, |
.sw_ops = &b53_switch_ops_25, |
}, |
{ |
.chip_id = BCM5365_DEVICE_ID, |
.dev_name = "BCM5365", |
.alias = "bcm5365", |
.vlans = 256, |
.enabled_ports = 0x1f, |
.cpu_port = B53_CPU_PORT_25, |
.duplex_reg = B53_DUPLEX_STAT_FE, |
.sw_ops = &b53_switch_ops_65, |
}, |
{ |
.chip_id = BCM5395_DEVICE_ID, |
.dev_name = "BCM5395", |
.alias = "bcm5395", |
.vlans = 4096, |
.enabled_ports = 0x1f, |
.cpu_port = B53_CPU_PORT, |
.vta_regs = B53_VTA_REGS, |
.duplex_reg = B53_DUPLEX_STAT_GE, |
.jumbo_pm_reg = B53_JUMBO_PORT_MASK, |
.jumbo_size_reg = B53_JUMBO_MAX_SIZE, |
.sw_ops = &b53_switch_ops, |
}, |
{ |
.chip_id = BCM5397_DEVICE_ID, |
.dev_name = "BCM5397", |
.alias = "bcm5397", |
.vlans = 4096, |
.enabled_ports = 0x1f, |
.cpu_port = B53_CPU_PORT, |
.vta_regs = B53_VTA_REGS_9798, |
.duplex_reg = B53_DUPLEX_STAT_GE, |
.jumbo_pm_reg = B53_JUMBO_PORT_MASK, |
.jumbo_size_reg = B53_JUMBO_MAX_SIZE, |
.sw_ops = &b53_switch_ops, |
}, |
{ |
.chip_id = BCM5398_DEVICE_ID, |
.dev_name = "BCM5398", |
.alias = "bcm5398", |
.vlans = 4096, |
.enabled_ports = 0x7f, |
.cpu_port = B53_CPU_PORT, |
.vta_regs = B53_VTA_REGS_9798, |
.duplex_reg = B53_DUPLEX_STAT_GE, |
.jumbo_pm_reg = B53_JUMBO_PORT_MASK, |
.jumbo_size_reg = B53_JUMBO_MAX_SIZE, |
.sw_ops = &b53_switch_ops, |
}, |
{ |
.chip_id = BCM53115_DEVICE_ID, |
.dev_name = "BCM53115", |
.alias = "bcm53115", |
.vlans = 4096, |
.enabled_ports = 0x1f, |
.vta_regs = B53_VTA_REGS, |
.cpu_port = B53_CPU_PORT, |
.duplex_reg = B53_DUPLEX_STAT_GE, |
.jumbo_pm_reg = B53_JUMBO_PORT_MASK, |
.jumbo_size_reg = B53_JUMBO_MAX_SIZE, |
.sw_ops = &b53_switch_ops, |
}, |
{ |
.chip_id = BCM53125_DEVICE_ID, |
.dev_name = "BCM53125", |
.alias = "bcm53125", |
.vlans = 4096, |
.enabled_ports = 0x1f, |
.cpu_port = B53_CPU_PORT, |
.vta_regs = B53_VTA_REGS, |
.duplex_reg = B53_DUPLEX_STAT_GE, |
.jumbo_pm_reg = B53_JUMBO_PORT_MASK, |
.jumbo_size_reg = B53_JUMBO_MAX_SIZE, |
.sw_ops = &b53_switch_ops, |
}, |
{ |
.chip_id = BCM53128_DEVICE_ID, |
.dev_name = "BCM53128", |
.alias = "bcm53128", |
.vlans = 4096, |
.enabled_ports = 0x1ff, |
.cpu_port = B53_CPU_PORT, |
.vta_regs = B53_VTA_REGS, |
.duplex_reg = B53_DUPLEX_STAT_GE, |
.jumbo_pm_reg = B53_JUMBO_PORT_MASK, |
.jumbo_size_reg = B53_JUMBO_MAX_SIZE, |
.sw_ops = &b53_switch_ops, |
}, |
{ |
.chip_id = BCM63XX_DEVICE_ID, |
.dev_name = "BCM63xx", |
.alias = "bcm63xx", |
.vlans = 4096, |
.enabled_ports = 0, /* pdata must provide them */ |
.cpu_port = B53_CPU_PORT, |
.vta_regs = B53_VTA_REGS_63XX, |
.duplex_reg = B53_DUPLEX_STAT_63XX, |
.jumbo_pm_reg = B53_JUMBO_PORT_MASK_63XX, |
.jumbo_size_reg = B53_JUMBO_MAX_SIZE_63XX, |
.sw_ops = &b53_switch_ops, |
}, |
{ |
.chip_id = BCM53010_DEVICE_ID, |
.dev_name = "BCM53010", |
.alias = "bcm53011", |
.vlans = 4096, |
.enabled_ports = 0x1f, |
.cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */ |
.vta_regs = B53_VTA_REGS, |
.duplex_reg = B53_DUPLEX_STAT_GE, |
.jumbo_pm_reg = B53_JUMBO_PORT_MASK, |
.jumbo_size_reg = B53_JUMBO_MAX_SIZE, |
.sw_ops = &b53_switch_ops, |
}, |
{ |
.chip_id = BCM53011_DEVICE_ID, |
.dev_name = "BCM53011", |
.alias = "bcm53011", |
.vlans = 4096, |
.enabled_ports = 0x1bf, |
.cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */ |
.vta_regs = B53_VTA_REGS, |
.duplex_reg = B53_DUPLEX_STAT_GE, |
.jumbo_pm_reg = B53_JUMBO_PORT_MASK, |
.jumbo_size_reg = B53_JUMBO_MAX_SIZE, |
.sw_ops = &b53_switch_ops, |
}, |
{ |
.chip_id = BCM53012_DEVICE_ID, |
.dev_name = "BCM53012", |
.alias = "bcm53011", |
.vlans = 4096, |
.enabled_ports = 0x1bf, |
.cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */ |
.vta_regs = B53_VTA_REGS, |
.duplex_reg = B53_DUPLEX_STAT_GE, |
.jumbo_pm_reg = B53_JUMBO_PORT_MASK, |
.jumbo_size_reg = B53_JUMBO_MAX_SIZE, |
.sw_ops = &b53_switch_ops, |
}, |
{ |
.chip_id = BCM53018_DEVICE_ID, |
.dev_name = "BCM53018", |
.alias = "bcm53018", |
.vlans = 4096, |
.enabled_ports = 0x1f, |
.cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */ |
.vta_regs = B53_VTA_REGS, |
.duplex_reg = B53_DUPLEX_STAT_GE, |
.jumbo_pm_reg = B53_JUMBO_PORT_MASK, |
.jumbo_size_reg = B53_JUMBO_MAX_SIZE, |
.sw_ops = &b53_switch_ops, |
}, |
{ |
.chip_id = BCM53019_DEVICE_ID, |
.dev_name = "BCM53019", |
.alias = "bcm53019", |
.vlans = 4096, |
.enabled_ports = 0x1f, |
.cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */ |
.vta_regs = B53_VTA_REGS, |
.duplex_reg = B53_DUPLEX_STAT_GE, |
.jumbo_pm_reg = B53_JUMBO_PORT_MASK, |
.jumbo_size_reg = B53_JUMBO_MAX_SIZE, |
.sw_ops = &b53_switch_ops, |
}, |
}; |
|
static int b53_switch_init_of(struct b53_device *dev) |
{ |
struct device_node *dn, *pn; |
const char *alias; |
u32 port_num; |
u16 ports = 0; |
|
dn = of_get_child_by_name(dev_of_node(dev->dev), "ports"); |
if (!dn) |
return -EINVAL; |
|
for_each_available_child_of_node(dn, pn) { |
const char *label; |
int len; |
|
if (of_property_read_u32(pn, "reg", &port_num)) |
continue; |
|
if (port_num > B53_CPU_PORT) |
continue; |
|
ports |= BIT(port_num); |
|
label = of_get_property(pn, "label", &len); |
if (label && !strcmp(label, "cpu")) |
dev->sw_dev.cpu_port = port_num; |
} |
|
dev->enabled_ports = ports; |
|
if (!of_property_read_string(dev_of_node(dev->dev), "lede,alias", |
&alias)) |
dev->sw_dev.alias = devm_kstrdup(dev->dev, alias, GFP_KERNEL); |
|
return 0; |
} |
|
static int b53_switch_init(struct b53_device *dev) |
{ |
struct switch_dev *sw_dev = &dev->sw_dev; |
unsigned i; |
int ret; |
|
for (i = 0; i < ARRAY_SIZE(b53_switch_chips); i++) { |
const struct b53_chip_data *chip = &b53_switch_chips[i]; |
|
if (chip->chip_id == dev->chip_id) { |
sw_dev->name = chip->dev_name; |
if (!sw_dev->alias) |
sw_dev->alias = chip->alias; |
if (!dev->enabled_ports) |
dev->enabled_ports = chip->enabled_ports; |
dev->duplex_reg = chip->duplex_reg; |
dev->vta_regs[0] = chip->vta_regs[0]; |
dev->vta_regs[1] = chip->vta_regs[1]; |
dev->vta_regs[2] = chip->vta_regs[2]; |
dev->jumbo_pm_reg = chip->jumbo_pm_reg; |
sw_dev->ops = chip->sw_ops; |
sw_dev->cpu_port = chip->cpu_port; |
sw_dev->vlans = chip->vlans; |
break; |
} |
} |
|
if (!sw_dev->name) |
return -EINVAL; |
|
/* check which BCM5325x version we have */ |
if (is5325(dev)) { |
u8 vc4; |
|
b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_25, &vc4); |
|
/* check reserved bits */ |
switch (vc4 & 3) { |
case 1: |
/* BCM5325E */ |
break; |
case 3: |
/* BCM5325F - do not use port 4 */ |
dev->enabled_ports &= ~BIT(4); |
break; |
default: |
/* On the BCM47XX SoCs this is the supported internal switch.*/ |
#ifndef CONFIG_BCM47XX |
/* BCM5325M */ |
return -EINVAL; |
#else |
break; |
#endif |
} |
} else if (dev->chip_id == BCM53115_DEVICE_ID) { |
u64 strap_value; |
|
b53_read48(dev, B53_STAT_PAGE, B53_STRAP_VALUE, &strap_value); |
/* use second IMP port if GMII is enabled */ |
if (strap_value & SV_GMII_CTRL_115) |
sw_dev->cpu_port = 5; |
} |
|
if (dev_of_node(dev->dev)) { |
ret = b53_switch_init_of(dev); |
if (ret) |
return ret; |
} |
|
dev->enabled_ports |= BIT(sw_dev->cpu_port); |
sw_dev->ports = fls(dev->enabled_ports); |
|
dev->ports = devm_kzalloc(dev->dev, |
sizeof(struct b53_port) * sw_dev->ports, |
GFP_KERNEL); |
if (!dev->ports) |
return -ENOMEM; |
|
dev->vlans = devm_kzalloc(dev->dev, |
sizeof(struct b53_vlan) * sw_dev->vlans, |
GFP_KERNEL); |
if (!dev->vlans) |
return -ENOMEM; |
|
dev->buf = devm_kzalloc(dev->dev, B53_BUF_SIZE, GFP_KERNEL); |
if (!dev->buf) |
return -ENOMEM; |
|
dev->reset_gpio = b53_switch_get_reset_gpio(dev); |
if (dev->reset_gpio >= 0) { |
ret = devm_gpio_request_one(dev->dev, dev->reset_gpio, |
GPIOF_OUT_INIT_HIGH, "robo_reset"); |
if (ret) |
return ret; |
} |
|
return b53_switch_reset(dev); |
} |
|
struct b53_device *b53_switch_alloc(struct device *base, struct b53_io_ops *ops, |
void *priv) |
{ |
struct b53_device *dev; |
|
dev = devm_kzalloc(base, sizeof(*dev), GFP_KERNEL); |
if (!dev) |
return NULL; |
|
dev->dev = base; |
dev->ops = ops; |
dev->priv = priv; |
mutex_init(&dev->reg_mutex); |
|
return dev; |
} |
EXPORT_SYMBOL(b53_switch_alloc); |
|
int b53_switch_detect(struct b53_device *dev) |
{ |
u32 id32; |
u16 tmp; |
u8 id8; |
int ret; |
|
ret = b53_read8(dev, B53_MGMT_PAGE, B53_DEVICE_ID, &id8); |
if (ret) |
return ret; |
|
switch (id8) { |
case 0: |
/* |
* BCM5325 and BCM5365 do not have this register so reads |
* return 0. But the read operation did succeed, so assume |
* this is one of them. |
* |
* Next check if we can write to the 5325's VTA register; for |
* 5365 it is read only. |
*/ |
|
b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_TABLE_ACCESS_25, 0xf); |
b53_read16(dev, B53_VLAN_PAGE, B53_VLAN_TABLE_ACCESS_25, &tmp); |
|
if (tmp == 0xf) |
dev->chip_id = BCM5325_DEVICE_ID; |
else |
dev->chip_id = BCM5365_DEVICE_ID; |
break; |
case BCM5395_DEVICE_ID: |
case BCM5397_DEVICE_ID: |
case BCM5398_DEVICE_ID: |
dev->chip_id = id8; |
break; |
default: |
ret = b53_read32(dev, B53_MGMT_PAGE, B53_DEVICE_ID, &id32); |
if (ret) |
return ret; |
|
switch (id32) { |
case BCM53115_DEVICE_ID: |
case BCM53125_DEVICE_ID: |
case BCM53128_DEVICE_ID: |
case BCM53010_DEVICE_ID: |
case BCM53011_DEVICE_ID: |
case BCM53012_DEVICE_ID: |
case BCM53018_DEVICE_ID: |
case BCM53019_DEVICE_ID: |
dev->chip_id = id32; |
break; |
default: |
pr_err("unsupported switch detected (BCM53%02x/BCM%x)\n", |
id8, id32); |
return -ENODEV; |
} |
} |
|
if (dev->chip_id == BCM5325_DEVICE_ID) |
return b53_read8(dev, B53_STAT_PAGE, B53_REV_ID_25, |
&dev->core_rev); |
else |
return b53_read8(dev, B53_MGMT_PAGE, B53_REV_ID, |
&dev->core_rev); |
} |
EXPORT_SYMBOL(b53_switch_detect); |
|
int b53_switch_register(struct b53_device *dev) |
{ |
int ret; |
|
if (dev->pdata) { |
dev->chip_id = dev->pdata->chip_id; |
dev->enabled_ports = dev->pdata->enabled_ports; |
dev->sw_dev.alias = dev->pdata->alias; |
} |
|
if (!dev->chip_id && b53_switch_detect(dev)) |
return -EINVAL; |
|
ret = b53_switch_init(dev); |
if (ret) |
return ret; |
|
pr_info("found switch: %s, rev %i\n", dev->sw_dev.name, dev->core_rev); |
|
return register_switch(&dev->sw_dev, NULL); |
} |
EXPORT_SYMBOL(b53_switch_register); |
|
MODULE_AUTHOR("Jonas Gorski <jogo@openwrt.org>"); |
MODULE_DESCRIPTION("B53 switch library"); |
MODULE_LICENSE("Dual BSD/GPL"); |
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/ip17xx.c |
@@ -0,0 +1,1377 @@ |
/* |
* ip17xx.c: Swconfig configuration for IC+ IP17xx switch family |
* |
* Copyright (C) 2008 Patrick Horn <patrick.horn@gmail.com> |
* Copyright (C) 2008, 2010 Martin Mares <mj@ucw.cz> |
* Copyright (C) 2009 Felix Fietkau <nbd@nbd.name> |
* |
* 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; either version 2 |
* of the License, or (at your option) any later version. |
* |
* 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. |
*/ |
|
#include <linux/kernel.h> |
#include <linux/module.h> |
#include <linux/init.h> |
#include <linux/list.h> |
#include <linux/skbuff.h> |
#include <linux/mii.h> |
#include <linux/phy.h> |
#include <linux/delay.h> |
#include <linux/switch.h> |
#include <linux/device.h> |
|
#define MAX_VLANS 16 |
#define MAX_PORTS 9 |
#undef DUMP_MII_IO |
|
typedef struct ip17xx_reg { |
u16 p; // phy |
u16 m; // mii |
} reg; |
typedef char bitnum; |
|
#define NOTSUPPORTED {-1,-1} |
|
#define REG_SUPP(x) (((x).m != ((u16)-1)) && ((x).p != (u16)-1)) |
|
struct ip17xx_state; |
|
/*********** CONSTANTS ***********/ |
struct register_mappings { |
char *NAME; |
u16 MODEL_NO; // Compare to bits 4-9 of MII register 0,3. |
bitnum NUM_PORTS; |
bitnum CPU_PORT; |
|
/* The default VLAN for each port. |
Default: 0x0001 for Ports 0,1,2,3 |
0x0002 for Ports 4,5 */ |
reg VLAN_DEFAULT_TAG_REG[MAX_PORTS]; |
|
/* These ports are tagged. |
Default: 0x00 */ |
reg ADD_TAG_REG; |
reg REMOVE_TAG_REG; |
bitnum ADD_TAG_BIT[MAX_PORTS]; |
/* These ports are untagged. |
Default: 0x00 (i.e. do not alter any VLAN tags...) |
Maybe set to 0 if user disables VLANs. */ |
bitnum REMOVE_TAG_BIT[MAX_PORTS]; |
|
/* Port M and Port N are on the same VLAN. |
Default: All ports on all VLANs. */ |
// Use register {29, 19+N/2} |
reg VLAN_LOOKUP_REG; |
// Port 5 uses register {30, 18} but same as odd bits. |
reg VLAN_LOOKUP_REG_5; // in a different register on IP175C. |
bitnum VLAN_LOOKUP_EVEN_BIT[MAX_PORTS]; |
bitnum VLAN_LOOKUP_ODD_BIT[MAX_PORTS]; |
|
/* This VLAN corresponds to which ports. |
Default: 0x2f,0x30,0x3f,0x3f... */ |
reg TAG_VLAN_MASK_REG; |
bitnum TAG_VLAN_MASK_EVEN_BIT[MAX_PORTS]; |
bitnum TAG_VLAN_MASK_ODD_BIT[MAX_PORTS]; |
|
int RESET_VAL; |
reg RESET_REG; |
|
reg MODE_REG; |
int MODE_VAL; |
|
/* General flags */ |
reg ROUTER_CONTROL_REG; |
reg VLAN_CONTROL_REG; |
bitnum TAG_VLAN_BIT; |
bitnum ROUTER_EN_BIT; |
bitnum NUMLAN_GROUPS_MAX; |
bitnum NUMLAN_GROUPS_BIT; |
|
reg MII_REGISTER_EN; |
bitnum MII_REGISTER_EN_BIT; |
|
// set to 1 for 178C, 0 for 175C. |
bitnum SIMPLE_VLAN_REGISTERS; // 175C has two vlans per register but 178C has only one. |
|
// Pointers to functions which manipulate hardware state |
int (*update_state)(struct ip17xx_state *state); |
int (*set_vlan_mode)(struct ip17xx_state *state); |
int (*reset)(struct ip17xx_state *state); |
}; |
|
static int ip175c_update_state(struct ip17xx_state *state); |
static int ip175c_set_vlan_mode(struct ip17xx_state *state); |
static int ip175c_reset(struct ip17xx_state *state); |
|
static const struct register_mappings IP178C = { |
.NAME = "IP178C", |
.MODEL_NO = 0x18, |
.VLAN_DEFAULT_TAG_REG = { |
{30,3},{30,4},{30,5},{30,6},{30,7},{30,8}, |
{30,9},{30,10},{30,11}, |
}, |
|
.ADD_TAG_REG = {30,12}, |
.ADD_TAG_BIT = {0,1,2,3,4,5,6,7,8}, |
.REMOVE_TAG_REG = {30,13}, |
.REMOVE_TAG_BIT = {4,5,6,7,8,9,10,11,12}, |
|
.SIMPLE_VLAN_REGISTERS = 1, |
|
.VLAN_LOOKUP_REG = {31,0},// +N |
.VLAN_LOOKUP_REG_5 = NOTSUPPORTED, // not used with SIMPLE_VLAN_REGISTERS |
.VLAN_LOOKUP_EVEN_BIT = {0,1,2,3,4,5,6,7,8}, |
.VLAN_LOOKUP_ODD_BIT = {0,1,2,3,4,5,6,7,8}, |
|
.TAG_VLAN_MASK_REG = {30,14}, // +N |
.TAG_VLAN_MASK_EVEN_BIT = {0,1,2,3,4,5,6,7,8}, |
.TAG_VLAN_MASK_ODD_BIT = {0,1,2,3,4,5,6,7,8}, |
|
.RESET_VAL = 0x55AA, |
.RESET_REG = {30,0}, |
.MODE_VAL = 0, |
.MODE_REG = NOTSUPPORTED, |
|
.ROUTER_CONTROL_REG = {30,30}, |
.ROUTER_EN_BIT = 11, |
.NUMLAN_GROUPS_MAX = 8, |
.NUMLAN_GROUPS_BIT = 8, // {0-2} |
|
.VLAN_CONTROL_REG = {30,13}, |
.TAG_VLAN_BIT = 3, |
|
.CPU_PORT = 8, |
.NUM_PORTS = 9, |
|
.MII_REGISTER_EN = NOTSUPPORTED, |
|
.update_state = ip175c_update_state, |
.set_vlan_mode = ip175c_set_vlan_mode, |
.reset = ip175c_reset, |
}; |
|
static const struct register_mappings IP175C = { |
.NAME = "IP175C", |
.MODEL_NO = 0x18, |
.VLAN_DEFAULT_TAG_REG = { |
{29,24},{29,25},{29,26},{29,27},{29,28},{29,30}, |
NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED |
}, |
|
.ADD_TAG_REG = {29,23}, |
.REMOVE_TAG_REG = {29,23}, |
.ADD_TAG_BIT = {11,12,13,14,15,1,-1,-1,-1}, |
.REMOVE_TAG_BIT = {6,7,8,9,10,0,-1,-1,-1}, |
|
.SIMPLE_VLAN_REGISTERS = 0, |
|
.VLAN_LOOKUP_REG = {29,19},// +N/2 |
.VLAN_LOOKUP_REG_5 = {30,18}, |
.VLAN_LOOKUP_EVEN_BIT = {8,9,10,11,12,15,-1,-1,-1}, |
.VLAN_LOOKUP_ODD_BIT = {0,1,2,3,4,7,-1,-1,-1}, |
|
.TAG_VLAN_MASK_REG = {30,1}, // +N/2 |
.TAG_VLAN_MASK_EVEN_BIT = {0,1,2,3,4,5,-1,-1,-1}, |
.TAG_VLAN_MASK_ODD_BIT = {8,9,10,11,12,13,-1,-1,-1}, |
|
.RESET_VAL = 0x175C, |
.RESET_REG = {30,0}, |
.MODE_VAL = 0x175C, |
.MODE_REG = {29,31}, |
|
.ROUTER_CONTROL_REG = {30,9}, |
.ROUTER_EN_BIT = 3, |
.NUMLAN_GROUPS_MAX = 8, |
.NUMLAN_GROUPS_BIT = 0, // {0-2} |
|
.VLAN_CONTROL_REG = {30,9}, |
.TAG_VLAN_BIT = 7, |
|
.NUM_PORTS = 6, |
.CPU_PORT = 5, |
|
.MII_REGISTER_EN = NOTSUPPORTED, |
|
.update_state = ip175c_update_state, |
.set_vlan_mode = ip175c_set_vlan_mode, |
.reset = ip175c_reset, |
}; |
|
static const struct register_mappings IP175A = { |
.NAME = "IP175A", |
.MODEL_NO = 0x05, |
.VLAN_DEFAULT_TAG_REG = { |
{0,24},{0,25},{0,26},{0,27},{0,28},NOTSUPPORTED, |
NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED |
}, |
|
.ADD_TAG_REG = {0,23}, |
.REMOVE_TAG_REG = {0,23}, |
.ADD_TAG_BIT = {11,12,13,14,15,-1,-1,-1,-1}, |
.REMOVE_TAG_BIT = {6,7,8,9,10,-1,-1,-1,-1}, |
|
.SIMPLE_VLAN_REGISTERS = 0, |
|
// Only programmable via EEPROM |
.VLAN_LOOKUP_REG = NOTSUPPORTED,// +N/2 |
.VLAN_LOOKUP_REG_5 = NOTSUPPORTED, |
.VLAN_LOOKUP_EVEN_BIT = {8,9,10,11,12,-1,-1,-1,-1}, |
.VLAN_LOOKUP_ODD_BIT = {0,1,2,3,4,-1,-1,-1,-1}, |
|
.TAG_VLAN_MASK_REG = NOTSUPPORTED, // +N/2, |
.TAG_VLAN_MASK_EVEN_BIT = {-1,-1,-1,-1,-1,-1,-1,-1,-1}, |
.TAG_VLAN_MASK_ODD_BIT = {-1,-1,-1,-1,-1,-1,-1,-1,-1}, |
|
.RESET_VAL = -1, |
.RESET_REG = NOTSUPPORTED, |
.MODE_VAL = 0, |
.MODE_REG = NOTSUPPORTED, |
|
.ROUTER_CONTROL_REG = NOTSUPPORTED, |
.VLAN_CONTROL_REG = NOTSUPPORTED, |
.TAG_VLAN_BIT = -1, |
.ROUTER_EN_BIT = -1, |
.NUMLAN_GROUPS_MAX = -1, |
.NUMLAN_GROUPS_BIT = -1, // {0-2} |
|
.NUM_PORTS = 5, |
.CPU_PORT = 4, |
|
.MII_REGISTER_EN = {0, 18}, |
.MII_REGISTER_EN_BIT = 7, |
|
.update_state = ip175c_update_state, |
.set_vlan_mode = ip175c_set_vlan_mode, |
.reset = ip175c_reset, |
}; |
|
|
static int ip175d_update_state(struct ip17xx_state *state); |
static int ip175d_set_vlan_mode(struct ip17xx_state *state); |
static int ip175d_reset(struct ip17xx_state *state); |
|
static const struct register_mappings IP175D = { |
.NAME = "IP175D", |
.MODEL_NO = 0x18, |
|
// The IP175D has a completely different interface, so we leave most |
// of the registers undefined and switch to different code paths. |
|
.VLAN_DEFAULT_TAG_REG = { |
NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED, |
NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED, |
}, |
|
.ADD_TAG_REG = NOTSUPPORTED, |
.REMOVE_TAG_REG = NOTSUPPORTED, |
|
.SIMPLE_VLAN_REGISTERS = 0, |
|
.VLAN_LOOKUP_REG = NOTSUPPORTED, |
.VLAN_LOOKUP_REG_5 = NOTSUPPORTED, |
.TAG_VLAN_MASK_REG = NOTSUPPORTED, |
|
.RESET_VAL = 0x175D, |
.RESET_REG = {20,2}, |
.MODE_REG = NOTSUPPORTED, |
|
.ROUTER_CONTROL_REG = NOTSUPPORTED, |
.ROUTER_EN_BIT = -1, |
.NUMLAN_GROUPS_BIT = -1, |
|
.VLAN_CONTROL_REG = NOTSUPPORTED, |
.TAG_VLAN_BIT = -1, |
|
.NUM_PORTS = 6, |
.CPU_PORT = 5, |
|
.MII_REGISTER_EN = NOTSUPPORTED, |
|
.update_state = ip175d_update_state, |
.set_vlan_mode = ip175d_set_vlan_mode, |
.reset = ip175d_reset, |
}; |
|
struct ip17xx_state { |
struct switch_dev dev; |
struct mii_bus *mii_bus; |
bool registered; |
|
int router_mode; // ROUTER_EN |
int vlan_enabled; // TAG_VLAN_EN |
struct port_state { |
u16 pvid; |
unsigned int shareports; |
} ports[MAX_PORTS]; |
unsigned int add_tag; |
unsigned int remove_tag; |
int num_vlans; |
struct vlan_state { |
unsigned int ports; |
unsigned int tag; // VLAN tag (IP175D only) |
} vlans[MAX_VLANS]; |
const struct register_mappings *regs; |
reg proc_mii; // phy/reg for the low level register access via swconfig |
|
char buf[80]; |
}; |
|
#define get_state(_dev) container_of((_dev), struct ip17xx_state, dev) |
|
static int ip_phy_read(struct ip17xx_state *state, int port, int reg) |
{ |
int val = mdiobus_read(state->mii_bus, port, reg); |
if (val < 0) |
pr_warning("IP17xx: Unable to get MII register %d,%d: error %d\n", port, reg, -val); |
#ifdef DUMP_MII_IO |
else |
pr_debug("IP17xx: Read MII(%d,%d) -> %04x\n", port, reg, val); |
#endif |
return val; |
} |
|
static int ip_phy_write(struct ip17xx_state *state, int port, int reg, u16 val) |
{ |
int err; |
|
#ifdef DUMP_MII_IO |
pr_debug("IP17xx: Write MII(%d,%d) <- %04x\n", port, reg, val); |
#endif |
err = mdiobus_write(state->mii_bus, port, reg, val); |
if (err < 0) |
pr_warning("IP17xx: Unable to write MII register %d,%d: error %d\n", port, reg, -err); |
return err; |
} |
|
static int ip_phy_write_masked(struct ip17xx_state *state, int port, int reg, unsigned int mask, unsigned int data) |
{ |
int val = ip_phy_read(state, port, reg); |
if (val < 0) |
return 0; |
return ip_phy_write(state, port, reg, (val & ~mask) | data); |
} |
|
static int getPhy(struct ip17xx_state *state, reg mii) |
{ |
if (!REG_SUPP(mii)) |
return -EFAULT; |
return ip_phy_read(state, mii.p, mii.m); |
} |
|
static int setPhy(struct ip17xx_state *state, reg mii, u16 value) |
{ |
int err; |
|
if (!REG_SUPP(mii)) |
return -EFAULT; |
err = ip_phy_write(state, mii.p, mii.m, value); |
if (err < 0) |
return err; |
mdelay(2); |
getPhy(state, mii); |
return 0; |
} |
|
|
/** |
* These two macros are to simplify the mapping of logical bits to the bits in hardware. |
* NOTE: these macros will return if there is an error! |
*/ |
|
#define GET_PORT_BITS(state, bits, addr, bit_lookup) \ |
do { \ |
int i, val = getPhy((state), (addr)); \ |
if (val < 0) \ |
return val; \ |
(bits) = 0; \ |
for (i = 0; i < MAX_PORTS; i++) { \ |
if ((bit_lookup)[i] == -1) continue; \ |
if (val & (1<<(bit_lookup)[i])) \ |
(bits) |= (1<<i); \ |
} \ |
} while (0) |
|
#define SET_PORT_BITS(state, bits, addr, bit_lookup) \ |
do { \ |
int i, val = getPhy((state), (addr)); \ |
if (val < 0) \ |
return val; \ |
for (i = 0; i < MAX_PORTS; i++) { \ |
unsigned int newmask = ((bits)&(1<<i)); \ |
if ((bit_lookup)[i] == -1) continue; \ |
val &= ~(1<<(bit_lookup)[i]); \ |
val |= ((newmask>>i)<<(bit_lookup)[i]); \ |
} \ |
val = setPhy((state), (addr), val); \ |
if (val < 0) \ |
return val; \ |
} while (0) |
|
|
static int get_model(struct ip17xx_state *state) |
{ |
int id1, id2; |
int oui_id, model_no, rev_no, chip_no; |
|
id1 = ip_phy_read(state, 0, 2); |
id2 = ip_phy_read(state, 0, 3); |
oui_id = (id1 << 6) | ((id2 >> 10) & 0x3f); |
model_no = (id2 >> 4) & 0x3f; |
rev_no = id2 & 0xf; |
pr_debug("IP17xx: Identified oui=%06x model=%02x rev=%X\n", oui_id, model_no, rev_no); |
|
if (oui_id != 0x0090c3) // No other oui_id should have reached us anyway |
return -ENODEV; |
|
if (model_no == IP175A.MODEL_NO) { |
state->regs = &IP175A; |
} else if (model_no == IP175C.MODEL_NO) { |
/* |
* Several models share the same model_no: |
* 178C has more PHYs, so we try whether the device responds to a read from PHY5 |
* 175D has a new chip ID register |
* 175C has neither |
*/ |
if (ip_phy_read(state, 5, 2) == 0x0243) { |
state->regs = &IP178C; |
} else { |
chip_no = ip_phy_read(state, 20, 0); |
pr_debug("IP17xx: Chip ID register reads %04x\n", chip_no); |
if (chip_no == 0x175d) { |
state->regs = &IP175D; |
} else { |
state->regs = &IP175C; |
} |
} |
} else { |
pr_warning("IP17xx: Found an unknown IC+ switch with model number %02x, revision %X.\n", model_no, rev_no); |
return -EPERM; |
} |
return 0; |
} |
|
/*** Low-level functions for the older models ***/ |
|
/** Only set vlan and router flags in the switch **/ |
static int ip175c_set_flags(struct ip17xx_state *state) |
{ |
int val; |
|
if (!REG_SUPP(state->regs->ROUTER_CONTROL_REG)) { |
return 0; |
} |
|
val = getPhy(state, state->regs->ROUTER_CONTROL_REG); |
if (val < 0) { |
return val; |
} |
if (state->regs->ROUTER_EN_BIT >= 0) { |
if (state->router_mode) { |
val |= (1<<state->regs->ROUTER_EN_BIT); |
} else { |
val &= (~(1<<state->regs->ROUTER_EN_BIT)); |
} |
} |
if (state->regs->TAG_VLAN_BIT >= 0) { |
if (state->vlan_enabled) { |
val |= (1<<state->regs->TAG_VLAN_BIT); |
} else { |
val &= (~(1<<state->regs->TAG_VLAN_BIT)); |
} |
} |
if (state->regs->NUMLAN_GROUPS_BIT >= 0) { |
val &= (~((state->regs->NUMLAN_GROUPS_MAX-1)<<state->regs->NUMLAN_GROUPS_BIT)); |
if (state->num_vlans > state->regs->NUMLAN_GROUPS_MAX) { |
val |= state->regs->NUMLAN_GROUPS_MAX << state->regs->NUMLAN_GROUPS_BIT; |
} else if (state->num_vlans >= 1) { |
val |= (state->num_vlans-1) << state->regs->NUMLAN_GROUPS_BIT; |
} |
} |
return setPhy(state, state->regs->ROUTER_CONTROL_REG, val); |
} |
|
/** Set all VLAN and port state. Usually you should call "correct_vlan_state" first. **/ |
static int ip175c_set_state(struct ip17xx_state *state) |
{ |
int j; |
int i; |
SET_PORT_BITS(state, state->add_tag, |
state->regs->ADD_TAG_REG, state->regs->ADD_TAG_BIT); |
SET_PORT_BITS(state, state->remove_tag, |
state->regs->REMOVE_TAG_REG, state->regs->REMOVE_TAG_BIT); |
|
if (REG_SUPP(state->regs->VLAN_LOOKUP_REG)) { |
for (j=0; j<state->regs->NUM_PORTS; j++) { |
reg addr; |
const bitnum *bit_lookup = (j%2==0)? |
state->regs->VLAN_LOOKUP_EVEN_BIT: |
state->regs->VLAN_LOOKUP_ODD_BIT; |
|
addr = state->regs->VLAN_LOOKUP_REG; |
if (state->regs->SIMPLE_VLAN_REGISTERS) { |
addr.m += j; |
} else { |
switch (j) { |
case 0: |
case 1: |
break; |
case 2: |
case 3: |
addr.m+=1; |
break; |
case 4: |
addr.m+=2; |
break; |
case 5: |
addr = state->regs->VLAN_LOOKUP_REG_5; |
break; |
default: |
addr.m = -1; // shouldn't get here, but... |
break; |
} |
} |
//printf("shareports for %d is %02X\n",j,state->ports[j].shareports); |
if (REG_SUPP(addr)) { |
SET_PORT_BITS(state, state->ports[j].shareports, addr, bit_lookup); |
} |
} |
} |
if (REG_SUPP(state->regs->TAG_VLAN_MASK_REG)) { |
for (j=0; j<MAX_VLANS; j++) { |
reg addr = state->regs->TAG_VLAN_MASK_REG; |
const bitnum *bit_lookup = (j%2==0)? |
state->regs->TAG_VLAN_MASK_EVEN_BIT: |
state->regs->TAG_VLAN_MASK_ODD_BIT; |
unsigned int vlan_mask; |
if (state->regs->SIMPLE_VLAN_REGISTERS) { |
addr.m += j; |
} else { |
addr.m += j/2; |
} |
vlan_mask = state->vlans[j].ports; |
SET_PORT_BITS(state, vlan_mask, addr, bit_lookup); |
} |
} |
|
for (i=0; i<MAX_PORTS; i++) { |
if (REG_SUPP(state->regs->VLAN_DEFAULT_TAG_REG[i])) { |
int err = setPhy(state, state->regs->VLAN_DEFAULT_TAG_REG[i], |
state->ports[i].pvid); |
if (err < 0) { |
return err; |
} |
} |
} |
|
return ip175c_set_flags(state); |
} |
|
/** |
* Uses only the VLAN port mask and the add tag mask to generate the other fields: |
* which ports are part of the same VLAN, removing vlan tags, and VLAN tag ids. |
*/ |
static void ip175c_correct_vlan_state(struct ip17xx_state *state) |
{ |
int i, j; |
state->num_vlans = 0; |
for (i=0; i<MAX_VLANS; i++) { |
if (state->vlans[i].ports != 0) { |
state->num_vlans = i+1; // Hack -- we need to store the "set" vlans somewhere... |
} |
} |
|
for (i=0; i<state->regs->NUM_PORTS; i++) { |
unsigned int portmask = (1<<i); |
if (!state->vlan_enabled) { |
// Share with everybody! |
state->ports[i].shareports = (1<<state->regs->NUM_PORTS)-1; |
continue; |
} |
state->ports[i].shareports = portmask; |
for (j=0; j<MAX_VLANS; j++) { |
if (state->vlans[j].ports & portmask) |
state->ports[i].shareports |= state->vlans[j].ports; |
} |
} |
} |
|
static int ip175c_update_state(struct ip17xx_state *state) |
{ |
ip175c_correct_vlan_state(state); |
return ip175c_set_state(state); |
} |
|
static int ip175c_set_vlan_mode(struct ip17xx_state *state) |
{ |
return ip175c_update_state(state); |
} |
|
static int ip175c_reset(struct ip17xx_state *state) |
{ |
int err; |
|
if (REG_SUPP(state->regs->MODE_REG)) { |
err = setPhy(state, state->regs->MODE_REG, state->regs->MODE_VAL); |
if (err < 0) |
return err; |
err = getPhy(state, state->regs->MODE_REG); |
if (err < 0) |
return err; |
} |
|
return ip175c_update_state(state); |
} |
|
/*** Low-level functions for IP175D ***/ |
|
static int ip175d_update_state(struct ip17xx_state *state) |
{ |
unsigned int filter_mask = 0; |
unsigned int ports[16], add[16], rem[16]; |
int i, j; |
int err = 0; |
|
for (i = 0; i < 16; i++) { |
ports[i] = 0; |
add[i] = 0; |
rem[i] = 0; |
if (!state->vlan_enabled) { |
err |= ip_phy_write(state, 22, 14+i, i+1); // default tags |
ports[i] = 0x3f; |
continue; |
} |
if (!state->vlans[i].tag) { |
// Reset the filter |
err |= ip_phy_write(state, 22, 14+i, 0); // tag |
continue; |
} |
filter_mask |= 1 << i; |
err |= ip_phy_write(state, 22, 14+i, state->vlans[i].tag); |
ports[i] = state->vlans[i].ports; |
for (j = 0; j < 6; j++) { |
if (ports[i] & (1 << j)) { |
if (state->add_tag & (1 << j)) |
add[i] |= 1 << j; |
if (state->remove_tag & (1 << j)) |
rem[i] |= 1 << j; |
} |
} |
} |
|
// Port masks, tag adds and removals |
for (i = 0; i < 8; i++) { |
err |= ip_phy_write(state, 23, i, ports[2*i] | (ports[2*i+1] << 8)); |
err |= ip_phy_write(state, 23, 8+i, add[2*i] | (add[2*i+1] << 8)); |
err |= ip_phy_write(state, 23, 16+i, rem[2*i] | (rem[2*i+1] << 8)); |
} |
err |= ip_phy_write(state, 22, 10, filter_mask); |
|
// Default VLAN tag for each port |
for (i = 0; i < 6; i++) |
err |= ip_phy_write(state, 22, 4+i, state->vlans[state->ports[i].pvid].tag); |
|
return (err ? -EIO : 0); |
} |
|
static int ip175d_set_vlan_mode(struct ip17xx_state *state) |
{ |
int i; |
int err = 0; |
|
if (state->vlan_enabled) { |
// VLAN classification rules: tag-based VLANs, use VID to classify, |
// drop packets that cannot be classified. |
err |= ip_phy_write_masked(state, 22, 0, 0x3fff, 0x003f); |
|
// Ingress rules: CFI=1 dropped, null VID is untagged, VID=1 passed, |
// VID=0xfff discarded, admin both tagged and untagged, ingress |
// filters enabled. |
err |= ip_phy_write_masked(state, 22, 1, 0x0fff, 0x0c3f); |
|
// Egress rules: IGMP processing off, keep VLAN header off |
err |= ip_phy_write_masked(state, 22, 2, 0x0fff, 0x0000); |
} else { |
// VLAN classification rules: everything off & clear table |
err |= ip_phy_write_masked(state, 22, 0, 0xbfff, 0x8000); |
|
// Ingress and egress rules: set to defaults |
err |= ip_phy_write_masked(state, 22, 1, 0x0fff, 0x0c3f); |
err |= ip_phy_write_masked(state, 22, 2, 0x0fff, 0x0000); |
} |
|
// Reset default VLAN for each port to 0 |
for (i = 0; i < 6; i++) |
state->ports[i].pvid = 0; |
|
err |= ip175d_update_state(state); |
|
return (err ? -EIO : 0); |
} |
|
static int ip175d_reset(struct ip17xx_state *state) |
{ |
int err = 0; |
|
// Disable the special tagging mode |
err |= ip_phy_write_masked(state, 21, 22, 0x0003, 0x0000); |
|
// Set 802.1q protocol type |
err |= ip_phy_write(state, 22, 3, 0x8100); |
|
state->vlan_enabled = 0; |
err |= ip175d_set_vlan_mode(state); |
|
return (err ? -EIO : 0); |
} |
|
/*** High-level functions ***/ |
|
static int ip17xx_get_enable_vlan(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) |
{ |
struct ip17xx_state *state = get_state(dev); |
|
val->value.i = state->vlan_enabled; |
return 0; |
} |
|
static void ip17xx_reset_vlan_config(struct ip17xx_state *state) |
{ |
int i; |
|
state->remove_tag = (state->vlan_enabled ? ((1<<state->regs->NUM_PORTS)-1) : 0x0000); |
state->add_tag = 0x0000; |
for (i = 0; i < MAX_VLANS; i++) { |
state->vlans[i].ports = 0x0000; |
state->vlans[i].tag = (i ? i : 16); |
} |
for (i = 0; i < MAX_PORTS; i++) |
state->ports[i].pvid = 0; |
} |
|
static int ip17xx_set_enable_vlan(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) |
{ |
struct ip17xx_state *state = get_state(dev); |
int enable; |
|
enable = val->value.i; |
if (state->vlan_enabled == enable) { |
// Do not change any state. |
return 0; |
} |
state->vlan_enabled = enable; |
|
// Otherwise, if we are switching state, set fields to a known default. |
ip17xx_reset_vlan_config(state); |
|
return state->regs->set_vlan_mode(state); |
} |
|
static int ip17xx_get_ports(struct switch_dev *dev, struct switch_val *val) |
{ |
struct ip17xx_state *state = get_state(dev); |
int b; |
int ind; |
unsigned int ports; |
|
if (val->port_vlan >= dev->vlans || val->port_vlan < 0) |
return -EINVAL; |
|
ports = state->vlans[val->port_vlan].ports; |
b = 0; |
ind = 0; |
while (b < MAX_PORTS) { |
if (ports&1) { |
int istagged = ((state->add_tag >> b) & 1); |
val->value.ports[ind].id = b; |
val->value.ports[ind].flags = (istagged << SWITCH_PORT_FLAG_TAGGED); |
ind++; |
} |
b++; |
ports >>= 1; |
} |
val->len = ind; |
|
return 0; |
} |
|
static int ip17xx_set_ports(struct switch_dev *dev, struct switch_val *val) |
{ |
struct ip17xx_state *state = get_state(dev); |
int i; |
|
if (val->port_vlan >= dev->vlans || val->port_vlan < 0) |
return -EINVAL; |
|
state->vlans[val->port_vlan].ports = 0; |
for (i = 0; i < val->len; i++) { |
unsigned int bitmask = (1<<val->value.ports[i].id); |
state->vlans[val->port_vlan].ports |= bitmask; |
if (val->value.ports[i].flags & (1<<SWITCH_PORT_FLAG_TAGGED)) { |
state->add_tag |= bitmask; |
state->remove_tag &= (~bitmask); |
} else { |
state->add_tag &= (~bitmask); |
state->remove_tag |= bitmask; |
} |
} |
|
return state->regs->update_state(state); |
} |
|
static int ip17xx_apply(struct switch_dev *dev) |
{ |
struct ip17xx_state *state = get_state(dev); |
|
if (REG_SUPP(state->regs->MII_REGISTER_EN)) { |
int val = getPhy(state, state->regs->MII_REGISTER_EN); |
if (val < 0) { |
return val; |
} |
val |= (1<<state->regs->MII_REGISTER_EN_BIT); |
return setPhy(state, state->regs->MII_REGISTER_EN, val); |
} |
return 0; |
} |
|
static int ip17xx_reset(struct switch_dev *dev) |
{ |
struct ip17xx_state *state = get_state(dev); |
int i, err; |
|
if (REG_SUPP(state->regs->RESET_REG)) { |
err = setPhy(state, state->regs->RESET_REG, state->regs->RESET_VAL); |
if (err < 0) |
return err; |
err = getPhy(state, state->regs->RESET_REG); |
|
/* |
* Data sheet specifies reset period to be 2 msec. |
* (I don't see any mention of the 2ms delay in the IP178C spec, only |
* in IP175C, but it can't hurt.) |
*/ |
mdelay(2); |
} |
|
/* reset switch ports */ |
for (i = 0; i < state->regs->NUM_PORTS-1; i++) { |
err = ip_phy_write(state, i, MII_BMCR, BMCR_RESET); |
if (err < 0) |
return err; |
} |
|
state->router_mode = 0; |
state->vlan_enabled = 0; |
ip17xx_reset_vlan_config(state); |
|
return state->regs->reset(state); |
} |
|
static int ip17xx_get_tagged(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) |
{ |
struct ip17xx_state *state = get_state(dev); |
|
if (state->add_tag & (1<<val->port_vlan)) { |
if (state->remove_tag & (1<<val->port_vlan)) |
val->value.i = 3; // shouldn't ever happen. |
else |
val->value.i = 1; |
} else { |
if (state->remove_tag & (1<<val->port_vlan)) |
val->value.i = 0; |
else |
val->value.i = 2; |
} |
return 0; |
} |
|
static int ip17xx_set_tagged(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) |
{ |
struct ip17xx_state *state = get_state(dev); |
|
state->add_tag &= ~(1<<val->port_vlan); |
state->remove_tag &= ~(1<<val->port_vlan); |
|
if (val->value.i == 0) |
state->remove_tag |= (1<<val->port_vlan); |
if (val->value.i == 1) |
state->add_tag |= (1<<val->port_vlan); |
|
return state->regs->update_state(state); |
} |
|
/** Get the current phy address */ |
static int ip17xx_get_phy(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) |
{ |
struct ip17xx_state *state = get_state(dev); |
|
val->value.i = state->proc_mii.p; |
return 0; |
} |
|
/** Set a new phy address for low level access to registers */ |
static int ip17xx_set_phy(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) |
{ |
struct ip17xx_state *state = get_state(dev); |
int new_reg = val->value.i; |
|
if (new_reg < 0 || new_reg > 31) |
state->proc_mii.p = (u16)-1; |
else |
state->proc_mii.p = (u16)new_reg; |
return 0; |
} |
|
/** Get the current register number */ |
static int ip17xx_get_reg(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) |
{ |
struct ip17xx_state *state = get_state(dev); |
|
val->value.i = state->proc_mii.m; |
return 0; |
} |
|
/** Set a new register address for low level access to registers */ |
static int ip17xx_set_reg(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) |
{ |
struct ip17xx_state *state = get_state(dev); |
int new_reg = val->value.i; |
|
if (new_reg < 0 || new_reg > 31) |
state->proc_mii.m = (u16)-1; |
else |
state->proc_mii.m = (u16)new_reg; |
return 0; |
} |
|
/** Get the register content of state->proc_mii */ |
static int ip17xx_get_val(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) |
{ |
struct ip17xx_state *state = get_state(dev); |
int retval = -EINVAL; |
if (REG_SUPP(state->proc_mii)) |
retval = getPhy(state, state->proc_mii); |
|
if (retval < 0) { |
return retval; |
} else { |
val->value.i = retval; |
return 0; |
} |
} |
|
/** Write a value to the register defined by phy/reg above */ |
static int ip17xx_set_val(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) |
{ |
struct ip17xx_state *state = get_state(dev); |
int myval, err = -EINVAL; |
|
myval = val->value.i; |
if (myval <= 0xffff && myval >= 0 && REG_SUPP(state->proc_mii)) { |
err = setPhy(state, state->proc_mii, (u16)myval); |
} |
return err; |
} |
|
static int ip17xx_read_name(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) |
{ |
struct ip17xx_state *state = get_state(dev); |
val->value.s = state->regs->NAME; // Just a const pointer, won't be freed by swconfig. |
return 0; |
} |
|
static int ip17xx_get_tag(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) |
{ |
struct ip17xx_state *state = get_state(dev); |
int vlan = val->port_vlan; |
|
if (vlan < 0 || vlan >= MAX_VLANS) |
return -EINVAL; |
|
val->value.i = state->vlans[vlan].tag; |
return 0; |
} |
|
static int ip17xx_set_tag(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) |
{ |
struct ip17xx_state *state = get_state(dev); |
int vlan = val->port_vlan; |
int tag = val->value.i; |
|
if (vlan < 0 || vlan >= MAX_VLANS) |
return -EINVAL; |
|
if (tag < 0 || tag > 4095) |
return -EINVAL; |
|
state->vlans[vlan].tag = tag; |
return state->regs->update_state(state); |
} |
|
static int ip17xx_set_port_speed(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) |
{ |
struct ip17xx_state *state = get_state(dev); |
int nr = val->port_vlan; |
int ctrl; |
int autoneg; |
int speed; |
if (val->value.i == 100) { |
speed = 1; |
autoneg = 0; |
} else if (val->value.i == 10) { |
speed = 0; |
autoneg = 0; |
} else { |
autoneg = 1; |
speed = 1; |
} |
|
/* Can't set speed for cpu port */ |
if (nr == state->regs->CPU_PORT) |
return -EINVAL; |
|
if (nr >= dev->ports || nr < 0) |
return -EINVAL; |
|
ctrl = ip_phy_read(state, nr, 0); |
if (ctrl < 0) |
return -EIO; |
|
ctrl &= (~(1<<12)); |
ctrl &= (~(1<<13)); |
ctrl |= (autoneg<<12); |
ctrl |= (speed<<13); |
|
return ip_phy_write(state, nr, 0, ctrl); |
} |
|
static int ip17xx_get_port_speed(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) |
{ |
struct ip17xx_state *state = get_state(dev); |
int nr = val->port_vlan; |
int speed, status; |
|
if (nr == state->regs->CPU_PORT) { |
val->value.i = 100; |
return 0; |
} |
|
if (nr >= dev->ports || nr < 0) |
return -EINVAL; |
|
status = ip_phy_read(state, nr, 1); |
speed = ip_phy_read(state, nr, 18); |
if (status < 0 || speed < 0) |
return -EIO; |
|
if (status & 4) |
val->value.i = ((speed & (1<<11)) ? 100 : 10); |
else |
val->value.i = 0; |
|
return 0; |
} |
|
static int ip17xx_get_port_status(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) |
{ |
struct ip17xx_state *state = get_state(dev); |
int ctrl, speed, status; |
int nr = val->port_vlan; |
int len; |
char *buf = state->buf; // fixed-length at 80. |
|
if (nr == state->regs->CPU_PORT) { |
sprintf(buf, "up, 100 Mbps, cpu port"); |
val->value.s = buf; |
return 0; |
} |
|
if (nr >= dev->ports || nr < 0) |
return -EINVAL; |
|
ctrl = ip_phy_read(state, nr, 0); |
status = ip_phy_read(state, nr, 1); |
speed = ip_phy_read(state, nr, 18); |
if (ctrl < 0 || status < 0 || speed < 0) |
return -EIO; |
|
if (status & 4) |
len = sprintf(buf, "up, %d Mbps, %s duplex", |
((speed & (1<<11)) ? 100 : 10), |
((speed & (1<<10)) ? "full" : "half")); |
else |
len = sprintf(buf, "down"); |
|
if (ctrl & (1<<12)) { |
len += sprintf(buf+len, ", auto-negotiate"); |
if (!(status & (1<<5))) |
len += sprintf(buf+len, " (in progress)"); |
} else { |
len += sprintf(buf+len, ", fixed speed (%d)", |
((ctrl & (1<<13)) ? 100 : 10)); |
} |
|
buf[len] = '\0'; |
val->value.s = buf; |
return 0; |
} |
|
static int ip17xx_get_pvid(struct switch_dev *dev, int port, int *val) |
{ |
struct ip17xx_state *state = get_state(dev); |
|
*val = state->ports[port].pvid; |
return 0; |
} |
|
static int ip17xx_set_pvid(struct switch_dev *dev, int port, int val) |
{ |
struct ip17xx_state *state = get_state(dev); |
|
if (val < 0 || val >= MAX_VLANS) |
return -EINVAL; |
|
state->ports[port].pvid = val; |
return state->regs->update_state(state); |
} |
|
|
enum Ports { |
IP17XX_PORT_STATUS, |
IP17XX_PORT_LINK, |
IP17XX_PORT_TAGGED, |
IP17XX_PORT_PVID, |
}; |
|
enum Globals { |
IP17XX_ENABLE_VLAN, |
IP17XX_GET_NAME, |
IP17XX_REGISTER_PHY, |
IP17XX_REGISTER_MII, |
IP17XX_REGISTER_VALUE, |
IP17XX_REGISTER_ERRNO, |
}; |
|
enum Vlans { |
IP17XX_VLAN_TAG, |
}; |
|
static const struct switch_attr ip17xx_global[] = { |
[IP17XX_ENABLE_VLAN] = { |
.id = IP17XX_ENABLE_VLAN, |
.type = SWITCH_TYPE_INT, |
.name = "enable_vlan", |
.description = "Flag to enable or disable VLANs and tagging", |
.get = ip17xx_get_enable_vlan, |
.set = ip17xx_set_enable_vlan, |
}, |
[IP17XX_GET_NAME] = { |
.id = IP17XX_GET_NAME, |
.type = SWITCH_TYPE_STRING, |
.description = "Returns the type of IC+ chip.", |
.name = "name", |
.get = ip17xx_read_name, |
.set = NULL, |
}, |
/* jal: added for low level debugging etc. */ |
[IP17XX_REGISTER_PHY] = { |
.id = IP17XX_REGISTER_PHY, |
.type = SWITCH_TYPE_INT, |
.description = "Direct register access: set PHY (0-4, or 29,30,31)", |
.name = "phy", |
.get = ip17xx_get_phy, |
.set = ip17xx_set_phy, |
}, |
[IP17XX_REGISTER_MII] = { |
.id = IP17XX_REGISTER_MII, |
.type = SWITCH_TYPE_INT, |
.description = "Direct register access: set MII register number (0-31)", |
.name = "reg", |
.get = ip17xx_get_reg, |
.set = ip17xx_set_reg, |
}, |
[IP17XX_REGISTER_VALUE] = { |
.id = IP17XX_REGISTER_VALUE, |
.type = SWITCH_TYPE_INT, |
.description = "Direct register access: read/write to register (0-65535)", |
.name = "val", |
.get = ip17xx_get_val, |
.set = ip17xx_set_val, |
}, |
}; |
|
static const struct switch_attr ip17xx_vlan[] = { |
[IP17XX_VLAN_TAG] = { |
.id = IP17XX_VLAN_TAG, |
.type = SWITCH_TYPE_INT, |
.description = "VLAN ID (0-4095) [IP175D only]", |
.name = "vid", |
.get = ip17xx_get_tag, |
.set = ip17xx_set_tag, |
} |
}; |
|
static const struct switch_attr ip17xx_port[] = { |
[IP17XX_PORT_STATUS] = { |
.id = IP17XX_PORT_STATUS, |
.type = SWITCH_TYPE_STRING, |
.description = "Returns Detailed port status", |
.name = "status", |
.get = ip17xx_get_port_status, |
.set = NULL, |
}, |
[IP17XX_PORT_LINK] = { |
.id = IP17XX_PORT_LINK, |
.type = SWITCH_TYPE_INT, |
.description = "Link speed. Can write 0 for auto-negotiate, or 10 or 100", |
.name = "link", |
.get = ip17xx_get_port_speed, |
.set = ip17xx_set_port_speed, |
}, |
[IP17XX_PORT_TAGGED] = { |
.id = IP17XX_PORT_LINK, |
.type = SWITCH_TYPE_INT, |
.description = "0 = untag, 1 = add tags, 2 = do not alter (This value is reset if vlans are altered)", |
.name = "tagged", |
.get = ip17xx_get_tagged, |
.set = ip17xx_set_tagged, |
}, |
}; |
|
static const struct switch_dev_ops ip17xx_ops = { |
.attr_global = { |
.attr = ip17xx_global, |
.n_attr = ARRAY_SIZE(ip17xx_global), |
}, |
.attr_port = { |
.attr = ip17xx_port, |
.n_attr = ARRAY_SIZE(ip17xx_port), |
}, |
.attr_vlan = { |
.attr = ip17xx_vlan, |
.n_attr = ARRAY_SIZE(ip17xx_vlan), |
}, |
|
.get_port_pvid = ip17xx_get_pvid, |
.set_port_pvid = ip17xx_set_pvid, |
.get_vlan_ports = ip17xx_get_ports, |
.set_vlan_ports = ip17xx_set_ports, |
.apply_config = ip17xx_apply, |
.reset_switch = ip17xx_reset, |
}; |
|
static int ip17xx_probe(struct phy_device *pdev) |
{ |
struct ip17xx_state *state; |
struct switch_dev *dev; |
int err; |
|
/* We only attach to PHY 0, but use all available PHYs */ |
if (pdev->mdio.addr != 0) |
return -ENODEV; |
|
state = kzalloc(sizeof(*state), GFP_KERNEL); |
if (!state) |
return -ENOMEM; |
|
dev = &state->dev; |
|
pdev->priv = state; |
state->mii_bus = pdev->mdio.bus; |
|
err = get_model(state); |
if (err < 0) |
goto error; |
|
dev->vlans = MAX_VLANS; |
dev->cpu_port = state->regs->CPU_PORT; |
dev->ports = state->regs->NUM_PORTS; |
dev->name = state->regs->NAME; |
dev->ops = &ip17xx_ops; |
|
pr_info("IP17xx: Found %s at %s\n", dev->name, dev_name(&pdev->mdio.dev)); |
return 0; |
|
error: |
kfree(state); |
return err; |
} |
|
static int ip17xx_config_init(struct phy_device *pdev) |
{ |
struct ip17xx_state *state = pdev->priv; |
struct net_device *dev = pdev->attached_dev; |
int err; |
|
err = register_switch(&state->dev, dev); |
if (err < 0) |
return err; |
|
state->registered = true; |
ip17xx_reset(&state->dev); |
return 0; |
} |
|
static void ip17xx_remove(struct phy_device *pdev) |
{ |
struct ip17xx_state *state = pdev->priv; |
|
if (state->registered) |
unregister_switch(&state->dev); |
kfree(state); |
} |
|
static int ip17xx_config_aneg(struct phy_device *pdev) |
{ |
return 0; |
} |
|
static int ip17xx_aneg_done(struct phy_device *pdev) |
{ |
return 1; /* Return any positive value */ |
} |
|
static int ip17xx_update_link(struct phy_device *pdev) |
{ |
pdev->link = 1; |
return 0; |
} |
|
static int ip17xx_read_status(struct phy_device *pdev) |
{ |
pdev->speed = SPEED_100; |
pdev->duplex = DUPLEX_FULL; |
pdev->pause = pdev->asym_pause = 0; |
pdev->link = 1; |
|
return 0; |
} |
|
static struct phy_driver ip17xx_driver[] = { |
{ |
.name = "IC+ IP17xx", |
.phy_id = 0x02430c00, |
.phy_id_mask = 0x0ffffc00, |
.features = PHY_BASIC_FEATURES, |
.probe = ip17xx_probe, |
.remove = ip17xx_remove, |
.config_init = ip17xx_config_init, |
.config_aneg = ip17xx_config_aneg, |
.aneg_done = ip17xx_aneg_done, |
.update_link = ip17xx_update_link, |
.read_status = ip17xx_read_status, |
} |
}; |
|
module_phy_driver(ip17xx_driver); |
|
MODULE_AUTHOR("Patrick Horn <patrick.horn@gmail.com>"); |
MODULE_AUTHOR("Felix Fietkau <nbd@nbd.name>"); |
MODULE_AUTHOR("Martin Mares <mj@ucw.cz>"); |
MODULE_LICENSE("GPL"); |
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/mvsw61xx.c |
@@ -0,0 +1,947 @@ |
/* |
* Marvell 88E61xx switch driver |
* |
* Copyright (c) 2014 Claudio Leite <leitec@staticky.com> |
* Copyright (c) 2014 Nikita Nazarenko <nnazarenko@radiofid.com> |
* |
* Based on code (c) 2008 Felix Fietkau <nbd@nbd.name> |
* |
* This program is free software; you can redistribute it and/or modify it |
* under the terms of the GNU General Public License v2 as published by the |
* Free Software Foundation |
*/ |
|
#include <linux/kernel.h> |
#include <linux/module.h> |
#include <linux/init.h> |
#include <linux/list.h> |
#include <linux/mii.h> |
#include <linux/phy.h> |
#include <linux/of.h> |
#include <linux/of_mdio.h> |
#include <linux/delay.h> |
#include <linux/switch.h> |
#include <linux/device.h> |
#include <linux/platform_device.h> |
|
#include "mvsw61xx.h" |
|
MODULE_DESCRIPTION("Marvell 88E61xx Switch driver"); |
MODULE_AUTHOR("Claudio Leite <leitec@staticky.com>"); |
MODULE_AUTHOR("Nikita Nazarenko <nnazarenko@radiofid.com>"); |
MODULE_LICENSE("GPL v2"); |
MODULE_ALIAS("platform:mvsw61xx"); |
|
/* |
* Register access is done through direct or indirect addressing, |
* depending on how the switch is physically connected. |
* |
* Direct addressing: all port and global registers directly |
* accessible via an address/register pair |
* |
* Indirect addressing: switch is mapped at a single address, |
* port and global registers accessible via a single command/data |
* register pair |
*/ |
|
static int |
mvsw61xx_wait_mask_raw(struct mii_bus *bus, int addr, |
int reg, u16 mask, u16 val) |
{ |
int i = 100; |
u16 r; |
|
do { |
r = bus->read(bus, addr, reg); |
if ((r & mask) == val) |
return 0; |
} while (--i > 0); |
|
return -ETIMEDOUT; |
} |
|
static u16 |
r16(struct mii_bus *bus, bool indirect, int base_addr, int addr, int reg) |
{ |
u16 ind_addr; |
|
if (!indirect) |
return bus->read(bus, addr, reg); |
|
/* Indirect read: First, make sure switch is free */ |
mvsw61xx_wait_mask_raw(bus, base_addr, MV_INDIRECT_REG_CMD, |
MV_INDIRECT_INPROGRESS, 0); |
|
/* Load address and request read */ |
ind_addr = MV_INDIRECT_READ | (addr << MV_INDIRECT_ADDR_S) | reg; |
bus->write(bus, base_addr, MV_INDIRECT_REG_CMD, |
ind_addr); |
|
/* Wait until it's ready */ |
mvsw61xx_wait_mask_raw(bus, base_addr, MV_INDIRECT_REG_CMD, |
MV_INDIRECT_INPROGRESS, 0); |
|
/* Read the requested data */ |
return bus->read(bus, base_addr, MV_INDIRECT_REG_DATA); |
} |
|
static void |
w16(struct mii_bus *bus, bool indirect, int base_addr, int addr, |
int reg, u16 val) |
{ |
u16 ind_addr; |
|
if (!indirect) { |
bus->write(bus, addr, reg, val); |
return; |
} |
|
/* Indirect write: First, make sure switch is free */ |
mvsw61xx_wait_mask_raw(bus, base_addr, MV_INDIRECT_REG_CMD, |
MV_INDIRECT_INPROGRESS, 0); |
|
/* Load the data to be written */ |
bus->write(bus, base_addr, MV_INDIRECT_REG_DATA, val); |
|
/* Wait again for switch to be free */ |
mvsw61xx_wait_mask_raw(bus, base_addr, MV_INDIRECT_REG_CMD, |
MV_INDIRECT_INPROGRESS, 0); |
|
/* Load address, and issue write command */ |
ind_addr = MV_INDIRECT_WRITE | (addr << MV_INDIRECT_ADDR_S) | reg; |
bus->write(bus, base_addr, MV_INDIRECT_REG_CMD, |
ind_addr); |
} |
|
/* swconfig support */ |
|
static inline u16 |
sr16(struct switch_dev *dev, int addr, int reg) |
{ |
struct mvsw61xx_state *state = get_state(dev); |
|
return r16(state->bus, state->is_indirect, state->base_addr, addr, reg); |
} |
|
static inline void |
sw16(struct switch_dev *dev, int addr, int reg, u16 val) |
{ |
struct mvsw61xx_state *state = get_state(dev); |
|
w16(state->bus, state->is_indirect, state->base_addr, addr, reg, val); |
} |
|
static int |
mvsw61xx_wait_mask_s(struct switch_dev *dev, int addr, |
int reg, u16 mask, u16 val) |
{ |
int i = 100; |
u16 r; |
|
do { |
r = sr16(dev, addr, reg) & mask; |
if (r == val) |
return 0; |
} while (--i > 0); |
|
return -ETIMEDOUT; |
} |
|
static int |
mvsw61xx_mdio_read(struct switch_dev *dev, int addr, int reg) |
{ |
sw16(dev, MV_GLOBAL2REG(SMI_OP), |
MV_INDIRECT_READ | (addr << MV_INDIRECT_ADDR_S) | reg); |
|
if (mvsw61xx_wait_mask_s(dev, MV_GLOBAL2REG(SMI_OP), |
MV_INDIRECT_INPROGRESS, 0) < 0) |
return -ETIMEDOUT; |
|
return sr16(dev, MV_GLOBAL2REG(SMI_DATA)); |
} |
|
static int |
mvsw61xx_mdio_write(struct switch_dev *dev, int addr, int reg, u16 val) |
{ |
sw16(dev, MV_GLOBAL2REG(SMI_DATA), val); |
|
sw16(dev, MV_GLOBAL2REG(SMI_OP), |
MV_INDIRECT_WRITE | (addr << MV_INDIRECT_ADDR_S) | reg); |
|
return mvsw61xx_wait_mask_s(dev, MV_GLOBAL2REG(SMI_OP), |
MV_INDIRECT_INPROGRESS, 0) < 0; |
} |
|
static int |
mvsw61xx_mdio_page_read(struct switch_dev *dev, int port, int page, int reg) |
{ |
int ret; |
|
mvsw61xx_mdio_write(dev, port, MII_MV_PAGE, page); |
ret = mvsw61xx_mdio_read(dev, port, reg); |
mvsw61xx_mdio_write(dev, port, MII_MV_PAGE, 0); |
|
return ret; |
} |
|
static void |
mvsw61xx_mdio_page_write(struct switch_dev *dev, int port, int page, int reg, |
u16 val) |
{ |
mvsw61xx_mdio_write(dev, port, MII_MV_PAGE, page); |
mvsw61xx_mdio_write(dev, port, reg, val); |
mvsw61xx_mdio_write(dev, port, MII_MV_PAGE, 0); |
} |
|
static int |
mvsw61xx_get_port_mask(struct switch_dev *dev, |
const struct switch_attr *attr, struct switch_val *val) |
{ |
struct mvsw61xx_state *state = get_state(dev); |
char *buf = state->buf; |
int port, len, i; |
u16 reg; |
|
port = val->port_vlan; |
reg = sr16(dev, MV_PORTREG(VLANMAP, port)) & MV_PORTS_MASK; |
|
len = sprintf(buf, "0x%04x: ", reg); |
|
for (i = 0; i < MV_PORTS; i++) { |
if (reg & (1 << i)) |
len += sprintf(buf + len, "%d ", i); |
else if (i == port) |
len += sprintf(buf + len, "(%d) ", i); |
} |
|
val->value.s = buf; |
|
return 0; |
} |
|
static int |
mvsw61xx_get_port_qmode(struct switch_dev *dev, |
const struct switch_attr *attr, struct switch_val *val) |
{ |
struct mvsw61xx_state *state = get_state(dev); |
|
val->value.i = state->ports[val->port_vlan].qmode; |
|
return 0; |
} |
|
static int |
mvsw61xx_set_port_qmode(struct switch_dev *dev, |
const struct switch_attr *attr, struct switch_val *val) |
{ |
struct mvsw61xx_state *state = get_state(dev); |
|
state->ports[val->port_vlan].qmode = val->value.i; |
|
return 0; |
} |
|
static int |
mvsw61xx_get_port_pvid(struct switch_dev *dev, int port, int *val) |
{ |
struct mvsw61xx_state *state = get_state(dev); |
|
*val = state->ports[port].pvid; |
|
return 0; |
} |
|
static int |
mvsw61xx_set_port_pvid(struct switch_dev *dev, int port, int val) |
{ |
struct mvsw61xx_state *state = get_state(dev); |
|
if (val < 0 || val >= MV_VLANS) |
return -EINVAL; |
|
state->ports[port].pvid = (u16)val; |
|
return 0; |
} |
|
static int |
mvsw61xx_get_port_link(struct switch_dev *dev, int port, |
struct switch_port_link *link) |
{ |
u16 status, speed; |
|
status = sr16(dev, MV_PORTREG(STATUS, port)); |
|
link->link = status & MV_PORT_STATUS_LINK; |
if (!link->link) |
return 0; |
|
link->duplex = status & MV_PORT_STATUS_FDX; |
|
speed = (status & MV_PORT_STATUS_SPEED_MASK) >> |
MV_PORT_STATUS_SPEED_SHIFT; |
|
switch (speed) { |
case MV_PORT_STATUS_SPEED_10: |
link->speed = SWITCH_PORT_SPEED_10; |
break; |
case MV_PORT_STATUS_SPEED_100: |
link->speed = SWITCH_PORT_SPEED_100; |
break; |
case MV_PORT_STATUS_SPEED_1000: |
link->speed = SWITCH_PORT_SPEED_1000; |
break; |
} |
|
return 0; |
} |
|
static int mvsw61xx_get_vlan_ports(struct switch_dev *dev, |
struct switch_val *val) |
{ |
struct mvsw61xx_state *state = get_state(dev); |
int i, j, mode, vno; |
|
vno = val->port_vlan; |
|
if (vno <= 0 || vno >= dev->vlans) |
return -EINVAL; |
|
for (i = 0, j = 0; i < dev->ports; i++) { |
if (state->vlans[vno].mask & (1 << i)) { |
val->value.ports[j].id = i; |
|
mode = (state->vlans[vno].port_mode >> (i * 4)) & 0xf; |
if (mode == MV_VTUCTL_EGRESS_TAGGED) |
val->value.ports[j].flags = |
(1 << SWITCH_PORT_FLAG_TAGGED); |
else |
val->value.ports[j].flags = 0; |
|
j++; |
} |
} |
|
val->len = j; |
|
return 0; |
} |
|
static int mvsw61xx_set_vlan_ports(struct switch_dev *dev, |
struct switch_val *val) |
{ |
struct mvsw61xx_state *state = get_state(dev); |
int i, mode, pno, vno; |
|
vno = val->port_vlan; |
|
if (vno <= 0 || vno >= dev->vlans) |
return -EINVAL; |
|
state->vlans[vno].mask = 0; |
state->vlans[vno].port_mode = 0; |
state->vlans[vno].port_sstate = 0; |
|
if(state->vlans[vno].vid == 0) |
state->vlans[vno].vid = vno; |
|
for (i = 0; i < val->len; i++) { |
pno = val->value.ports[i].id; |
|
state->vlans[vno].mask |= (1 << pno); |
if (val->value.ports[i].flags & |
(1 << SWITCH_PORT_FLAG_TAGGED)) |
mode = MV_VTUCTL_EGRESS_TAGGED; |
else |
mode = MV_VTUCTL_EGRESS_UNTAGGED; |
|
state->vlans[vno].port_mode |= mode << (pno * 4); |
state->vlans[vno].port_sstate |= |
MV_STUCTL_STATE_FORWARDING << (pno * 4 + 2); |
} |
|
/* |
* DISCARD is nonzero, so it must be explicitly |
* set on ports not in the VLAN. |
*/ |
for (i = 0; i < dev->ports; i++) |
if (!(state->vlans[vno].mask & (1 << i))) |
state->vlans[vno].port_mode |= |
MV_VTUCTL_DISCARD << (i * 4); |
|
return 0; |
} |
|
static int mvsw61xx_get_vlan_port_based(struct switch_dev *dev, |
const struct switch_attr *attr, struct switch_val *val) |
{ |
struct mvsw61xx_state *state = get_state(dev); |
int vno = val->port_vlan; |
|
if (vno <= 0 || vno >= dev->vlans) |
return -EINVAL; |
|
if (state->vlans[vno].port_based) |
val->value.i = 1; |
else |
val->value.i = 0; |
|
return 0; |
} |
|
static int mvsw61xx_set_vlan_port_based(struct switch_dev *dev, |
const struct switch_attr *attr, struct switch_val *val) |
{ |
struct mvsw61xx_state *state = get_state(dev); |
int vno = val->port_vlan; |
|
if (vno <= 0 || vno >= dev->vlans) |
return -EINVAL; |
|
if (val->value.i == 1) |
state->vlans[vno].port_based = true; |
else |
state->vlans[vno].port_based = false; |
|
return 0; |
} |
|
static int mvsw61xx_get_vid(struct switch_dev *dev, |
const struct switch_attr *attr, struct switch_val *val) |
{ |
struct mvsw61xx_state *state = get_state(dev); |
int vno = val->port_vlan; |
|
if (vno <= 0 || vno >= dev->vlans) |
return -EINVAL; |
|
val->value.i = state->vlans[vno].vid; |
|
return 0; |
} |
|
static int mvsw61xx_set_vid(struct switch_dev *dev, |
const struct switch_attr *attr, struct switch_val *val) |
{ |
struct mvsw61xx_state *state = get_state(dev); |
int vno = val->port_vlan; |
|
if (vno <= 0 || vno >= dev->vlans) |
return -EINVAL; |
|
state->vlans[vno].vid = val->value.i; |
|
return 0; |
} |
|
static int mvsw61xx_get_enable_vlan(struct switch_dev *dev, |
const struct switch_attr *attr, struct switch_val *val) |
{ |
struct mvsw61xx_state *state = get_state(dev); |
|
val->value.i = state->vlan_enabled; |
|
return 0; |
} |
|
static int mvsw61xx_set_enable_vlan(struct switch_dev *dev, |
const struct switch_attr *attr, struct switch_val *val) |
{ |
struct mvsw61xx_state *state = get_state(dev); |
|
state->vlan_enabled = val->value.i; |
|
return 0; |
} |
|
static int mvsw61xx_vtu_program(struct switch_dev *dev) |
{ |
struct mvsw61xx_state *state = get_state(dev); |
u16 v1, v2, s1, s2; |
int i; |
|
/* Flush */ |
mvsw61xx_wait_mask_s(dev, MV_GLOBALREG(VTU_OP), |
MV_VTUOP_INPROGRESS, 0); |
sw16(dev, MV_GLOBALREG(VTU_OP), |
MV_VTUOP_INPROGRESS | MV_VTUOP_PURGE); |
|
/* Write VLAN table */ |
for (i = 1; i < dev->vlans; i++) { |
if (state->vlans[i].mask == 0 || |
state->vlans[i].vid == 0 || |
state->vlans[i].port_based == true) |
continue; |
|
mvsw61xx_wait_mask_s(dev, MV_GLOBALREG(VTU_OP), |
MV_VTUOP_INPROGRESS, 0); |
|
/* Write per-VLAN port state into STU */ |
s1 = (u16) (state->vlans[i].port_sstate & 0xffff); |
s2 = (u16) ((state->vlans[i].port_sstate >> 16) & 0xffff); |
|
sw16(dev, MV_GLOBALREG(VTU_VID), MV_VTU_VID_VALID); |
sw16(dev, MV_GLOBALREG(VTU_SID), i); |
sw16(dev, MV_GLOBALREG(VTU_DATA1), s1); |
sw16(dev, MV_GLOBALREG(VTU_DATA2), s2); |
sw16(dev, MV_GLOBALREG(VTU_DATA3), 0); |
|
sw16(dev, MV_GLOBALREG(VTU_OP), |
MV_VTUOP_INPROGRESS | MV_VTUOP_STULOAD); |
mvsw61xx_wait_mask_s(dev, MV_GLOBALREG(VTU_OP), |
MV_VTUOP_INPROGRESS, 0); |
|
/* Write VLAN information into VTU */ |
v1 = (u16) (state->vlans[i].port_mode & 0xffff); |
v2 = (u16) ((state->vlans[i].port_mode >> 16) & 0xffff); |
|
sw16(dev, MV_GLOBALREG(VTU_VID), |
MV_VTU_VID_VALID | state->vlans[i].vid); |
sw16(dev, MV_GLOBALREG(VTU_SID), i); |
sw16(dev, MV_GLOBALREG(VTU_FID), i); |
sw16(dev, MV_GLOBALREG(VTU_DATA1), v1); |
sw16(dev, MV_GLOBALREG(VTU_DATA2), v2); |
sw16(dev, MV_GLOBALREG(VTU_DATA3), 0); |
|
sw16(dev, MV_GLOBALREG(VTU_OP), |
MV_VTUOP_INPROGRESS | MV_VTUOP_LOAD); |
mvsw61xx_wait_mask_s(dev, MV_GLOBALREG(VTU_OP), |
MV_VTUOP_INPROGRESS, 0); |
} |
|
return 0; |
} |
|
static void mvsw61xx_vlan_port_config(struct switch_dev *dev, int vno) |
{ |
struct mvsw61xx_state *state = get_state(dev); |
int i, mode; |
|
for (i = 0; i < dev->ports; i++) { |
if (!(state->vlans[vno].mask & (1 << i))) |
continue; |
|
mode = (state->vlans[vno].port_mode >> (i * 4)) & 0xf; |
|
if(mode != MV_VTUCTL_EGRESS_TAGGED) |
state->ports[i].pvid = state->vlans[vno].vid; |
|
if (state->vlans[vno].port_based) { |
state->ports[i].mask |= state->vlans[vno].mask; |
state->ports[i].fdb = vno; |
} |
else |
state->ports[i].qmode = MV_8021Q_MODE_SECURE; |
} |
} |
|
static int mvsw61xx_update_state(struct switch_dev *dev) |
{ |
struct mvsw61xx_state *state = get_state(dev); |
int i; |
u16 reg; |
|
if (!state->registered) |
return -EINVAL; |
|
/* |
* Set 802.1q-only mode if vlan_enabled is true. |
* |
* Without this, even if 802.1q is enabled for |
* a port/VLAN, it still depends on the port-based |
* VLAN mask being set. |
* |
* With this setting, port-based VLANs are still |
* functional, provided the VID is not in the VTU. |
*/ |
reg = sr16(dev, MV_GLOBAL2REG(SDET_POLARITY)); |
|
if (state->vlan_enabled) |
reg |= MV_8021Q_VLAN_ONLY; |
else |
reg &= ~MV_8021Q_VLAN_ONLY; |
|
sw16(dev, MV_GLOBAL2REG(SDET_POLARITY), reg); |
|
/* |
* Set port-based VLAN masks on each port |
* based only on VLAN definitions known to |
* the driver (i.e. in state). |
* |
* This means any pre-existing port mapping is |
* wiped out once our driver is initialized. |
*/ |
for (i = 0; i < dev->ports; i++) { |
state->ports[i].mask = 0; |
state->ports[i].qmode = MV_8021Q_MODE_DISABLE; |
} |
|
for (i = 0; i < dev->vlans; i++) |
mvsw61xx_vlan_port_config(dev, i); |
|
for (i = 0; i < dev->ports; i++) { |
reg = sr16(dev, MV_PORTREG(VLANID, i)) & ~MV_PVID_MASK; |
reg |= state->ports[i].pvid; |
sw16(dev, MV_PORTREG(VLANID, i), reg); |
|
state->ports[i].mask &= ~(1 << i); |
|
/* set default forwarding DB number and port mask */ |
reg = sr16(dev, MV_PORTREG(CONTROL1, i)) & ~MV_FDB_HI_MASK; |
reg |= (state->ports[i].fdb >> MV_FDB_HI_SHIFT) & |
MV_FDB_HI_MASK; |
sw16(dev, MV_PORTREG(CONTROL1, i), reg); |
|
reg = ((state->ports[i].fdb & 0xf) << MV_FDB_LO_SHIFT) | |
state->ports[i].mask; |
sw16(dev, MV_PORTREG(VLANMAP, i), reg); |
|
reg = sr16(dev, MV_PORTREG(CONTROL2, i)) & |
~MV_8021Q_MODE_MASK; |
reg |= state->ports[i].qmode << MV_8021Q_MODE_SHIFT; |
sw16(dev, MV_PORTREG(CONTROL2, i), reg); |
} |
|
mvsw61xx_vtu_program(dev); |
|
return 0; |
} |
|
static int mvsw61xx_apply(struct switch_dev *dev) |
{ |
return mvsw61xx_update_state(dev); |
} |
|
static void mvsw61xx_enable_serdes(struct switch_dev *dev) |
{ |
int bmcr = mvsw61xx_mdio_page_read(dev, MV_REG_FIBER_SERDES, |
MV_PAGE_FIBER_SERDES, MII_BMCR); |
if (bmcr < 0) |
return; |
|
if (bmcr & BMCR_PDOWN) |
mvsw61xx_mdio_page_write(dev, MV_REG_FIBER_SERDES, |
MV_PAGE_FIBER_SERDES, MII_BMCR, |
bmcr & ~BMCR_PDOWN); |
} |
|
static int _mvsw61xx_reset(struct switch_dev *dev, bool full) |
{ |
struct mvsw61xx_state *state = get_state(dev); |
int i; |
u16 reg; |
|
/* Disable all ports before reset */ |
for (i = 0; i < dev->ports; i++) { |
reg = sr16(dev, MV_PORTREG(CONTROL, i)) & |
~MV_PORTCTRL_FORWARDING; |
sw16(dev, MV_PORTREG(CONTROL, i), reg); |
} |
|
reg = sr16(dev, MV_GLOBALREG(CONTROL)) | MV_CONTROL_RESET; |
|
sw16(dev, MV_GLOBALREG(CONTROL), reg); |
if (mvsw61xx_wait_mask_s(dev, MV_GLOBALREG(CONTROL), |
MV_CONTROL_RESET, 0) < 0) |
return -ETIMEDOUT; |
|
for (i = 0; i < dev->ports; i++) { |
state->ports[i].fdb = 0; |
state->ports[i].qmode = 0; |
state->ports[i].mask = 0; |
state->ports[i].pvid = 0; |
|
/* Force flow control off */ |
reg = sr16(dev, MV_PORTREG(PHYCTL, i)) & ~MV_PHYCTL_FC_MASK; |
reg |= MV_PHYCTL_FC_DISABLE; |
sw16(dev, MV_PORTREG(PHYCTL, i), reg); |
|
/* Set port association vector */ |
sw16(dev, MV_PORTREG(ASSOC, i), (1 << i)); |
|
/* power up phys */ |
if (full && i < 5) { |
mvsw61xx_mdio_write(dev, i, MII_MV_SPEC_CTRL, |
MV_SPEC_MDI_CROSS_AUTO | |
MV_SPEC_ENERGY_DETECT | |
MV_SPEC_DOWNSHIFT_COUNTER); |
mvsw61xx_mdio_write(dev, i, MII_BMCR, BMCR_RESET | |
BMCR_ANENABLE | BMCR_FULLDPLX | |
BMCR_SPEED1000); |
} |
|
/* enable SerDes if necessary */ |
if (full && i >= 5 && state->model == MV_IDENT_VALUE_6176) { |
u16 sts = sr16(dev, MV_PORTREG(STATUS, i)); |
u16 mode = sts & MV_PORT_STATUS_CMODE_MASK; |
|
if (mode == MV_PORT_STATUS_CMODE_100BASE_X || |
mode == MV_PORT_STATUS_CMODE_1000BASE_X || |
mode == MV_PORT_STATUS_CMODE_SGMII) { |
mvsw61xx_enable_serdes(dev); |
} |
} |
} |
|
for (i = 0; i < dev->vlans; i++) { |
state->vlans[i].port_based = false; |
state->vlans[i].mask = 0; |
state->vlans[i].vid = 0; |
state->vlans[i].port_mode = 0; |
state->vlans[i].port_sstate = 0; |
} |
|
state->vlan_enabled = 0; |
|
mvsw61xx_update_state(dev); |
|
/* Re-enable ports */ |
for (i = 0; i < dev->ports; i++) { |
reg = sr16(dev, MV_PORTREG(CONTROL, i)) | |
MV_PORTCTRL_FORWARDING; |
sw16(dev, MV_PORTREG(CONTROL, i), reg); |
} |
|
return 0; |
} |
|
static int mvsw61xx_reset(struct switch_dev *dev) |
{ |
return _mvsw61xx_reset(dev, false); |
} |
|
enum { |
MVSW61XX_ENABLE_VLAN, |
}; |
|
enum { |
MVSW61XX_VLAN_PORT_BASED, |
MVSW61XX_VLAN_ID, |
}; |
|
enum { |
MVSW61XX_PORT_MASK, |
MVSW61XX_PORT_QMODE, |
}; |
|
static const struct switch_attr mvsw61xx_global[] = { |
[MVSW61XX_ENABLE_VLAN] = { |
.id = MVSW61XX_ENABLE_VLAN, |
.type = SWITCH_TYPE_INT, |
.name = "enable_vlan", |
.description = "Enable 802.1q VLAN support", |
.get = mvsw61xx_get_enable_vlan, |
.set = mvsw61xx_set_enable_vlan, |
}, |
}; |
|
static const struct switch_attr mvsw61xx_vlan[] = { |
[MVSW61XX_VLAN_PORT_BASED] = { |
.id = MVSW61XX_VLAN_PORT_BASED, |
.type = SWITCH_TYPE_INT, |
.name = "port_based", |
.description = "Use port-based (non-802.1q) VLAN only", |
.get = mvsw61xx_get_vlan_port_based, |
.set = mvsw61xx_set_vlan_port_based, |
}, |
[MVSW61XX_VLAN_ID] = { |
.id = MVSW61XX_VLAN_ID, |
.type = SWITCH_TYPE_INT, |
.name = "vid", |
.description = "Get/set VLAN ID", |
.get = mvsw61xx_get_vid, |
.set = mvsw61xx_set_vid, |
}, |
}; |
|
static const struct switch_attr mvsw61xx_port[] = { |
[MVSW61XX_PORT_MASK] = { |
.id = MVSW61XX_PORT_MASK, |
.type = SWITCH_TYPE_STRING, |
.description = "Port-based VLAN mask", |
.name = "mask", |
.get = mvsw61xx_get_port_mask, |
.set = NULL, |
}, |
[MVSW61XX_PORT_QMODE] = { |
.id = MVSW61XX_PORT_QMODE, |
.type = SWITCH_TYPE_INT, |
.description = "802.1q mode: 0=off/1=fallback/2=check/3=secure", |
.name = "qmode", |
.get = mvsw61xx_get_port_qmode, |
.set = mvsw61xx_set_port_qmode, |
}, |
}; |
|
static const struct switch_dev_ops mvsw61xx_ops = { |
.attr_global = { |
.attr = mvsw61xx_global, |
.n_attr = ARRAY_SIZE(mvsw61xx_global), |
}, |
.attr_vlan = { |
.attr = mvsw61xx_vlan, |
.n_attr = ARRAY_SIZE(mvsw61xx_vlan), |
}, |
.attr_port = { |
.attr = mvsw61xx_port, |
.n_attr = ARRAY_SIZE(mvsw61xx_port), |
}, |
.get_port_link = mvsw61xx_get_port_link, |
.get_port_pvid = mvsw61xx_get_port_pvid, |
.set_port_pvid = mvsw61xx_set_port_pvid, |
.get_vlan_ports = mvsw61xx_get_vlan_ports, |
.set_vlan_ports = mvsw61xx_set_vlan_ports, |
.apply_config = mvsw61xx_apply, |
.reset_switch = mvsw61xx_reset, |
}; |
|
/* end swconfig stuff */ |
|
static int mvsw61xx_probe(struct platform_device *pdev) |
{ |
struct mvsw61xx_state *state; |
struct device_node *np = pdev->dev.of_node; |
struct device_node *mdio; |
char *model_str; |
u32 val; |
int err; |
|
state = kzalloc(sizeof(*state), GFP_KERNEL); |
if (!state) |
return -ENOMEM; |
|
mdio = of_parse_phandle(np, "mii-bus", 0); |
if (!mdio) { |
dev_err(&pdev->dev, "Couldn't get MII bus handle\n"); |
err = -ENODEV; |
goto out_err; |
} |
|
state->bus = of_mdio_find_bus(mdio); |
if (!state->bus) { |
dev_err(&pdev->dev, "Couldn't find MII bus from handle\n"); |
err = -ENODEV; |
goto out_err; |
} |
|
state->is_indirect = of_property_read_bool(np, "is-indirect"); |
|
if (state->is_indirect) { |
if (of_property_read_u32(np, "reg", &val)) { |
dev_err(&pdev->dev, "Switch address not specified\n"); |
err = -ENODEV; |
goto out_err; |
} |
|
state->base_addr = val; |
} else { |
state->base_addr = MV_BASE; |
} |
|
state->model = r16(state->bus, state->is_indirect, state->base_addr, |
MV_PORTREG(IDENT, 0)) & MV_IDENT_MASK; |
|
switch(state->model) { |
case MV_IDENT_VALUE_6171: |
model_str = MV_IDENT_STR_6171; |
break; |
case MV_IDENT_VALUE_6172: |
model_str = MV_IDENT_STR_6172; |
break; |
case MV_IDENT_VALUE_6176: |
model_str = MV_IDENT_STR_6176; |
break; |
case MV_IDENT_VALUE_6352: |
model_str = MV_IDENT_STR_6352; |
break; |
default: |
dev_err(&pdev->dev, "No compatible switch found at 0x%02x\n", |
state->base_addr); |
err = -ENODEV; |
goto out_err; |
} |
|
platform_set_drvdata(pdev, state); |
dev_info(&pdev->dev, "Found %s at %s:%02x\n", model_str, |
state->bus->id, state->base_addr); |
|
dev_info(&pdev->dev, "Using %sdirect addressing\n", |
(state->is_indirect ? "in" : "")); |
|
if (of_property_read_u32(np, "cpu-port-0", &val)) { |
dev_err(&pdev->dev, "CPU port not set\n"); |
err = -ENODEV; |
goto out_err; |
} |
|
state->cpu_port0 = val; |
|
if (!of_property_read_u32(np, "cpu-port-1", &val)) |
state->cpu_port1 = val; |
else |
state->cpu_port1 = -1; |
|
state->dev.vlans = MV_VLANS; |
state->dev.cpu_port = state->cpu_port0; |
state->dev.ports = MV_PORTS; |
state->dev.name = model_str; |
state->dev.ops = &mvsw61xx_ops; |
state->dev.alias = dev_name(&pdev->dev); |
|
_mvsw61xx_reset(&state->dev, true); |
|
err = register_switch(&state->dev, NULL); |
if (err < 0) |
goto out_err; |
|
state->registered = true; |
|
return 0; |
out_err: |
kfree(state); |
return err; |
} |
|
static int |
mvsw61xx_remove(struct platform_device *pdev) |
{ |
struct mvsw61xx_state *state = platform_get_drvdata(pdev); |
|
if (state->registered) |
unregister_switch(&state->dev); |
|
kfree(state); |
|
return 0; |
} |
|
static const struct of_device_id mvsw61xx_match[] = { |
{ .compatible = "marvell,88e6171" }, |
{ .compatible = "marvell,88e6172" }, |
{ .compatible = "marvell,88e6176" }, |
{ .compatible = "marvell,88e6352" }, |
{ } |
}; |
MODULE_DEVICE_TABLE(of, mvsw61xx_match); |
|
static struct platform_driver mvsw61xx_driver = { |
.probe = mvsw61xx_probe, |
.remove = mvsw61xx_remove, |
.driver = { |
.name = "mvsw61xx", |
.of_match_table = of_match_ptr(mvsw61xx_match), |
.owner = THIS_MODULE, |
}, |
}; |
|
static int __init mvsw61xx_module_init(void) |
{ |
return platform_driver_register(&mvsw61xx_driver); |
} |
late_initcall(mvsw61xx_module_init); |
|
static void __exit mvsw61xx_module_exit(void) |
{ |
platform_driver_unregister(&mvsw61xx_driver); |
} |
module_exit(mvsw61xx_module_exit); |
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/rtl8306.c |
@@ -0,0 +1,1066 @@ |
/* |
* rtl8306.c: RTL8306S switch driver |
* |
* Copyright (C) 2009 Felix Fietkau <nbd@nbd.name> |
* |
* This program is free software; you can redistribute it and/or |
* modify it under the terms of the GNU General Public License |
* version 2 as published by the Free Software Foundation. |
* |
* 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. |
*/ |
|
#include <linux/if.h> |
#include <linux/module.h> |
#include <linux/init.h> |
#include <linux/list.h> |
#include <linux/if_ether.h> |
#include <linux/skbuff.h> |
#include <linux/netdevice.h> |
#include <linux/netlink.h> |
#include <net/genetlink.h> |
#include <linux/switch.h> |
#include <linux/delay.h> |
#include <linux/phy.h> |
#include <linux/version.h> |
|
//#define DEBUG 1 |
|
/* Global (PHY0) */ |
#define RTL8306_REG_PAGE 16 |
#define RTL8306_REG_PAGE_LO (1 << 15) |
#define RTL8306_REG_PAGE_HI (1 << 1) /* inverted */ |
|
#define RTL8306_NUM_VLANS 16 |
#define RTL8306_NUM_PORTS 6 |
#define RTL8306_PORT_CPU 5 |
#define RTL8306_NUM_PAGES 4 |
#define RTL8306_NUM_REGS 32 |
|
#define RTL_NAME_S "RTL8306S" |
#define RTL_NAME_SD "RTL8306SD" |
#define RTL_NAME_SDM "RTL8306SDM" |
#define RTL_NAME_UNKNOWN "RTL8306(unknown)" |
|
#define RTL8306_MAGIC 0x8306 |
|
static LIST_HEAD(phydevs); |
|
struct rtl_priv { |
struct list_head list; |
struct switch_dev dev; |
int page; |
int type; |
int do_cpu; |
struct mii_bus *bus; |
char hwname[sizeof(RTL_NAME_UNKNOWN)]; |
bool fixup; |
}; |
|
struct rtl_phyregs { |
int nway; |
int speed; |
int duplex; |
}; |
|
#define to_rtl(_dev) container_of(_dev, struct rtl_priv, dev) |
|
enum { |
RTL_TYPE_S, |
RTL_TYPE_SD, |
RTL_TYPE_SDM, |
}; |
|
struct rtl_reg { |
int page; |
int phy; |
int reg; |
int bits; |
int shift; |
int inverted; |
}; |
|
#define RTL_VLAN_REGOFS(name) \ |
(RTL_REG_VLAN1_##name - RTL_REG_VLAN0_##name) |
|
#define RTL_PORT_REGOFS(name) \ |
(RTL_REG_PORT1_##name - RTL_REG_PORT0_##name) |
|
#define RTL_PORT_REG(id, reg) \ |
(RTL_REG_PORT0_##reg + (id * RTL_PORT_REGOFS(reg))) |
|
#define RTL_VLAN_REG(id, reg) \ |
(RTL_REG_VLAN0_##reg + (id * RTL_VLAN_REGOFS(reg))) |
|
#define RTL_GLOBAL_REGATTR(reg) \ |
.id = RTL_REG_##reg, \ |
.type = SWITCH_TYPE_INT, \ |
.ofs = 0, \ |
.set = rtl_attr_set_int, \ |
.get = rtl_attr_get_int |
|
#define RTL_PORT_REGATTR(reg) \ |
.id = RTL_REG_PORT0_##reg, \ |
.type = SWITCH_TYPE_INT, \ |
.ofs = RTL_PORT_REGOFS(reg), \ |
.set = rtl_attr_set_port_int, \ |
.get = rtl_attr_get_port_int |
|
#define RTL_VLAN_REGATTR(reg) \ |
.id = RTL_REG_VLAN0_##reg, \ |
.type = SWITCH_TYPE_INT, \ |
.ofs = RTL_VLAN_REGOFS(reg), \ |
.set = rtl_attr_set_vlan_int, \ |
.get = rtl_attr_get_vlan_int |
|
enum rtl_regidx { |
RTL_REG_CHIPID, |
RTL_REG_CHIPVER, |
RTL_REG_CHIPTYPE, |
RTL_REG_CPUPORT, |
|
RTL_REG_EN_CPUPORT, |
RTL_REG_EN_TAG_OUT, |
RTL_REG_EN_TAG_CLR, |
RTL_REG_EN_TAG_IN, |
RTL_REG_TRAP_CPU, |
RTL_REG_CPU_LINKUP, |
RTL_REG_TRUNK_PORTSEL, |
RTL_REG_EN_TRUNK, |
RTL_REG_RESET, |
|
RTL_REG_VLAN_ENABLE, |
RTL_REG_VLAN_FILTER, |
RTL_REG_VLAN_TAG_ONLY, |
RTL_REG_VLAN_TAG_AWARE, |
#define RTL_VLAN_ENUM(id) \ |
RTL_REG_VLAN##id##_VID, \ |
RTL_REG_VLAN##id##_PORTMASK |
RTL_VLAN_ENUM(0), |
RTL_VLAN_ENUM(1), |
RTL_VLAN_ENUM(2), |
RTL_VLAN_ENUM(3), |
RTL_VLAN_ENUM(4), |
RTL_VLAN_ENUM(5), |
RTL_VLAN_ENUM(6), |
RTL_VLAN_ENUM(7), |
RTL_VLAN_ENUM(8), |
RTL_VLAN_ENUM(9), |
RTL_VLAN_ENUM(10), |
RTL_VLAN_ENUM(11), |
RTL_VLAN_ENUM(12), |
RTL_VLAN_ENUM(13), |
RTL_VLAN_ENUM(14), |
RTL_VLAN_ENUM(15), |
#define RTL_PORT_ENUM(id) \ |
RTL_REG_PORT##id##_PVID, \ |
RTL_REG_PORT##id##_NULL_VID_REPLACE, \ |
RTL_REG_PORT##id##_NON_PVID_DISCARD, \ |
RTL_REG_PORT##id##_VID_INSERT, \ |
RTL_REG_PORT##id##_TAG_INSERT, \ |
RTL_REG_PORT##id##_LINK, \ |
RTL_REG_PORT##id##_SPEED, \ |
RTL_REG_PORT##id##_NWAY, \ |
RTL_REG_PORT##id##_NRESTART, \ |
RTL_REG_PORT##id##_DUPLEX, \ |
RTL_REG_PORT##id##_RXEN, \ |
RTL_REG_PORT##id##_TXEN |
RTL_PORT_ENUM(0), |
RTL_PORT_ENUM(1), |
RTL_PORT_ENUM(2), |
RTL_PORT_ENUM(3), |
RTL_PORT_ENUM(4), |
RTL_PORT_ENUM(5), |
}; |
|
static const struct rtl_reg rtl_regs[] = { |
[RTL_REG_CHIPID] = { 0, 4, 30, 16, 0, 0 }, |
[RTL_REG_CHIPVER] = { 0, 4, 31, 8, 0, 0 }, |
[RTL_REG_CHIPTYPE] = { 0, 4, 31, 2, 8, 0 }, |
|
/* CPU port number */ |
[RTL_REG_CPUPORT] = { 2, 4, 21, 3, 0, 0 }, |
/* Enable CPU port function */ |
[RTL_REG_EN_CPUPORT] = { 3, 2, 21, 1, 15, 1 }, |
/* Enable CPU port tag insertion */ |
[RTL_REG_EN_TAG_OUT] = { 3, 2, 21, 1, 12, 0 }, |
/* Enable CPU port tag removal */ |
[RTL_REG_EN_TAG_CLR] = { 3, 2, 21, 1, 11, 0 }, |
/* Enable CPU port tag checking */ |
[RTL_REG_EN_TAG_IN] = { 0, 4, 21, 1, 7, 0 }, |
[RTL_REG_EN_TRUNK] = { 0, 0, 19, 1, 11, 1 }, |
[RTL_REG_TRUNK_PORTSEL] = { 0, 0, 16, 1, 6, 1 }, |
[RTL_REG_RESET] = { 0, 0, 16, 1, 12, 0 }, |
|
[RTL_REG_TRAP_CPU] = { 3, 2, 22, 1, 6, 0 }, |
[RTL_REG_CPU_LINKUP] = { 0, 6, 22, 1, 15, 0 }, |
|
[RTL_REG_VLAN_TAG_ONLY] = { 0, 0, 16, 1, 8, 1 }, |
[RTL_REG_VLAN_FILTER] = { 0, 0, 16, 1, 9, 1 }, |
[RTL_REG_VLAN_TAG_AWARE] = { 0, 0, 16, 1, 10, 1 }, |
[RTL_REG_VLAN_ENABLE] = { 0, 0, 18, 1, 8, 1 }, |
|
#define RTL_VLAN_REGS(id, phy, page, regofs) \ |
[RTL_REG_VLAN##id##_VID] = { page, phy, 25 + regofs, 12, 0, 0 }, \ |
[RTL_REG_VLAN##id##_PORTMASK] = { page, phy, 24 + regofs, 6, 0, 0 } |
RTL_VLAN_REGS( 0, 0, 0, 0), |
RTL_VLAN_REGS( 1, 1, 0, 0), |
RTL_VLAN_REGS( 2, 2, 0, 0), |
RTL_VLAN_REGS( 3, 3, 0, 0), |
RTL_VLAN_REGS( 4, 4, 0, 0), |
RTL_VLAN_REGS( 5, 0, 1, 2), |
RTL_VLAN_REGS( 6, 1, 1, 2), |
RTL_VLAN_REGS( 7, 2, 1, 2), |
RTL_VLAN_REGS( 8, 3, 1, 2), |
RTL_VLAN_REGS( 9, 4, 1, 2), |
RTL_VLAN_REGS(10, 0, 1, 4), |
RTL_VLAN_REGS(11, 1, 1, 4), |
RTL_VLAN_REGS(12, 2, 1, 4), |
RTL_VLAN_REGS(13, 3, 1, 4), |
RTL_VLAN_REGS(14, 4, 1, 4), |
RTL_VLAN_REGS(15, 0, 1, 6), |
|
#define REG_PORT_SETTING(port, phy) \ |
[RTL_REG_PORT##port##_SPEED] = { 0, phy, 0, 1, 13, 0 }, \ |
[RTL_REG_PORT##port##_NWAY] = { 0, phy, 0, 1, 12, 0 }, \ |
[RTL_REG_PORT##port##_NRESTART] = { 0, phy, 0, 1, 9, 0 }, \ |
[RTL_REG_PORT##port##_DUPLEX] = { 0, phy, 0, 1, 8, 0 }, \ |
[RTL_REG_PORT##port##_TXEN] = { 0, phy, 24, 1, 11, 0 }, \ |
[RTL_REG_PORT##port##_RXEN] = { 0, phy, 24, 1, 10, 0 }, \ |
[RTL_REG_PORT##port##_LINK] = { 0, phy, 1, 1, 2, 0 }, \ |
[RTL_REG_PORT##port##_NULL_VID_REPLACE] = { 0, phy, 22, 1, 12, 0 }, \ |
[RTL_REG_PORT##port##_NON_PVID_DISCARD] = { 0, phy, 22, 1, 11, 0 }, \ |
[RTL_REG_PORT##port##_VID_INSERT] = { 0, phy, 22, 2, 9, 0 }, \ |
[RTL_REG_PORT##port##_TAG_INSERT] = { 0, phy, 22, 2, 0, 0 } |
|
REG_PORT_SETTING(0, 0), |
REG_PORT_SETTING(1, 1), |
REG_PORT_SETTING(2, 2), |
REG_PORT_SETTING(3, 3), |
REG_PORT_SETTING(4, 4), |
REG_PORT_SETTING(5, 6), |
|
#define REG_PORT_PVID(phy, page, regofs) \ |
{ page, phy, 24 + regofs, 4, 12, 0 } |
[RTL_REG_PORT0_PVID] = REG_PORT_PVID(0, 0, 0), |
[RTL_REG_PORT1_PVID] = REG_PORT_PVID(1, 0, 0), |
[RTL_REG_PORT2_PVID] = REG_PORT_PVID(2, 0, 0), |
[RTL_REG_PORT3_PVID] = REG_PORT_PVID(3, 0, 0), |
[RTL_REG_PORT4_PVID] = REG_PORT_PVID(4, 0, 0), |
[RTL_REG_PORT5_PVID] = REG_PORT_PVID(0, 1, 2), |
}; |
|
|
static inline void |
rtl_set_page(struct rtl_priv *priv, unsigned int page) |
{ |
struct mii_bus *bus = priv->bus; |
u16 pgsel; |
|
if (priv->fixup) |
return; |
|
if (priv->page == page) |
return; |
|
BUG_ON(page > RTL8306_NUM_PAGES); |
pgsel = bus->read(bus, 0, RTL8306_REG_PAGE); |
pgsel &= ~(RTL8306_REG_PAGE_LO | RTL8306_REG_PAGE_HI); |
if (page & (1 << 0)) |
pgsel |= RTL8306_REG_PAGE_LO; |
if (!(page & (1 << 1))) /* bit is inverted */ |
pgsel |= RTL8306_REG_PAGE_HI; |
bus->write(bus, 0, RTL8306_REG_PAGE, pgsel); |
} |
|
static inline int |
rtl_w16(struct switch_dev *dev, unsigned int page, unsigned int phy, unsigned int reg, u16 val) |
{ |
struct rtl_priv *priv = to_rtl(dev); |
struct mii_bus *bus = priv->bus; |
|
rtl_set_page(priv, page); |
bus->write(bus, phy, reg, val); |
bus->read(bus, phy, reg); /* flush */ |
return 0; |
} |
|
static inline int |
rtl_r16(struct switch_dev *dev, unsigned int page, unsigned int phy, unsigned int reg) |
{ |
struct rtl_priv *priv = to_rtl(dev); |
struct mii_bus *bus = priv->bus; |
|
rtl_set_page(priv, page); |
return bus->read(bus, phy, reg); |
} |
|
static inline u16 |
rtl_rmw(struct switch_dev *dev, unsigned int page, unsigned int phy, unsigned int reg, u16 mask, u16 val) |
{ |
struct rtl_priv *priv = to_rtl(dev); |
struct mii_bus *bus = priv->bus; |
u16 r; |
|
rtl_set_page(priv, page); |
r = bus->read(bus, phy, reg); |
r &= ~mask; |
r |= val; |
bus->write(bus, phy, reg, r); |
return bus->read(bus, phy, reg); /* flush */ |
} |
|
|
static inline int |
rtl_get(struct switch_dev *dev, enum rtl_regidx s) |
{ |
const struct rtl_reg *r = &rtl_regs[s]; |
u16 val; |
|
BUG_ON(s >= ARRAY_SIZE(rtl_regs)); |
if (r->bits == 0) /* unimplemented */ |
return 0; |
|
val = rtl_r16(dev, r->page, r->phy, r->reg); |
|
if (r->shift > 0) |
val >>= r->shift; |
|
if (r->inverted) |
val = ~val; |
|
val &= (1 << r->bits) - 1; |
|
return val; |
} |
|
static int |
rtl_set(struct switch_dev *dev, enum rtl_regidx s, unsigned int val) |
{ |
const struct rtl_reg *r = &rtl_regs[s]; |
u16 mask = 0xffff; |
|
BUG_ON(s >= ARRAY_SIZE(rtl_regs)); |
|
if (r->bits == 0) /* unimplemented */ |
return 0; |
|
if (r->shift > 0) |
val <<= r->shift; |
|
if (r->inverted) |
val = ~val; |
|
if (r->bits != 16) { |
mask = (1 << r->bits) - 1; |
mask <<= r->shift; |
} |
val &= mask; |
return rtl_rmw(dev, r->page, r->phy, r->reg, mask, val); |
} |
|
static void |
rtl_phy_save(struct switch_dev *dev, int port, struct rtl_phyregs *regs) |
{ |
regs->nway = rtl_get(dev, RTL_PORT_REG(port, NWAY)); |
regs->speed = rtl_get(dev, RTL_PORT_REG(port, SPEED)); |
regs->duplex = rtl_get(dev, RTL_PORT_REG(port, DUPLEX)); |
} |
|
static void |
rtl_phy_restore(struct switch_dev *dev, int port, struct rtl_phyregs *regs) |
{ |
rtl_set(dev, RTL_PORT_REG(port, NWAY), regs->nway); |
rtl_set(dev, RTL_PORT_REG(port, SPEED), regs->speed); |
rtl_set(dev, RTL_PORT_REG(port, DUPLEX), regs->duplex); |
} |
|
static void |
rtl_port_set_enable(struct switch_dev *dev, int port, int enabled) |
{ |
rtl_set(dev, RTL_PORT_REG(port, RXEN), enabled); |
rtl_set(dev, RTL_PORT_REG(port, TXEN), enabled); |
|
if ((port >= 5) || !enabled) |
return; |
|
/* restart autonegotiation if enabled */ |
rtl_set(dev, RTL_PORT_REG(port, NRESTART), 1); |
} |
|
static int |
rtl_hw_apply(struct switch_dev *dev) |
{ |
int i; |
int trunk_en, trunk_psel; |
struct rtl_phyregs port5; |
|
rtl_phy_save(dev, 5, &port5); |
|
/* disable rx/tx from PHYs */ |
for (i = 0; i < RTL8306_NUM_PORTS - 1; i++) { |
rtl_port_set_enable(dev, i, 0); |
} |
|
/* save trunking status */ |
trunk_en = rtl_get(dev, RTL_REG_EN_TRUNK); |
trunk_psel = rtl_get(dev, RTL_REG_TRUNK_PORTSEL); |
|
/* trunk port 3 and 4 |
* XXX: Big WTF, but RealTek seems to do it */ |
rtl_set(dev, RTL_REG_EN_TRUNK, 1); |
rtl_set(dev, RTL_REG_TRUNK_PORTSEL, 1); |
|
/* execute the software reset */ |
rtl_set(dev, RTL_REG_RESET, 1); |
|
/* wait for the reset to complete, |
* but don't wait for too long */ |
for (i = 0; i < 10; i++) { |
if (rtl_get(dev, RTL_REG_RESET) == 0) |
break; |
|
msleep(1); |
} |
|
/* enable rx/tx from PHYs */ |
for (i = 0; i < RTL8306_NUM_PORTS - 1; i++) { |
rtl_port_set_enable(dev, i, 1); |
} |
|
/* restore trunking settings */ |
rtl_set(dev, RTL_REG_EN_TRUNK, trunk_en); |
rtl_set(dev, RTL_REG_TRUNK_PORTSEL, trunk_psel); |
rtl_phy_restore(dev, 5, &port5); |
|
rtl_set(dev, RTL_REG_CPU_LINKUP, 1); |
|
return 0; |
} |
|
static void |
rtl_hw_init(struct switch_dev *dev) |
{ |
struct rtl_priv *priv = to_rtl(dev); |
int cpu_mask = 1 << dev->cpu_port; |
int i; |
|
rtl_set(dev, RTL_REG_VLAN_ENABLE, 0); |
rtl_set(dev, RTL_REG_VLAN_FILTER, 0); |
rtl_set(dev, RTL_REG_EN_TRUNK, 0); |
rtl_set(dev, RTL_REG_TRUNK_PORTSEL, 0); |
|
/* initialize cpu port settings */ |
if (priv->do_cpu) { |
rtl_set(dev, RTL_REG_CPUPORT, dev->cpu_port); |
rtl_set(dev, RTL_REG_EN_CPUPORT, 1); |
} else { |
rtl_set(dev, RTL_REG_CPUPORT, 7); |
rtl_set(dev, RTL_REG_EN_CPUPORT, 0); |
} |
rtl_set(dev, RTL_REG_EN_TAG_OUT, 0); |
rtl_set(dev, RTL_REG_EN_TAG_IN, 0); |
rtl_set(dev, RTL_REG_EN_TAG_CLR, 0); |
|
/* reset all vlans */ |
for (i = 0; i < RTL8306_NUM_VLANS; i++) { |
rtl_set(dev, RTL_VLAN_REG(i, VID), i); |
rtl_set(dev, RTL_VLAN_REG(i, PORTMASK), 0); |
} |
|
/* default to port isolation */ |
for (i = 0; i < RTL8306_NUM_PORTS; i++) { |
unsigned long mask; |
|
if ((1 << i) == cpu_mask) |
mask = ((1 << RTL8306_NUM_PORTS) - 1) & ~cpu_mask; /* all bits set */ |
else |
mask = cpu_mask | (1 << i); |
|
rtl_set(dev, RTL_VLAN_REG(i, PORTMASK), mask); |
rtl_set(dev, RTL_PORT_REG(i, PVID), i); |
rtl_set(dev, RTL_PORT_REG(i, NULL_VID_REPLACE), 1); |
rtl_set(dev, RTL_PORT_REG(i, VID_INSERT), 1); |
rtl_set(dev, RTL_PORT_REG(i, TAG_INSERT), 3); |
} |
rtl_hw_apply(dev); |
} |
|
#ifdef DEBUG |
static int |
rtl_set_use_cpuport(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) |
{ |
struct rtl_priv *priv = to_rtl(dev); |
priv->do_cpu = val->value.i; |
rtl_hw_init(dev); |
return 0; |
} |
|
static int |
rtl_get_use_cpuport(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) |
{ |
struct rtl_priv *priv = to_rtl(dev); |
val->value.i = priv->do_cpu; |
return 0; |
} |
|
static int |
rtl_set_cpuport(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) |
{ |
dev->cpu_port = val->value.i; |
rtl_hw_init(dev); |
return 0; |
} |
|
static int |
rtl_get_cpuport(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) |
{ |
val->value.i = dev->cpu_port; |
return 0; |
} |
#endif |
|
static int |
rtl_reset(struct switch_dev *dev) |
{ |
rtl_hw_init(dev); |
return 0; |
} |
|
static int |
rtl_attr_set_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) |
{ |
int idx = attr->id + (val->port_vlan * attr->ofs); |
struct rtl_phyregs port; |
|
if (attr->id >= ARRAY_SIZE(rtl_regs)) |
return -EINVAL; |
|
if ((attr->max > 0) && (val->value.i > attr->max)) |
return -EINVAL; |
|
/* access to phy register 22 on port 4/5 |
* needs phy status save/restore */ |
if ((val->port_vlan > 3) && |
(rtl_regs[idx].reg == 22) && |
(rtl_regs[idx].page == 0)) { |
|
rtl_phy_save(dev, val->port_vlan, &port); |
rtl_set(dev, idx, val->value.i); |
rtl_phy_restore(dev, val->port_vlan, &port); |
} else { |
rtl_set(dev, idx, val->value.i); |
} |
|
return 0; |
} |
|
static int |
rtl_attr_get_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) |
{ |
int idx = attr->id + (val->port_vlan * attr->ofs); |
|
if (idx >= ARRAY_SIZE(rtl_regs)) |
return -EINVAL; |
|
val->value.i = rtl_get(dev, idx); |
return 0; |
} |
|
static int |
rtl_attr_set_port_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) |
{ |
if (val->port_vlan >= RTL8306_NUM_PORTS) |
return -EINVAL; |
|
return rtl_attr_set_int(dev, attr, val); |
} |
|
static int |
rtl_attr_get_port_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) |
{ |
if (val->port_vlan >= RTL8306_NUM_PORTS) |
return -EINVAL; |
return rtl_attr_get_int(dev, attr, val); |
} |
|
static int |
rtl_get_port_link(struct switch_dev *dev, int port, struct switch_port_link *link) |
{ |
if (port >= RTL8306_NUM_PORTS) |
return -EINVAL; |
|
/* in case the link changes from down to up, the register is only updated on read */ |
link->link = rtl_get(dev, RTL_PORT_REG(port, LINK)); |
if (!link->link) |
link->link = rtl_get(dev, RTL_PORT_REG(port, LINK)); |
|
if (!link->link) |
return 0; |
|
link->duplex = rtl_get(dev, RTL_PORT_REG(port, DUPLEX)); |
link->aneg = rtl_get(dev, RTL_PORT_REG(port, NWAY)); |
|
if (rtl_get(dev, RTL_PORT_REG(port, SPEED))) |
link->speed = SWITCH_PORT_SPEED_100; |
else |
link->speed = SWITCH_PORT_SPEED_10; |
|
return 0; |
} |
|
static int |
rtl_attr_set_vlan_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) |
{ |
if (val->port_vlan >= dev->vlans) |
return -EINVAL; |
|
return rtl_attr_set_int(dev, attr, val); |
} |
|
static int |
rtl_attr_get_vlan_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) |
{ |
if (val->port_vlan >= dev->vlans) |
return -EINVAL; |
|
return rtl_attr_get_int(dev, attr, val); |
} |
|
static int |
rtl_get_ports(struct switch_dev *dev, struct switch_val *val) |
{ |
unsigned int i, mask; |
|
mask = rtl_get(dev, RTL_VLAN_REG(val->port_vlan, PORTMASK)); |
for (i = 0; i < RTL8306_NUM_PORTS; i++) { |
struct switch_port *port; |
|
if (!(mask & (1 << i))) |
continue; |
|
port = &val->value.ports[val->len]; |
port->id = i; |
if (rtl_get(dev, RTL_PORT_REG(i, TAG_INSERT)) == 2 || i == dev->cpu_port) |
port->flags = (1 << SWITCH_PORT_FLAG_TAGGED); |
val->len++; |
} |
|
return 0; |
} |
|
static int |
rtl_set_vlan(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) |
{ |
struct rtl_priv *priv = to_rtl(dev); |
struct rtl_phyregs port; |
int en = val->value.i; |
int i; |
|
rtl_set(dev, RTL_REG_EN_TAG_OUT, en && priv->do_cpu); |
rtl_set(dev, RTL_REG_EN_TAG_IN, en && priv->do_cpu); |
rtl_set(dev, RTL_REG_EN_TAG_CLR, en && priv->do_cpu); |
rtl_set(dev, RTL_REG_VLAN_TAG_AWARE, en); |
if (en) |
rtl_set(dev, RTL_REG_VLAN_FILTER, en); |
|
for (i = 0; i < RTL8306_NUM_PORTS; i++) { |
if (i > 3) |
rtl_phy_save(dev, val->port_vlan, &port); |
rtl_set(dev, RTL_PORT_REG(i, NULL_VID_REPLACE), 1); |
rtl_set(dev, RTL_PORT_REG(i, VID_INSERT), (en ? (i == dev->cpu_port ? 0 : 1) : 1)); |
rtl_set(dev, RTL_PORT_REG(i, TAG_INSERT), (en ? (i == dev->cpu_port ? 2 : 1) : 3)); |
if (i > 3) |
rtl_phy_restore(dev, val->port_vlan, &port); |
} |
rtl_set(dev, RTL_REG_VLAN_ENABLE, en); |
|
return 0; |
} |
|
static int |
rtl_get_vlan(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) |
{ |
val->value.i = rtl_get(dev, RTL_REG_VLAN_ENABLE); |
return 0; |
} |
|
static int |
rtl_set_ports(struct switch_dev *dev, struct switch_val *val) |
{ |
unsigned int mask = 0; |
unsigned int oldmask; |
int i; |
|
for(i = 0; i < val->len; i++) |
{ |
struct switch_port *port = &val->value.ports[i]; |
bool tagged = false; |
|
mask |= (1 << port->id); |
|
if (port->id == dev->cpu_port) |
continue; |
|
if ((i == dev->cpu_port) || |
(port->flags & (1 << SWITCH_PORT_FLAG_TAGGED))) |
tagged = true; |
|
/* fix up PVIDs for added ports */ |
if (!tagged) |
rtl_set(dev, RTL_PORT_REG(port->id, PVID), val->port_vlan); |
|
rtl_set(dev, RTL_PORT_REG(port->id, NON_PVID_DISCARD), (tagged ? 0 : 1)); |
rtl_set(dev, RTL_PORT_REG(port->id, VID_INSERT), (tagged ? 0 : 1)); |
rtl_set(dev, RTL_PORT_REG(port->id, TAG_INSERT), (tagged ? 2 : 1)); |
} |
|
oldmask = rtl_get(dev, RTL_VLAN_REG(val->port_vlan, PORTMASK)); |
rtl_set(dev, RTL_VLAN_REG(val->port_vlan, PORTMASK), mask); |
|
/* fix up PVIDs for removed ports, default to last vlan */ |
oldmask &= ~mask; |
for (i = 0; i < RTL8306_NUM_PORTS; i++) { |
if (!(oldmask & (1 << i))) |
continue; |
|
if (i == dev->cpu_port) |
continue; |
|
if (rtl_get(dev, RTL_PORT_REG(i, PVID)) == val->port_vlan) |
rtl_set(dev, RTL_PORT_REG(i, PVID), dev->vlans - 1); |
} |
|
return 0; |
} |
|
static struct switch_attr rtl_globals[] = { |
{ |
.type = SWITCH_TYPE_INT, |
.name = "enable_vlan", |
.description = "Enable VLAN mode", |
.max = 1, |
.set = rtl_set_vlan, |
.get = rtl_get_vlan, |
}, |
{ |
RTL_GLOBAL_REGATTR(EN_TRUNK), |
.name = "trunk", |
.description = "Enable port trunking", |
.max = 1, |
}, |
{ |
RTL_GLOBAL_REGATTR(TRUNK_PORTSEL), |
.name = "trunk_sel", |
.description = "Select ports for trunking (0: 0,1 - 1: 3,4)", |
.max = 1, |
}, |
#ifdef DEBUG |
{ |
RTL_GLOBAL_REGATTR(VLAN_FILTER), |
.name = "vlan_filter", |
.description = "Filter incoming packets for allowed VLANS", |
.max = 1, |
}, |
{ |
.type = SWITCH_TYPE_INT, |
.name = "cpuport", |
.description = "CPU Port", |
.set = rtl_set_cpuport, |
.get = rtl_get_cpuport, |
.max = RTL8306_NUM_PORTS, |
}, |
{ |
.type = SWITCH_TYPE_INT, |
.name = "use_cpuport", |
.description = "CPU Port handling flag", |
.set = rtl_set_use_cpuport, |
.get = rtl_get_use_cpuport, |
.max = RTL8306_NUM_PORTS, |
}, |
{ |
RTL_GLOBAL_REGATTR(TRAP_CPU), |
.name = "trap_cpu", |
.description = "VLAN trap to CPU", |
.max = 1, |
}, |
{ |
RTL_GLOBAL_REGATTR(VLAN_TAG_AWARE), |
.name = "vlan_tag_aware", |
.description = "Enable VLAN tag awareness", |
.max = 1, |
}, |
{ |
RTL_GLOBAL_REGATTR(VLAN_TAG_ONLY), |
.name = "tag_only", |
.description = "Only accept tagged packets", |
.max = 1, |
}, |
#endif |
}; |
static struct switch_attr rtl_port[] = { |
{ |
RTL_PORT_REGATTR(PVID), |
.name = "pvid", |
.description = "Port VLAN ID", |
.max = RTL8306_NUM_VLANS - 1, |
}, |
#ifdef DEBUG |
{ |
RTL_PORT_REGATTR(NULL_VID_REPLACE), |
.name = "null_vid", |
.description = "NULL VID gets replaced by port default vid", |
.max = 1, |
}, |
{ |
RTL_PORT_REGATTR(NON_PVID_DISCARD), |
.name = "non_pvid_discard", |
.description = "discard packets with VID != PVID", |
.max = 1, |
}, |
{ |
RTL_PORT_REGATTR(VID_INSERT), |
.name = "vid_insert_remove", |
.description = "how should the switch insert and remove vids ?", |
.max = 3, |
}, |
{ |
RTL_PORT_REGATTR(TAG_INSERT), |
.name = "tag_insert", |
.description = "tag insertion handling", |
.max = 3, |
}, |
#endif |
}; |
|
static struct switch_attr rtl_vlan[] = { |
{ |
RTL_VLAN_REGATTR(VID), |
.name = "vid", |
.description = "VLAN ID (1-4095)", |
.max = 4095, |
}, |
}; |
|
static const struct switch_dev_ops rtl8306_ops = { |
.attr_global = { |
.attr = rtl_globals, |
.n_attr = ARRAY_SIZE(rtl_globals), |
}, |
.attr_port = { |
.attr = rtl_port, |
.n_attr = ARRAY_SIZE(rtl_port), |
}, |
.attr_vlan = { |
.attr = rtl_vlan, |
.n_attr = ARRAY_SIZE(rtl_vlan), |
}, |
|
.get_vlan_ports = rtl_get_ports, |
.set_vlan_ports = rtl_set_ports, |
.apply_config = rtl_hw_apply, |
.reset_switch = rtl_reset, |
.get_port_link = rtl_get_port_link, |
}; |
|
static int |
rtl8306_config_init(struct phy_device *pdev) |
{ |
struct net_device *netdev = pdev->attached_dev; |
struct rtl_priv *priv = pdev->priv; |
struct switch_dev *dev = &priv->dev; |
struct switch_val val; |
unsigned int chipid, chipver, chiptype; |
int err; |
|
/* Only init the switch for the primary PHY */ |
if (pdev->mdio.addr != 0) |
return 0; |
|
val.value.i = 1; |
priv->dev.cpu_port = RTL8306_PORT_CPU; |
priv->dev.ports = RTL8306_NUM_PORTS; |
priv->dev.vlans = RTL8306_NUM_VLANS; |
priv->dev.ops = &rtl8306_ops; |
priv->do_cpu = 0; |
priv->page = -1; |
priv->bus = pdev->mdio.bus; |
|
chipid = rtl_get(dev, RTL_REG_CHIPID); |
chipver = rtl_get(dev, RTL_REG_CHIPVER); |
chiptype = rtl_get(dev, RTL_REG_CHIPTYPE); |
switch(chiptype) { |
case 0: |
case 2: |
strncpy(priv->hwname, RTL_NAME_S, sizeof(priv->hwname)); |
priv->type = RTL_TYPE_S; |
break; |
case 1: |
strncpy(priv->hwname, RTL_NAME_SD, sizeof(priv->hwname)); |
priv->type = RTL_TYPE_SD; |
break; |
case 3: |
strncpy(priv->hwname, RTL_NAME_SDM, sizeof(priv->hwname)); |
priv->type = RTL_TYPE_SDM; |
break; |
default: |
strncpy(priv->hwname, RTL_NAME_UNKNOWN, sizeof(priv->hwname)); |
break; |
} |
|
dev->name = priv->hwname; |
rtl_hw_init(dev); |
|
printk(KERN_INFO "Registering %s switch with Chip ID: 0x%04x, version: 0x%04x\n", priv->hwname, chipid, chipver); |
|
err = register_switch(dev, netdev); |
if (err < 0) { |
kfree(priv); |
return err; |
} |
|
return 0; |
} |
|
|
static int |
rtl8306_fixup(struct phy_device *pdev) |
{ |
struct rtl_priv priv; |
u16 chipid; |
|
/* Attach to primary LAN port and WAN port */ |
if (pdev->mdio.addr != 0 && pdev->mdio.addr != 4) |
return 0; |
|
memset(&priv, 0, sizeof(priv)); |
priv.fixup = true; |
priv.page = -1; |
priv.bus = pdev->mdio.bus; |
chipid = rtl_get(&priv.dev, RTL_REG_CHIPID); |
if (chipid == 0x5988) |
pdev->phy_id = RTL8306_MAGIC; |
|
return 0; |
} |
|
static int |
rtl8306_probe(struct phy_device *pdev) |
{ |
struct rtl_priv *priv; |
|
list_for_each_entry(priv, &phydevs, list) { |
/* |
* share one rtl_priv instance between virtual phy |
* devices on the same bus |
*/ |
if (priv->bus == pdev->mdio.bus) |
goto found; |
} |
priv = kzalloc(sizeof(struct rtl_priv), GFP_KERNEL); |
if (!priv) |
return -ENOMEM; |
|
priv->bus = pdev->mdio.bus; |
|
found: |
pdev->priv = priv; |
return 0; |
} |
|
static void |
rtl8306_remove(struct phy_device *pdev) |
{ |
struct rtl_priv *priv = pdev->priv; |
unregister_switch(&priv->dev); |
kfree(priv); |
} |
|
static int |
rtl8306_config_aneg(struct phy_device *pdev) |
{ |
struct rtl_priv *priv = pdev->priv; |
|
/* Only for WAN */ |
if (pdev->mdio.addr == 0) |
return 0; |
|
/* Restart autonegotiation */ |
rtl_set(&priv->dev, RTL_PORT_REG(4, NWAY), 1); |
rtl_set(&priv->dev, RTL_PORT_REG(4, NRESTART), 1); |
|
return 0; |
} |
|
static int |
rtl8306_read_status(struct phy_device *pdev) |
{ |
struct rtl_priv *priv = pdev->priv; |
struct switch_dev *dev = &priv->dev; |
|
if (pdev->mdio.addr == 4) { |
/* WAN */ |
pdev->speed = rtl_get(dev, RTL_PORT_REG(4, SPEED)) ? SPEED_100 : SPEED_10; |
pdev->duplex = rtl_get(dev, RTL_PORT_REG(4, DUPLEX)) ? DUPLEX_FULL : DUPLEX_HALF; |
pdev->link = !!rtl_get(dev, RTL_PORT_REG(4, LINK)); |
} else { |
/* LAN */ |
pdev->speed = SPEED_100; |
pdev->duplex = DUPLEX_FULL; |
pdev->link = 1; |
} |
|
/* |
* Bypass generic PHY status read, |
* it doesn't work with this switch |
*/ |
if (pdev->link) { |
pdev->state = PHY_RUNNING; |
netif_carrier_on(pdev->attached_dev); |
pdev->adjust_link(pdev->attached_dev); |
} else { |
pdev->state = PHY_NOLINK; |
netif_carrier_off(pdev->attached_dev); |
pdev->adjust_link(pdev->attached_dev); |
} |
|
return 0; |
} |
|
|
static struct phy_driver rtl8306_driver = { |
.name = "Realtek RTL8306S", |
#if (LINUX_VERSION_CODE < KERNEL_VERSION(4,13,0)) |
.flags = PHY_HAS_MAGICANEG, |
#endif |
.phy_id = RTL8306_MAGIC, |
.phy_id_mask = 0xffffffff, |
.features = PHY_BASIC_FEATURES, |
.probe = &rtl8306_probe, |
.remove = &rtl8306_remove, |
.config_init = &rtl8306_config_init, |
.config_aneg = &rtl8306_config_aneg, |
.read_status = &rtl8306_read_status, |
}; |
|
|
static int __init |
rtl_init(void) |
{ |
phy_register_fixup_for_id(PHY_ANY_ID, rtl8306_fixup); |
return phy_driver_register(&rtl8306_driver, THIS_MODULE); |
} |
|
static void __exit |
rtl_exit(void) |
{ |
phy_driver_unregister(&rtl8306_driver); |
} |
|
module_init(rtl_init); |
module_exit(rtl_exit); |
MODULE_LICENSE("GPL"); |
|
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/rtl8366_smi.c |
@@ -0,0 +1,1494 @@ |
/* |
* Realtek RTL8366 SMI interface driver |
* |
* Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org> |
* |
* This program is free software; you can redistribute it and/or modify it |
* under the terms of the GNU General Public License version 2 as published |
* by the Free Software Foundation. |
*/ |
|
#include <linux/kernel.h> |
#include <linux/module.h> |
#include <linux/device.h> |
#include <linux/delay.h> |
#include <linux/gpio.h> |
#include <linux/spinlock.h> |
#include <linux/skbuff.h> |
#include <linux/of.h> |
#include <linux/of_platform.h> |
#include <linux/of_gpio.h> |
#include <linux/rtl8366.h> |
#include <linux/version.h> |
#include <linux/of_mdio.h> |
|
#ifdef CONFIG_RTL8366_SMI_DEBUG_FS |
#include <linux/debugfs.h> |
#endif |
|
#include "rtl8366_smi.h" |
|
#define RTL8366_SMI_ACK_RETRY_COUNT 5 |
|
#define RTL8366_SMI_HW_STOP_DELAY 25 /* msecs */ |
#define RTL8366_SMI_HW_START_DELAY 100 /* msecs */ |
|
static inline void rtl8366_smi_clk_delay(struct rtl8366_smi *smi) |
{ |
ndelay(smi->clk_delay); |
} |
|
static void rtl8366_smi_start(struct rtl8366_smi *smi) |
{ |
unsigned int sda = smi->gpio_sda; |
unsigned int sck = smi->gpio_sck; |
|
/* |
* Set GPIO pins to output mode, with initial state: |
* SCK = 0, SDA = 1 |
*/ |
gpio_direction_output(sck, 0); |
gpio_direction_output(sda, 1); |
rtl8366_smi_clk_delay(smi); |
|
/* CLK 1: 0 -> 1, 1 -> 0 */ |
gpio_set_value(sck, 1); |
rtl8366_smi_clk_delay(smi); |
gpio_set_value(sck, 0); |
rtl8366_smi_clk_delay(smi); |
|
/* CLK 2: */ |
gpio_set_value(sck, 1); |
rtl8366_smi_clk_delay(smi); |
gpio_set_value(sda, 0); |
rtl8366_smi_clk_delay(smi); |
gpio_set_value(sck, 0); |
rtl8366_smi_clk_delay(smi); |
gpio_set_value(sda, 1); |
} |
|
static void rtl8366_smi_stop(struct rtl8366_smi *smi) |
{ |
unsigned int sda = smi->gpio_sda; |
unsigned int sck = smi->gpio_sck; |
|
rtl8366_smi_clk_delay(smi); |
gpio_set_value(sda, 0); |
gpio_set_value(sck, 1); |
rtl8366_smi_clk_delay(smi); |
gpio_set_value(sda, 1); |
rtl8366_smi_clk_delay(smi); |
gpio_set_value(sck, 1); |
rtl8366_smi_clk_delay(smi); |
gpio_set_value(sck, 0); |
rtl8366_smi_clk_delay(smi); |
gpio_set_value(sck, 1); |
|
/* add a click */ |
rtl8366_smi_clk_delay(smi); |
gpio_set_value(sck, 0); |
rtl8366_smi_clk_delay(smi); |
gpio_set_value(sck, 1); |
|
/* set GPIO pins to input mode */ |
gpio_direction_input(sda); |
gpio_direction_input(sck); |
} |
|
static void rtl8366_smi_write_bits(struct rtl8366_smi *smi, u32 data, u32 len) |
{ |
unsigned int sda = smi->gpio_sda; |
unsigned int sck = smi->gpio_sck; |
|
for (; len > 0; len--) { |
rtl8366_smi_clk_delay(smi); |
|
/* prepare data */ |
gpio_set_value(sda, !!(data & ( 1 << (len - 1)))); |
rtl8366_smi_clk_delay(smi); |
|
/* clocking */ |
gpio_set_value(sck, 1); |
rtl8366_smi_clk_delay(smi); |
gpio_set_value(sck, 0); |
} |
} |
|
static void rtl8366_smi_read_bits(struct rtl8366_smi *smi, u32 len, u32 *data) |
{ |
unsigned int sda = smi->gpio_sda; |
unsigned int sck = smi->gpio_sck; |
|
gpio_direction_input(sda); |
|
for (*data = 0; len > 0; len--) { |
u32 u; |
|
rtl8366_smi_clk_delay(smi); |
|
/* clocking */ |
gpio_set_value(sck, 1); |
rtl8366_smi_clk_delay(smi); |
u = !!gpio_get_value(sda); |
gpio_set_value(sck, 0); |
|
*data |= (u << (len - 1)); |
} |
|
gpio_direction_output(sda, 0); |
} |
|
static int rtl8366_smi_wait_for_ack(struct rtl8366_smi *smi) |
{ |
int retry_cnt; |
|
retry_cnt = 0; |
do { |
u32 ack; |
|
rtl8366_smi_read_bits(smi, 1, &ack); |
if (ack == 0) |
break; |
|
if (++retry_cnt > RTL8366_SMI_ACK_RETRY_COUNT) { |
dev_err(smi->parent, "ACK timeout\n"); |
return -ETIMEDOUT; |
} |
} while (1); |
|
return 0; |
} |
|
static int rtl8366_smi_write_byte(struct rtl8366_smi *smi, u8 data) |
{ |
rtl8366_smi_write_bits(smi, data, 8); |
return rtl8366_smi_wait_for_ack(smi); |
} |
|
static int rtl8366_smi_write_byte_noack(struct rtl8366_smi *smi, u8 data) |
{ |
rtl8366_smi_write_bits(smi, data, 8); |
return 0; |
} |
|
static int rtl8366_smi_read_byte0(struct rtl8366_smi *smi, u8 *data) |
{ |
u32 t; |
|
/* read data */ |
rtl8366_smi_read_bits(smi, 8, &t); |
*data = (t & 0xff); |
|
/* send an ACK */ |
rtl8366_smi_write_bits(smi, 0x00, 1); |
|
return 0; |
} |
|
static int rtl8366_smi_read_byte1(struct rtl8366_smi *smi, u8 *data) |
{ |
u32 t; |
|
/* read data */ |
rtl8366_smi_read_bits(smi, 8, &t); |
*data = (t & 0xff); |
|
/* send an ACK */ |
rtl8366_smi_write_bits(smi, 0x01, 1); |
|
return 0; |
} |
|
int rtl8366_smi_read_reg(struct rtl8366_smi *smi, u32 addr, u32 *data) |
{ |
unsigned long flags; |
u8 lo = 0; |
u8 hi = 0; |
int ret; |
|
spin_lock_irqsave(&smi->lock, flags); |
|
rtl8366_smi_start(smi); |
|
/* send READ command */ |
ret = rtl8366_smi_write_byte(smi, smi->cmd_read); |
if (ret) |
goto out; |
|
/* set ADDR[7:0] */ |
ret = rtl8366_smi_write_byte(smi, addr & 0xff); |
if (ret) |
goto out; |
|
/* set ADDR[15:8] */ |
ret = rtl8366_smi_write_byte(smi, addr >> 8); |
if (ret) |
goto out; |
|
/* read DATA[7:0] */ |
rtl8366_smi_read_byte0(smi, &lo); |
/* read DATA[15:8] */ |
rtl8366_smi_read_byte1(smi, &hi); |
|
*data = ((u32) lo) | (((u32) hi) << 8); |
|
ret = 0; |
|
out: |
rtl8366_smi_stop(smi); |
spin_unlock_irqrestore(&smi->lock, flags); |
|
return ret; |
} |
EXPORT_SYMBOL_GPL(rtl8366_smi_read_reg); |
|
static int __rtl8366_smi_write_reg(struct rtl8366_smi *smi, |
u32 addr, u32 data, bool ack) |
{ |
unsigned long flags; |
int ret; |
|
spin_lock_irqsave(&smi->lock, flags); |
|
rtl8366_smi_start(smi); |
|
/* send WRITE command */ |
ret = rtl8366_smi_write_byte(smi, smi->cmd_write); |
if (ret) |
goto out; |
|
/* set ADDR[7:0] */ |
ret = rtl8366_smi_write_byte(smi, addr & 0xff); |
if (ret) |
goto out; |
|
/* set ADDR[15:8] */ |
ret = rtl8366_smi_write_byte(smi, addr >> 8); |
if (ret) |
goto out; |
|
/* write DATA[7:0] */ |
ret = rtl8366_smi_write_byte(smi, data & 0xff); |
if (ret) |
goto out; |
|
/* write DATA[15:8] */ |
if (ack) |
ret = rtl8366_smi_write_byte(smi, data >> 8); |
else |
ret = rtl8366_smi_write_byte_noack(smi, data >> 8); |
if (ret) |
goto out; |
|
ret = 0; |
|
out: |
rtl8366_smi_stop(smi); |
spin_unlock_irqrestore(&smi->lock, flags); |
|
return ret; |
} |
|
int rtl8366_smi_write_reg(struct rtl8366_smi *smi, u32 addr, u32 data) |
{ |
return __rtl8366_smi_write_reg(smi, addr, data, true); |
} |
EXPORT_SYMBOL_GPL(rtl8366_smi_write_reg); |
|
int rtl8366_smi_write_reg_noack(struct rtl8366_smi *smi, u32 addr, u32 data) |
{ |
return __rtl8366_smi_write_reg(smi, addr, data, false); |
} |
EXPORT_SYMBOL_GPL(rtl8366_smi_write_reg_noack); |
|
int rtl8366_smi_rmwr(struct rtl8366_smi *smi, u32 addr, u32 mask, u32 data) |
{ |
u32 t; |
int err; |
|
err = rtl8366_smi_read_reg(smi, addr, &t); |
if (err) |
return err; |
|
err = rtl8366_smi_write_reg(smi, addr, (t & ~mask) | data); |
return err; |
|
} |
EXPORT_SYMBOL_GPL(rtl8366_smi_rmwr); |
|
static int rtl8366_reset(struct rtl8366_smi *smi) |
{ |
if (smi->hw_reset) { |
smi->hw_reset(true); |
msleep(RTL8366_SMI_HW_STOP_DELAY); |
smi->hw_reset(false); |
msleep(RTL8366_SMI_HW_START_DELAY); |
return 0; |
} |
|
return smi->ops->reset_chip(smi); |
} |
|
static int rtl8366_mc_is_used(struct rtl8366_smi *smi, int mc_index, int *used) |
{ |
int err; |
int i; |
|
*used = 0; |
for (i = 0; i < smi->num_ports; i++) { |
int index = 0; |
|
err = smi->ops->get_mc_index(smi, i, &index); |
if (err) |
return err; |
|
if (mc_index == index) { |
*used = 1; |
break; |
} |
} |
|
return 0; |
} |
|
static int rtl8366_set_vlan(struct rtl8366_smi *smi, int vid, u32 member, |
u32 untag, u32 fid) |
{ |
struct rtl8366_vlan_4k vlan4k; |
int err; |
int i; |
|
/* Update the 4K table */ |
err = smi->ops->get_vlan_4k(smi, vid, &vlan4k); |
if (err) |
return err; |
|
vlan4k.member = member; |
vlan4k.untag = untag; |
vlan4k.fid = fid; |
err = smi->ops->set_vlan_4k(smi, &vlan4k); |
if (err) |
return err; |
|
/* Try to find an existing MC entry for this VID */ |
for (i = 0; i < smi->num_vlan_mc; i++) { |
struct rtl8366_vlan_mc vlanmc; |
|
err = smi->ops->get_vlan_mc(smi, i, &vlanmc); |
if (err) |
return err; |
|
if (vid == vlanmc.vid) { |
/* update the MC entry */ |
vlanmc.member = member; |
vlanmc.untag = untag; |
vlanmc.fid = fid; |
|
err = smi->ops->set_vlan_mc(smi, i, &vlanmc); |
break; |
} |
} |
|
return err; |
} |
|
static int rtl8366_get_pvid(struct rtl8366_smi *smi, int port, int *val) |
{ |
struct rtl8366_vlan_mc vlanmc; |
int err; |
int index; |
|
err = smi->ops->get_mc_index(smi, port, &index); |
if (err) |
return err; |
|
err = smi->ops->get_vlan_mc(smi, index, &vlanmc); |
if (err) |
return err; |
|
*val = vlanmc.vid; |
return 0; |
} |
|
static int rtl8366_set_pvid(struct rtl8366_smi *smi, unsigned port, |
unsigned vid) |
{ |
struct rtl8366_vlan_mc vlanmc; |
struct rtl8366_vlan_4k vlan4k; |
int err; |
int i; |
|
/* Try to find an existing MC entry for this VID */ |
for (i = 0; i < smi->num_vlan_mc; i++) { |
err = smi->ops->get_vlan_mc(smi, i, &vlanmc); |
if (err) |
return err; |
|
if (vid == vlanmc.vid) { |
err = smi->ops->set_vlan_mc(smi, i, &vlanmc); |
if (err) |
return err; |
|
err = smi->ops->set_mc_index(smi, port, i); |
return err; |
} |
} |
|
/* We have no MC entry for this VID, try to find an empty one */ |
for (i = 0; i < smi->num_vlan_mc; i++) { |
err = smi->ops->get_vlan_mc(smi, i, &vlanmc); |
if (err) |
return err; |
|
if (vlanmc.vid == 0 && vlanmc.member == 0) { |
/* Update the entry from the 4K table */ |
err = smi->ops->get_vlan_4k(smi, vid, &vlan4k); |
if (err) |
return err; |
|
vlanmc.vid = vid; |
vlanmc.member = vlan4k.member; |
vlanmc.untag = vlan4k.untag; |
vlanmc.fid = vlan4k.fid; |
err = smi->ops->set_vlan_mc(smi, i, &vlanmc); |
if (err) |
return err; |
|
err = smi->ops->set_mc_index(smi, port, i); |
return err; |
} |
} |
|
/* MC table is full, try to find an unused entry and replace it */ |
for (i = 0; i < smi->num_vlan_mc; i++) { |
int used; |
|
err = rtl8366_mc_is_used(smi, i, &used); |
if (err) |
return err; |
|
if (!used) { |
/* Update the entry from the 4K table */ |
err = smi->ops->get_vlan_4k(smi, vid, &vlan4k); |
if (err) |
return err; |
|
vlanmc.vid = vid; |
vlanmc.member = vlan4k.member; |
vlanmc.untag = vlan4k.untag; |
vlanmc.fid = vlan4k.fid; |
err = smi->ops->set_vlan_mc(smi, i, &vlanmc); |
if (err) |
return err; |
|
err = smi->ops->set_mc_index(smi, port, i); |
return err; |
} |
} |
|
dev_err(smi->parent, |
"all VLAN member configurations are in use\n"); |
|
return -ENOSPC; |
} |
|
int rtl8366_enable_vlan(struct rtl8366_smi *smi, int enable) |
{ |
int err; |
|
err = smi->ops->enable_vlan(smi, enable); |
if (err) |
return err; |
|
smi->vlan_enabled = enable; |
|
if (!enable) { |
smi->vlan4k_enabled = 0; |
err = smi->ops->enable_vlan4k(smi, enable); |
} |
|
return err; |
} |
EXPORT_SYMBOL_GPL(rtl8366_enable_vlan); |
|
static int rtl8366_enable_vlan4k(struct rtl8366_smi *smi, int enable) |
{ |
int err; |
|
if (enable) { |
err = smi->ops->enable_vlan(smi, enable); |
if (err) |
return err; |
|
smi->vlan_enabled = enable; |
} |
|
err = smi->ops->enable_vlan4k(smi, enable); |
if (err) |
return err; |
|
smi->vlan4k_enabled = enable; |
return 0; |
} |
|
int rtl8366_enable_all_ports(struct rtl8366_smi *smi, int enable) |
{ |
int port; |
int err; |
|
for (port = 0; port < smi->num_ports; port++) { |
err = smi->ops->enable_port(smi, port, enable); |
if (err) |
return err; |
} |
|
return 0; |
} |
EXPORT_SYMBOL_GPL(rtl8366_enable_all_ports); |
|
int rtl8366_reset_vlan(struct rtl8366_smi *smi) |
{ |
struct rtl8366_vlan_mc vlanmc; |
int err; |
int i; |
|
rtl8366_enable_vlan(smi, 0); |
rtl8366_enable_vlan4k(smi, 0); |
|
/* clear VLAN member configurations */ |
vlanmc.vid = 0; |
vlanmc.priority = 0; |
vlanmc.member = 0; |
vlanmc.untag = 0; |
vlanmc.fid = 0; |
for (i = 0; i < smi->num_vlan_mc; i++) { |
err = smi->ops->set_vlan_mc(smi, i, &vlanmc); |
if (err) |
return err; |
} |
|
return 0; |
} |
EXPORT_SYMBOL_GPL(rtl8366_reset_vlan); |
|
static int rtl8366_init_vlan(struct rtl8366_smi *smi) |
{ |
int port; |
int err; |
|
err = rtl8366_reset_vlan(smi); |
if (err) |
return err; |
|
for (port = 0; port < smi->num_ports; port++) { |
u32 mask; |
|
if (port == smi->cpu_port) |
mask = (1 << smi->num_ports) - 1; |
else |
mask = (1 << port) | (1 << smi->cpu_port); |
|
err = rtl8366_set_vlan(smi, (port + 1), mask, mask, 0); |
if (err) |
return err; |
|
err = rtl8366_set_pvid(smi, port, (port + 1)); |
if (err) |
return err; |
} |
|
return rtl8366_enable_vlan(smi, 1); |
} |
|
#ifdef CONFIG_RTL8366_SMI_DEBUG_FS |
int rtl8366_debugfs_open(struct inode *inode, struct file *file) |
{ |
file->private_data = inode->i_private; |
return 0; |
} |
EXPORT_SYMBOL_GPL(rtl8366_debugfs_open); |
|
static ssize_t rtl8366_read_debugfs_vlan_mc(struct file *file, |
char __user *user_buf, |
size_t count, loff_t *ppos) |
{ |
struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data; |
int i, len = 0; |
char *buf = smi->buf; |
|
len += snprintf(buf + len, sizeof(smi->buf) - len, |
"%2s %6s %4s %6s %6s %3s\n", |
"id", "vid","prio", "member", "untag", "fid"); |
|
for (i = 0; i < smi->num_vlan_mc; ++i) { |
struct rtl8366_vlan_mc vlanmc; |
|
smi->ops->get_vlan_mc(smi, i, &vlanmc); |
|
len += snprintf(buf + len, sizeof(smi->buf) - len, |
"%2d %6d %4d 0x%04x 0x%04x %3d\n", |
i, vlanmc.vid, vlanmc.priority, |
vlanmc.member, vlanmc.untag, vlanmc.fid); |
} |
|
return simple_read_from_buffer(user_buf, count, ppos, buf, len); |
} |
|
#define RTL8366_VLAN4K_PAGE_SIZE 64 |
#define RTL8366_VLAN4K_NUM_PAGES (4096 / RTL8366_VLAN4K_PAGE_SIZE) |
|
static ssize_t rtl8366_read_debugfs_vlan_4k(struct file *file, |
char __user *user_buf, |
size_t count, loff_t *ppos) |
{ |
struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data; |
int i, len = 0; |
int offset; |
char *buf = smi->buf; |
|
if (smi->dbg_vlan_4k_page >= RTL8366_VLAN4K_NUM_PAGES) { |
len += snprintf(buf + len, sizeof(smi->buf) - len, |
"invalid page: %u\n", smi->dbg_vlan_4k_page); |
return simple_read_from_buffer(user_buf, count, ppos, buf, len); |
} |
|
len += snprintf(buf + len, sizeof(smi->buf) - len, |
"%4s %6s %6s %3s\n", |
"vid", "member", "untag", "fid"); |
|
offset = RTL8366_VLAN4K_PAGE_SIZE * smi->dbg_vlan_4k_page; |
for (i = 0; i < RTL8366_VLAN4K_PAGE_SIZE; i++) { |
struct rtl8366_vlan_4k vlan4k; |
|
smi->ops->get_vlan_4k(smi, offset + i, &vlan4k); |
|
len += snprintf(buf + len, sizeof(smi->buf) - len, |
"%4d 0x%04x 0x%04x %3d\n", |
vlan4k.vid, vlan4k.member, |
vlan4k.untag, vlan4k.fid); |
} |
|
return simple_read_from_buffer(user_buf, count, ppos, buf, len); |
} |
|
static ssize_t rtl8366_read_debugfs_pvid(struct file *file, |
char __user *user_buf, |
size_t count, loff_t *ppos) |
{ |
struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data; |
char *buf = smi->buf; |
int len = 0; |
int i; |
|
len += snprintf(buf + len, sizeof(smi->buf) - len, "%4s %4s\n", |
"port", "pvid"); |
|
for (i = 0; i < smi->num_ports; i++) { |
int pvid; |
int err; |
|
err = rtl8366_get_pvid(smi, i, &pvid); |
if (err) |
len += snprintf(buf + len, sizeof(smi->buf) - len, |
"%4d error\n", i); |
else |
len += snprintf(buf + len, sizeof(smi->buf) - len, |
"%4d %4d\n", i, pvid); |
} |
|
return simple_read_from_buffer(user_buf, count, ppos, buf, len); |
} |
|
static ssize_t rtl8366_read_debugfs_reg(struct file *file, |
char __user *user_buf, |
size_t count, loff_t *ppos) |
{ |
struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data; |
u32 t, reg = smi->dbg_reg; |
int err, len = 0; |
char *buf = smi->buf; |
|
memset(buf, '\0', sizeof(smi->buf)); |
|
err = rtl8366_smi_read_reg(smi, reg, &t); |
if (err) { |
len += snprintf(buf, sizeof(smi->buf), |
"Read failed (reg: 0x%04x)\n", reg); |
return simple_read_from_buffer(user_buf, count, ppos, buf, len); |
} |
|
len += snprintf(buf, sizeof(smi->buf), "reg = 0x%04x, val = 0x%04x\n", |
reg, t); |
|
return simple_read_from_buffer(user_buf, count, ppos, buf, len); |
} |
|
static ssize_t rtl8366_write_debugfs_reg(struct file *file, |
const char __user *user_buf, |
size_t count, loff_t *ppos) |
{ |
struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data; |
unsigned long data; |
u32 reg = smi->dbg_reg; |
int err; |
size_t len; |
char *buf = smi->buf; |
|
len = min(count, sizeof(smi->buf) - 1); |
if (copy_from_user(buf, user_buf, len)) { |
dev_err(smi->parent, "copy from user failed\n"); |
return -EFAULT; |
} |
|
buf[len] = '\0'; |
if (len > 0 && buf[len - 1] == '\n') |
buf[len - 1] = '\0'; |
|
|
if (kstrtoul(buf, 16, &data)) { |
dev_err(smi->parent, "Invalid reg value %s\n", buf); |
} else { |
err = rtl8366_smi_write_reg(smi, reg, data); |
if (err) { |
dev_err(smi->parent, |
"writing reg 0x%04x val 0x%04lx failed\n", |
reg, data); |
} |
} |
|
return count; |
} |
|
static ssize_t rtl8366_read_debugfs_mibs(struct file *file, |
char __user *user_buf, |
size_t count, loff_t *ppos) |
{ |
struct rtl8366_smi *smi = file->private_data; |
int i, j, len = 0; |
char *buf = smi->buf; |
|
len += snprintf(buf + len, sizeof(smi->buf) - len, "%-36s", |
"Counter"); |
|
for (i = 0; i < smi->num_ports; i++) { |
char port_buf[10]; |
|
snprintf(port_buf, sizeof(port_buf), "Port %d", i); |
len += snprintf(buf + len, sizeof(smi->buf) - len, " %12s", |
port_buf); |
} |
len += snprintf(buf + len, sizeof(smi->buf) - len, "\n"); |
|
for (i = 0; i < smi->num_mib_counters; i++) { |
len += snprintf(buf + len, sizeof(smi->buf) - len, "%-36s ", |
smi->mib_counters[i].name); |
for (j = 0; j < smi->num_ports; j++) { |
unsigned long long counter = 0; |
|
if (!smi->ops->get_mib_counter(smi, i, j, &counter)) |
len += snprintf(buf + len, |
sizeof(smi->buf) - len, |
"%12llu ", counter); |
else |
len += snprintf(buf + len, |
sizeof(smi->buf) - len, |
"%12s ", "error"); |
} |
len += snprintf(buf + len, sizeof(smi->buf) - len, "\n"); |
} |
|
return simple_read_from_buffer(user_buf, count, ppos, buf, len); |
} |
|
static const struct file_operations fops_rtl8366_regs = { |
.read = rtl8366_read_debugfs_reg, |
.write = rtl8366_write_debugfs_reg, |
.open = rtl8366_debugfs_open, |
.owner = THIS_MODULE |
}; |
|
static const struct file_operations fops_rtl8366_vlan_mc = { |
.read = rtl8366_read_debugfs_vlan_mc, |
.open = rtl8366_debugfs_open, |
.owner = THIS_MODULE |
}; |
|
static const struct file_operations fops_rtl8366_vlan_4k = { |
.read = rtl8366_read_debugfs_vlan_4k, |
.open = rtl8366_debugfs_open, |
.owner = THIS_MODULE |
}; |
|
static const struct file_operations fops_rtl8366_pvid = { |
.read = rtl8366_read_debugfs_pvid, |
.open = rtl8366_debugfs_open, |
.owner = THIS_MODULE |
}; |
|
static const struct file_operations fops_rtl8366_mibs = { |
.read = rtl8366_read_debugfs_mibs, |
.open = rtl8366_debugfs_open, |
.owner = THIS_MODULE |
}; |
|
static void rtl8366_debugfs_init(struct rtl8366_smi *smi) |
{ |
struct dentry *node; |
struct dentry *root; |
|
if (!smi->debugfs_root) |
smi->debugfs_root = debugfs_create_dir(dev_name(smi->parent), |
NULL); |
|
if (!smi->debugfs_root) { |
dev_err(smi->parent, "Unable to create debugfs dir\n"); |
return; |
} |
root = smi->debugfs_root; |
|
node = debugfs_create_x16("reg", S_IRUGO | S_IWUSR, root, |
&smi->dbg_reg); |
if (!node) { |
dev_err(smi->parent, "Creating debugfs file '%s' failed\n", |
"reg"); |
return; |
} |
|
node = debugfs_create_file("val", S_IRUGO | S_IWUSR, root, smi, |
&fops_rtl8366_regs); |
if (!node) { |
dev_err(smi->parent, "Creating debugfs file '%s' failed\n", |
"val"); |
return; |
} |
|
node = debugfs_create_file("vlan_mc", S_IRUSR, root, smi, |
&fops_rtl8366_vlan_mc); |
if (!node) { |
dev_err(smi->parent, "Creating debugfs file '%s' failed\n", |
"vlan_mc"); |
return; |
} |
|
node = debugfs_create_u8("vlan_4k_page", S_IRUGO | S_IWUSR, root, |
&smi->dbg_vlan_4k_page); |
if (!node) { |
dev_err(smi->parent, "Creating debugfs file '%s' failed\n", |
"vlan_4k_page"); |
return; |
} |
|
node = debugfs_create_file("vlan_4k", S_IRUSR, root, smi, |
&fops_rtl8366_vlan_4k); |
if (!node) { |
dev_err(smi->parent, "Creating debugfs file '%s' failed\n", |
"vlan_4k"); |
return; |
} |
|
node = debugfs_create_file("pvid", S_IRUSR, root, smi, |
&fops_rtl8366_pvid); |
if (!node) { |
dev_err(smi->parent, "Creating debugfs file '%s' failed\n", |
"pvid"); |
return; |
} |
|
node = debugfs_create_file("mibs", S_IRUSR, smi->debugfs_root, smi, |
&fops_rtl8366_mibs); |
if (!node) |
dev_err(smi->parent, "Creating debugfs file '%s' failed\n", |
"mibs"); |
} |
|
static void rtl8366_debugfs_remove(struct rtl8366_smi *smi) |
{ |
if (smi->debugfs_root) { |
debugfs_remove_recursive(smi->debugfs_root); |
smi->debugfs_root = NULL; |
} |
} |
#else |
static inline void rtl8366_debugfs_init(struct rtl8366_smi *smi) {} |
static inline void rtl8366_debugfs_remove(struct rtl8366_smi *smi) {} |
#endif /* CONFIG_RTL8366_SMI_DEBUG_FS */ |
|
static int rtl8366_smi_mii_init(struct rtl8366_smi *smi) |
{ |
int ret; |
|
#ifdef CONFIG_OF |
struct device_node *np = NULL; |
|
np = of_get_child_by_name(smi->parent->of_node, "mdio-bus"); |
#endif |
|
smi->mii_bus = mdiobus_alloc(); |
if (smi->mii_bus == NULL) { |
ret = -ENOMEM; |
goto err; |
} |
|
smi->mii_bus->priv = (void *) smi; |
smi->mii_bus->name = dev_name(smi->parent); |
smi->mii_bus->read = smi->ops->mii_read; |
smi->mii_bus->write = smi->ops->mii_write; |
snprintf(smi->mii_bus->id, MII_BUS_ID_SIZE, "%s", |
dev_name(smi->parent)); |
smi->mii_bus->parent = smi->parent; |
smi->mii_bus->phy_mask = ~(0x1f); |
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,5,0) |
{ |
int i; |
smi->mii_bus->irq = smi->mii_irq; |
for (i = 0; i < PHY_MAX_ADDR; i++) |
smi->mii_irq[i] = PHY_POLL; |
} |
#endif |
|
#ifdef CONFIG_OF |
if (np) |
ret = of_mdiobus_register(smi->mii_bus, np); |
else |
#endif |
ret = mdiobus_register(smi->mii_bus); |
|
if (ret) |
goto err_free; |
|
return 0; |
|
err_free: |
mdiobus_free(smi->mii_bus); |
err: |
return ret; |
} |
|
static void rtl8366_smi_mii_cleanup(struct rtl8366_smi *smi) |
{ |
mdiobus_unregister(smi->mii_bus); |
mdiobus_free(smi->mii_bus); |
} |
|
int rtl8366_sw_reset_switch(struct switch_dev *dev) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
int err; |
|
err = rtl8366_reset(smi); |
if (err) |
return err; |
|
err = smi->ops->setup(smi); |
if (err) |
return err; |
|
err = rtl8366_reset_vlan(smi); |
if (err) |
return err; |
|
err = rtl8366_enable_vlan(smi, 1); |
if (err) |
return err; |
|
return rtl8366_enable_all_ports(smi, 1); |
} |
EXPORT_SYMBOL_GPL(rtl8366_sw_reset_switch); |
|
int rtl8366_sw_get_port_pvid(struct switch_dev *dev, int port, int *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
return rtl8366_get_pvid(smi, port, val); |
} |
EXPORT_SYMBOL_GPL(rtl8366_sw_get_port_pvid); |
|
int rtl8366_sw_set_port_pvid(struct switch_dev *dev, int port, int val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
return rtl8366_set_pvid(smi, port, val); |
} |
EXPORT_SYMBOL_GPL(rtl8366_sw_set_port_pvid); |
|
int rtl8366_sw_get_port_mib(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
int i, len = 0; |
unsigned long long counter = 0; |
char *buf = smi->buf; |
|
if (val->port_vlan >= smi->num_ports) |
return -EINVAL; |
|
len += snprintf(buf + len, sizeof(smi->buf) - len, |
"Port %d MIB counters\n", |
val->port_vlan); |
|
for (i = 0; i < smi->num_mib_counters; ++i) { |
len += snprintf(buf + len, sizeof(smi->buf) - len, |
"%-36s: ", smi->mib_counters[i].name); |
if (!smi->ops->get_mib_counter(smi, i, val->port_vlan, |
&counter)) |
len += snprintf(buf + len, sizeof(smi->buf) - len, |
"%llu\n", counter); |
else |
len += snprintf(buf + len, sizeof(smi->buf) - len, |
"%s\n", "error"); |
} |
|
val->value.s = buf; |
val->len = len; |
return 0; |
} |
EXPORT_SYMBOL_GPL(rtl8366_sw_get_port_mib); |
|
int rtl8366_sw_get_port_stats(struct switch_dev *dev, int port, |
struct switch_port_stats *stats, |
int txb_id, int rxb_id) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
unsigned long long counter = 0; |
int ret; |
|
if (port >= smi->num_ports) |
return -EINVAL; |
|
ret = smi->ops->get_mib_counter(smi, txb_id, port, &counter); |
if (ret) |
return ret; |
|
stats->tx_bytes = counter; |
|
ret = smi->ops->get_mib_counter(smi, rxb_id, port, &counter); |
if (ret) |
return ret; |
|
stats->rx_bytes = counter; |
|
return 0; |
} |
EXPORT_SYMBOL_GPL(rtl8366_sw_get_port_stats); |
|
int rtl8366_sw_get_vlan_info(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
int i; |
u32 len = 0; |
struct rtl8366_vlan_4k vlan4k; |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
char *buf = smi->buf; |
int err; |
|
if (!smi->ops->is_vlan_valid(smi, val->port_vlan)) |
return -EINVAL; |
|
memset(buf, '\0', sizeof(smi->buf)); |
|
err = smi->ops->get_vlan_4k(smi, val->port_vlan, &vlan4k); |
if (err) |
return err; |
|
len += snprintf(buf + len, sizeof(smi->buf) - len, |
"VLAN %d: Ports: '", vlan4k.vid); |
|
for (i = 0; i < smi->num_ports; i++) { |
if (!(vlan4k.member & (1 << i))) |
continue; |
|
len += snprintf(buf + len, sizeof(smi->buf) - len, "%d%s", i, |
(vlan4k.untag & (1 << i)) ? "" : "t"); |
} |
|
len += snprintf(buf + len, sizeof(smi->buf) - len, |
"', members=%04x, untag=%04x, fid=%u", |
vlan4k.member, vlan4k.untag, vlan4k.fid); |
|
val->value.s = buf; |
val->len = len; |
|
return 0; |
} |
EXPORT_SYMBOL_GPL(rtl8366_sw_get_vlan_info); |
|
int rtl8366_sw_get_vlan_ports(struct switch_dev *dev, struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
struct switch_port *port; |
struct rtl8366_vlan_4k vlan4k; |
int i; |
|
if (!smi->ops->is_vlan_valid(smi, val->port_vlan)) |
return -EINVAL; |
|
smi->ops->get_vlan_4k(smi, val->port_vlan, &vlan4k); |
|
port = &val->value.ports[0]; |
val->len = 0; |
for (i = 0; i < smi->num_ports; i++) { |
if (!(vlan4k.member & BIT(i))) |
continue; |
|
port->id = i; |
port->flags = (vlan4k.untag & BIT(i)) ? |
0 : BIT(SWITCH_PORT_FLAG_TAGGED); |
val->len++; |
port++; |
} |
return 0; |
} |
EXPORT_SYMBOL_GPL(rtl8366_sw_get_vlan_ports); |
|
int rtl8366_sw_set_vlan_ports(struct switch_dev *dev, struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
struct switch_port *port; |
u32 member = 0; |
u32 untag = 0; |
int err; |
int i; |
|
if (!smi->ops->is_vlan_valid(smi, val->port_vlan)) |
return -EINVAL; |
|
port = &val->value.ports[0]; |
for (i = 0; i < val->len; i++, port++) { |
int pvid = 0; |
member |= BIT(port->id); |
|
if (!(port->flags & BIT(SWITCH_PORT_FLAG_TAGGED))) |
untag |= BIT(port->id); |
|
/* |
* To ensure that we have a valid MC entry for this VLAN, |
* initialize the port VLAN ID here. |
*/ |
err = rtl8366_get_pvid(smi, port->id, &pvid); |
if (err < 0) |
return err; |
if (pvid == 0) { |
err = rtl8366_set_pvid(smi, port->id, val->port_vlan); |
if (err < 0) |
return err; |
} |
} |
|
return rtl8366_set_vlan(smi, val->port_vlan, member, untag, 0); |
} |
EXPORT_SYMBOL_GPL(rtl8366_sw_set_vlan_ports); |
|
int rtl8366_sw_get_vlan_fid(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_vlan_4k vlan4k; |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
int err; |
|
if (!smi->ops->is_vlan_valid(smi, val->port_vlan)) |
return -EINVAL; |
|
err = smi->ops->get_vlan_4k(smi, val->port_vlan, &vlan4k); |
if (err) |
return err; |
|
val->value.i = vlan4k.fid; |
|
return 0; |
} |
EXPORT_SYMBOL_GPL(rtl8366_sw_get_vlan_fid); |
|
int rtl8366_sw_set_vlan_fid(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_vlan_4k vlan4k; |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
int err; |
|
if (!smi->ops->is_vlan_valid(smi, val->port_vlan)) |
return -EINVAL; |
|
if (val->value.i < 0 || val->value.i > attr->max) |
return -EINVAL; |
|
err = smi->ops->get_vlan_4k(smi, val->port_vlan, &vlan4k); |
if (err) |
return err; |
|
return rtl8366_set_vlan(smi, val->port_vlan, |
vlan4k.member, |
vlan4k.untag, |
val->value.i); |
} |
EXPORT_SYMBOL_GPL(rtl8366_sw_set_vlan_fid); |
|
int rtl8366_sw_get_vlan_enable(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
|
if (attr->ofs > 2) |
return -EINVAL; |
|
if (attr->ofs == 1) |
val->value.i = smi->vlan_enabled; |
else |
val->value.i = smi->vlan4k_enabled; |
|
return 0; |
} |
EXPORT_SYMBOL_GPL(rtl8366_sw_get_vlan_enable); |
|
int rtl8366_sw_set_vlan_enable(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
int err; |
|
if (attr->ofs > 2) |
return -EINVAL; |
|
if (attr->ofs == 1) |
err = rtl8366_enable_vlan(smi, val->value.i); |
else |
err = rtl8366_enable_vlan4k(smi, val->value.i); |
|
return err; |
} |
EXPORT_SYMBOL_GPL(rtl8366_sw_set_vlan_enable); |
|
struct rtl8366_smi *rtl8366_smi_alloc(struct device *parent) |
{ |
struct rtl8366_smi *smi; |
|
BUG_ON(!parent); |
|
smi = kzalloc(sizeof(*smi), GFP_KERNEL); |
if (!smi) { |
dev_err(parent, "no memory for private data\n"); |
return NULL; |
} |
|
smi->parent = parent; |
return smi; |
} |
EXPORT_SYMBOL_GPL(rtl8366_smi_alloc); |
|
static int __rtl8366_smi_init(struct rtl8366_smi *smi, const char *name) |
{ |
int err; |
|
err = gpio_request(smi->gpio_sda, name); |
if (err) { |
printk(KERN_ERR "rtl8366_smi: gpio_request failed for %u, err=%d\n", |
smi->gpio_sda, err); |
goto err_out; |
} |
|
err = gpio_request(smi->gpio_sck, name); |
if (err) { |
printk(KERN_ERR "rtl8366_smi: gpio_request failed for %u, err=%d\n", |
smi->gpio_sck, err); |
goto err_free_sda; |
} |
|
spin_lock_init(&smi->lock); |
|
/* start the switch */ |
if (smi->hw_reset) { |
smi->hw_reset(false); |
msleep(RTL8366_SMI_HW_START_DELAY); |
} |
|
return 0; |
|
err_free_sda: |
gpio_free(smi->gpio_sda); |
err_out: |
return err; |
} |
|
static void __rtl8366_smi_cleanup(struct rtl8366_smi *smi) |
{ |
if (smi->hw_reset) |
smi->hw_reset(true); |
|
gpio_free(smi->gpio_sck); |
gpio_free(smi->gpio_sda); |
} |
|
enum rtl8366_type rtl8366_smi_detect(struct rtl8366_platform_data *pdata) |
{ |
static struct rtl8366_smi smi; |
enum rtl8366_type type = RTL8366_TYPE_UNKNOWN; |
u32 reg = 0; |
|
memset(&smi, 0, sizeof(smi)); |
smi.gpio_sda = pdata->gpio_sda; |
smi.gpio_sck = pdata->gpio_sck; |
smi.clk_delay = 10; |
smi.cmd_read = 0xa9; |
smi.cmd_write = 0xa8; |
|
if (__rtl8366_smi_init(&smi, "rtl8366")) |
goto out; |
|
if (rtl8366_smi_read_reg(&smi, 0x5c, ®)) |
goto cleanup; |
|
switch(reg) { |
case 0x6027: |
printk("Found an RTL8366S switch\n"); |
type = RTL8366_TYPE_S; |
break; |
case 0x5937: |
printk("Found an RTL8366RB switch\n"); |
type = RTL8366_TYPE_RB; |
break; |
default: |
printk("Found an Unknown RTL8366 switch (id=0x%04x)\n", reg); |
break; |
} |
|
cleanup: |
__rtl8366_smi_cleanup(&smi); |
out: |
return type; |
} |
|
int rtl8366_smi_init(struct rtl8366_smi *smi) |
{ |
int err; |
|
if (!smi->ops) |
return -EINVAL; |
|
err = __rtl8366_smi_init(smi, dev_name(smi->parent)); |
if (err) |
goto err_out; |
|
dev_info(smi->parent, "using GPIO pins %u (SDA) and %u (SCK)\n", |
smi->gpio_sda, smi->gpio_sck); |
|
err = smi->ops->detect(smi); |
if (err) { |
dev_err(smi->parent, "chip detection failed, err=%d\n", err); |
goto err_free_sck; |
} |
|
err = rtl8366_reset(smi); |
if (err) |
goto err_free_sck; |
|
err = smi->ops->setup(smi); |
if (err) { |
dev_err(smi->parent, "chip setup failed, err=%d\n", err); |
goto err_free_sck; |
} |
|
err = rtl8366_init_vlan(smi); |
if (err) { |
dev_err(smi->parent, "VLAN initialization failed, err=%d\n", |
err); |
goto err_free_sck; |
} |
|
err = rtl8366_enable_all_ports(smi, 1); |
if (err) |
goto err_free_sck; |
|
err = rtl8366_smi_mii_init(smi); |
if (err) |
goto err_free_sck; |
|
rtl8366_debugfs_init(smi); |
|
return 0; |
|
err_free_sck: |
__rtl8366_smi_cleanup(smi); |
err_out: |
return err; |
} |
EXPORT_SYMBOL_GPL(rtl8366_smi_init); |
|
void rtl8366_smi_cleanup(struct rtl8366_smi *smi) |
{ |
rtl8366_debugfs_remove(smi); |
rtl8366_smi_mii_cleanup(smi); |
__rtl8366_smi_cleanup(smi); |
} |
EXPORT_SYMBOL_GPL(rtl8366_smi_cleanup); |
|
#ifdef CONFIG_OF |
int rtl8366_smi_probe_of(struct platform_device *pdev, struct rtl8366_smi *smi) |
{ |
int sck = of_get_named_gpio(pdev->dev.of_node, "gpio-sck", 0); |
int sda = of_get_named_gpio(pdev->dev.of_node, "gpio-sda", 0); |
|
if (!gpio_is_valid(sck) || !gpio_is_valid(sda)) { |
dev_err(&pdev->dev, "gpios missing in devictree\n"); |
return -EINVAL; |
} |
|
smi->gpio_sda = sda; |
smi->gpio_sck = sck; |
|
return 0; |
} |
#else |
static inline int rtl8366_smi_probe_of(struct platform_device *pdev, struct rtl8366_smi *smi) |
{ |
return -ENODEV; |
} |
#endif |
|
int rtl8366_smi_probe_plat(struct platform_device *pdev, struct rtl8366_smi *smi) |
{ |
struct rtl8366_platform_data *pdata = pdev->dev.platform_data; |
|
if (!pdev->dev.platform_data) { |
dev_err(&pdev->dev, "no platform data specified\n"); |
return -EINVAL; |
} |
|
smi->gpio_sda = pdata->gpio_sda; |
smi->gpio_sck = pdata->gpio_sck; |
smi->hw_reset = pdata->hw_reset; |
|
return 0; |
} |
|
|
struct rtl8366_smi *rtl8366_smi_probe(struct platform_device *pdev) |
{ |
struct rtl8366_smi *smi; |
int err; |
|
smi = rtl8366_smi_alloc(&pdev->dev); |
if (!smi) |
return NULL; |
|
if (pdev->dev.of_node) |
err = rtl8366_smi_probe_of(pdev, smi); |
else |
err = rtl8366_smi_probe_plat(pdev, smi); |
|
if (err) |
goto free_smi; |
|
return smi; |
|
free_smi: |
kfree(smi); |
return NULL; |
} |
EXPORT_SYMBOL_GPL(rtl8366_smi_probe); |
|
MODULE_DESCRIPTION("Realtek RTL8366 SMI interface driver"); |
MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>"); |
MODULE_LICENSE("GPL v2"); |
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/rtl8366rb.c |
@@ -0,0 +1,1532 @@ |
/* |
* Platform driver for the Realtek RTL8366RB ethernet switch |
* |
* Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org> |
* Copyright (C) 2010 Antti Seppälä <a.seppala@gmail.com> |
* Copyright (C) 2010 Roman Yeryomin <roman@advem.lv> |
* Copyright (C) 2011 Colin Leitner <colin.leitner@googlemail.com> |
* |
* This program is free software; you can redistribute it and/or modify it |
* under the terms of the GNU General Public License version 2 as published |
* by the Free Software Foundation. |
*/ |
|
#include <linux/kernel.h> |
#include <linux/module.h> |
#include <linux/init.h> |
#include <linux/device.h> |
#include <linux/of.h> |
#include <linux/of_platform.h> |
#include <linux/delay.h> |
#include <linux/skbuff.h> |
#include <linux/rtl8366.h> |
|
#include "rtl8366_smi.h" |
|
#define RTL8366RB_DRIVER_DESC "Realtek RTL8366RB ethernet switch driver" |
#define RTL8366RB_DRIVER_VER "0.2.4" |
|
#define RTL8366RB_PHY_NO_MAX 4 |
#define RTL8366RB_PHY_PAGE_MAX 7 |
#define RTL8366RB_PHY_ADDR_MAX 31 |
|
/* Switch Global Configuration register */ |
#define RTL8366RB_SGCR 0x0000 |
#define RTL8366RB_SGCR_EN_BC_STORM_CTRL BIT(0) |
#define RTL8366RB_SGCR_MAX_LENGTH(_x) (_x << 4) |
#define RTL8366RB_SGCR_MAX_LENGTH_MASK RTL8366RB_SGCR_MAX_LENGTH(0x3) |
#define RTL8366RB_SGCR_MAX_LENGTH_1522 RTL8366RB_SGCR_MAX_LENGTH(0x0) |
#define RTL8366RB_SGCR_MAX_LENGTH_1536 RTL8366RB_SGCR_MAX_LENGTH(0x1) |
#define RTL8366RB_SGCR_MAX_LENGTH_1552 RTL8366RB_SGCR_MAX_LENGTH(0x2) |
#define RTL8366RB_SGCR_MAX_LENGTH_9216 RTL8366RB_SGCR_MAX_LENGTH(0x3) |
#define RTL8366RB_SGCR_EN_VLAN BIT(13) |
#define RTL8366RB_SGCR_EN_VLAN_4KTB BIT(14) |
|
/* Port Enable Control register */ |
#define RTL8366RB_PECR 0x0001 |
|
/* Port Mirror Control Register */ |
#define RTL8366RB_PMCR 0x0007 |
#define RTL8366RB_PMCR_SOURCE_PORT(_x) (_x) |
#define RTL8366RB_PMCR_SOURCE_PORT_MASK 0x000f |
#define RTL8366RB_PMCR_MONITOR_PORT(_x) ((_x) << 4) |
#define RTL8366RB_PMCR_MONITOR_PORT_MASK 0x00f0 |
#define RTL8366RB_PMCR_MIRROR_RX BIT(8) |
#define RTL8366RB_PMCR_MIRROR_TX BIT(9) |
#define RTL8366RB_PMCR_MIRROR_SPC BIT(10) |
#define RTL8366RB_PMCR_MIRROR_ISO BIT(11) |
|
/* Switch Security Control registers */ |
#define RTL8366RB_SSCR0 0x0002 |
#define RTL8366RB_SSCR1 0x0003 |
#define RTL8366RB_SSCR2 0x0004 |
#define RTL8366RB_SSCR2_DROP_UNKNOWN_DA BIT(0) |
|
#define RTL8366RB_RESET_CTRL_REG 0x0100 |
#define RTL8366RB_CHIP_CTRL_RESET_HW 1 |
#define RTL8366RB_CHIP_CTRL_RESET_SW (1 << 1) |
|
#define RTL8366RB_CHIP_VERSION_CTRL_REG 0x050A |
#define RTL8366RB_CHIP_VERSION_MASK 0xf |
#define RTL8366RB_CHIP_ID_REG 0x0509 |
#define RTL8366RB_CHIP_ID_8366 0x5937 |
|
/* PHY registers control */ |
#define RTL8366RB_PHY_ACCESS_CTRL_REG 0x8000 |
#define RTL8366RB_PHY_ACCESS_DATA_REG 0x8002 |
|
#define RTL8366RB_PHY_CTRL_READ 1 |
#define RTL8366RB_PHY_CTRL_WRITE 0 |
|
#define RTL8366RB_PHY_REG_MASK 0x1f |
#define RTL8366RB_PHY_PAGE_OFFSET 5 |
#define RTL8366RB_PHY_PAGE_MASK (0xf << 5) |
#define RTL8366RB_PHY_NO_OFFSET 9 |
#define RTL8366RB_PHY_NO_MASK (0x1f << 9) |
|
#define RTL8366RB_VLAN_INGRESS_CTRL2_REG 0x037f |
|
/* LED control registers */ |
#define RTL8366RB_LED_BLINKRATE_REG 0x0430 |
#define RTL8366RB_LED_BLINKRATE_BIT 0 |
#define RTL8366RB_LED_BLINKRATE_MASK 0x0007 |
|
#define RTL8366RB_LED_CTRL_REG 0x0431 |
#define RTL8366RB_LED_0_1_CTRL_REG 0x0432 |
#define RTL8366RB_LED_2_3_CTRL_REG 0x0433 |
|
#define RTL8366RB_MIB_COUNT 33 |
#define RTL8366RB_GLOBAL_MIB_COUNT 1 |
#define RTL8366RB_MIB_COUNTER_PORT_OFFSET 0x0050 |
#define RTL8366RB_MIB_COUNTER_BASE 0x1000 |
#define RTL8366RB_MIB_CTRL_REG 0x13F0 |
#define RTL8366RB_MIB_CTRL_USER_MASK 0x0FFC |
#define RTL8366RB_MIB_CTRL_BUSY_MASK BIT(0) |
#define RTL8366RB_MIB_CTRL_RESET_MASK BIT(1) |
#define RTL8366RB_MIB_CTRL_PORT_RESET(_p) BIT(2 + (_p)) |
#define RTL8366RB_MIB_CTRL_GLOBAL_RESET BIT(11) |
|
#define RTL8366RB_PORT_VLAN_CTRL_BASE 0x0063 |
#define RTL8366RB_PORT_VLAN_CTRL_REG(_p) \ |
(RTL8366RB_PORT_VLAN_CTRL_BASE + (_p) / 4) |
#define RTL8366RB_PORT_VLAN_CTRL_MASK 0xf |
#define RTL8366RB_PORT_VLAN_CTRL_SHIFT(_p) (4 * ((_p) % 4)) |
|
|
#define RTL8366RB_VLAN_TABLE_READ_BASE 0x018C |
#define RTL8366RB_VLAN_TABLE_WRITE_BASE 0x0185 |
|
|
#define RTL8366RB_TABLE_ACCESS_CTRL_REG 0x0180 |
#define RTL8366RB_TABLE_VLAN_READ_CTRL 0x0E01 |
#define RTL8366RB_TABLE_VLAN_WRITE_CTRL 0x0F01 |
|
#define RTL8366RB_VLAN_MC_BASE(_x) (0x0020 + (_x) * 3) |
|
|
#define RTL8366RB_PORT_LINK_STATUS_BASE 0x0014 |
#define RTL8366RB_PORT_STATUS_SPEED_MASK 0x0003 |
#define RTL8366RB_PORT_STATUS_DUPLEX_MASK 0x0004 |
#define RTL8366RB_PORT_STATUS_LINK_MASK 0x0010 |
#define RTL8366RB_PORT_STATUS_TXPAUSE_MASK 0x0020 |
#define RTL8366RB_PORT_STATUS_RXPAUSE_MASK 0x0040 |
#define RTL8366RB_PORT_STATUS_AN_MASK 0x0080 |
|
|
#define RTL8366RB_PORT_NUM_CPU 5 |
#define RTL8366RB_NUM_PORTS 6 |
#define RTL8366RB_NUM_VLANS 16 |
#define RTL8366RB_NUM_LEDGROUPS 4 |
#define RTL8366RB_NUM_VIDS 4096 |
#define RTL8366RB_PRIORITYMAX 7 |
#define RTL8366RB_FIDMAX 7 |
|
|
#define RTL8366RB_PORT_1 (1 << 0) /* In userspace port 0 */ |
#define RTL8366RB_PORT_2 (1 << 1) /* In userspace port 1 */ |
#define RTL8366RB_PORT_3 (1 << 2) /* In userspace port 2 */ |
#define RTL8366RB_PORT_4 (1 << 3) /* In userspace port 3 */ |
#define RTL8366RB_PORT_5 (1 << 4) /* In userspace port 4 */ |
|
#define RTL8366RB_PORT_CPU (1 << 5) /* CPU port */ |
|
#define RTL8366RB_PORT_ALL (RTL8366RB_PORT_1 | \ |
RTL8366RB_PORT_2 | \ |
RTL8366RB_PORT_3 | \ |
RTL8366RB_PORT_4 | \ |
RTL8366RB_PORT_5 | \ |
RTL8366RB_PORT_CPU) |
|
#define RTL8366RB_PORT_ALL_BUT_CPU (RTL8366RB_PORT_1 | \ |
RTL8366RB_PORT_2 | \ |
RTL8366RB_PORT_3 | \ |
RTL8366RB_PORT_4 | \ |
RTL8366RB_PORT_5) |
|
#define RTL8366RB_PORT_ALL_EXTERNAL (RTL8366RB_PORT_1 | \ |
RTL8366RB_PORT_2 | \ |
RTL8366RB_PORT_3 | \ |
RTL8366RB_PORT_4) |
|
#define RTL8366RB_PORT_ALL_INTERNAL RTL8366RB_PORT_CPU |
|
#define RTL8366RB_VLAN_VID_MASK 0xfff |
#define RTL8366RB_VLAN_PRIORITY_SHIFT 12 |
#define RTL8366RB_VLAN_PRIORITY_MASK 0x7 |
#define RTL8366RB_VLAN_UNTAG_SHIFT 8 |
#define RTL8366RB_VLAN_UNTAG_MASK 0xff |
#define RTL8366RB_VLAN_MEMBER_MASK 0xff |
#define RTL8366RB_VLAN_FID_MASK 0x7 |
|
|
/* Port ingress bandwidth control */ |
#define RTL8366RB_IB_BASE 0x0200 |
#define RTL8366RB_IB_REG(pnum) (RTL8366RB_IB_BASE + pnum) |
#define RTL8366RB_IB_BDTH_MASK 0x3fff |
#define RTL8366RB_IB_PREIFG_OFFSET 14 |
#define RTL8366RB_IB_PREIFG_MASK (1 << RTL8366RB_IB_PREIFG_OFFSET) |
|
/* Port egress bandwidth control */ |
#define RTL8366RB_EB_BASE 0x02d1 |
#define RTL8366RB_EB_REG(pnum) (RTL8366RB_EB_BASE + pnum) |
#define RTL8366RB_EB_BDTH_MASK 0x3fff |
#define RTL8366RB_EB_PREIFG_REG 0x02f8 |
#define RTL8366RB_EB_PREIFG_OFFSET 9 |
#define RTL8366RB_EB_PREIFG_MASK (1 << RTL8366RB_EB_PREIFG_OFFSET) |
|
#define RTL8366RB_BDTH_SW_MAX 1048512 |
#define RTL8366RB_BDTH_UNIT 64 |
#define RTL8366RB_BDTH_REG_DEFAULT 16383 |
|
/* QOS */ |
#define RTL8366RB_QOS_BIT 15 |
#define RTL8366RB_QOS_MASK (1 << RTL8366RB_QOS_BIT) |
/* Include/Exclude Preamble and IFG (20 bytes). 0:Exclude, 1:Include. */ |
#define RTL8366RB_QOS_DEFAULT_PREIFG 1 |
|
|
#define RTL8366RB_MIB_RXB_ID 0 /* IfInOctets */ |
#define RTL8366RB_MIB_TXB_ID 20 /* IfOutOctets */ |
|
static struct rtl8366_mib_counter rtl8366rb_mib_counters[] = { |
{ 0, 0, 4, "IfInOctets" }, |
{ 0, 4, 4, "EtherStatsOctets" }, |
{ 0, 8, 2, "EtherStatsUnderSizePkts" }, |
{ 0, 10, 2, "EtherFragments" }, |
{ 0, 12, 2, "EtherStatsPkts64Octets" }, |
{ 0, 14, 2, "EtherStatsPkts65to127Octets" }, |
{ 0, 16, 2, "EtherStatsPkts128to255Octets" }, |
{ 0, 18, 2, "EtherStatsPkts256to511Octets" }, |
{ 0, 20, 2, "EtherStatsPkts512to1023Octets" }, |
{ 0, 22, 2, "EtherStatsPkts1024to1518Octets" }, |
{ 0, 24, 2, "EtherOversizeStats" }, |
{ 0, 26, 2, "EtherStatsJabbers" }, |
{ 0, 28, 2, "IfInUcastPkts" }, |
{ 0, 30, 2, "EtherStatsMulticastPkts" }, |
{ 0, 32, 2, "EtherStatsBroadcastPkts" }, |
{ 0, 34, 2, "EtherStatsDropEvents" }, |
{ 0, 36, 2, "Dot3StatsFCSErrors" }, |
{ 0, 38, 2, "Dot3StatsSymbolErrors" }, |
{ 0, 40, 2, "Dot3InPauseFrames" }, |
{ 0, 42, 2, "Dot3ControlInUnknownOpcodes" }, |
{ 0, 44, 4, "IfOutOctets" }, |
{ 0, 48, 2, "Dot3StatsSingleCollisionFrames" }, |
{ 0, 50, 2, "Dot3StatMultipleCollisionFrames" }, |
{ 0, 52, 2, "Dot3sDeferredTransmissions" }, |
{ 0, 54, 2, "Dot3StatsLateCollisions" }, |
{ 0, 56, 2, "EtherStatsCollisions" }, |
{ 0, 58, 2, "Dot3StatsExcessiveCollisions" }, |
{ 0, 60, 2, "Dot3OutPauseFrames" }, |
{ 0, 62, 2, "Dot1dBasePortDelayExceededDiscards" }, |
{ 0, 64, 2, "Dot1dTpPortInDiscards" }, |
{ 0, 66, 2, "IfOutUcastPkts" }, |
{ 0, 68, 2, "IfOutMulticastPkts" }, |
{ 0, 70, 2, "IfOutBroadcastPkts" }, |
}; |
|
#define REG_WR(_smi, _reg, _val) \ |
do { \ |
err = rtl8366_smi_write_reg(_smi, _reg, _val); \ |
if (err) \ |
return err; \ |
} while (0) |
|
#define REG_RMW(_smi, _reg, _mask, _val) \ |
do { \ |
err = rtl8366_smi_rmwr(_smi, _reg, _mask, _val); \ |
if (err) \ |
return err; \ |
} while (0) |
|
static int rtl8366rb_reset_chip(struct rtl8366_smi *smi) |
{ |
int timeout = 10; |
u32 data; |
|
rtl8366_smi_write_reg_noack(smi, RTL8366RB_RESET_CTRL_REG, |
RTL8366RB_CHIP_CTRL_RESET_HW); |
do { |
msleep(1); |
if (rtl8366_smi_read_reg(smi, RTL8366RB_RESET_CTRL_REG, &data)) |
return -EIO; |
|
if (!(data & RTL8366RB_CHIP_CTRL_RESET_HW)) |
break; |
} while (--timeout); |
|
if (!timeout) { |
printk("Timeout waiting for the switch to reset\n"); |
return -EIO; |
} |
|
return 0; |
} |
|
static int rtl8366rb_setup(struct rtl8366_smi *smi) |
{ |
int err; |
#ifdef CONFIG_OF |
unsigned i; |
struct device_node *np; |
unsigned num_initvals; |
const __be32 *paddr; |
|
np = smi->parent->of_node; |
|
paddr = of_get_property(np, "realtek,initvals", &num_initvals); |
if (paddr) { |
dev_info(smi->parent, "applying initvals from DTS\n"); |
|
if (num_initvals < (2 * sizeof(*paddr))) |
return -EINVAL; |
|
num_initvals /= sizeof(*paddr); |
|
for (i = 0; i < num_initvals - 1; i += 2) { |
u32 reg = be32_to_cpup(paddr + i); |
u32 val = be32_to_cpup(paddr + i + 1); |
|
REG_WR(smi, reg, val); |
} |
} |
#endif |
|
/* set maximum packet length to 1536 bytes */ |
REG_RMW(smi, RTL8366RB_SGCR, RTL8366RB_SGCR_MAX_LENGTH_MASK, |
RTL8366RB_SGCR_MAX_LENGTH_1536); |
|
/* enable learning for all ports */ |
REG_WR(smi, RTL8366RB_SSCR0, 0); |
|
/* enable auto ageing for all ports */ |
REG_WR(smi, RTL8366RB_SSCR1, 0); |
|
/* |
* discard VLAN tagged packets if the port is not a member of |
* the VLAN with which the packets is associated. |
*/ |
REG_WR(smi, RTL8366RB_VLAN_INGRESS_CTRL2_REG, RTL8366RB_PORT_ALL); |
|
/* don't drop packets whose DA has not been learned */ |
REG_RMW(smi, RTL8366RB_SSCR2, RTL8366RB_SSCR2_DROP_UNKNOWN_DA, 0); |
|
return 0; |
} |
|
static int rtl8366rb_read_phy_reg(struct rtl8366_smi *smi, |
u32 phy_no, u32 page, u32 addr, u32 *data) |
{ |
u32 reg; |
int ret; |
|
if (phy_no > RTL8366RB_PHY_NO_MAX) |
return -EINVAL; |
|
if (page > RTL8366RB_PHY_PAGE_MAX) |
return -EINVAL; |
|
if (addr > RTL8366RB_PHY_ADDR_MAX) |
return -EINVAL; |
|
ret = rtl8366_smi_write_reg(smi, RTL8366RB_PHY_ACCESS_CTRL_REG, |
RTL8366RB_PHY_CTRL_READ); |
if (ret) |
return ret; |
|
reg = 0x8000 | (1 << (phy_no + RTL8366RB_PHY_NO_OFFSET)) | |
((page << RTL8366RB_PHY_PAGE_OFFSET) & RTL8366RB_PHY_PAGE_MASK) | |
(addr & RTL8366RB_PHY_REG_MASK); |
|
ret = rtl8366_smi_write_reg(smi, reg, 0); |
if (ret) |
return ret; |
|
ret = rtl8366_smi_read_reg(smi, RTL8366RB_PHY_ACCESS_DATA_REG, data); |
if (ret) |
return ret; |
|
return 0; |
} |
|
static int rtl8366rb_write_phy_reg(struct rtl8366_smi *smi, |
u32 phy_no, u32 page, u32 addr, u32 data) |
{ |
u32 reg; |
int ret; |
|
if (phy_no > RTL8366RB_PHY_NO_MAX) |
return -EINVAL; |
|
if (page > RTL8366RB_PHY_PAGE_MAX) |
return -EINVAL; |
|
if (addr > RTL8366RB_PHY_ADDR_MAX) |
return -EINVAL; |
|
ret = rtl8366_smi_write_reg(smi, RTL8366RB_PHY_ACCESS_CTRL_REG, |
RTL8366RB_PHY_CTRL_WRITE); |
if (ret) |
return ret; |
|
reg = 0x8000 | (1 << (phy_no + RTL8366RB_PHY_NO_OFFSET)) | |
((page << RTL8366RB_PHY_PAGE_OFFSET) & RTL8366RB_PHY_PAGE_MASK) | |
(addr & RTL8366RB_PHY_REG_MASK); |
|
ret = rtl8366_smi_write_reg(smi, reg, data); |
if (ret) |
return ret; |
|
return 0; |
} |
|
static int rtl8366rb_get_mib_counter(struct rtl8366_smi *smi, int counter, |
int port, unsigned long long *val) |
{ |
int i; |
int err; |
u32 addr, data; |
u64 mibvalue; |
|
if (port > RTL8366RB_NUM_PORTS || counter >= RTL8366RB_MIB_COUNT) |
return -EINVAL; |
|
addr = RTL8366RB_MIB_COUNTER_BASE + |
RTL8366RB_MIB_COUNTER_PORT_OFFSET * (port) + |
rtl8366rb_mib_counters[counter].offset; |
|
/* |
* Writing access counter address first |
* then ASIC will prepare 64bits counter wait for being retrived |
*/ |
data = 0; /* writing data will be discard by ASIC */ |
err = rtl8366_smi_write_reg(smi, addr, data); |
if (err) |
return err; |
|
/* read MIB control register */ |
err = rtl8366_smi_read_reg(smi, RTL8366RB_MIB_CTRL_REG, &data); |
if (err) |
return err; |
|
if (data & RTL8366RB_MIB_CTRL_BUSY_MASK) |
return -EBUSY; |
|
if (data & RTL8366RB_MIB_CTRL_RESET_MASK) |
return -EIO; |
|
mibvalue = 0; |
for (i = rtl8366rb_mib_counters[counter].length; i > 0; i--) { |
err = rtl8366_smi_read_reg(smi, addr + (i - 1), &data); |
if (err) |
return err; |
|
mibvalue = (mibvalue << 16) | (data & 0xFFFF); |
} |
|
*val = mibvalue; |
return 0; |
} |
|
static int rtl8366rb_get_vlan_4k(struct rtl8366_smi *smi, u32 vid, |
struct rtl8366_vlan_4k *vlan4k) |
{ |
u32 data[3]; |
int err; |
int i; |
|
memset(vlan4k, '\0', sizeof(struct rtl8366_vlan_4k)); |
|
if (vid >= RTL8366RB_NUM_VIDS) |
return -EINVAL; |
|
/* write VID */ |
err = rtl8366_smi_write_reg(smi, RTL8366RB_VLAN_TABLE_WRITE_BASE, |
vid & RTL8366RB_VLAN_VID_MASK); |
if (err) |
return err; |
|
/* write table access control word */ |
err = rtl8366_smi_write_reg(smi, RTL8366RB_TABLE_ACCESS_CTRL_REG, |
RTL8366RB_TABLE_VLAN_READ_CTRL); |
if (err) |
return err; |
|
for (i = 0; i < 3; i++) { |
err = rtl8366_smi_read_reg(smi, |
RTL8366RB_VLAN_TABLE_READ_BASE + i, |
&data[i]); |
if (err) |
return err; |
} |
|
vlan4k->vid = vid; |
vlan4k->untag = (data[1] >> RTL8366RB_VLAN_UNTAG_SHIFT) & |
RTL8366RB_VLAN_UNTAG_MASK; |
vlan4k->member = data[1] & RTL8366RB_VLAN_MEMBER_MASK; |
vlan4k->fid = data[2] & RTL8366RB_VLAN_FID_MASK; |
|
return 0; |
} |
|
static int rtl8366rb_set_vlan_4k(struct rtl8366_smi *smi, |
const struct rtl8366_vlan_4k *vlan4k) |
{ |
u32 data[3]; |
int err; |
int i; |
|
if (vlan4k->vid >= RTL8366RB_NUM_VIDS || |
vlan4k->member > RTL8366RB_VLAN_MEMBER_MASK || |
vlan4k->untag > RTL8366RB_VLAN_UNTAG_MASK || |
vlan4k->fid > RTL8366RB_FIDMAX) |
return -EINVAL; |
|
data[0] = vlan4k->vid & RTL8366RB_VLAN_VID_MASK; |
data[1] = (vlan4k->member & RTL8366RB_VLAN_MEMBER_MASK) | |
((vlan4k->untag & RTL8366RB_VLAN_UNTAG_MASK) << |
RTL8366RB_VLAN_UNTAG_SHIFT); |
data[2] = vlan4k->fid & RTL8366RB_VLAN_FID_MASK; |
|
for (i = 0; i < 3; i++) { |
err = rtl8366_smi_write_reg(smi, |
RTL8366RB_VLAN_TABLE_WRITE_BASE + i, |
data[i]); |
if (err) |
return err; |
} |
|
/* write table access control word */ |
err = rtl8366_smi_write_reg(smi, RTL8366RB_TABLE_ACCESS_CTRL_REG, |
RTL8366RB_TABLE_VLAN_WRITE_CTRL); |
|
return err; |
} |
|
static int rtl8366rb_get_vlan_mc(struct rtl8366_smi *smi, u32 index, |
struct rtl8366_vlan_mc *vlanmc) |
{ |
u32 data[3]; |
int err; |
int i; |
|
memset(vlanmc, '\0', sizeof(struct rtl8366_vlan_mc)); |
|
if (index >= RTL8366RB_NUM_VLANS) |
return -EINVAL; |
|
for (i = 0; i < 3; i++) { |
err = rtl8366_smi_read_reg(smi, |
RTL8366RB_VLAN_MC_BASE(index) + i, |
&data[i]); |
if (err) |
return err; |
} |
|
vlanmc->vid = data[0] & RTL8366RB_VLAN_VID_MASK; |
vlanmc->priority = (data[0] >> RTL8366RB_VLAN_PRIORITY_SHIFT) & |
RTL8366RB_VLAN_PRIORITY_MASK; |
vlanmc->untag = (data[1] >> RTL8366RB_VLAN_UNTAG_SHIFT) & |
RTL8366RB_VLAN_UNTAG_MASK; |
vlanmc->member = data[1] & RTL8366RB_VLAN_MEMBER_MASK; |
vlanmc->fid = data[2] & RTL8366RB_VLAN_FID_MASK; |
|
return 0; |
} |
|
static int rtl8366rb_set_vlan_mc(struct rtl8366_smi *smi, u32 index, |
const struct rtl8366_vlan_mc *vlanmc) |
{ |
u32 data[3]; |
int err; |
int i; |
|
if (index >= RTL8366RB_NUM_VLANS || |
vlanmc->vid >= RTL8366RB_NUM_VIDS || |
vlanmc->priority > RTL8366RB_PRIORITYMAX || |
vlanmc->member > RTL8366RB_VLAN_MEMBER_MASK || |
vlanmc->untag > RTL8366RB_VLAN_UNTAG_MASK || |
vlanmc->fid > RTL8366RB_FIDMAX) |
return -EINVAL; |
|
data[0] = (vlanmc->vid & RTL8366RB_VLAN_VID_MASK) | |
((vlanmc->priority & RTL8366RB_VLAN_PRIORITY_MASK) << |
RTL8366RB_VLAN_PRIORITY_SHIFT); |
data[1] = (vlanmc->member & RTL8366RB_VLAN_MEMBER_MASK) | |
((vlanmc->untag & RTL8366RB_VLAN_UNTAG_MASK) << |
RTL8366RB_VLAN_UNTAG_SHIFT); |
data[2] = vlanmc->fid & RTL8366RB_VLAN_FID_MASK; |
|
for (i = 0; i < 3; i++) { |
err = rtl8366_smi_write_reg(smi, |
RTL8366RB_VLAN_MC_BASE(index) + i, |
data[i]); |
if (err) |
return err; |
} |
|
return 0; |
} |
|
static int rtl8366rb_get_mc_index(struct rtl8366_smi *smi, int port, int *val) |
{ |
u32 data; |
int err; |
|
if (port >= RTL8366RB_NUM_PORTS) |
return -EINVAL; |
|
err = rtl8366_smi_read_reg(smi, RTL8366RB_PORT_VLAN_CTRL_REG(port), |
&data); |
if (err) |
return err; |
|
*val = (data >> RTL8366RB_PORT_VLAN_CTRL_SHIFT(port)) & |
RTL8366RB_PORT_VLAN_CTRL_MASK; |
|
return 0; |
|
} |
|
static int rtl8366rb_set_mc_index(struct rtl8366_smi *smi, int port, int index) |
{ |
if (port >= RTL8366RB_NUM_PORTS || index >= RTL8366RB_NUM_VLANS) |
return -EINVAL; |
|
return rtl8366_smi_rmwr(smi, RTL8366RB_PORT_VLAN_CTRL_REG(port), |
RTL8366RB_PORT_VLAN_CTRL_MASK << |
RTL8366RB_PORT_VLAN_CTRL_SHIFT(port), |
(index & RTL8366RB_PORT_VLAN_CTRL_MASK) << |
RTL8366RB_PORT_VLAN_CTRL_SHIFT(port)); |
} |
|
static int rtl8366rb_is_vlan_valid(struct rtl8366_smi *smi, unsigned vlan) |
{ |
unsigned max = RTL8366RB_NUM_VLANS; |
|
if (smi->vlan4k_enabled) |
max = RTL8366RB_NUM_VIDS - 1; |
|
if (vlan == 0 || vlan >= max) |
return 0; |
|
return 1; |
} |
|
static int rtl8366rb_enable_vlan(struct rtl8366_smi *smi, int enable) |
{ |
return rtl8366_smi_rmwr(smi, RTL8366RB_SGCR, RTL8366RB_SGCR_EN_VLAN, |
(enable) ? RTL8366RB_SGCR_EN_VLAN : 0); |
} |
|
static int rtl8366rb_enable_vlan4k(struct rtl8366_smi *smi, int enable) |
{ |
return rtl8366_smi_rmwr(smi, RTL8366RB_SGCR, |
RTL8366RB_SGCR_EN_VLAN_4KTB, |
(enable) ? RTL8366RB_SGCR_EN_VLAN_4KTB : 0); |
} |
|
static int rtl8366rb_enable_port(struct rtl8366_smi *smi, int port, int enable) |
{ |
return rtl8366_smi_rmwr(smi, RTL8366RB_PECR, (1 << port), |
(enable) ? 0 : (1 << port)); |
} |
|
static int rtl8366rb_sw_reset_mibs(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
|
return rtl8366_smi_rmwr(smi, RTL8366RB_MIB_CTRL_REG, 0, |
RTL8366RB_MIB_CTRL_GLOBAL_RESET); |
} |
|
static int rtl8366rb_sw_get_blinkrate(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
u32 data; |
|
rtl8366_smi_read_reg(smi, RTL8366RB_LED_BLINKRATE_REG, &data); |
|
val->value.i = (data & (RTL8366RB_LED_BLINKRATE_MASK)); |
|
return 0; |
} |
|
static int rtl8366rb_sw_set_blinkrate(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
|
if (val->value.i >= 6) |
return -EINVAL; |
|
return rtl8366_smi_rmwr(smi, RTL8366RB_LED_BLINKRATE_REG, |
RTL8366RB_LED_BLINKRATE_MASK, |
val->value.i); |
} |
|
static int rtl8366rb_sw_get_learning_enable(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
u32 data; |
|
rtl8366_smi_read_reg(smi, RTL8366RB_SSCR0, &data); |
val->value.i = !data; |
|
return 0; |
} |
|
|
static int rtl8366rb_sw_set_learning_enable(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
u32 portmask = 0; |
int err = 0; |
|
if (!val->value.i) |
portmask = RTL8366RB_PORT_ALL; |
|
/* set learning for all ports */ |
REG_WR(smi, RTL8366RB_SSCR0, portmask); |
|
/* set auto ageing for all ports */ |
REG_WR(smi, RTL8366RB_SSCR1, portmask); |
|
return 0; |
} |
|
static int rtl8366rb_sw_get_port_link(struct switch_dev *dev, |
int port, |
struct switch_port_link *link) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
u32 data = 0; |
u32 speed; |
|
if (port >= RTL8366RB_NUM_PORTS) |
return -EINVAL; |
|
rtl8366_smi_read_reg(smi, RTL8366RB_PORT_LINK_STATUS_BASE + (port / 2), |
&data); |
|
if (port % 2) |
data = data >> 8; |
|
link->link = !!(data & RTL8366RB_PORT_STATUS_LINK_MASK); |
if (!link->link) |
return 0; |
|
link->duplex = !!(data & RTL8366RB_PORT_STATUS_DUPLEX_MASK); |
link->rx_flow = !!(data & RTL8366RB_PORT_STATUS_RXPAUSE_MASK); |
link->tx_flow = !!(data & RTL8366RB_PORT_STATUS_TXPAUSE_MASK); |
link->aneg = !!(data & RTL8366RB_PORT_STATUS_AN_MASK); |
|
speed = (data & RTL8366RB_PORT_STATUS_SPEED_MASK); |
switch (speed) { |
case 0: |
link->speed = SWITCH_PORT_SPEED_10; |
break; |
case 1: |
link->speed = SWITCH_PORT_SPEED_100; |
break; |
case 2: |
link->speed = SWITCH_PORT_SPEED_1000; |
break; |
default: |
link->speed = SWITCH_PORT_SPEED_UNKNOWN; |
break; |
} |
|
return 0; |
} |
|
static int rtl8366rb_sw_set_port_led(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
u32 data; |
u32 mask; |
u32 reg; |
|
if (val->port_vlan >= RTL8366RB_NUM_PORTS) |
return -EINVAL; |
|
if (val->port_vlan == RTL8366RB_PORT_NUM_CPU) { |
reg = RTL8366RB_LED_BLINKRATE_REG; |
mask = 0xF << 4; |
data = val->value.i << 4; |
} else { |
reg = RTL8366RB_LED_CTRL_REG; |
mask = 0xF << (val->port_vlan * 4), |
data = val->value.i << (val->port_vlan * 4); |
} |
|
return rtl8366_smi_rmwr(smi, reg, mask, data); |
} |
|
static int rtl8366rb_sw_get_port_led(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
u32 data = 0; |
|
if (val->port_vlan >= RTL8366RB_NUM_LEDGROUPS) |
return -EINVAL; |
|
rtl8366_smi_read_reg(smi, RTL8366RB_LED_CTRL_REG, &data); |
val->value.i = (data >> (val->port_vlan * 4)) & 0x000F; |
|
return 0; |
} |
|
static int rtl8366rb_sw_set_port_disable(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
u32 mask, data; |
|
if (val->port_vlan >= RTL8366RB_NUM_PORTS) |
return -EINVAL; |
|
mask = 1 << val->port_vlan ; |
if (val->value.i) |
data = mask; |
else |
data = 0; |
|
return rtl8366_smi_rmwr(smi, RTL8366RB_PECR, mask, data); |
} |
|
static int rtl8366rb_sw_get_port_disable(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
u32 data; |
|
if (val->port_vlan >= RTL8366RB_NUM_PORTS) |
return -EINVAL; |
|
rtl8366_smi_read_reg(smi, RTL8366RB_PECR, &data); |
if (data & (1 << val->port_vlan)) |
val->value.i = 1; |
else |
val->value.i = 0; |
|
return 0; |
} |
|
static int rtl8366rb_sw_set_port_rate_in(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
|
if (val->port_vlan >= RTL8366RB_NUM_PORTS) |
return -EINVAL; |
|
if (val->value.i > 0 && val->value.i < RTL8366RB_BDTH_SW_MAX) |
val->value.i = (val->value.i - 1) / RTL8366RB_BDTH_UNIT; |
else |
val->value.i = RTL8366RB_BDTH_REG_DEFAULT; |
|
return rtl8366_smi_rmwr(smi, RTL8366RB_IB_REG(val->port_vlan), |
RTL8366RB_IB_BDTH_MASK | RTL8366RB_IB_PREIFG_MASK, |
val->value.i | |
(RTL8366RB_QOS_DEFAULT_PREIFG << RTL8366RB_IB_PREIFG_OFFSET)); |
|
} |
|
static int rtl8366rb_sw_get_port_rate_in(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
u32 data; |
|
if (val->port_vlan >= RTL8366RB_NUM_PORTS) |
return -EINVAL; |
|
rtl8366_smi_read_reg(smi, RTL8366RB_IB_REG(val->port_vlan), &data); |
data &= RTL8366RB_IB_BDTH_MASK; |
if (data < RTL8366RB_IB_BDTH_MASK) |
data += 1; |
|
val->value.i = (int)data * RTL8366RB_BDTH_UNIT; |
|
return 0; |
} |
|
static int rtl8366rb_sw_set_port_rate_out(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
|
if (val->port_vlan >= RTL8366RB_NUM_PORTS) |
return -EINVAL; |
|
rtl8366_smi_rmwr(smi, RTL8366RB_EB_PREIFG_REG, |
RTL8366RB_EB_PREIFG_MASK, |
(RTL8366RB_QOS_DEFAULT_PREIFG << RTL8366RB_EB_PREIFG_OFFSET)); |
|
if (val->value.i > 0 && val->value.i < RTL8366RB_BDTH_SW_MAX) |
val->value.i = (val->value.i - 1) / RTL8366RB_BDTH_UNIT; |
else |
val->value.i = RTL8366RB_BDTH_REG_DEFAULT; |
|
return rtl8366_smi_rmwr(smi, RTL8366RB_EB_REG(val->port_vlan), |
RTL8366RB_EB_BDTH_MASK, val->value.i ); |
|
} |
|
static int rtl8366rb_sw_get_port_rate_out(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
u32 data; |
|
if (val->port_vlan >= RTL8366RB_NUM_PORTS) |
return -EINVAL; |
|
rtl8366_smi_read_reg(smi, RTL8366RB_EB_REG(val->port_vlan), &data); |
data &= RTL8366RB_EB_BDTH_MASK; |
if (data < RTL8366RB_EB_BDTH_MASK) |
data += 1; |
|
val->value.i = (int)data * RTL8366RB_BDTH_UNIT; |
|
return 0; |
} |
|
static int rtl8366rb_sw_set_qos_enable(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
u32 data; |
|
if (val->value.i) |
data = RTL8366RB_QOS_MASK; |
else |
data = 0; |
|
return rtl8366_smi_rmwr(smi, RTL8366RB_SGCR, RTL8366RB_QOS_MASK, data); |
} |
|
static int rtl8366rb_sw_get_qos_enable(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
u32 data; |
|
rtl8366_smi_read_reg(smi, RTL8366RB_SGCR, &data); |
if (data & RTL8366RB_QOS_MASK) |
val->value.i = 1; |
else |
val->value.i = 0; |
|
return 0; |
} |
|
static int rtl8366rb_sw_set_mirror_rx_enable(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
u32 data; |
|
if (val->value.i) |
data = RTL8366RB_PMCR_MIRROR_RX; |
else |
data = 0; |
|
return rtl8366_smi_rmwr(smi, RTL8366RB_PMCR, RTL8366RB_PMCR_MIRROR_RX, data); |
} |
|
static int rtl8366rb_sw_get_mirror_rx_enable(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
u32 data; |
|
rtl8366_smi_read_reg(smi, RTL8366RB_PMCR, &data); |
if (data & RTL8366RB_PMCR_MIRROR_RX) |
val->value.i = 1; |
else |
val->value.i = 0; |
|
return 0; |
} |
|
static int rtl8366rb_sw_set_mirror_tx_enable(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
u32 data; |
|
if (val->value.i) |
data = RTL8366RB_PMCR_MIRROR_TX; |
else |
data = 0; |
|
return rtl8366_smi_rmwr(smi, RTL8366RB_PMCR, RTL8366RB_PMCR_MIRROR_TX, data); |
} |
|
static int rtl8366rb_sw_get_mirror_tx_enable(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
u32 data; |
|
rtl8366_smi_read_reg(smi, RTL8366RB_PMCR, &data); |
if (data & RTL8366RB_PMCR_MIRROR_TX) |
val->value.i = 1; |
else |
val->value.i = 0; |
|
return 0; |
} |
|
static int rtl8366rb_sw_set_monitor_isolation_enable(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
u32 data; |
|
if (val->value.i) |
data = RTL8366RB_PMCR_MIRROR_ISO; |
else |
data = 0; |
|
return rtl8366_smi_rmwr(smi, RTL8366RB_PMCR, RTL8366RB_PMCR_MIRROR_ISO, data); |
} |
|
static int rtl8366rb_sw_get_monitor_isolation_enable(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
u32 data; |
|
rtl8366_smi_read_reg(smi, RTL8366RB_PMCR, &data); |
if (data & RTL8366RB_PMCR_MIRROR_ISO) |
val->value.i = 1; |
else |
val->value.i = 0; |
|
return 0; |
} |
|
static int rtl8366rb_sw_set_mirror_pause_frames_enable(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
u32 data; |
|
if (val->value.i) |
data = RTL8366RB_PMCR_MIRROR_SPC; |
else |
data = 0; |
|
return rtl8366_smi_rmwr(smi, RTL8366RB_PMCR, RTL8366RB_PMCR_MIRROR_SPC, data); |
} |
|
static int rtl8366rb_sw_get_mirror_pause_frames_enable(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
u32 data; |
|
rtl8366_smi_read_reg(smi, RTL8366RB_PMCR, &data); |
if (data & RTL8366RB_PMCR_MIRROR_SPC) |
val->value.i = 1; |
else |
val->value.i = 0; |
|
return 0; |
} |
|
static int rtl8366rb_sw_set_mirror_monitor_port(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
u32 data; |
|
data = RTL8366RB_PMCR_MONITOR_PORT(val->value.i); |
|
return rtl8366_smi_rmwr(smi, RTL8366RB_PMCR, RTL8366RB_PMCR_MONITOR_PORT_MASK, data); |
} |
|
static int rtl8366rb_sw_get_mirror_monitor_port(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
u32 data; |
|
rtl8366_smi_read_reg(smi, RTL8366RB_PMCR, &data); |
val->value.i = (data & RTL8366RB_PMCR_MONITOR_PORT_MASK) >> 4; |
|
return 0; |
} |
|
static int rtl8366rb_sw_set_mirror_source_port(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
u32 data; |
|
data = RTL8366RB_PMCR_SOURCE_PORT(val->value.i); |
|
return rtl8366_smi_rmwr(smi, RTL8366RB_PMCR, RTL8366RB_PMCR_SOURCE_PORT_MASK, data); |
} |
|
static int rtl8366rb_sw_get_mirror_source_port(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
u32 data; |
|
rtl8366_smi_read_reg(smi, RTL8366RB_PMCR, &data); |
val->value.i = data & RTL8366RB_PMCR_SOURCE_PORT_MASK; |
|
return 0; |
} |
|
static int rtl8366rb_sw_reset_port_mibs(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
|
if (val->port_vlan >= RTL8366RB_NUM_PORTS) |
return -EINVAL; |
|
return rtl8366_smi_rmwr(smi, RTL8366RB_MIB_CTRL_REG, 0, |
RTL8366RB_MIB_CTRL_PORT_RESET(val->port_vlan)); |
} |
|
static int rtl8366rb_sw_get_port_stats(struct switch_dev *dev, int port, |
struct switch_port_stats *stats) |
{ |
return (rtl8366_sw_get_port_stats(dev, port, stats, |
RTL8366RB_MIB_TXB_ID, RTL8366RB_MIB_RXB_ID)); |
} |
|
static struct switch_attr rtl8366rb_globals[] = { |
{ |
.type = SWITCH_TYPE_INT, |
.name = "enable_learning", |
.description = "Enable learning, enable aging", |
.set = rtl8366rb_sw_set_learning_enable, |
.get = rtl8366rb_sw_get_learning_enable, |
.max = 1 |
}, { |
.type = SWITCH_TYPE_INT, |
.name = "enable_vlan", |
.description = "Enable VLAN mode", |
.set = rtl8366_sw_set_vlan_enable, |
.get = rtl8366_sw_get_vlan_enable, |
.max = 1, |
.ofs = 1 |
}, { |
.type = SWITCH_TYPE_INT, |
.name = "enable_vlan4k", |
.description = "Enable VLAN 4K mode", |
.set = rtl8366_sw_set_vlan_enable, |
.get = rtl8366_sw_get_vlan_enable, |
.max = 1, |
.ofs = 2 |
}, { |
.type = SWITCH_TYPE_NOVAL, |
.name = "reset_mibs", |
.description = "Reset all MIB counters", |
.set = rtl8366rb_sw_reset_mibs, |
}, { |
.type = SWITCH_TYPE_INT, |
.name = "blinkrate", |
.description = "Get/Set LED blinking rate (0 = 43ms, 1 = 84ms," |
" 2 = 120ms, 3 = 170ms, 4 = 340ms, 5 = 670ms)", |
.set = rtl8366rb_sw_set_blinkrate, |
.get = rtl8366rb_sw_get_blinkrate, |
.max = 5 |
}, { |
.type = SWITCH_TYPE_INT, |
.name = "enable_qos", |
.description = "Enable QOS", |
.set = rtl8366rb_sw_set_qos_enable, |
.get = rtl8366rb_sw_get_qos_enable, |
.max = 1 |
}, { |
.type = SWITCH_TYPE_INT, |
.name = "enable_mirror_rx", |
.description = "Enable mirroring of RX packets", |
.set = rtl8366rb_sw_set_mirror_rx_enable, |
.get = rtl8366rb_sw_get_mirror_rx_enable, |
.max = 1 |
}, { |
.type = SWITCH_TYPE_INT, |
.name = "enable_mirror_tx", |
.description = "Enable mirroring of TX packets", |
.set = rtl8366rb_sw_set_mirror_tx_enable, |
.get = rtl8366rb_sw_get_mirror_tx_enable, |
.max = 1 |
}, { |
.type = SWITCH_TYPE_INT, |
.name = "enable_monitor_isolation", |
.description = "Enable isolation of monitor port (TX packets will be dropped)", |
.set = rtl8366rb_sw_set_monitor_isolation_enable, |
.get = rtl8366rb_sw_get_monitor_isolation_enable, |
.max = 1 |
}, { |
.type = SWITCH_TYPE_INT, |
.name = "enable_mirror_pause_frames", |
.description = "Enable mirroring of RX pause frames", |
.set = rtl8366rb_sw_set_mirror_pause_frames_enable, |
.get = rtl8366rb_sw_get_mirror_pause_frames_enable, |
.max = 1 |
}, { |
.type = SWITCH_TYPE_INT, |
.name = "mirror_monitor_port", |
.description = "Mirror monitor port", |
.set = rtl8366rb_sw_set_mirror_monitor_port, |
.get = rtl8366rb_sw_get_mirror_monitor_port, |
.max = 5 |
}, { |
.type = SWITCH_TYPE_INT, |
.name = "mirror_source_port", |
.description = "Mirror source port", |
.set = rtl8366rb_sw_set_mirror_source_port, |
.get = rtl8366rb_sw_get_mirror_source_port, |
.max = 5 |
}, |
}; |
|
static struct switch_attr rtl8366rb_port[] = { |
{ |
.type = SWITCH_TYPE_NOVAL, |
.name = "reset_mib", |
.description = "Reset single port MIB counters", |
.set = rtl8366rb_sw_reset_port_mibs, |
}, { |
.type = SWITCH_TYPE_STRING, |
.name = "mib", |
.description = "Get MIB counters for port", |
.max = 33, |
.set = NULL, |
.get = rtl8366_sw_get_port_mib, |
}, { |
.type = SWITCH_TYPE_INT, |
.name = "led", |
.description = "Get/Set port group (0 - 3) led mode (0 - 15)", |
.max = 15, |
.set = rtl8366rb_sw_set_port_led, |
.get = rtl8366rb_sw_get_port_led, |
}, { |
.type = SWITCH_TYPE_INT, |
.name = "disable", |
.description = "Get/Set port state (enabled or disabled)", |
.max = 1, |
.set = rtl8366rb_sw_set_port_disable, |
.get = rtl8366rb_sw_get_port_disable, |
}, { |
.type = SWITCH_TYPE_INT, |
.name = "rate_in", |
.description = "Get/Set port ingress (incoming) bandwidth limit in kbps", |
.max = RTL8366RB_BDTH_SW_MAX, |
.set = rtl8366rb_sw_set_port_rate_in, |
.get = rtl8366rb_sw_get_port_rate_in, |
}, { |
.type = SWITCH_TYPE_INT, |
.name = "rate_out", |
.description = "Get/Set port egress (outgoing) bandwidth limit in kbps", |
.max = RTL8366RB_BDTH_SW_MAX, |
.set = rtl8366rb_sw_set_port_rate_out, |
.get = rtl8366rb_sw_get_port_rate_out, |
}, |
}; |
|
static struct switch_attr rtl8366rb_vlan[] = { |
{ |
.type = SWITCH_TYPE_STRING, |
.name = "info", |
.description = "Get vlan information", |
.max = 1, |
.set = NULL, |
.get = rtl8366_sw_get_vlan_info, |
}, { |
.type = SWITCH_TYPE_INT, |
.name = "fid", |
.description = "Get/Set vlan FID", |
.max = RTL8366RB_FIDMAX, |
.set = rtl8366_sw_set_vlan_fid, |
.get = rtl8366_sw_get_vlan_fid, |
}, |
}; |
|
static const struct switch_dev_ops rtl8366_ops = { |
.attr_global = { |
.attr = rtl8366rb_globals, |
.n_attr = ARRAY_SIZE(rtl8366rb_globals), |
}, |
.attr_port = { |
.attr = rtl8366rb_port, |
.n_attr = ARRAY_SIZE(rtl8366rb_port), |
}, |
.attr_vlan = { |
.attr = rtl8366rb_vlan, |
.n_attr = ARRAY_SIZE(rtl8366rb_vlan), |
}, |
|
.get_vlan_ports = rtl8366_sw_get_vlan_ports, |
.set_vlan_ports = rtl8366_sw_set_vlan_ports, |
.get_port_pvid = rtl8366_sw_get_port_pvid, |
.set_port_pvid = rtl8366_sw_set_port_pvid, |
.reset_switch = rtl8366_sw_reset_switch, |
.get_port_link = rtl8366rb_sw_get_port_link, |
.get_port_stats = rtl8366rb_sw_get_port_stats, |
}; |
|
static int rtl8366rb_switch_init(struct rtl8366_smi *smi) |
{ |
struct switch_dev *dev = &smi->sw_dev; |
int err; |
|
dev->name = "RTL8366RB"; |
dev->cpu_port = RTL8366RB_PORT_NUM_CPU; |
dev->ports = RTL8366RB_NUM_PORTS; |
dev->vlans = RTL8366RB_NUM_VIDS; |
dev->ops = &rtl8366_ops; |
dev->alias = dev_name(smi->parent); |
|
err = register_switch(dev, NULL); |
if (err) |
dev_err(smi->parent, "switch registration failed\n"); |
|
return err; |
} |
|
static void rtl8366rb_switch_cleanup(struct rtl8366_smi *smi) |
{ |
unregister_switch(&smi->sw_dev); |
} |
|
static int rtl8366rb_mii_read(struct mii_bus *bus, int addr, int reg) |
{ |
struct rtl8366_smi *smi = bus->priv; |
u32 val = 0; |
int err; |
|
err = rtl8366rb_read_phy_reg(smi, addr, 0, reg, &val); |
if (err) |
return 0xffff; |
|
return val; |
} |
|
static int rtl8366rb_mii_write(struct mii_bus *bus, int addr, int reg, u16 val) |
{ |
struct rtl8366_smi *smi = bus->priv; |
u32 t; |
int err; |
|
err = rtl8366rb_write_phy_reg(smi, addr, 0, reg, val); |
/* flush write */ |
(void) rtl8366rb_read_phy_reg(smi, addr, 0, reg, &t); |
|
return err; |
} |
|
static int rtl8366rb_detect(struct rtl8366_smi *smi) |
{ |
u32 chip_id = 0; |
u32 chip_ver = 0; |
int ret; |
|
ret = rtl8366_smi_read_reg(smi, RTL8366RB_CHIP_ID_REG, &chip_id); |
if (ret) { |
dev_err(smi->parent, "unable to read chip id\n"); |
return ret; |
} |
|
switch (chip_id) { |
case RTL8366RB_CHIP_ID_8366: |
break; |
default: |
dev_err(smi->parent, "unknown chip id (%04x)\n", chip_id); |
return -ENODEV; |
} |
|
ret = rtl8366_smi_read_reg(smi, RTL8366RB_CHIP_VERSION_CTRL_REG, |
&chip_ver); |
if (ret) { |
dev_err(smi->parent, "unable to read chip version\n"); |
return ret; |
} |
|
dev_info(smi->parent, "RTL%04x ver. %u chip found\n", |
chip_id, chip_ver & RTL8366RB_CHIP_VERSION_MASK); |
|
return 0; |
} |
|
static struct rtl8366_smi_ops rtl8366rb_smi_ops = { |
.detect = rtl8366rb_detect, |
.reset_chip = rtl8366rb_reset_chip, |
.setup = rtl8366rb_setup, |
|
.mii_read = rtl8366rb_mii_read, |
.mii_write = rtl8366rb_mii_write, |
|
.get_vlan_mc = rtl8366rb_get_vlan_mc, |
.set_vlan_mc = rtl8366rb_set_vlan_mc, |
.get_vlan_4k = rtl8366rb_get_vlan_4k, |
.set_vlan_4k = rtl8366rb_set_vlan_4k, |
.get_mc_index = rtl8366rb_get_mc_index, |
.set_mc_index = rtl8366rb_set_mc_index, |
.get_mib_counter = rtl8366rb_get_mib_counter, |
.is_vlan_valid = rtl8366rb_is_vlan_valid, |
.enable_vlan = rtl8366rb_enable_vlan, |
.enable_vlan4k = rtl8366rb_enable_vlan4k, |
.enable_port = rtl8366rb_enable_port, |
}; |
|
static int rtl8366rb_probe(struct platform_device *pdev) |
{ |
static int rtl8366_smi_version_printed; |
struct rtl8366_smi *smi; |
int err; |
|
if (!rtl8366_smi_version_printed++) |
printk(KERN_NOTICE RTL8366RB_DRIVER_DESC |
" version " RTL8366RB_DRIVER_VER"\n"); |
|
smi = rtl8366_smi_probe(pdev); |
if (!smi) |
return -ENODEV; |
|
smi->clk_delay = 10; |
smi->cmd_read = 0xa9; |
smi->cmd_write = 0xa8; |
smi->ops = &rtl8366rb_smi_ops; |
smi->cpu_port = RTL8366RB_PORT_NUM_CPU; |
smi->num_ports = RTL8366RB_NUM_PORTS; |
smi->num_vlan_mc = RTL8366RB_NUM_VLANS; |
smi->mib_counters = rtl8366rb_mib_counters; |
smi->num_mib_counters = ARRAY_SIZE(rtl8366rb_mib_counters); |
|
err = rtl8366_smi_init(smi); |
if (err) |
goto err_free_smi; |
|
platform_set_drvdata(pdev, smi); |
|
err = rtl8366rb_switch_init(smi); |
if (err) |
goto err_clear_drvdata; |
|
return 0; |
|
err_clear_drvdata: |
platform_set_drvdata(pdev, NULL); |
rtl8366_smi_cleanup(smi); |
err_free_smi: |
kfree(smi); |
return err; |
} |
|
static int rtl8366rb_remove(struct platform_device *pdev) |
{ |
struct rtl8366_smi *smi = platform_get_drvdata(pdev); |
|
if (smi) { |
rtl8366rb_switch_cleanup(smi); |
platform_set_drvdata(pdev, NULL); |
rtl8366_smi_cleanup(smi); |
kfree(smi); |
} |
|
return 0; |
} |
|
#ifdef CONFIG_OF |
static const struct of_device_id rtl8366rb_match[] = { |
{ .compatible = "realtek,rtl8366rb" }, |
{}, |
}; |
MODULE_DEVICE_TABLE(of, rtl8366rb_match); |
#endif |
|
static struct platform_driver rtl8366rb_driver = { |
.driver = { |
.name = RTL8366RB_DRIVER_NAME, |
.owner = THIS_MODULE, |
.of_match_table = of_match_ptr(rtl8366rb_match), |
}, |
.probe = rtl8366rb_probe, |
.remove = rtl8366rb_remove, |
}; |
|
static int __init rtl8366rb_module_init(void) |
{ |
return platform_driver_register(&rtl8366rb_driver); |
} |
module_init(rtl8366rb_module_init); |
|
static void __exit rtl8366rb_module_exit(void) |
{ |
platform_driver_unregister(&rtl8366rb_driver); |
} |
module_exit(rtl8366rb_module_exit); |
|
MODULE_DESCRIPTION(RTL8366RB_DRIVER_DESC); |
MODULE_VERSION(RTL8366RB_DRIVER_VER); |
MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>"); |
MODULE_AUTHOR("Antti Seppälä <a.seppala@gmail.com>"); |
MODULE_AUTHOR("Roman Yeryomin <roman@advem.lv>"); |
MODULE_AUTHOR("Colin Leitner <colin.leitner@googlemail.com>"); |
MODULE_LICENSE("GPL v2"); |
MODULE_ALIAS("platform:" RTL8366RB_DRIVER_NAME); |
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/rtl8366s.c |
@@ -0,0 +1,1320 @@ |
/* |
* Platform driver for the Realtek RTL8366S ethernet switch |
* |
* Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org> |
* Copyright (C) 2010 Antti Seppälä <a.seppala@gmail.com> |
* |
* This program is free software; you can redistribute it and/or modify it |
* under the terms of the GNU General Public License version 2 as published |
* by the Free Software Foundation. |
*/ |
|
#include <linux/kernel.h> |
#include <linux/module.h> |
#include <linux/init.h> |
#include <linux/device.h> |
#include <linux/of.h> |
#include <linux/of_platform.h> |
#include <linux/delay.h> |
#include <linux/skbuff.h> |
#include <linux/rtl8366.h> |
|
#include "rtl8366_smi.h" |
|
#define RTL8366S_DRIVER_DESC "Realtek RTL8366S ethernet switch driver" |
#define RTL8366S_DRIVER_VER "0.2.2" |
|
#define RTL8366S_PHY_NO_MAX 4 |
#define RTL8366S_PHY_PAGE_MAX 7 |
#define RTL8366S_PHY_ADDR_MAX 31 |
|
/* Switch Global Configuration register */ |
#define RTL8366S_SGCR 0x0000 |
#define RTL8366S_SGCR_EN_BC_STORM_CTRL BIT(0) |
#define RTL8366S_SGCR_MAX_LENGTH(_x) (_x << 4) |
#define RTL8366S_SGCR_MAX_LENGTH_MASK RTL8366S_SGCR_MAX_LENGTH(0x3) |
#define RTL8366S_SGCR_MAX_LENGTH_1522 RTL8366S_SGCR_MAX_LENGTH(0x0) |
#define RTL8366S_SGCR_MAX_LENGTH_1536 RTL8366S_SGCR_MAX_LENGTH(0x1) |
#define RTL8366S_SGCR_MAX_LENGTH_1552 RTL8366S_SGCR_MAX_LENGTH(0x2) |
#define RTL8366S_SGCR_MAX_LENGTH_16000 RTL8366S_SGCR_MAX_LENGTH(0x3) |
#define RTL8366S_SGCR_EN_VLAN BIT(13) |
|
/* Port Enable Control register */ |
#define RTL8366S_PECR 0x0001 |
|
/* Green Ethernet Feature (based on GPL_BELKIN_F5D8235-4_v1000 v1.01.24) */ |
#define RTL8366S_GREEN_ETHERNET_CTRL_REG 0x000a |
#define RTL8366S_GREEN_ETHERNET_CTRL_MASK 0x0018 |
#define RTL8366S_GREEN_ETHERNET_TX_BIT (1 << 3) |
#define RTL8366S_GREEN_ETHERNET_RX_BIT (1 << 4) |
|
/* Switch Security Control registers */ |
#define RTL8366S_SSCR0 0x0002 |
#define RTL8366S_SSCR1 0x0003 |
#define RTL8366S_SSCR2 0x0004 |
#define RTL8366S_SSCR2_DROP_UNKNOWN_DA BIT(0) |
|
#define RTL8366S_RESET_CTRL_REG 0x0100 |
#define RTL8366S_CHIP_CTRL_RESET_HW 1 |
#define RTL8366S_CHIP_CTRL_RESET_SW (1 << 1) |
|
#define RTL8366S_CHIP_VERSION_CTRL_REG 0x0104 |
#define RTL8366S_CHIP_VERSION_MASK 0xf |
#define RTL8366S_CHIP_ID_REG 0x0105 |
#define RTL8366S_CHIP_ID_8366 0x8366 |
|
/* PHY registers control */ |
#define RTL8366S_PHY_ACCESS_CTRL_REG 0x8028 |
#define RTL8366S_PHY_ACCESS_DATA_REG 0x8029 |
|
#define RTL8366S_PHY_CTRL_READ 1 |
#define RTL8366S_PHY_CTRL_WRITE 0 |
|
#define RTL8366S_PHY_REG_MASK 0x1f |
#define RTL8366S_PHY_PAGE_OFFSET 5 |
#define RTL8366S_PHY_PAGE_MASK (0x7 << 5) |
#define RTL8366S_PHY_NO_OFFSET 9 |
#define RTL8366S_PHY_NO_MASK (0x1f << 9) |
|
/* Green Ethernet Feature for PHY ports */ |
#define RTL8366S_PHY_POWER_SAVING_CTRL_REG 12 |
#define RTL8366S_PHY_POWER_SAVING_MASK 0x1000 |
|
/* LED control registers */ |
#define RTL8366S_LED_BLINKRATE_REG 0x0420 |
#define RTL8366S_LED_BLINKRATE_BIT 0 |
#define RTL8366S_LED_BLINKRATE_MASK 0x0007 |
|
#define RTL8366S_LED_CTRL_REG 0x0421 |
#define RTL8366S_LED_0_1_CTRL_REG 0x0422 |
#define RTL8366S_LED_2_3_CTRL_REG 0x0423 |
|
#define RTL8366S_MIB_COUNT 33 |
#define RTL8366S_GLOBAL_MIB_COUNT 1 |
#define RTL8366S_MIB_COUNTER_PORT_OFFSET 0x0040 |
#define RTL8366S_MIB_COUNTER_BASE 0x1000 |
#define RTL8366S_MIB_COUNTER_PORT_OFFSET2 0x0008 |
#define RTL8366S_MIB_COUNTER_BASE2 0x1180 |
#define RTL8366S_MIB_CTRL_REG 0x11F0 |
#define RTL8366S_MIB_CTRL_USER_MASK 0x01FF |
#define RTL8366S_MIB_CTRL_BUSY_MASK 0x0001 |
#define RTL8366S_MIB_CTRL_RESET_MASK 0x0002 |
|
#define RTL8366S_MIB_CTRL_GLOBAL_RESET_MASK 0x0004 |
#define RTL8366S_MIB_CTRL_PORT_RESET_BIT 0x0003 |
#define RTL8366S_MIB_CTRL_PORT_RESET_MASK 0x01FC |
|
|
#define RTL8366S_PORT_VLAN_CTRL_BASE 0x0058 |
#define RTL8366S_PORT_VLAN_CTRL_REG(_p) \ |
(RTL8366S_PORT_VLAN_CTRL_BASE + (_p) / 4) |
#define RTL8366S_PORT_VLAN_CTRL_MASK 0xf |
#define RTL8366S_PORT_VLAN_CTRL_SHIFT(_p) (4 * ((_p) % 4)) |
|
|
#define RTL8366S_VLAN_TABLE_READ_BASE 0x018B |
#define RTL8366S_VLAN_TABLE_WRITE_BASE 0x0185 |
|
#define RTL8366S_VLAN_TB_CTRL_REG 0x010F |
|
#define RTL8366S_TABLE_ACCESS_CTRL_REG 0x0180 |
#define RTL8366S_TABLE_VLAN_READ_CTRL 0x0E01 |
#define RTL8366S_TABLE_VLAN_WRITE_CTRL 0x0F01 |
|
#define RTL8366S_VLAN_MC_BASE(_x) (0x0016 + (_x) * 2) |
|
#define RTL8366S_VLAN_MEMBERINGRESS_REG 0x0379 |
|
#define RTL8366S_PORT_LINK_STATUS_BASE 0x0060 |
#define RTL8366S_PORT_STATUS_SPEED_MASK 0x0003 |
#define RTL8366S_PORT_STATUS_DUPLEX_MASK 0x0004 |
#define RTL8366S_PORT_STATUS_LINK_MASK 0x0010 |
#define RTL8366S_PORT_STATUS_TXPAUSE_MASK 0x0020 |
#define RTL8366S_PORT_STATUS_RXPAUSE_MASK 0x0040 |
#define RTL8366S_PORT_STATUS_AN_MASK 0x0080 |
|
|
#define RTL8366S_PORT_NUM_CPU 5 |
#define RTL8366S_NUM_PORTS 6 |
#define RTL8366S_NUM_VLANS 16 |
#define RTL8366S_NUM_LEDGROUPS 4 |
#define RTL8366S_NUM_VIDS 4096 |
#define RTL8366S_PRIORITYMAX 7 |
#define RTL8366S_FIDMAX 7 |
|
|
#define RTL8366S_PORT_1 (1 << 0) /* In userspace port 0 */ |
#define RTL8366S_PORT_2 (1 << 1) /* In userspace port 1 */ |
#define RTL8366S_PORT_3 (1 << 2) /* In userspace port 2 */ |
#define RTL8366S_PORT_4 (1 << 3) /* In userspace port 3 */ |
|
#define RTL8366S_PORT_UNKNOWN (1 << 4) /* No known connection */ |
#define RTL8366S_PORT_CPU (1 << 5) /* CPU port */ |
|
#define RTL8366S_PORT_ALL (RTL8366S_PORT_1 | \ |
RTL8366S_PORT_2 | \ |
RTL8366S_PORT_3 | \ |
RTL8366S_PORT_4 | \ |
RTL8366S_PORT_UNKNOWN | \ |
RTL8366S_PORT_CPU) |
|
#define RTL8366S_PORT_ALL_BUT_CPU (RTL8366S_PORT_1 | \ |
RTL8366S_PORT_2 | \ |
RTL8366S_PORT_3 | \ |
RTL8366S_PORT_4 | \ |
RTL8366S_PORT_UNKNOWN) |
|
#define RTL8366S_PORT_ALL_EXTERNAL (RTL8366S_PORT_1 | \ |
RTL8366S_PORT_2 | \ |
RTL8366S_PORT_3 | \ |
RTL8366S_PORT_4) |
|
#define RTL8366S_PORT_ALL_INTERNAL (RTL8366S_PORT_UNKNOWN | \ |
RTL8366S_PORT_CPU) |
|
#define RTL8366S_VLAN_VID_MASK 0xfff |
#define RTL8366S_VLAN_PRIORITY_SHIFT 12 |
#define RTL8366S_VLAN_PRIORITY_MASK 0x7 |
#define RTL8366S_VLAN_MEMBER_MASK 0x3f |
#define RTL8366S_VLAN_UNTAG_SHIFT 6 |
#define RTL8366S_VLAN_UNTAG_MASK 0x3f |
#define RTL8366S_VLAN_FID_SHIFT 12 |
#define RTL8366S_VLAN_FID_MASK 0x7 |
|
#define RTL8366S_MIB_RXB_ID 0 /* IfInOctets */ |
#define RTL8366S_MIB_TXB_ID 20 /* IfOutOctets */ |
|
static struct rtl8366_mib_counter rtl8366s_mib_counters[] = { |
{ 0, 0, 4, "IfInOctets" }, |
{ 0, 4, 4, "EtherStatsOctets" }, |
{ 0, 8, 2, "EtherStatsUnderSizePkts" }, |
{ 0, 10, 2, "EtherFragments" }, |
{ 0, 12, 2, "EtherStatsPkts64Octets" }, |
{ 0, 14, 2, "EtherStatsPkts65to127Octets" }, |
{ 0, 16, 2, "EtherStatsPkts128to255Octets" }, |
{ 0, 18, 2, "EtherStatsPkts256to511Octets" }, |
{ 0, 20, 2, "EtherStatsPkts512to1023Octets" }, |
{ 0, 22, 2, "EtherStatsPkts1024to1518Octets" }, |
{ 0, 24, 2, "EtherOversizeStats" }, |
{ 0, 26, 2, "EtherStatsJabbers" }, |
{ 0, 28, 2, "IfInUcastPkts" }, |
{ 0, 30, 2, "EtherStatsMulticastPkts" }, |
{ 0, 32, 2, "EtherStatsBroadcastPkts" }, |
{ 0, 34, 2, "EtherStatsDropEvents" }, |
{ 0, 36, 2, "Dot3StatsFCSErrors" }, |
{ 0, 38, 2, "Dot3StatsSymbolErrors" }, |
{ 0, 40, 2, "Dot3InPauseFrames" }, |
{ 0, 42, 2, "Dot3ControlInUnknownOpcodes" }, |
{ 0, 44, 4, "IfOutOctets" }, |
{ 0, 48, 2, "Dot3StatsSingleCollisionFrames" }, |
{ 0, 50, 2, "Dot3StatMultipleCollisionFrames" }, |
{ 0, 52, 2, "Dot3sDeferredTransmissions" }, |
{ 0, 54, 2, "Dot3StatsLateCollisions" }, |
{ 0, 56, 2, "EtherStatsCollisions" }, |
{ 0, 58, 2, "Dot3StatsExcessiveCollisions" }, |
{ 0, 60, 2, "Dot3OutPauseFrames" }, |
{ 0, 62, 2, "Dot1dBasePortDelayExceededDiscards" }, |
|
/* |
* The following counters are accessible at a different |
* base address. |
*/ |
{ 1, 0, 2, "Dot1dTpPortInDiscards" }, |
{ 1, 2, 2, "IfOutUcastPkts" }, |
{ 1, 4, 2, "IfOutMulticastPkts" }, |
{ 1, 6, 2, "IfOutBroadcastPkts" }, |
}; |
|
#define REG_WR(_smi, _reg, _val) \ |
do { \ |
err = rtl8366_smi_write_reg(_smi, _reg, _val); \ |
if (err) \ |
return err; \ |
} while (0) |
|
#define REG_RMW(_smi, _reg, _mask, _val) \ |
do { \ |
err = rtl8366_smi_rmwr(_smi, _reg, _mask, _val); \ |
if (err) \ |
return err; \ |
} while (0) |
|
static int rtl8366s_reset_chip(struct rtl8366_smi *smi) |
{ |
int timeout = 10; |
u32 data; |
|
rtl8366_smi_write_reg_noack(smi, RTL8366S_RESET_CTRL_REG, |
RTL8366S_CHIP_CTRL_RESET_HW); |
do { |
msleep(1); |
if (rtl8366_smi_read_reg(smi, RTL8366S_RESET_CTRL_REG, &data)) |
return -EIO; |
|
if (!(data & RTL8366S_CHIP_CTRL_RESET_HW)) |
break; |
} while (--timeout); |
|
if (!timeout) { |
printk("Timeout waiting for the switch to reset\n"); |
return -EIO; |
} |
|
return 0; |
} |
|
static int rtl8366s_read_phy_reg(struct rtl8366_smi *smi, |
u32 phy_no, u32 page, u32 addr, u32 *data) |
{ |
u32 reg; |
int ret; |
|
if (phy_no > RTL8366S_PHY_NO_MAX) |
return -EINVAL; |
|
if (page > RTL8366S_PHY_PAGE_MAX) |
return -EINVAL; |
|
if (addr > RTL8366S_PHY_ADDR_MAX) |
return -EINVAL; |
|
ret = rtl8366_smi_write_reg(smi, RTL8366S_PHY_ACCESS_CTRL_REG, |
RTL8366S_PHY_CTRL_READ); |
if (ret) |
return ret; |
|
reg = 0x8000 | (1 << (phy_no + RTL8366S_PHY_NO_OFFSET)) | |
((page << RTL8366S_PHY_PAGE_OFFSET) & RTL8366S_PHY_PAGE_MASK) | |
(addr & RTL8366S_PHY_REG_MASK); |
|
ret = rtl8366_smi_write_reg(smi, reg, 0); |
if (ret) |
return ret; |
|
ret = rtl8366_smi_read_reg(smi, RTL8366S_PHY_ACCESS_DATA_REG, data); |
if (ret) |
return ret; |
|
return 0; |
} |
|
static int rtl8366s_write_phy_reg(struct rtl8366_smi *smi, |
u32 phy_no, u32 page, u32 addr, u32 data) |
{ |
u32 reg; |
int ret; |
|
if (phy_no > RTL8366S_PHY_NO_MAX) |
return -EINVAL; |
|
if (page > RTL8366S_PHY_PAGE_MAX) |
return -EINVAL; |
|
if (addr > RTL8366S_PHY_ADDR_MAX) |
return -EINVAL; |
|
ret = rtl8366_smi_write_reg(smi, RTL8366S_PHY_ACCESS_CTRL_REG, |
RTL8366S_PHY_CTRL_WRITE); |
if (ret) |
return ret; |
|
reg = 0x8000 | (1 << (phy_no + RTL8366S_PHY_NO_OFFSET)) | |
((page << RTL8366S_PHY_PAGE_OFFSET) & RTL8366S_PHY_PAGE_MASK) | |
(addr & RTL8366S_PHY_REG_MASK); |
|
ret = rtl8366_smi_write_reg(smi, reg, data); |
if (ret) |
return ret; |
|
return 0; |
} |
|
static int rtl8366s_set_green_port(struct rtl8366_smi *smi, int port, int enable) |
{ |
int err; |
u32 phyData; |
|
if (port >= RTL8366S_NUM_PORTS) |
return -EINVAL; |
|
err = rtl8366s_read_phy_reg(smi, port, 0, RTL8366S_PHY_POWER_SAVING_CTRL_REG, &phyData); |
if (err) |
return err; |
|
if (enable) |
phyData |= RTL8366S_PHY_POWER_SAVING_MASK; |
else |
phyData &= ~RTL8366S_PHY_POWER_SAVING_MASK; |
|
err = rtl8366s_write_phy_reg(smi, port, 0, RTL8366S_PHY_POWER_SAVING_CTRL_REG, phyData); |
if (err) |
return err; |
|
return 0; |
} |
|
static int rtl8366s_set_green(struct rtl8366_smi *smi, int enable) |
{ |
int err; |
unsigned i; |
u32 data = 0; |
|
if (!enable) { |
for (i = 0; i <= RTL8366S_PHY_NO_MAX; i++) { |
rtl8366s_set_green_port(smi, i, 0); |
} |
} |
|
if (enable) |
data = (RTL8366S_GREEN_ETHERNET_TX_BIT | RTL8366S_GREEN_ETHERNET_RX_BIT); |
|
REG_RMW(smi, RTL8366S_GREEN_ETHERNET_CTRL_REG, RTL8366S_GREEN_ETHERNET_CTRL_MASK, data); |
|
return 0; |
} |
|
static int rtl8366s_setup(struct rtl8366_smi *smi) |
{ |
struct rtl8366_platform_data *pdata; |
int err; |
unsigned i; |
#ifdef CONFIG_OF |
struct device_node *np; |
unsigned num_initvals; |
const __be32 *paddr; |
#endif |
|
pdata = smi->parent->platform_data; |
if (pdata && pdata->num_initvals && pdata->initvals) { |
dev_info(smi->parent, "applying initvals\n"); |
for (i = 0; i < pdata->num_initvals; i++) |
REG_WR(smi, pdata->initvals[i].reg, |
pdata->initvals[i].val); |
} |
|
#ifdef CONFIG_OF |
np = smi->parent->of_node; |
|
paddr = of_get_property(np, "realtek,initvals", &num_initvals); |
if (paddr) { |
dev_info(smi->parent, "applying initvals from DTS\n"); |
|
if (num_initvals < (2 * sizeof(*paddr))) |
return -EINVAL; |
|
num_initvals /= sizeof(*paddr); |
|
for (i = 0; i < num_initvals - 1; i += 2) { |
u32 reg = be32_to_cpup(paddr + i); |
u32 val = be32_to_cpup(paddr + i + 1); |
|
REG_WR(smi, reg, val); |
} |
} |
|
if (of_property_read_bool(np, "realtek,green-ethernet-features")) { |
dev_info(smi->parent, "activating Green Ethernet features\n"); |
|
err = rtl8366s_set_green(smi, 1); |
if (err) |
return err; |
|
for (i = 0; i <= RTL8366S_PHY_NO_MAX; i++) { |
err = rtl8366s_set_green_port(smi, i, 1); |
if (err) |
return err; |
} |
} |
#endif |
|
/* set maximum packet length to 1536 bytes */ |
REG_RMW(smi, RTL8366S_SGCR, RTL8366S_SGCR_MAX_LENGTH_MASK, |
RTL8366S_SGCR_MAX_LENGTH_1536); |
|
/* enable learning for all ports */ |
REG_WR(smi, RTL8366S_SSCR0, 0); |
|
/* enable auto ageing for all ports */ |
REG_WR(smi, RTL8366S_SSCR1, 0); |
|
/* |
* discard VLAN tagged packets if the port is not a member of |
* the VLAN with which the packets is associated. |
*/ |
REG_WR(smi, RTL8366S_VLAN_MEMBERINGRESS_REG, RTL8366S_PORT_ALL); |
|
/* don't drop packets whose DA has not been learned */ |
REG_RMW(smi, RTL8366S_SSCR2, RTL8366S_SSCR2_DROP_UNKNOWN_DA, 0); |
|
return 0; |
} |
|
static int rtl8366_get_mib_counter(struct rtl8366_smi *smi, int counter, |
int port, unsigned long long *val) |
{ |
int i; |
int err; |
u32 addr, data; |
u64 mibvalue; |
|
if (port > RTL8366S_NUM_PORTS || counter >= RTL8366S_MIB_COUNT) |
return -EINVAL; |
|
switch (rtl8366s_mib_counters[counter].base) { |
case 0: |
addr = RTL8366S_MIB_COUNTER_BASE + |
RTL8366S_MIB_COUNTER_PORT_OFFSET * port; |
break; |
|
case 1: |
addr = RTL8366S_MIB_COUNTER_BASE2 + |
RTL8366S_MIB_COUNTER_PORT_OFFSET2 * port; |
break; |
|
default: |
return -EINVAL; |
} |
|
addr += rtl8366s_mib_counters[counter].offset; |
|
/* |
* Writing access counter address first |
* then ASIC will prepare 64bits counter wait for being retrived |
*/ |
data = 0; /* writing data will be discard by ASIC */ |
err = rtl8366_smi_write_reg(smi, addr, data); |
if (err) |
return err; |
|
/* read MIB control register */ |
err = rtl8366_smi_read_reg(smi, RTL8366S_MIB_CTRL_REG, &data); |
if (err) |
return err; |
|
if (data & RTL8366S_MIB_CTRL_BUSY_MASK) |
return -EBUSY; |
|
if (data & RTL8366S_MIB_CTRL_RESET_MASK) |
return -EIO; |
|
mibvalue = 0; |
for (i = rtl8366s_mib_counters[counter].length; i > 0; i--) { |
err = rtl8366_smi_read_reg(smi, addr + (i - 1), &data); |
if (err) |
return err; |
|
mibvalue = (mibvalue << 16) | (data & 0xFFFF); |
} |
|
*val = mibvalue; |
return 0; |
} |
|
static int rtl8366s_get_vlan_4k(struct rtl8366_smi *smi, u32 vid, |
struct rtl8366_vlan_4k *vlan4k) |
{ |
u32 data[2]; |
int err; |
int i; |
|
memset(vlan4k, '\0', sizeof(struct rtl8366_vlan_4k)); |
|
if (vid >= RTL8366S_NUM_VIDS) |
return -EINVAL; |
|
/* write VID */ |
err = rtl8366_smi_write_reg(smi, RTL8366S_VLAN_TABLE_WRITE_BASE, |
vid & RTL8366S_VLAN_VID_MASK); |
if (err) |
return err; |
|
/* write table access control word */ |
err = rtl8366_smi_write_reg(smi, RTL8366S_TABLE_ACCESS_CTRL_REG, |
RTL8366S_TABLE_VLAN_READ_CTRL); |
if (err) |
return err; |
|
for (i = 0; i < 2; i++) { |
err = rtl8366_smi_read_reg(smi, |
RTL8366S_VLAN_TABLE_READ_BASE + i, |
&data[i]); |
if (err) |
return err; |
} |
|
vlan4k->vid = vid; |
vlan4k->untag = (data[1] >> RTL8366S_VLAN_UNTAG_SHIFT) & |
RTL8366S_VLAN_UNTAG_MASK; |
vlan4k->member = data[1] & RTL8366S_VLAN_MEMBER_MASK; |
vlan4k->fid = (data[1] >> RTL8366S_VLAN_FID_SHIFT) & |
RTL8366S_VLAN_FID_MASK; |
|
return 0; |
} |
|
static int rtl8366s_set_vlan_4k(struct rtl8366_smi *smi, |
const struct rtl8366_vlan_4k *vlan4k) |
{ |
u32 data[2]; |
int err; |
int i; |
|
if (vlan4k->vid >= RTL8366S_NUM_VIDS || |
vlan4k->member > RTL8366S_VLAN_MEMBER_MASK || |
vlan4k->untag > RTL8366S_VLAN_UNTAG_MASK || |
vlan4k->fid > RTL8366S_FIDMAX) |
return -EINVAL; |
|
data[0] = vlan4k->vid & RTL8366S_VLAN_VID_MASK; |
data[1] = (vlan4k->member & RTL8366S_VLAN_MEMBER_MASK) | |
((vlan4k->untag & RTL8366S_VLAN_UNTAG_MASK) << |
RTL8366S_VLAN_UNTAG_SHIFT) | |
((vlan4k->fid & RTL8366S_VLAN_FID_MASK) << |
RTL8366S_VLAN_FID_SHIFT); |
|
for (i = 0; i < 2; i++) { |
err = rtl8366_smi_write_reg(smi, |
RTL8366S_VLAN_TABLE_WRITE_BASE + i, |
data[i]); |
if (err) |
return err; |
} |
|
/* write table access control word */ |
err = rtl8366_smi_write_reg(smi, RTL8366S_TABLE_ACCESS_CTRL_REG, |
RTL8366S_TABLE_VLAN_WRITE_CTRL); |
|
return err; |
} |
|
static int rtl8366s_get_vlan_mc(struct rtl8366_smi *smi, u32 index, |
struct rtl8366_vlan_mc *vlanmc) |
{ |
u32 data[2]; |
int err; |
int i; |
|
memset(vlanmc, '\0', sizeof(struct rtl8366_vlan_mc)); |
|
if (index >= RTL8366S_NUM_VLANS) |
return -EINVAL; |
|
for (i = 0; i < 2; i++) { |
err = rtl8366_smi_read_reg(smi, |
RTL8366S_VLAN_MC_BASE(index) + i, |
&data[i]); |
if (err) |
return err; |
} |
|
vlanmc->vid = data[0] & RTL8366S_VLAN_VID_MASK; |
vlanmc->priority = (data[0] >> RTL8366S_VLAN_PRIORITY_SHIFT) & |
RTL8366S_VLAN_PRIORITY_MASK; |
vlanmc->untag = (data[1] >> RTL8366S_VLAN_UNTAG_SHIFT) & |
RTL8366S_VLAN_UNTAG_MASK; |
vlanmc->member = data[1] & RTL8366S_VLAN_MEMBER_MASK; |
vlanmc->fid = (data[1] >> RTL8366S_VLAN_FID_SHIFT) & |
RTL8366S_VLAN_FID_MASK; |
|
return 0; |
} |
|
static int rtl8366s_set_vlan_mc(struct rtl8366_smi *smi, u32 index, |
const struct rtl8366_vlan_mc *vlanmc) |
{ |
u32 data[2]; |
int err; |
int i; |
|
if (index >= RTL8366S_NUM_VLANS || |
vlanmc->vid >= RTL8366S_NUM_VIDS || |
vlanmc->priority > RTL8366S_PRIORITYMAX || |
vlanmc->member > RTL8366S_VLAN_MEMBER_MASK || |
vlanmc->untag > RTL8366S_VLAN_UNTAG_MASK || |
vlanmc->fid > RTL8366S_FIDMAX) |
return -EINVAL; |
|
data[0] = (vlanmc->vid & RTL8366S_VLAN_VID_MASK) | |
((vlanmc->priority & RTL8366S_VLAN_PRIORITY_MASK) << |
RTL8366S_VLAN_PRIORITY_SHIFT); |
data[1] = (vlanmc->member & RTL8366S_VLAN_MEMBER_MASK) | |
((vlanmc->untag & RTL8366S_VLAN_UNTAG_MASK) << |
RTL8366S_VLAN_UNTAG_SHIFT) | |
((vlanmc->fid & RTL8366S_VLAN_FID_MASK) << |
RTL8366S_VLAN_FID_SHIFT); |
|
for (i = 0; i < 2; i++) { |
err = rtl8366_smi_write_reg(smi, |
RTL8366S_VLAN_MC_BASE(index) + i, |
data[i]); |
if (err) |
return err; |
} |
|
return 0; |
} |
|
static int rtl8366s_get_mc_index(struct rtl8366_smi *smi, int port, int *val) |
{ |
u32 data; |
int err; |
|
if (port >= RTL8366S_NUM_PORTS) |
return -EINVAL; |
|
err = rtl8366_smi_read_reg(smi, RTL8366S_PORT_VLAN_CTRL_REG(port), |
&data); |
if (err) |
return err; |
|
*val = (data >> RTL8366S_PORT_VLAN_CTRL_SHIFT(port)) & |
RTL8366S_PORT_VLAN_CTRL_MASK; |
|
return 0; |
} |
|
static int rtl8366s_set_mc_index(struct rtl8366_smi *smi, int port, int index) |
{ |
if (port >= RTL8366S_NUM_PORTS || index >= RTL8366S_NUM_VLANS) |
return -EINVAL; |
|
return rtl8366_smi_rmwr(smi, RTL8366S_PORT_VLAN_CTRL_REG(port), |
RTL8366S_PORT_VLAN_CTRL_MASK << |
RTL8366S_PORT_VLAN_CTRL_SHIFT(port), |
(index & RTL8366S_PORT_VLAN_CTRL_MASK) << |
RTL8366S_PORT_VLAN_CTRL_SHIFT(port)); |
} |
|
static int rtl8366s_enable_vlan(struct rtl8366_smi *smi, int enable) |
{ |
return rtl8366_smi_rmwr(smi, RTL8366S_SGCR, RTL8366S_SGCR_EN_VLAN, |
(enable) ? RTL8366S_SGCR_EN_VLAN : 0); |
} |
|
static int rtl8366s_enable_vlan4k(struct rtl8366_smi *smi, int enable) |
{ |
return rtl8366_smi_rmwr(smi, RTL8366S_VLAN_TB_CTRL_REG, |
1, (enable) ? 1 : 0); |
} |
|
static int rtl8366s_is_vlan_valid(struct rtl8366_smi *smi, unsigned vlan) |
{ |
unsigned max = RTL8366S_NUM_VLANS; |
|
if (smi->vlan4k_enabled) |
max = RTL8366S_NUM_VIDS - 1; |
|
if (vlan == 0 || vlan >= max) |
return 0; |
|
return 1; |
} |
|
static int rtl8366s_enable_port(struct rtl8366_smi *smi, int port, int enable) |
{ |
return rtl8366_smi_rmwr(smi, RTL8366S_PECR, (1 << port), |
(enable) ? 0 : (1 << port)); |
} |
|
static int rtl8366s_sw_reset_mibs(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
|
return rtl8366_smi_rmwr(smi, RTL8366S_MIB_CTRL_REG, 0, (1 << 2)); |
} |
|
static int rtl8366s_sw_get_blinkrate(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
u32 data; |
|
rtl8366_smi_read_reg(smi, RTL8366S_LED_BLINKRATE_REG, &data); |
|
val->value.i = (data & (RTL8366S_LED_BLINKRATE_MASK)); |
|
return 0; |
} |
|
static int rtl8366s_sw_set_blinkrate(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
|
if (val->value.i >= 6) |
return -EINVAL; |
|
return rtl8366_smi_rmwr(smi, RTL8366S_LED_BLINKRATE_REG, |
RTL8366S_LED_BLINKRATE_MASK, |
val->value.i); |
} |
|
static int rtl8366s_sw_get_max_length(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
u32 data; |
|
rtl8366_smi_read_reg(smi, RTL8366S_SGCR, &data); |
|
val->value.i = ((data & (RTL8366S_SGCR_MAX_LENGTH_MASK)) >> 4); |
|
return 0; |
} |
|
static int rtl8366s_sw_set_max_length(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
char length_code; |
|
switch (val->value.i) { |
case 0: |
length_code = RTL8366S_SGCR_MAX_LENGTH_1522; |
break; |
case 1: |
length_code = RTL8366S_SGCR_MAX_LENGTH_1536; |
break; |
case 2: |
length_code = RTL8366S_SGCR_MAX_LENGTH_1552; |
break; |
case 3: |
length_code = RTL8366S_SGCR_MAX_LENGTH_16000; |
break; |
default: |
return -EINVAL; |
} |
|
return rtl8366_smi_rmwr(smi, RTL8366S_SGCR, |
RTL8366S_SGCR_MAX_LENGTH_MASK, |
length_code); |
} |
|
static int rtl8366s_sw_get_learning_enable(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
u32 data; |
|
rtl8366_smi_read_reg(smi,RTL8366S_SSCR0, &data); |
val->value.i = !data; |
|
return 0; |
} |
|
|
static int rtl8366s_sw_set_learning_enable(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
u32 portmask = 0; |
int err = 0; |
|
if (!val->value.i) |
portmask = RTL8366S_PORT_ALL; |
|
/* set learning for all ports */ |
REG_WR(smi, RTL8366S_SSCR0, portmask); |
|
/* set auto ageing for all ports */ |
REG_WR(smi, RTL8366S_SSCR1, portmask); |
|
return 0; |
} |
|
static int rtl8366s_sw_get_green(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
u32 data; |
int err; |
|
err = rtl8366_smi_read_reg(smi, RTL8366S_GREEN_ETHERNET_CTRL_REG, &data); |
if (err) |
return err; |
|
val->value.i = ((data & (RTL8366S_GREEN_ETHERNET_TX_BIT | RTL8366S_GREEN_ETHERNET_RX_BIT)) != 0) ? 1 : 0; |
|
return 0; |
} |
|
static int rtl8366s_sw_set_green(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
|
return rtl8366s_set_green(smi, val->value.i); |
} |
|
static int rtl8366s_sw_get_port_link(struct switch_dev *dev, |
int port, |
struct switch_port_link *link) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
u32 data = 0; |
u32 speed; |
|
if (port >= RTL8366S_NUM_PORTS) |
return -EINVAL; |
|
rtl8366_smi_read_reg(smi, RTL8366S_PORT_LINK_STATUS_BASE + (port / 2), |
&data); |
|
if (port % 2) |
data = data >> 8; |
|
link->link = !!(data & RTL8366S_PORT_STATUS_LINK_MASK); |
if (!link->link) |
return 0; |
|
link->duplex = !!(data & RTL8366S_PORT_STATUS_DUPLEX_MASK); |
link->rx_flow = !!(data & RTL8366S_PORT_STATUS_RXPAUSE_MASK); |
link->tx_flow = !!(data & RTL8366S_PORT_STATUS_TXPAUSE_MASK); |
link->aneg = !!(data & RTL8366S_PORT_STATUS_AN_MASK); |
|
speed = (data & RTL8366S_PORT_STATUS_SPEED_MASK); |
switch (speed) { |
case 0: |
link->speed = SWITCH_PORT_SPEED_10; |
break; |
case 1: |
link->speed = SWITCH_PORT_SPEED_100; |
break; |
case 2: |
link->speed = SWITCH_PORT_SPEED_1000; |
break; |
default: |
link->speed = SWITCH_PORT_SPEED_UNKNOWN; |
break; |
} |
|
return 0; |
} |
|
static int rtl8366s_sw_set_port_led(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
u32 data; |
u32 mask; |
u32 reg; |
|
if (val->port_vlan >= RTL8366S_NUM_PORTS || |
(1 << val->port_vlan) == RTL8366S_PORT_UNKNOWN) |
return -EINVAL; |
|
if (val->port_vlan == RTL8366S_PORT_NUM_CPU) { |
reg = RTL8366S_LED_BLINKRATE_REG; |
mask = 0xF << 4; |
data = val->value.i << 4; |
} else { |
reg = RTL8366S_LED_CTRL_REG; |
mask = 0xF << (val->port_vlan * 4), |
data = val->value.i << (val->port_vlan * 4); |
} |
|
return rtl8366_smi_rmwr(smi, reg, mask, data); |
} |
|
static int rtl8366s_sw_get_port_led(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
u32 data = 0; |
|
if (val->port_vlan >= RTL8366S_NUM_LEDGROUPS) |
return -EINVAL; |
|
rtl8366_smi_read_reg(smi, RTL8366S_LED_CTRL_REG, &data); |
val->value.i = (data >> (val->port_vlan * 4)) & 0x000F; |
|
return 0; |
} |
|
static int rtl8366s_sw_get_green_port(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
int err; |
u32 phyData; |
|
if (val->port_vlan >= RTL8366S_NUM_PORTS) |
return -EINVAL; |
|
err = rtl8366s_read_phy_reg(smi, val->port_vlan, 0, RTL8366S_PHY_POWER_SAVING_CTRL_REG, &phyData); |
if (err) |
return err; |
|
val->value.i = ((phyData & RTL8366S_PHY_POWER_SAVING_MASK) != 0) ? 1 : 0; |
|
return 0; |
} |
|
static int rtl8366s_sw_set_green_port(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
return rtl8366s_set_green_port(smi, val->port_vlan, val->value.i); |
} |
|
static int rtl8366s_sw_reset_port_mibs(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
|
if (val->port_vlan >= RTL8366S_NUM_PORTS) |
return -EINVAL; |
|
|
return rtl8366_smi_rmwr(smi, RTL8366S_MIB_CTRL_REG, |
0, (1 << (val->port_vlan + 3))); |
} |
|
static int rtl8366s_sw_get_port_stats(struct switch_dev *dev, int port, |
struct switch_port_stats *stats) |
{ |
return (rtl8366_sw_get_port_stats(dev, port, stats, |
RTL8366S_MIB_TXB_ID, RTL8366S_MIB_RXB_ID)); |
} |
|
static struct switch_attr rtl8366s_globals[] = { |
{ |
.type = SWITCH_TYPE_INT, |
.name = "enable_learning", |
.description = "Enable learning, enable aging", |
.set = rtl8366s_sw_set_learning_enable, |
.get = rtl8366s_sw_get_learning_enable, |
.max = 1, |
}, { |
.type = SWITCH_TYPE_INT, |
.name = "enable_vlan", |
.description = "Enable VLAN mode", |
.set = rtl8366_sw_set_vlan_enable, |
.get = rtl8366_sw_get_vlan_enable, |
.max = 1, |
.ofs = 1 |
}, { |
.type = SWITCH_TYPE_INT, |
.name = "enable_vlan4k", |
.description = "Enable VLAN 4K mode", |
.set = rtl8366_sw_set_vlan_enable, |
.get = rtl8366_sw_get_vlan_enable, |
.max = 1, |
.ofs = 2 |
}, { |
.type = SWITCH_TYPE_NOVAL, |
.name = "reset_mibs", |
.description = "Reset all MIB counters", |
.set = rtl8366s_sw_reset_mibs, |
}, { |
.type = SWITCH_TYPE_INT, |
.name = "blinkrate", |
.description = "Get/Set LED blinking rate (0 = 43ms, 1 = 84ms," |
" 2 = 120ms, 3 = 170ms, 4 = 340ms, 5 = 670ms)", |
.set = rtl8366s_sw_set_blinkrate, |
.get = rtl8366s_sw_get_blinkrate, |
.max = 5 |
}, { |
.type = SWITCH_TYPE_INT, |
.name = "max_length", |
.description = "Get/Set the maximum length of valid packets" |
" (0 = 1522, 1 = 1536, 2 = 1552, 3 = 16000 (9216?))", |
.set = rtl8366s_sw_set_max_length, |
.get = rtl8366s_sw_get_max_length, |
.max = 3, |
}, { |
.type = SWITCH_TYPE_INT, |
.name = "green_mode", |
.description = "Get/Set the router green feature", |
.set = rtl8366s_sw_set_green, |
.get = rtl8366s_sw_get_green, |
.max = 1, |
}, |
}; |
|
static struct switch_attr rtl8366s_port[] = { |
{ |
.type = SWITCH_TYPE_NOVAL, |
.name = "reset_mib", |
.description = "Reset single port MIB counters", |
.set = rtl8366s_sw_reset_port_mibs, |
}, { |
.type = SWITCH_TYPE_STRING, |
.name = "mib", |
.description = "Get MIB counters for port", |
.max = 33, |
.set = NULL, |
.get = rtl8366_sw_get_port_mib, |
}, { |
.type = SWITCH_TYPE_INT, |
.name = "led", |
.description = "Get/Set port group (0 - 3) led mode (0 - 15)", |
.max = 15, |
.set = rtl8366s_sw_set_port_led, |
.get = rtl8366s_sw_get_port_led, |
}, { |
.type = SWITCH_TYPE_INT, |
.name = "green_port", |
.description = "Get/Set port green feature (0 - 1)", |
.max = 1, |
.set = rtl8366s_sw_set_green_port, |
.get = rtl8366s_sw_get_green_port, |
}, |
}; |
|
static struct switch_attr rtl8366s_vlan[] = { |
{ |
.type = SWITCH_TYPE_STRING, |
.name = "info", |
.description = "Get vlan information", |
.max = 1, |
.set = NULL, |
.get = rtl8366_sw_get_vlan_info, |
}, { |
.type = SWITCH_TYPE_INT, |
.name = "fid", |
.description = "Get/Set vlan FID", |
.max = RTL8366S_FIDMAX, |
.set = rtl8366_sw_set_vlan_fid, |
.get = rtl8366_sw_get_vlan_fid, |
}, |
}; |
|
static const struct switch_dev_ops rtl8366_ops = { |
.attr_global = { |
.attr = rtl8366s_globals, |
.n_attr = ARRAY_SIZE(rtl8366s_globals), |
}, |
.attr_port = { |
.attr = rtl8366s_port, |
.n_attr = ARRAY_SIZE(rtl8366s_port), |
}, |
.attr_vlan = { |
.attr = rtl8366s_vlan, |
.n_attr = ARRAY_SIZE(rtl8366s_vlan), |
}, |
|
.get_vlan_ports = rtl8366_sw_get_vlan_ports, |
.set_vlan_ports = rtl8366_sw_set_vlan_ports, |
.get_port_pvid = rtl8366_sw_get_port_pvid, |
.set_port_pvid = rtl8366_sw_set_port_pvid, |
.reset_switch = rtl8366_sw_reset_switch, |
.get_port_link = rtl8366s_sw_get_port_link, |
.get_port_stats = rtl8366s_sw_get_port_stats, |
}; |
|
static int rtl8366s_switch_init(struct rtl8366_smi *smi) |
{ |
struct switch_dev *dev = &smi->sw_dev; |
int err; |
|
dev->name = "RTL8366S"; |
dev->cpu_port = RTL8366S_PORT_NUM_CPU; |
dev->ports = RTL8366S_NUM_PORTS; |
dev->vlans = RTL8366S_NUM_VIDS; |
dev->ops = &rtl8366_ops; |
dev->alias = dev_name(smi->parent); |
|
err = register_switch(dev, NULL); |
if (err) |
dev_err(smi->parent, "switch registration failed\n"); |
|
return err; |
} |
|
static void rtl8366s_switch_cleanup(struct rtl8366_smi *smi) |
{ |
unregister_switch(&smi->sw_dev); |
} |
|
static int rtl8366s_mii_read(struct mii_bus *bus, int addr, int reg) |
{ |
struct rtl8366_smi *smi = bus->priv; |
u32 val = 0; |
int err; |
|
err = rtl8366s_read_phy_reg(smi, addr, 0, reg, &val); |
if (err) |
return 0xffff; |
|
return val; |
} |
|
static int rtl8366s_mii_write(struct mii_bus *bus, int addr, int reg, u16 val) |
{ |
struct rtl8366_smi *smi = bus->priv; |
u32 t; |
int err; |
|
err = rtl8366s_write_phy_reg(smi, addr, 0, reg, val); |
/* flush write */ |
(void) rtl8366s_read_phy_reg(smi, addr, 0, reg, &t); |
|
return err; |
} |
|
static int rtl8366s_detect(struct rtl8366_smi *smi) |
{ |
u32 chip_id = 0; |
u32 chip_ver = 0; |
int ret; |
|
ret = rtl8366_smi_read_reg(smi, RTL8366S_CHIP_ID_REG, &chip_id); |
if (ret) { |
dev_err(smi->parent, "unable to read chip id\n"); |
return ret; |
} |
|
switch (chip_id) { |
case RTL8366S_CHIP_ID_8366: |
break; |
default: |
dev_err(smi->parent, "unknown chip id (%04x)\n", chip_id); |
return -ENODEV; |
} |
|
ret = rtl8366_smi_read_reg(smi, RTL8366S_CHIP_VERSION_CTRL_REG, |
&chip_ver); |
if (ret) { |
dev_err(smi->parent, "unable to read chip version\n"); |
return ret; |
} |
|
dev_info(smi->parent, "RTL%04x ver. %u chip found\n", |
chip_id, chip_ver & RTL8366S_CHIP_VERSION_MASK); |
|
return 0; |
} |
|
static struct rtl8366_smi_ops rtl8366s_smi_ops = { |
.detect = rtl8366s_detect, |
.reset_chip = rtl8366s_reset_chip, |
.setup = rtl8366s_setup, |
|
.mii_read = rtl8366s_mii_read, |
.mii_write = rtl8366s_mii_write, |
|
.get_vlan_mc = rtl8366s_get_vlan_mc, |
.set_vlan_mc = rtl8366s_set_vlan_mc, |
.get_vlan_4k = rtl8366s_get_vlan_4k, |
.set_vlan_4k = rtl8366s_set_vlan_4k, |
.get_mc_index = rtl8366s_get_mc_index, |
.set_mc_index = rtl8366s_set_mc_index, |
.get_mib_counter = rtl8366_get_mib_counter, |
.is_vlan_valid = rtl8366s_is_vlan_valid, |
.enable_vlan = rtl8366s_enable_vlan, |
.enable_vlan4k = rtl8366s_enable_vlan4k, |
.enable_port = rtl8366s_enable_port, |
}; |
|
static int rtl8366s_probe(struct platform_device *pdev) |
{ |
static int rtl8366_smi_version_printed; |
struct rtl8366_smi *smi; |
int err; |
|
if (!rtl8366_smi_version_printed++) |
printk(KERN_NOTICE RTL8366S_DRIVER_DESC |
" version " RTL8366S_DRIVER_VER"\n"); |
|
smi = rtl8366_smi_probe(pdev); |
if (!smi) |
return -ENODEV; |
|
smi->clk_delay = 10; |
smi->cmd_read = 0xa9; |
smi->cmd_write = 0xa8; |
smi->ops = &rtl8366s_smi_ops; |
smi->cpu_port = RTL8366S_PORT_NUM_CPU; |
smi->num_ports = RTL8366S_NUM_PORTS; |
smi->num_vlan_mc = RTL8366S_NUM_VLANS; |
smi->mib_counters = rtl8366s_mib_counters; |
smi->num_mib_counters = ARRAY_SIZE(rtl8366s_mib_counters); |
|
err = rtl8366_smi_init(smi); |
if (err) |
goto err_free_smi; |
|
platform_set_drvdata(pdev, smi); |
|
err = rtl8366s_switch_init(smi); |
if (err) |
goto err_clear_drvdata; |
|
return 0; |
|
err_clear_drvdata: |
platform_set_drvdata(pdev, NULL); |
rtl8366_smi_cleanup(smi); |
err_free_smi: |
kfree(smi); |
return err; |
} |
|
static int rtl8366s_remove(struct platform_device *pdev) |
{ |
struct rtl8366_smi *smi = platform_get_drvdata(pdev); |
|
if (smi) { |
rtl8366s_switch_cleanup(smi); |
platform_set_drvdata(pdev, NULL); |
rtl8366_smi_cleanup(smi); |
kfree(smi); |
} |
|
return 0; |
} |
|
#ifdef CONFIG_OF |
static const struct of_device_id rtl8366s_match[] = { |
{ .compatible = "realtek,rtl8366s" }, |
{}, |
}; |
MODULE_DEVICE_TABLE(of, rtl8366s_match); |
#endif |
|
static struct platform_driver rtl8366s_driver = { |
.driver = { |
.name = RTL8366S_DRIVER_NAME, |
.owner = THIS_MODULE, |
#ifdef CONFIG_OF |
.of_match_table = of_match_ptr(rtl8366s_match), |
#endif |
}, |
.probe = rtl8366s_probe, |
.remove = rtl8366s_remove, |
}; |
|
static int __init rtl8366s_module_init(void) |
{ |
return platform_driver_register(&rtl8366s_driver); |
} |
module_init(rtl8366s_module_init); |
|
static void __exit rtl8366s_module_exit(void) |
{ |
platform_driver_unregister(&rtl8366s_driver); |
} |
module_exit(rtl8366s_module_exit); |
|
MODULE_DESCRIPTION(RTL8366S_DRIVER_DESC); |
MODULE_VERSION(RTL8366S_DRIVER_VER); |
MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>"); |
MODULE_AUTHOR("Antti Seppälä <a.seppala@gmail.com>"); |
MODULE_LICENSE("GPL v2"); |
MODULE_ALIAS("platform:" RTL8366S_DRIVER_NAME); |
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/rtl8367.c |
@@ -0,0 +1,1846 @@ |
/* |
* Platform driver for the Realtek RTL8367R/M ethernet switches |
* |
* Copyright (C) 2011 Gabor Juhos <juhosg@openwrt.org> |
* |
* This program is free software; you can redistribute it and/or modify it |
* under the terms of the GNU General Public License version 2 as published |
* by the Free Software Foundation. |
*/ |
|
#include <linux/kernel.h> |
#include <linux/module.h> |
#include <linux/init.h> |
#include <linux/device.h> |
#include <linux/of.h> |
#include <linux/of_platform.h> |
#include <linux/delay.h> |
#include <linux/skbuff.h> |
#include <linux/rtl8367.h> |
|
#include "rtl8366_smi.h" |
|
#define RTL8367_RESET_DELAY 1000 /* msecs*/ |
|
#define RTL8367_PHY_ADDR_MAX 8 |
#define RTL8367_PHY_REG_MAX 31 |
|
#define RTL8367_VID_MASK 0xffff |
#define RTL8367_FID_MASK 0xfff |
#define RTL8367_UNTAG_MASK 0xffff |
#define RTL8367_MEMBER_MASK 0xffff |
|
#define RTL8367_PORT_CFG_REG(_p) (0x000e + 0x20 * (_p)) |
#define RTL8367_PORT_CFG_EGRESS_MODE_SHIFT 4 |
#define RTL8367_PORT_CFG_EGRESS_MODE_MASK 0x3 |
#define RTL8367_PORT_CFG_EGRESS_MODE_ORIGINAL 0 |
#define RTL8367_PORT_CFG_EGRESS_MODE_KEEP 1 |
#define RTL8367_PORT_CFG_EGRESS_MODE_PRI 2 |
#define RTL8367_PORT_CFG_EGRESS_MODE_REAL 3 |
|
#define RTL8367_BYPASS_LINE_RATE_REG 0x03f7 |
|
#define RTL8367_TA_CTRL_REG 0x0500 |
#define RTL8367_TA_CTRL_STATUS BIT(12) |
#define RTL8367_TA_CTRL_METHOD BIT(5) |
#define RTL8367_TA_CTRL_CMD_SHIFT 4 |
#define RTL8367_TA_CTRL_CMD_READ 0 |
#define RTL8367_TA_CTRL_CMD_WRITE 1 |
#define RTL8367_TA_CTRL_TABLE_SHIFT 0 |
#define RTL8367_TA_CTRL_TABLE_ACLRULE 1 |
#define RTL8367_TA_CTRL_TABLE_ACLACT 2 |
#define RTL8367_TA_CTRL_TABLE_CVLAN 3 |
#define RTL8367_TA_CTRL_TABLE_L2 4 |
#define RTL8367_TA_CTRL_CVLAN_READ \ |
((RTL8367_TA_CTRL_CMD_READ << RTL8367_TA_CTRL_CMD_SHIFT) | \ |
RTL8367_TA_CTRL_TABLE_CVLAN) |
#define RTL8367_TA_CTRL_CVLAN_WRITE \ |
((RTL8367_TA_CTRL_CMD_WRITE << RTL8367_TA_CTRL_CMD_SHIFT) | \ |
RTL8367_TA_CTRL_TABLE_CVLAN) |
|
#define RTL8367_TA_ADDR_REG 0x0501 |
#define RTL8367_TA_ADDR_MASK 0x3fff |
|
#define RTL8367_TA_DATA_REG(_x) (0x0503 + (_x)) |
#define RTL8367_TA_VLAN_DATA_SIZE 4 |
#define RTL8367_TA_VLAN_VID_MASK RTL8367_VID_MASK |
#define RTL8367_TA_VLAN_MEMBER_SHIFT 0 |
#define RTL8367_TA_VLAN_MEMBER_MASK RTL8367_MEMBER_MASK |
#define RTL8367_TA_VLAN_FID_SHIFT 0 |
#define RTL8367_TA_VLAN_FID_MASK RTL8367_FID_MASK |
#define RTL8367_TA_VLAN_UNTAG1_SHIFT 14 |
#define RTL8367_TA_VLAN_UNTAG1_MASK 0x3 |
#define RTL8367_TA_VLAN_UNTAG2_SHIFT 0 |
#define RTL8367_TA_VLAN_UNTAG2_MASK 0x3fff |
|
#define RTL8367_VLAN_PVID_CTRL_REG(_p) (0x0700 + (_p) / 2) |
#define RTL8367_VLAN_PVID_CTRL_MASK 0x1f |
#define RTL8367_VLAN_PVID_CTRL_SHIFT(_p) (8 * ((_p) % 2)) |
|
#define RTL8367_VLAN_MC_BASE(_x) (0x0728 + (_x) * 4) |
#define RTL8367_VLAN_MC_DATA_SIZE 4 |
#define RTL8367_VLAN_MC_MEMBER_SHIFT 0 |
#define RTL8367_VLAN_MC_MEMBER_MASK RTL8367_MEMBER_MASK |
#define RTL8367_VLAN_MC_FID_SHIFT 0 |
#define RTL8367_VLAN_MC_FID_MASK RTL8367_FID_MASK |
#define RTL8367_VLAN_MC_EVID_SHIFT 0 |
#define RTL8367_VLAN_MC_EVID_MASK RTL8367_VID_MASK |
|
#define RTL8367_VLAN_CTRL_REG 0x07a8 |
#define RTL8367_VLAN_CTRL_ENABLE BIT(0) |
|
#define RTL8367_VLAN_INGRESS_REG 0x07a9 |
|
#define RTL8367_PORT_ISOLATION_REG(_p) (0x08a2 + (_p)) |
|
#define RTL8367_MIB_COUNTER_REG(_x) (0x1000 + (_x)) |
|
#define RTL8367_MIB_ADDRESS_REG 0x1004 |
|
#define RTL8367_MIB_CTRL_REG(_x) (0x1005 + (_x)) |
#define RTL8367_MIB_CTRL_GLOBAL_RESET_MASK BIT(11) |
#define RTL8367_MIB_CTRL_QM_RESET_MASK BIT(10) |
#define RTL8367_MIB_CTRL_PORT_RESET_MASK(_p) BIT(2 + (_p)) |
#define RTL8367_MIB_CTRL_RESET_MASK BIT(1) |
#define RTL8367_MIB_CTRL_BUSY_MASK BIT(0) |
|
#define RTL8367_MIB_COUNT 36 |
#define RTL8367_MIB_COUNTER_PORT_OFFSET 0x0050 |
|
#define RTL8367_SWC0_REG 0x1200 |
#define RTL8367_SWC0_MAX_LENGTH_SHIFT 13 |
#define RTL8367_SWC0_MAX_LENGTH(_x) ((_x) << 13) |
#define RTL8367_SWC0_MAX_LENGTH_MASK RTL8367_SWC0_MAX_LENGTH(0x3) |
#define RTL8367_SWC0_MAX_LENGTH_1522 RTL8367_SWC0_MAX_LENGTH(0) |
#define RTL8367_SWC0_MAX_LENGTH_1536 RTL8367_SWC0_MAX_LENGTH(1) |
#define RTL8367_SWC0_MAX_LENGTH_1552 RTL8367_SWC0_MAX_LENGTH(2) |
#define RTL8367_SWC0_MAX_LENGTH_16000 RTL8367_SWC0_MAX_LENGTH(3) |
|
#define RTL8367_CHIP_NUMBER_REG 0x1300 |
|
#define RTL8367_CHIP_VER_REG 0x1301 |
#define RTL8367_CHIP_VER_RLVID_SHIFT 12 |
#define RTL8367_CHIP_VER_RLVID_MASK 0xf |
#define RTL8367_CHIP_VER_MCID_SHIFT 8 |
#define RTL8367_CHIP_VER_MCID_MASK 0xf |
#define RTL8367_CHIP_VER_BOID_SHIFT 4 |
#define RTL8367_CHIP_VER_BOID_MASK 0xf |
|
#define RTL8367_CHIP_MODE_REG 0x1302 |
#define RTL8367_CHIP_MODE_MASK 0x7 |
|
#define RTL8367_CHIP_DEBUG0_REG 0x1303 |
#define RTL8367_CHIP_DEBUG0_DUMMY0(_x) BIT(8 + (_x)) |
|
#define RTL8367_CHIP_DEBUG1_REG 0x1304 |
|
#define RTL8367_DIS_REG 0x1305 |
#define RTL8367_DIS_SKIP_MII_RXER(_x) BIT(12 + (_x)) |
#define RTL8367_DIS_RGMII_SHIFT(_x) (4 * (_x)) |
#define RTL8367_DIS_RGMII_MASK 0x7 |
|
#define RTL8367_EXT_RGMXF_REG(_x) (0x1306 + (_x)) |
#define RTL8367_EXT_RGMXF_DUMMY0_SHIFT 5 |
#define RTL8367_EXT_RGMXF_DUMMY0_MASK 0x7ff |
#define RTL8367_EXT_RGMXF_TXDELAY_SHIFT 3 |
#define RTL8367_EXT_RGMXF_TXDELAY_MASK 1 |
#define RTL8367_EXT_RGMXF_RXDELAY_MASK 0x7 |
|
#define RTL8367_DI_FORCE_REG(_x) (0x1310 + (_x)) |
#define RTL8367_DI_FORCE_MODE BIT(12) |
#define RTL8367_DI_FORCE_NWAY BIT(7) |
#define RTL8367_DI_FORCE_TXPAUSE BIT(6) |
#define RTL8367_DI_FORCE_RXPAUSE BIT(5) |
#define RTL8367_DI_FORCE_LINK BIT(4) |
#define RTL8367_DI_FORCE_DUPLEX BIT(2) |
#define RTL8367_DI_FORCE_SPEED_MASK 3 |
#define RTL8367_DI_FORCE_SPEED_10 0 |
#define RTL8367_DI_FORCE_SPEED_100 1 |
#define RTL8367_DI_FORCE_SPEED_1000 2 |
|
#define RTL8367_MAC_FORCE_REG(_x) (0x1312 + (_x)) |
|
#define RTL8367_CHIP_RESET_REG 0x1322 |
#define RTL8367_CHIP_RESET_SW BIT(1) |
#define RTL8367_CHIP_RESET_HW BIT(0) |
|
#define RTL8367_PORT_STATUS_REG(_p) (0x1352 + (_p)) |
#define RTL8367_PORT_STATUS_NWAY BIT(7) |
#define RTL8367_PORT_STATUS_TXPAUSE BIT(6) |
#define RTL8367_PORT_STATUS_RXPAUSE BIT(5) |
#define RTL8367_PORT_STATUS_LINK BIT(4) |
#define RTL8367_PORT_STATUS_DUPLEX BIT(2) |
#define RTL8367_PORT_STATUS_SPEED_MASK 0x0003 |
#define RTL8367_PORT_STATUS_SPEED_10 0 |
#define RTL8367_PORT_STATUS_SPEED_100 1 |
#define RTL8367_PORT_STATUS_SPEED_1000 2 |
|
#define RTL8367_RTL_NO_REG 0x13c0 |
#define RTL8367_RTL_NO_8367R 0x3670 |
#define RTL8367_RTL_NO_8367M 0x3671 |
|
#define RTL8367_RTL_VER_REG 0x13c1 |
#define RTL8367_RTL_VER_MASK 0xf |
|
#define RTL8367_RTL_MAGIC_ID_REG 0x13c2 |
#define RTL8367_RTL_MAGIC_ID_VAL 0x0249 |
|
#define RTL8367_LED_SYS_CONFIG_REG 0x1b00 |
#define RTL8367_LED_MODE_REG 0x1b02 |
#define RTL8367_LED_MODE_RATE_M 0x7 |
#define RTL8367_LED_MODE_RATE_S 1 |
|
#define RTL8367_LED_CONFIG_REG 0x1b03 |
#define RTL8367_LED_CONFIG_DATA_S 12 |
#define RTL8367_LED_CONFIG_DATA_M 0x3 |
#define RTL8367_LED_CONFIG_SEL BIT(14) |
#define RTL8367_LED_CONFIG_LED_CFG_M 0xf |
|
#define RTL8367_PARA_LED_IO_EN1_REG 0x1b24 |
#define RTL8367_PARA_LED_IO_EN2_REG 0x1b25 |
#define RTL8367_PARA_LED_IO_EN_PMASK 0xff |
|
#define RTL8367_IA_CTRL_REG 0x1f00 |
#define RTL8367_IA_CTRL_RW(_x) ((_x) << 1) |
#define RTL8367_IA_CTRL_RW_READ RTL8367_IA_CTRL_RW(0) |
#define RTL8367_IA_CTRL_RW_WRITE RTL8367_IA_CTRL_RW(1) |
#define RTL8367_IA_CTRL_CMD_MASK BIT(0) |
|
#define RTL8367_IA_STATUS_REG 0x1f01 |
#define RTL8367_IA_STATUS_PHY_BUSY BIT(2) |
#define RTL8367_IA_STATUS_SDS_BUSY BIT(1) |
#define RTL8367_IA_STATUS_MDX_BUSY BIT(0) |
|
#define RTL8367_IA_ADDRESS_REG 0x1f02 |
|
#define RTL8367_IA_WRITE_DATA_REG 0x1f03 |
#define RTL8367_IA_READ_DATA_REG 0x1f04 |
|
#define RTL8367_INTERNAL_PHY_REG(_a, _r) (0x2000 + 32 * (_a) + (_r)) |
|
#define RTL8367_CPU_PORT_NUM 9 |
#define RTL8367_NUM_PORTS 10 |
#define RTL8367_NUM_VLANS 32 |
#define RTL8367_NUM_LEDGROUPS 4 |
#define RTL8367_NUM_VIDS 4096 |
#define RTL8367_PRIORITYMAX 7 |
#define RTL8367_FIDMAX 7 |
|
#define RTL8367_PORT_0 BIT(0) |
#define RTL8367_PORT_1 BIT(1) |
#define RTL8367_PORT_2 BIT(2) |
#define RTL8367_PORT_3 BIT(3) |
#define RTL8367_PORT_4 BIT(4) |
#define RTL8367_PORT_5 BIT(5) |
#define RTL8367_PORT_6 BIT(6) |
#define RTL8367_PORT_7 BIT(7) |
#define RTL8367_PORT_E1 BIT(8) /* external port 1 */ |
#define RTL8367_PORT_E0 BIT(9) /* external port 0 */ |
|
#define RTL8367_PORTS_ALL \ |
(RTL8367_PORT_0 | RTL8367_PORT_1 | RTL8367_PORT_2 | \ |
RTL8367_PORT_3 | RTL8367_PORT_4 | RTL8367_PORT_5 | \ |
RTL8367_PORT_6 | RTL8367_PORT_7 | RTL8367_PORT_E1 | \ |
RTL8367_PORT_E0) |
|
#define RTL8367_PORTS_ALL_BUT_CPU \ |
(RTL8367_PORT_0 | RTL8367_PORT_1 | RTL8367_PORT_2 | \ |
RTL8367_PORT_3 | RTL8367_PORT_4 | RTL8367_PORT_5 | \ |
RTL8367_PORT_6 | RTL8367_PORT_7 | RTL8367_PORT_E1) |
|
struct rtl8367_initval { |
u16 reg; |
u16 val; |
}; |
|
#define RTL8367_MIB_RXB_ID 0 /* IfInOctets */ |
#define RTL8367_MIB_TXB_ID 20 /* IfOutOctets */ |
|
static struct rtl8366_mib_counter rtl8367_mib_counters[] = { |
{ 0, 0, 4, "IfInOctets" }, |
{ 0, 4, 2, "Dot3StatsFCSErrors" }, |
{ 0, 6, 2, "Dot3StatsSymbolErrors" }, |
{ 0, 8, 2, "Dot3InPauseFrames" }, |
{ 0, 10, 2, "Dot3ControlInUnknownOpcodes" }, |
{ 0, 12, 2, "EtherStatsFragments" }, |
{ 0, 14, 2, "EtherStatsJabbers" }, |
{ 0, 16, 2, "IfInUcastPkts" }, |
{ 0, 18, 2, "EtherStatsDropEvents" }, |
{ 0, 20, 4, "EtherStatsOctets" }, |
|
{ 0, 24, 2, "EtherStatsUnderSizePkts" }, |
{ 0, 26, 2, "EtherOversizeStats" }, |
{ 0, 28, 2, "EtherStatsPkts64Octets" }, |
{ 0, 30, 2, "EtherStatsPkts65to127Octets" }, |
{ 0, 32, 2, "EtherStatsPkts128to255Octets" }, |
{ 0, 34, 2, "EtherStatsPkts256to511Octets" }, |
{ 0, 36, 2, "EtherStatsPkts512to1023Octets" }, |
{ 0, 38, 2, "EtherStatsPkts1024to1518Octets" }, |
{ 0, 40, 2, "EtherStatsMulticastPkts" }, |
{ 0, 42, 2, "EtherStatsBroadcastPkts" }, |
|
{ 0, 44, 4, "IfOutOctets" }, |
|
{ 0, 48, 2, "Dot3StatsSingleCollisionFrames" }, |
{ 0, 50, 2, "Dot3StatMultipleCollisionFrames" }, |
{ 0, 52, 2, "Dot3sDeferredTransmissions" }, |
{ 0, 54, 2, "Dot3StatsLateCollisions" }, |
{ 0, 56, 2, "EtherStatsCollisions" }, |
{ 0, 58, 2, "Dot3StatsExcessiveCollisions" }, |
{ 0, 60, 2, "Dot3OutPauseFrames" }, |
{ 0, 62, 2, "Dot1dBasePortDelayExceededDiscards" }, |
{ 0, 64, 2, "Dot1dTpPortInDiscards" }, |
{ 0, 66, 2, "IfOutUcastPkts" }, |
{ 0, 68, 2, "IfOutMulticastPkts" }, |
{ 0, 70, 2, "IfOutBroadcastPkts" }, |
{ 0, 72, 2, "OutOampduPkts" }, |
{ 0, 74, 2, "InOampduPkts" }, |
{ 0, 76, 2, "PktgenPkts" }, |
}; |
|
#define REG_RD(_smi, _reg, _val) \ |
do { \ |
err = rtl8366_smi_read_reg(_smi, _reg, _val); \ |
if (err) \ |
return err; \ |
} while (0) |
|
#define REG_WR(_smi, _reg, _val) \ |
do { \ |
err = rtl8366_smi_write_reg(_smi, _reg, _val); \ |
if (err) \ |
return err; \ |
} while (0) |
|
#define REG_RMW(_smi, _reg, _mask, _val) \ |
do { \ |
err = rtl8366_smi_rmwr(_smi, _reg, _mask, _val); \ |
if (err) \ |
return err; \ |
} while (0) |
|
static const struct rtl8367_initval rtl8367_initvals_0_0[] = { |
{0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0000}, {0x2215, 0x1006}, |
{0x221f, 0x0005}, {0x2200, 0x00c6}, {0x221f, 0x0007}, {0x221e, 0x0048}, |
{0x2215, 0x6412}, {0x2216, 0x6412}, {0x2217, 0x6412}, {0x2218, 0x6412}, |
{0x2219, 0x6412}, {0x221A, 0x6412}, {0x221f, 0x0001}, {0x220c, 0xdbf0}, |
{0x2209, 0x2576}, {0x2207, 0x287E}, {0x220A, 0x68E5}, {0x221D, 0x3DA4}, |
{0x221C, 0xE7F7}, {0x2214, 0x7F52}, {0x2218, 0x7FCE}, {0x2208, 0x04B7}, |
{0x2206, 0x4072}, {0x2210, 0xF05E}, {0x221B, 0xB414}, {0x221F, 0x0003}, |
{0x221A, 0x06A6}, {0x2210, 0xF05E}, {0x2213, 0x06EB}, {0x2212, 0xF4D2}, |
{0x220E, 0xE120}, {0x2200, 0x7C00}, {0x2202, 0x5FD0}, {0x220D, 0x0207}, |
{0x221f, 0x0002}, {0x2205, 0x0978}, {0x2202, 0x8C01}, {0x2207, 0x3620}, |
{0x221C, 0x0001}, {0x2203, 0x0420}, {0x2204, 0x80C8}, {0x133e, 0x0ede}, |
{0x221f, 0x0002}, {0x220c, 0x0073}, {0x220d, 0xEB65}, {0x220e, 0x51d1}, |
{0x220f, 0x5dcb}, {0x2210, 0x3044}, {0x2211, 0x1800}, {0x2212, 0x7E00}, |
{0x2213, 0x0000}, {0x133f, 0x0010}, {0x133e, 0x0ffe}, {0x207f, 0x0002}, |
{0x2074, 0x3D22}, {0x2075, 0x2000}, {0x2076, 0x6040}, {0x2077, 0x0000}, |
{0x2078, 0x0f0a}, {0x2079, 0x50AB}, {0x207a, 0x0000}, {0x207b, 0x0f0f}, |
{0x205f, 0x0002}, {0x2054, 0xFF00}, {0x2055, 0x000A}, {0x2056, 0x000A}, |
{0x2057, 0x0005}, {0x2058, 0x0005}, {0x2059, 0x0000}, {0x205A, 0x0005}, |
{0x205B, 0x0005}, {0x205C, 0x0005}, {0x209f, 0x0002}, {0x2094, 0x00AA}, |
{0x2095, 0x00AA}, {0x2096, 0x00AA}, {0x2097, 0x00AA}, {0x2098, 0x0055}, |
{0x2099, 0x00AA}, {0x209A, 0x00AA}, {0x209B, 0x00AA}, {0x1363, 0x8354}, |
{0x1270, 0x3333}, {0x1271, 0x3333}, {0x1272, 0x3333}, {0x1330, 0x00DB}, |
{0x1203, 0xff00}, {0x1200, 0x7fc4}, {0x121d, 0x1006}, {0x121e, 0x03e8}, |
{0x121f, 0x02b3}, {0x1220, 0x028f}, {0x1221, 0x029b}, {0x1222, 0x0277}, |
{0x1223, 0x02b3}, {0x1224, 0x028f}, {0x1225, 0x029b}, {0x1226, 0x0277}, |
{0x1227, 0x00c0}, {0x1228, 0x00b4}, {0x122f, 0x00c0}, {0x1230, 0x00b4}, |
{0x1229, 0x0020}, {0x122a, 0x000c}, {0x1231, 0x0030}, {0x1232, 0x0024}, |
{0x0219, 0x0032}, {0x0200, 0x03e8}, {0x0201, 0x03e8}, {0x0202, 0x03e8}, |
{0x0203, 0x03e8}, {0x0204, 0x03e8}, {0x0205, 0x03e8}, {0x0206, 0x03e8}, |
{0x0207, 0x03e8}, {0x0218, 0x0032}, {0x0208, 0x029b}, {0x0209, 0x029b}, |
{0x020a, 0x029b}, {0x020b, 0x029b}, {0x020c, 0x029b}, {0x020d, 0x029b}, |
{0x020e, 0x029b}, {0x020f, 0x029b}, {0x0210, 0x029b}, {0x0211, 0x029b}, |
{0x0212, 0x029b}, {0x0213, 0x029b}, {0x0214, 0x029b}, {0x0215, 0x029b}, |
{0x0216, 0x029b}, {0x0217, 0x029b}, {0x0900, 0x0000}, {0x0901, 0x0000}, |
{0x0902, 0x0000}, {0x0903, 0x0000}, {0x0865, 0x3210}, {0x087b, 0x0000}, |
{0x087c, 0xff00}, {0x087d, 0x0000}, {0x087e, 0x0000}, {0x0801, 0x0100}, |
{0x0802, 0x0100}, {0x1700, 0x014C}, {0x0301, 0x00FF}, {0x12AA, 0x0096}, |
{0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0005}, {0x2200, 0x00C4}, |
{0x221f, 0x0000}, {0x2210, 0x05EF}, {0x2204, 0x05E1}, {0x2200, 0x1340}, |
{0x133f, 0x0010}, {0x20A0, 0x1940}, {0x20C0, 0x1940}, {0x20E0, 0x1940}, |
}; |
|
static const struct rtl8367_initval rtl8367_initvals_0_1[] = { |
{0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0000}, {0x2215, 0x1006}, |
{0x221f, 0x0005}, {0x2200, 0x00c6}, {0x221f, 0x0007}, {0x221e, 0x0048}, |
{0x2215, 0x6412}, {0x2216, 0x6412}, {0x2217, 0x6412}, {0x2218, 0x6412}, |
{0x2219, 0x6412}, {0x221A, 0x6412}, {0x221f, 0x0001}, {0x220c, 0xdbf0}, |
{0x2209, 0x2576}, {0x2207, 0x287E}, {0x220A, 0x68E5}, {0x221D, 0x3DA4}, |
{0x221C, 0xE7F7}, {0x2214, 0x7F52}, {0x2218, 0x7FCE}, {0x2208, 0x04B7}, |
{0x2206, 0x4072}, {0x2210, 0xF05E}, {0x221B, 0xB414}, {0x221F, 0x0003}, |
{0x221A, 0x06A6}, {0x2210, 0xF05E}, {0x2213, 0x06EB}, {0x2212, 0xF4D2}, |
{0x220E, 0xE120}, {0x2200, 0x7C00}, {0x2202, 0x5FD0}, {0x220D, 0x0207}, |
{0x221f, 0x0002}, {0x2205, 0x0978}, {0x2202, 0x8C01}, {0x2207, 0x3620}, |
{0x221C, 0x0001}, {0x2203, 0x0420}, {0x2204, 0x80C8}, {0x133e, 0x0ede}, |
{0x221f, 0x0002}, {0x220c, 0x0073}, {0x220d, 0xEB65}, {0x220e, 0x51d1}, |
{0x220f, 0x5dcb}, {0x2210, 0x3044}, {0x2211, 0x1800}, {0x2212, 0x7E00}, |
{0x2213, 0x0000}, {0x133f, 0x0010}, {0x133e, 0x0ffe}, {0x207f, 0x0002}, |
{0x2074, 0x3D22}, {0x2075, 0x2000}, {0x2076, 0x6040}, {0x2077, 0x0000}, |
{0x2078, 0x0f0a}, {0x2079, 0x50AB}, {0x207a, 0x0000}, {0x207b, 0x0f0f}, |
{0x205f, 0x0002}, {0x2054, 0xFF00}, {0x2055, 0x000A}, {0x2056, 0x000A}, |
{0x2057, 0x0005}, {0x2058, 0x0005}, {0x2059, 0x0000}, {0x205A, 0x0005}, |
{0x205B, 0x0005}, {0x205C, 0x0005}, {0x209f, 0x0002}, {0x2094, 0x00AA}, |
{0x2095, 0x00AA}, {0x2096, 0x00AA}, {0x2097, 0x00AA}, {0x2098, 0x0055}, |
{0x2099, 0x00AA}, {0x209A, 0x00AA}, {0x209B, 0x00AA}, {0x1363, 0x8354}, |
{0x1270, 0x3333}, {0x1271, 0x3333}, {0x1272, 0x3333}, {0x1330, 0x00DB}, |
{0x1203, 0xff00}, {0x1200, 0x7fc4}, {0x121d, 0x1b06}, {0x121e, 0x07f0}, |
{0x121f, 0x0438}, {0x1220, 0x040f}, {0x1221, 0x040f}, {0x1222, 0x03eb}, |
{0x1223, 0x0438}, {0x1224, 0x040f}, {0x1225, 0x040f}, {0x1226, 0x03eb}, |
{0x1227, 0x0144}, {0x1228, 0x0138}, {0x122f, 0x0144}, {0x1230, 0x0138}, |
{0x1229, 0x0020}, {0x122a, 0x000c}, {0x1231, 0x0030}, {0x1232, 0x0024}, |
{0x0219, 0x0032}, {0x0200, 0x07d0}, {0x0201, 0x07d0}, {0x0202, 0x07d0}, |
{0x0203, 0x07d0}, {0x0204, 0x07d0}, {0x0205, 0x07d0}, {0x0206, 0x07d0}, |
{0x0207, 0x07d0}, {0x0218, 0x0032}, {0x0208, 0x0190}, {0x0209, 0x0190}, |
{0x020a, 0x0190}, {0x020b, 0x0190}, {0x020c, 0x0190}, {0x020d, 0x0190}, |
{0x020e, 0x0190}, {0x020f, 0x0190}, {0x0210, 0x0190}, {0x0211, 0x0190}, |
{0x0212, 0x0190}, {0x0213, 0x0190}, {0x0214, 0x0190}, {0x0215, 0x0190}, |
{0x0216, 0x0190}, {0x0217, 0x0190}, {0x0900, 0x0000}, {0x0901, 0x0000}, |
{0x0902, 0x0000}, {0x0903, 0x0000}, {0x0865, 0x3210}, {0x087b, 0x0000}, |
{0x087c, 0xff00}, {0x087d, 0x0000}, {0x087e, 0x0000}, {0x0801, 0x0100}, |
{0x0802, 0x0100}, {0x1700, 0x0125}, {0x0301, 0x00FF}, {0x12AA, 0x0096}, |
{0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0005}, {0x2200, 0x00C4}, |
{0x221f, 0x0000}, {0x2210, 0x05EF}, {0x2204, 0x05E1}, {0x2200, 0x1340}, |
{0x133f, 0x0010}, |
}; |
|
static const struct rtl8367_initval rtl8367_initvals_1_0[] = { |
{0x1B24, 0x0000}, {0x1B25, 0x0000}, {0x1B26, 0x0000}, {0x1B27, 0x0000}, |
{0x207F, 0x0002}, {0x2079, 0x0200}, {0x207F, 0x0000}, {0x133F, 0x0030}, |
{0x133E, 0x000E}, {0x221F, 0x0005}, {0x2201, 0x0700}, {0x2205, 0x8B82}, |
{0x2206, 0x05CB}, {0x221F, 0x0002}, {0x2204, 0x80C2}, {0x2205, 0x0938}, |
{0x221F, 0x0003}, {0x2212, 0xC4D2}, {0x220D, 0x0207}, {0x221F, 0x0001}, |
{0x2207, 0x267E}, {0x221C, 0xE5F7}, {0x221B, 0x0424}, {0x221F, 0x0007}, |
{0x221E, 0x0040}, {0x2218, 0x0000}, {0x221F, 0x0007}, {0x221E, 0x002C}, |
{0x2218, 0x008B}, {0x221F, 0x0005}, {0x2205, 0xFFF6}, {0x2206, 0x0080}, |
{0x2205, 0x8000}, {0x2206, 0xF8E0}, {0x2206, 0xE000}, {0x2206, 0xE1E0}, |
{0x2206, 0x01AC}, {0x2206, 0x2408}, {0x2206, 0xE08B}, {0x2206, 0x84F7}, |
{0x2206, 0x20E4}, {0x2206, 0x8B84}, {0x2206, 0xFC05}, {0x2206, 0xF8FA}, |
{0x2206, 0xEF69}, {0x2206, 0xE08B}, {0x2206, 0x86AC}, {0x2206, 0x201A}, |
{0x2206, 0xBF80}, {0x2206, 0x59D0}, {0x2206, 0x2402}, {0x2206, 0x803D}, |
{0x2206, 0xE0E0}, {0x2206, 0xE4E1}, {0x2206, 0xE0E5}, {0x2206, 0x5806}, |
{0x2206, 0x68C0}, {0x2206, 0xD1D2}, {0x2206, 0xE4E0}, {0x2206, 0xE4E5}, |
{0x2206, 0xE0E5}, {0x2206, 0xEF96}, {0x2206, 0xFEFC}, {0x2206, 0x05FB}, |
{0x2206, 0x0BFB}, {0x2206, 0x58FF}, {0x2206, 0x9E11}, {0x2206, 0x06F0}, |
{0x2206, 0x0C81}, {0x2206, 0x8AE0}, {0x2206, 0x0019}, {0x2206, 0x1B89}, |
{0x2206, 0xCFEB}, {0x2206, 0x19EB}, {0x2206, 0x19B0}, {0x2206, 0xEFFF}, |
{0x2206, 0x0BFF}, {0x2206, 0x0425}, {0x2206, 0x0807}, {0x2206, 0x2640}, |
{0x2206, 0x7227}, {0x2206, 0x267E}, {0x2206, 0x2804}, {0x2206, 0xB729}, |
{0x2206, 0x2576}, {0x2206, 0x2A68}, {0x2206, 0xE52B}, {0x2206, 0xAD00}, |
{0x2206, 0x2CDB}, {0x2206, 0xF02D}, {0x2206, 0x67BB}, {0x2206, 0x2E7B}, |
{0x2206, 0x0F2F}, {0x2206, 0x7365}, {0x2206, 0x31AC}, {0x2206, 0xCC32}, |
{0x2206, 0x2300}, {0x2206, 0x332D}, {0x2206, 0x1734}, {0x2206, 0x7F52}, |
{0x2206, 0x3510}, {0x2206, 0x0036}, {0x2206, 0x0600}, {0x2206, 0x370C}, |
{0x2206, 0xC038}, {0x2206, 0x7FCE}, {0x2206, 0x3CE5}, {0x2206, 0xF73D}, |
{0x2206, 0x3DA4}, {0x2206, 0x6530}, {0x2206, 0x3E67}, {0x2206, 0x0053}, |
{0x2206, 0x69D2}, {0x2206, 0x0F6A}, {0x2206, 0x012C}, {0x2206, 0x6C2B}, |
{0x2206, 0x136E}, {0x2206, 0xE100}, {0x2206, 0x6F12}, {0x2206, 0xF771}, |
{0x2206, 0x006B}, {0x2206, 0x7306}, {0x2206, 0xEB74}, {0x2206, 0x94C7}, |
{0x2206, 0x7698}, {0x2206, 0x0A77}, {0x2206, 0x5000}, {0x2206, 0x788A}, |
{0x2206, 0x1579}, {0x2206, 0x7F6F}, {0x2206, 0x7A06}, {0x2206, 0xA600}, |
{0x2205, 0x8B90}, {0x2206, 0x8000}, {0x2205, 0x8B92}, {0x2206, 0x8000}, |
{0x2205, 0x8B94}, {0x2206, 0x8014}, {0x2208, 0xFFFA}, {0x2202, 0x3C65}, |
{0x2205, 0xFFF6}, {0x2206, 0x00F7}, {0x221F, 0x0000}, {0x221F, 0x0007}, |
{0x221E, 0x0042}, {0x2218, 0x0000}, {0x221E, 0x002D}, {0x2218, 0xF010}, |
{0x221E, 0x0020}, {0x2215, 0x0000}, {0x221E, 0x0023}, {0x2216, 0x8000}, |
{0x221F, 0x0000}, {0x133F, 0x0010}, {0x133E, 0x0FFE}, {0x1362, 0x0115}, |
{0x1363, 0x0002}, {0x1363, 0x0000}, {0x1306, 0x000C}, {0x1307, 0x000C}, |
{0x1303, 0x0067}, {0x1304, 0x4444}, {0x1203, 0xFF00}, {0x1200, 0x7FC4}, |
{0x121D, 0x7D16}, {0x121E, 0x03E8}, {0x121F, 0x024E}, {0x1220, 0x0230}, |
{0x1221, 0x0244}, {0x1222, 0x0226}, {0x1223, 0x024E}, {0x1224, 0x0230}, |
{0x1225, 0x0244}, {0x1226, 0x0226}, {0x1227, 0x00C0}, {0x1228, 0x00B4}, |
{0x122F, 0x00C0}, {0x1230, 0x00B4}, {0x0208, 0x03E8}, {0x0209, 0x03E8}, |
{0x020A, 0x03E8}, {0x020B, 0x03E8}, {0x020C, 0x03E8}, {0x020D, 0x03E8}, |
{0x020E, 0x03E8}, {0x020F, 0x03E8}, {0x0210, 0x03E8}, {0x0211, 0x03E8}, |
{0x0212, 0x03E8}, {0x0213, 0x03E8}, {0x0214, 0x03E8}, {0x0215, 0x03E8}, |
{0x0216, 0x03E8}, {0x0217, 0x03E8}, {0x0900, 0x0000}, {0x0901, 0x0000}, |
{0x0902, 0x0000}, {0x0903, 0x0000}, {0x0865, 0x3210}, {0x087B, 0x0000}, |
{0x087C, 0xFF00}, {0x087D, 0x0000}, {0x087E, 0x0000}, {0x0801, 0x0100}, |
{0x0802, 0x0100}, {0x0A20, 0x2040}, {0x0A21, 0x2040}, {0x0A22, 0x2040}, |
{0x0A23, 0x2040}, {0x0A24, 0x2040}, {0x0A28, 0x2040}, {0x0A29, 0x2040}, |
{0x133F, 0x0030}, {0x133E, 0x000E}, {0x221F, 0x0000}, {0x2200, 0x1340}, |
{0x221F, 0x0000}, {0x133F, 0x0010}, {0x133E, 0x0FFE}, {0x20A0, 0x1940}, |
{0x20C0, 0x1940}, {0x20E0, 0x1940}, {0x130c, 0x0050}, |
}; |
|
static const struct rtl8367_initval rtl8367_initvals_1_1[] = { |
{0x1B24, 0x0000}, {0x1B25, 0x0000}, {0x1B26, 0x0000}, {0x1B27, 0x0000}, |
{0x207F, 0x0002}, {0x2079, 0x0200}, {0x207F, 0x0000}, {0x133F, 0x0030}, |
{0x133E, 0x000E}, {0x221F, 0x0005}, {0x2201, 0x0700}, {0x2205, 0x8B82}, |
{0x2206, 0x05CB}, {0x221F, 0x0002}, {0x2204, 0x80C2}, {0x2205, 0x0938}, |
{0x221F, 0x0003}, {0x2212, 0xC4D2}, {0x220D, 0x0207}, {0x221F, 0x0001}, |
{0x2207, 0x267E}, {0x221C, 0xE5F7}, {0x221B, 0x0424}, {0x221F, 0x0007}, |
{0x221E, 0x0040}, {0x2218, 0x0000}, {0x221F, 0x0007}, {0x221E, 0x002C}, |
{0x2218, 0x008B}, {0x221F, 0x0005}, {0x2205, 0xFFF6}, {0x2206, 0x0080}, |
{0x2205, 0x8000}, {0x2206, 0xF8E0}, {0x2206, 0xE000}, {0x2206, 0xE1E0}, |
{0x2206, 0x01AC}, {0x2206, 0x2408}, {0x2206, 0xE08B}, {0x2206, 0x84F7}, |
{0x2206, 0x20E4}, {0x2206, 0x8B84}, {0x2206, 0xFC05}, {0x2206, 0xF8FA}, |
{0x2206, 0xEF69}, {0x2206, 0xE08B}, {0x2206, 0x86AC}, {0x2206, 0x201A}, |
{0x2206, 0xBF80}, {0x2206, 0x59D0}, {0x2206, 0x2402}, {0x2206, 0x803D}, |
{0x2206, 0xE0E0}, {0x2206, 0xE4E1}, {0x2206, 0xE0E5}, {0x2206, 0x5806}, |
{0x2206, 0x68C0}, {0x2206, 0xD1D2}, {0x2206, 0xE4E0}, {0x2206, 0xE4E5}, |
{0x2206, 0xE0E5}, {0x2206, 0xEF96}, {0x2206, 0xFEFC}, {0x2206, 0x05FB}, |
{0x2206, 0x0BFB}, {0x2206, 0x58FF}, {0x2206, 0x9E11}, {0x2206, 0x06F0}, |
{0x2206, 0x0C81}, {0x2206, 0x8AE0}, {0x2206, 0x0019}, {0x2206, 0x1B89}, |
{0x2206, 0xCFEB}, {0x2206, 0x19EB}, {0x2206, 0x19B0}, {0x2206, 0xEFFF}, |
{0x2206, 0x0BFF}, {0x2206, 0x0425}, {0x2206, 0x0807}, {0x2206, 0x2640}, |
{0x2206, 0x7227}, {0x2206, 0x267E}, {0x2206, 0x2804}, {0x2206, 0xB729}, |
{0x2206, 0x2576}, {0x2206, 0x2A68}, {0x2206, 0xE52B}, {0x2206, 0xAD00}, |
{0x2206, 0x2CDB}, {0x2206, 0xF02D}, {0x2206, 0x67BB}, {0x2206, 0x2E7B}, |
{0x2206, 0x0F2F}, {0x2206, 0x7365}, {0x2206, 0x31AC}, {0x2206, 0xCC32}, |
{0x2206, 0x2300}, {0x2206, 0x332D}, {0x2206, 0x1734}, {0x2206, 0x7F52}, |
{0x2206, 0x3510}, {0x2206, 0x0036}, {0x2206, 0x0600}, {0x2206, 0x370C}, |
{0x2206, 0xC038}, {0x2206, 0x7FCE}, {0x2206, 0x3CE5}, {0x2206, 0xF73D}, |
{0x2206, 0x3DA4}, {0x2206, 0x6530}, {0x2206, 0x3E67}, {0x2206, 0x0053}, |
{0x2206, 0x69D2}, {0x2206, 0x0F6A}, {0x2206, 0x012C}, {0x2206, 0x6C2B}, |
{0x2206, 0x136E}, {0x2206, 0xE100}, {0x2206, 0x6F12}, {0x2206, 0xF771}, |
{0x2206, 0x006B}, {0x2206, 0x7306}, {0x2206, 0xEB74}, {0x2206, 0x94C7}, |
{0x2206, 0x7698}, {0x2206, 0x0A77}, {0x2206, 0x5000}, {0x2206, 0x788A}, |
{0x2206, 0x1579}, {0x2206, 0x7F6F}, {0x2206, 0x7A06}, {0x2206, 0xA600}, |
{0x2205, 0x8B90}, {0x2206, 0x8000}, {0x2205, 0x8B92}, {0x2206, 0x8000}, |
{0x2205, 0x8B94}, {0x2206, 0x8014}, {0x2208, 0xFFFA}, {0x2202, 0x3C65}, |
{0x2205, 0xFFF6}, {0x2206, 0x00F7}, {0x221F, 0x0000}, {0x221F, 0x0007}, |
{0x221E, 0x0042}, {0x2218, 0x0000}, {0x221E, 0x002D}, {0x2218, 0xF010}, |
{0x221E, 0x0020}, {0x2215, 0x0000}, {0x221E, 0x0023}, {0x2216, 0x8000}, |
{0x221F, 0x0000}, {0x133F, 0x0010}, {0x133E, 0x0FFE}, {0x1362, 0x0115}, |
{0x1363, 0x0002}, {0x1363, 0x0000}, {0x1306, 0x000C}, {0x1307, 0x000C}, |
{0x1303, 0x0067}, {0x1304, 0x4444}, {0x1203, 0xFF00}, {0x1200, 0x7FC4}, |
{0x0900, 0x0000}, {0x0901, 0x0000}, {0x0902, 0x0000}, {0x0903, 0x0000}, |
{0x0865, 0x3210}, {0x087B, 0x0000}, {0x087C, 0xFF00}, {0x087D, 0x0000}, |
{0x087E, 0x0000}, {0x0801, 0x0100}, {0x0802, 0x0100}, {0x0A20, 0x2040}, |
{0x0A21, 0x2040}, {0x0A22, 0x2040}, {0x0A23, 0x2040}, {0x0A24, 0x2040}, |
{0x0A25, 0x2040}, {0x0A26, 0x2040}, {0x0A27, 0x2040}, {0x0A28, 0x2040}, |
{0x0A29, 0x2040}, {0x133F, 0x0030}, {0x133E, 0x000E}, {0x221F, 0x0000}, |
{0x2200, 0x1340}, {0x221F, 0x0000}, {0x133F, 0x0010}, {0x133E, 0x0FFE}, |
{0x1B03, 0x0876}, |
}; |
|
static const struct rtl8367_initval rtl8367_initvals_2_0[] = { |
{0x1b24, 0x0000}, {0x1b25, 0x0000}, {0x1b26, 0x0000}, {0x1b27, 0x0000}, |
{0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0007}, {0x221e, 0x0048}, |
{0x2219, 0x4012}, {0x221f, 0x0003}, {0x2201, 0x3554}, {0x2202, 0x63e8}, |
{0x2203, 0x99c2}, {0x2204, 0x0113}, {0x2205, 0x303e}, {0x220d, 0x0207}, |
{0x220e, 0xe100}, {0x221f, 0x0007}, {0x221e, 0x0040}, {0x2218, 0x0000}, |
{0x221f, 0x0007}, {0x221e, 0x002c}, {0x2218, 0x008b}, {0x221f, 0x0005}, |
{0x2205, 0xfff6}, {0x2206, 0x0080}, {0x221f, 0x0005}, {0x2205, 0x8000}, |
{0x2206, 0x0280}, {0x2206, 0x2bf7}, {0x2206, 0x00e0}, {0x2206, 0xfff7}, |
{0x2206, 0xa080}, {0x2206, 0x02ae}, {0x2206, 0xf602}, {0x2206, 0x804e}, |
{0x2206, 0x0201}, {0x2206, 0x5002}, {0x2206, 0x0163}, {0x2206, 0x0201}, |
{0x2206, 0x79e0}, {0x2206, 0x8b8c}, {0x2206, 0xe18b}, {0x2206, 0x8d1e}, |
{0x2206, 0x01e1}, {0x2206, 0x8b8e}, {0x2206, 0x1e01}, {0x2206, 0xa000}, |
{0x2206, 0xe4ae}, {0x2206, 0xd8bf}, {0x2206, 0x8b88}, {0x2206, 0xec00}, |
{0x2206, 0x19a9}, {0x2206, 0x8b90}, {0x2206, 0xf9ee}, {0x2206, 0xfff6}, |
{0x2206, 0x00ee}, {0x2206, 0xfff7}, {0x2206, 0xfce0}, {0x2206, 0xe140}, |
{0x2206, 0xe1e1}, {0x2206, 0x41f7}, {0x2206, 0x2ff6}, {0x2206, 0x28e4}, |
{0x2206, 0xe140}, {0x2206, 0xe5e1}, {0x2206, 0x4104}, {0x2206, 0xf8fa}, |
{0x2206, 0xef69}, {0x2206, 0xe08b}, {0x2206, 0x86ac}, {0x2206, 0x201a}, |
{0x2206, 0xbf80}, {0x2206, 0x77d0}, {0x2206, 0x6c02}, {0x2206, 0x2978}, |
{0x2206, 0xe0e0}, {0x2206, 0xe4e1}, {0x2206, 0xe0e5}, {0x2206, 0x5806}, |
{0x2206, 0x68c0}, {0x2206, 0xd1d2}, {0x2206, 0xe4e0}, {0x2206, 0xe4e5}, |
{0x2206, 0xe0e5}, {0x2206, 0xef96}, {0x2206, 0xfefc}, {0x2206, 0x0425}, |
{0x2206, 0x0807}, {0x2206, 0x2640}, {0x2206, 0x7227}, {0x2206, 0x267e}, |
{0x2206, 0x2804}, {0x2206, 0xb729}, {0x2206, 0x2576}, {0x2206, 0x2a68}, |
{0x2206, 0xe52b}, {0x2206, 0xad00}, {0x2206, 0x2cdb}, {0x2206, 0xf02d}, |
{0x2206, 0x67bb}, {0x2206, 0x2e7b}, {0x2206, 0x0f2f}, {0x2206, 0x7365}, |
{0x2206, 0x31ac}, {0x2206, 0xcc32}, {0x2206, 0x2300}, {0x2206, 0x332d}, |
{0x2206, 0x1734}, {0x2206, 0x7f52}, {0x2206, 0x3510}, {0x2206, 0x0036}, |
{0x2206, 0x0600}, {0x2206, 0x370c}, {0x2206, 0xc038}, {0x2206, 0x7fce}, |
{0x2206, 0x3ce5}, {0x2206, 0xf73d}, {0x2206, 0x3da4}, {0x2206, 0x6530}, |
{0x2206, 0x3e67}, {0x2206, 0x0053}, {0x2206, 0x69d2}, {0x2206, 0x0f6a}, |
{0x2206, 0x012c}, {0x2206, 0x6c2b}, {0x2206, 0x136e}, {0x2206, 0xe100}, |
{0x2206, 0x6f12}, {0x2206, 0xf771}, {0x2206, 0x006b}, {0x2206, 0x7306}, |
{0x2206, 0xeb74}, {0x2206, 0x94c7}, {0x2206, 0x7698}, {0x2206, 0x0a77}, |
{0x2206, 0x5000}, {0x2206, 0x788a}, {0x2206, 0x1579}, {0x2206, 0x7f6f}, |
{0x2206, 0x7a06}, {0x2206, 0xa600}, {0x2201, 0x0701}, {0x2200, 0x0405}, |
{0x221f, 0x0000}, {0x2200, 0x1340}, {0x221f, 0x0000}, {0x133f, 0x0010}, |
{0x133e, 0x0ffe}, {0x1203, 0xff00}, {0x1200, 0x7fc4}, {0x121d, 0x7D16}, |
{0x121e, 0x03e8}, {0x121f, 0x024e}, {0x1220, 0x0230}, {0x1221, 0x0244}, |
{0x1222, 0x0226}, {0x1223, 0x024e}, {0x1224, 0x0230}, {0x1225, 0x0244}, |
{0x1226, 0x0226}, {0x1227, 0x00c0}, {0x1228, 0x00b4}, {0x122f, 0x00c0}, |
{0x1230, 0x00b4}, {0x0208, 0x03e8}, {0x0209, 0x03e8}, {0x020a, 0x03e8}, |
{0x020b, 0x03e8}, {0x020c, 0x03e8}, {0x020d, 0x03e8}, {0x020e, 0x03e8}, |
{0x020f, 0x03e8}, {0x0210, 0x03e8}, {0x0211, 0x03e8}, {0x0212, 0x03e8}, |
{0x0213, 0x03e8}, {0x0214, 0x03e8}, {0x0215, 0x03e8}, {0x0216, 0x03e8}, |
{0x0217, 0x03e8}, {0x0900, 0x0000}, {0x0901, 0x0000}, {0x0902, 0x0000}, |
{0x0903, 0x0000}, {0x0865, 0x3210}, {0x087b, 0x0000}, {0x087c, 0xff00}, |
{0x087d, 0x0000}, {0x087e, 0x0000}, {0x0801, 0x0100}, {0x0802, 0x0100}, |
{0x0A20, 0x2040}, {0x0A21, 0x2040}, {0x0A22, 0x2040}, {0x0A23, 0x2040}, |
{0x0A24, 0x2040}, {0x0A28, 0x2040}, {0x0A29, 0x2040}, {0x20A0, 0x1940}, |
{0x20C0, 0x1940}, {0x20E0, 0x1940}, {0x130c, 0x0050}, |
}; |
|
static const struct rtl8367_initval rtl8367_initvals_2_1[] = { |
{0x1b24, 0x0000}, {0x1b25, 0x0000}, {0x1b26, 0x0000}, {0x1b27, 0x0000}, |
{0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0007}, {0x221e, 0x0048}, |
{0x2219, 0x4012}, {0x221f, 0x0003}, {0x2201, 0x3554}, {0x2202, 0x63e8}, |
{0x2203, 0x99c2}, {0x2204, 0x0113}, {0x2205, 0x303e}, {0x220d, 0x0207}, |
{0x220e, 0xe100}, {0x221f, 0x0007}, {0x221e, 0x0040}, {0x2218, 0x0000}, |
{0x221f, 0x0007}, {0x221e, 0x002c}, {0x2218, 0x008b}, {0x221f, 0x0005}, |
{0x2205, 0xfff6}, {0x2206, 0x0080}, {0x221f, 0x0005}, {0x2205, 0x8000}, |
{0x2206, 0x0280}, {0x2206, 0x2bf7}, {0x2206, 0x00e0}, {0x2206, 0xfff7}, |
{0x2206, 0xa080}, {0x2206, 0x02ae}, {0x2206, 0xf602}, {0x2206, 0x804e}, |
{0x2206, 0x0201}, {0x2206, 0x5002}, {0x2206, 0x0163}, {0x2206, 0x0201}, |
{0x2206, 0x79e0}, {0x2206, 0x8b8c}, {0x2206, 0xe18b}, {0x2206, 0x8d1e}, |
{0x2206, 0x01e1}, {0x2206, 0x8b8e}, {0x2206, 0x1e01}, {0x2206, 0xa000}, |
{0x2206, 0xe4ae}, {0x2206, 0xd8bf}, {0x2206, 0x8b88}, {0x2206, 0xec00}, |
{0x2206, 0x19a9}, {0x2206, 0x8b90}, {0x2206, 0xf9ee}, {0x2206, 0xfff6}, |
{0x2206, 0x00ee}, {0x2206, 0xfff7}, {0x2206, 0xfce0}, {0x2206, 0xe140}, |
{0x2206, 0xe1e1}, {0x2206, 0x41f7}, {0x2206, 0x2ff6}, {0x2206, 0x28e4}, |
{0x2206, 0xe140}, {0x2206, 0xe5e1}, {0x2206, 0x4104}, {0x2206, 0xf8fa}, |
{0x2206, 0xef69}, {0x2206, 0xe08b}, {0x2206, 0x86ac}, {0x2206, 0x201a}, |
{0x2206, 0xbf80}, {0x2206, 0x77d0}, {0x2206, 0x6c02}, {0x2206, 0x2978}, |
{0x2206, 0xe0e0}, {0x2206, 0xe4e1}, {0x2206, 0xe0e5}, {0x2206, 0x5806}, |
{0x2206, 0x68c0}, {0x2206, 0xd1d2}, {0x2206, 0xe4e0}, {0x2206, 0xe4e5}, |
{0x2206, 0xe0e5}, {0x2206, 0xef96}, {0x2206, 0xfefc}, {0x2206, 0x0425}, |
{0x2206, 0x0807}, {0x2206, 0x2640}, {0x2206, 0x7227}, {0x2206, 0x267e}, |
{0x2206, 0x2804}, {0x2206, 0xb729}, {0x2206, 0x2576}, {0x2206, 0x2a68}, |
{0x2206, 0xe52b}, {0x2206, 0xad00}, {0x2206, 0x2cdb}, {0x2206, 0xf02d}, |
{0x2206, 0x67bb}, {0x2206, 0x2e7b}, {0x2206, 0x0f2f}, {0x2206, 0x7365}, |
{0x2206, 0x31ac}, {0x2206, 0xcc32}, {0x2206, 0x2300}, {0x2206, 0x332d}, |
{0x2206, 0x1734}, {0x2206, 0x7f52}, {0x2206, 0x3510}, {0x2206, 0x0036}, |
{0x2206, 0x0600}, {0x2206, 0x370c}, {0x2206, 0xc038}, {0x2206, 0x7fce}, |
{0x2206, 0x3ce5}, {0x2206, 0xf73d}, {0x2206, 0x3da4}, {0x2206, 0x6530}, |
{0x2206, 0x3e67}, {0x2206, 0x0053}, {0x2206, 0x69d2}, {0x2206, 0x0f6a}, |
{0x2206, 0x012c}, {0x2206, 0x6c2b}, {0x2206, 0x136e}, {0x2206, 0xe100}, |
{0x2206, 0x6f12}, {0x2206, 0xf771}, {0x2206, 0x006b}, {0x2206, 0x7306}, |
{0x2206, 0xeb74}, {0x2206, 0x94c7}, {0x2206, 0x7698}, {0x2206, 0x0a77}, |
{0x2206, 0x5000}, {0x2206, 0x788a}, {0x2206, 0x1579}, {0x2206, 0x7f6f}, |
{0x2206, 0x7a06}, {0x2206, 0xa600}, {0x2201, 0x0701}, {0x2200, 0x0405}, |
{0x221f, 0x0000}, {0x2200, 0x1340}, {0x221f, 0x0000}, {0x133f, 0x0010}, |
{0x133e, 0x0ffe}, {0x1203, 0xff00}, {0x1200, 0x7fc4}, {0x0900, 0x0000}, |
{0x0901, 0x0000}, {0x0902, 0x0000}, {0x0903, 0x0000}, {0x0865, 0x3210}, |
{0x087b, 0x0000}, {0x087c, 0xff00}, {0x087d, 0x0000}, {0x087e, 0x0000}, |
{0x0801, 0x0100}, {0x0802, 0x0100}, {0x0A20, 0x2040}, {0x0A21, 0x2040}, |
{0x0A22, 0x2040}, {0x0A23, 0x2040}, {0x0A24, 0x2040}, {0x0A25, 0x2040}, |
{0x0A26, 0x2040}, {0x0A27, 0x2040}, {0x0A28, 0x2040}, {0x0A29, 0x2040}, |
{0x130c, 0x0050}, |
}; |
|
static int rtl8367_write_initvals(struct rtl8366_smi *smi, |
const struct rtl8367_initval *initvals, |
int count) |
{ |
int err; |
int i; |
|
for (i = 0; i < count; i++) |
REG_WR(smi, initvals[i].reg, initvals[i].val); |
|
return 0; |
} |
|
static int rtl8367_read_phy_reg(struct rtl8366_smi *smi, |
u32 phy_addr, u32 phy_reg, u32 *val) |
{ |
int timeout; |
u32 data; |
int err; |
|
if (phy_addr > RTL8367_PHY_ADDR_MAX) |
return -EINVAL; |
|
if (phy_reg > RTL8367_PHY_REG_MAX) |
return -EINVAL; |
|
REG_RD(smi, RTL8367_IA_STATUS_REG, &data); |
if (data & RTL8367_IA_STATUS_PHY_BUSY) |
return -ETIMEDOUT; |
|
/* prepare address */ |
REG_WR(smi, RTL8367_IA_ADDRESS_REG, |
RTL8367_INTERNAL_PHY_REG(phy_addr, phy_reg)); |
|
/* send read command */ |
REG_WR(smi, RTL8367_IA_CTRL_REG, |
RTL8367_IA_CTRL_CMD_MASK | RTL8367_IA_CTRL_RW_READ); |
|
timeout = 5; |
do { |
REG_RD(smi, RTL8367_IA_STATUS_REG, &data); |
if ((data & RTL8367_IA_STATUS_PHY_BUSY) == 0) |
break; |
|
if (timeout--) { |
dev_err(smi->parent, "phy read timed out\n"); |
return -ETIMEDOUT; |
} |
|
udelay(1); |
} while (1); |
|
/* read data */ |
REG_RD(smi, RTL8367_IA_READ_DATA_REG, val); |
|
dev_dbg(smi->parent, "phy_read: addr:%02x, reg:%02x, val:%04x\n", |
phy_addr, phy_reg, *val); |
return 0; |
} |
|
static int rtl8367_write_phy_reg(struct rtl8366_smi *smi, |
u32 phy_addr, u32 phy_reg, u32 val) |
{ |
int timeout; |
u32 data; |
int err; |
|
dev_dbg(smi->parent, "phy_write: addr:%02x, reg:%02x, val:%04x\n", |
phy_addr, phy_reg, val); |
|
if (phy_addr > RTL8367_PHY_ADDR_MAX) |
return -EINVAL; |
|
if (phy_reg > RTL8367_PHY_REG_MAX) |
return -EINVAL; |
|
REG_RD(smi, RTL8367_IA_STATUS_REG, &data); |
if (data & RTL8367_IA_STATUS_PHY_BUSY) |
return -ETIMEDOUT; |
|
/* preapre data */ |
REG_WR(smi, RTL8367_IA_WRITE_DATA_REG, val); |
|
/* prepare address */ |
REG_WR(smi, RTL8367_IA_ADDRESS_REG, |
RTL8367_INTERNAL_PHY_REG(phy_addr, phy_reg)); |
|
/* send write command */ |
REG_WR(smi, RTL8367_IA_CTRL_REG, |
RTL8367_IA_CTRL_CMD_MASK | RTL8367_IA_CTRL_RW_WRITE); |
|
timeout = 5; |
do { |
REG_RD(smi, RTL8367_IA_STATUS_REG, &data); |
if ((data & RTL8367_IA_STATUS_PHY_BUSY) == 0) |
break; |
|
if (timeout--) { |
dev_err(smi->parent, "phy write timed out\n"); |
return -ETIMEDOUT; |
} |
|
udelay(1); |
} while (1); |
|
return 0; |
} |
|
static int rtl8367_init_regs0(struct rtl8366_smi *smi, unsigned mode) |
{ |
const struct rtl8367_initval *initvals; |
int count; |
int err; |
|
switch (mode) { |
case 0: |
initvals = rtl8367_initvals_0_0; |
count = ARRAY_SIZE(rtl8367_initvals_0_0); |
break; |
|
case 1: |
case 2: |
initvals = rtl8367_initvals_0_1; |
count = ARRAY_SIZE(rtl8367_initvals_0_1); |
break; |
|
default: |
dev_err(smi->parent, "%s: unknow mode %u\n", __func__, mode); |
return -ENODEV; |
} |
|
err = rtl8367_write_initvals(smi, initvals, count); |
if (err) |
return err; |
|
/* TODO: complete this */ |
|
return 0; |
} |
|
static int rtl8367_init_regs1(struct rtl8366_smi *smi, unsigned mode) |
{ |
const struct rtl8367_initval *initvals; |
int count; |
|
switch (mode) { |
case 0: |
initvals = rtl8367_initvals_1_0; |
count = ARRAY_SIZE(rtl8367_initvals_1_0); |
break; |
|
case 1: |
case 2: |
initvals = rtl8367_initvals_1_1; |
count = ARRAY_SIZE(rtl8367_initvals_1_1); |
break; |
|
default: |
dev_err(smi->parent, "%s: unknow mode %u\n", __func__, mode); |
return -ENODEV; |
} |
|
return rtl8367_write_initvals(smi, initvals, count); |
} |
|
static int rtl8367_init_regs2(struct rtl8366_smi *smi, unsigned mode) |
{ |
const struct rtl8367_initval *initvals; |
int count; |
|
switch (mode) { |
case 0: |
initvals = rtl8367_initvals_2_0; |
count = ARRAY_SIZE(rtl8367_initvals_2_0); |
break; |
|
case 1: |
case 2: |
initvals = rtl8367_initvals_2_1; |
count = ARRAY_SIZE(rtl8367_initvals_2_1); |
break; |
|
default: |
dev_err(smi->parent, "%s: unknow mode %u\n", __func__, mode); |
return -ENODEV; |
} |
|
return rtl8367_write_initvals(smi, initvals, count); |
} |
|
static int rtl8367_init_regs(struct rtl8366_smi *smi) |
{ |
u32 data; |
u32 rlvid; |
u32 mode; |
int err; |
|
REG_WR(smi, RTL8367_RTL_MAGIC_ID_REG, RTL8367_RTL_MAGIC_ID_VAL); |
|
REG_RD(smi, RTL8367_CHIP_VER_REG, &data); |
rlvid = (data >> RTL8367_CHIP_VER_RLVID_SHIFT) & |
RTL8367_CHIP_VER_RLVID_MASK; |
|
REG_RD(smi, RTL8367_CHIP_MODE_REG, &data); |
mode = data & RTL8367_CHIP_MODE_MASK; |
|
switch (rlvid) { |
case 0: |
err = rtl8367_init_regs0(smi, mode); |
break; |
|
case 1: |
err = rtl8367_write_phy_reg(smi, 0, 31, 5); |
if (err) |
break; |
|
err = rtl8367_write_phy_reg(smi, 0, 5, 0x3ffe); |
if (err) |
break; |
|
err = rtl8367_read_phy_reg(smi, 0, 6, &data); |
if (err) |
break; |
|
if (data == 0x94eb) { |
err = rtl8367_init_regs1(smi, mode); |
} else if (data == 0x2104) { |
err = rtl8367_init_regs2(smi, mode); |
} else { |
dev_err(smi->parent, "unknow phy data %04x\n", data); |
return -ENODEV; |
} |
|
break; |
|
default: |
dev_err(smi->parent, "unknow rlvid %u\n", rlvid); |
err = -ENODEV; |
break; |
} |
|
return err; |
} |
|
static int rtl8367_reset_chip(struct rtl8366_smi *smi) |
{ |
int timeout = 10; |
int err; |
u32 data; |
|
REG_WR(smi, RTL8367_CHIP_RESET_REG, RTL8367_CHIP_RESET_HW); |
msleep(RTL8367_RESET_DELAY); |
|
do { |
REG_RD(smi, RTL8367_CHIP_RESET_REG, &data); |
if (!(data & RTL8367_CHIP_RESET_HW)) |
break; |
|
msleep(1); |
} while (--timeout); |
|
if (!timeout) { |
dev_err(smi->parent, "chip reset timed out\n"); |
return -ETIMEDOUT; |
} |
|
return 0; |
} |
|
static int rtl8367_extif_set_mode(struct rtl8366_smi *smi, int id, |
enum rtl8367_extif_mode mode) |
{ |
int err; |
|
/* set port mode */ |
switch (mode) { |
case RTL8367_EXTIF_MODE_RGMII: |
case RTL8367_EXTIF_MODE_RGMII_33V: |
REG_WR(smi, RTL8367_CHIP_DEBUG0_REG, 0x0367); |
REG_WR(smi, RTL8367_CHIP_DEBUG1_REG, 0x7777); |
break; |
|
case RTL8367_EXTIF_MODE_TMII_MAC: |
case RTL8367_EXTIF_MODE_TMII_PHY: |
REG_RMW(smi, RTL8367_BYPASS_LINE_RATE_REG, |
BIT((id + 1) % 2), BIT((id + 1) % 2)); |
break; |
|
case RTL8367_EXTIF_MODE_GMII: |
REG_RMW(smi, RTL8367_CHIP_DEBUG0_REG, |
RTL8367_CHIP_DEBUG0_DUMMY0(id), |
RTL8367_CHIP_DEBUG0_DUMMY0(id)); |
REG_RMW(smi, RTL8367_EXT_RGMXF_REG(id), BIT(6), BIT(6)); |
break; |
|
case RTL8367_EXTIF_MODE_MII_MAC: |
case RTL8367_EXTIF_MODE_MII_PHY: |
case RTL8367_EXTIF_MODE_DISABLED: |
REG_RMW(smi, RTL8367_BYPASS_LINE_RATE_REG, |
BIT((id + 1) % 2), 0); |
REG_RMW(smi, RTL8367_EXT_RGMXF_REG(id), BIT(6), 0); |
break; |
|
default: |
dev_err(smi->parent, |
"invalid mode for external interface %d\n", id); |
return -EINVAL; |
} |
|
REG_RMW(smi, RTL8367_DIS_REG, |
RTL8367_DIS_RGMII_MASK << RTL8367_DIS_RGMII_SHIFT(id), |
mode << RTL8367_DIS_RGMII_SHIFT(id)); |
|
return 0; |
} |
|
static int rtl8367_extif_set_force(struct rtl8366_smi *smi, int id, |
struct rtl8367_port_ability *pa) |
{ |
u32 mask; |
u32 val; |
int err; |
|
mask = (RTL8367_DI_FORCE_MODE | |
RTL8367_DI_FORCE_NWAY | |
RTL8367_DI_FORCE_TXPAUSE | |
RTL8367_DI_FORCE_RXPAUSE | |
RTL8367_DI_FORCE_LINK | |
RTL8367_DI_FORCE_DUPLEX | |
RTL8367_DI_FORCE_SPEED_MASK); |
|
val = pa->speed; |
val |= pa->force_mode ? RTL8367_DI_FORCE_MODE : 0; |
val |= pa->nway ? RTL8367_DI_FORCE_NWAY : 0; |
val |= pa->txpause ? RTL8367_DI_FORCE_TXPAUSE : 0; |
val |= pa->rxpause ? RTL8367_DI_FORCE_RXPAUSE : 0; |
val |= pa->link ? RTL8367_DI_FORCE_LINK : 0; |
val |= pa->duplex ? RTL8367_DI_FORCE_DUPLEX : 0; |
|
REG_RMW(smi, RTL8367_DI_FORCE_REG(id), mask, val); |
|
return 0; |
} |
|
static int rtl8367_extif_set_rgmii_delay(struct rtl8366_smi *smi, int id, |
unsigned txdelay, unsigned rxdelay) |
{ |
u32 mask; |
u32 val; |
int err; |
|
mask = (RTL8367_EXT_RGMXF_RXDELAY_MASK | |
(RTL8367_EXT_RGMXF_TXDELAY_MASK << |
RTL8367_EXT_RGMXF_TXDELAY_SHIFT)); |
|
val = rxdelay; |
val |= txdelay << RTL8367_EXT_RGMXF_TXDELAY_SHIFT; |
|
REG_RMW(smi, RTL8367_EXT_RGMXF_REG(id), mask, val); |
|
return 0; |
} |
|
static int rtl8367_extif_init(struct rtl8366_smi *smi, int id, |
struct rtl8367_extif_config *cfg) |
{ |
enum rtl8367_extif_mode mode; |
int err; |
|
mode = (cfg) ? cfg->mode : RTL8367_EXTIF_MODE_DISABLED; |
|
err = rtl8367_extif_set_mode(smi, id, mode); |
if (err) |
return err; |
|
if (mode != RTL8367_EXTIF_MODE_DISABLED) { |
err = rtl8367_extif_set_force(smi, id, &cfg->ability); |
if (err) |
return err; |
|
err = rtl8367_extif_set_rgmii_delay(smi, id, cfg->txdelay, |
cfg->rxdelay); |
if (err) |
return err; |
} |
|
return 0; |
} |
|
static int rtl8367_led_group_set_ports(struct rtl8366_smi *smi, |
unsigned int group, u16 port_mask) |
{ |
u32 reg; |
u32 s; |
int err; |
|
port_mask &= RTL8367_PARA_LED_IO_EN_PMASK; |
s = (group % 2) * 8; |
reg = RTL8367_PARA_LED_IO_EN1_REG + (group / 2); |
|
REG_RMW(smi, reg, (RTL8367_PARA_LED_IO_EN_PMASK << s), port_mask << s); |
|
return 0; |
} |
|
static int rtl8367_led_group_set_mode(struct rtl8366_smi *smi, |
unsigned int mode) |
{ |
u16 mask; |
u16 set; |
int err; |
|
mode &= RTL8367_LED_CONFIG_DATA_M; |
|
mask = (RTL8367_LED_CONFIG_DATA_M << RTL8367_LED_CONFIG_DATA_S) | |
RTL8367_LED_CONFIG_SEL; |
set = (mode << RTL8367_LED_CONFIG_DATA_S) | RTL8367_LED_CONFIG_SEL; |
|
REG_RMW(smi, RTL8367_LED_CONFIG_REG, mask, set); |
|
return 0; |
} |
|
static int rtl8367_led_group_set_config(struct rtl8366_smi *smi, |
unsigned int led, unsigned int cfg) |
{ |
u16 mask; |
u16 set; |
int err; |
|
mask = (RTL8367_LED_CONFIG_LED_CFG_M << (led * 4)) | |
RTL8367_LED_CONFIG_SEL; |
set = (cfg & RTL8367_LED_CONFIG_LED_CFG_M) << (led * 4); |
|
REG_RMW(smi, RTL8367_LED_CONFIG_REG, mask, set); |
return 0; |
} |
|
static int rtl8367_led_op_select_parallel(struct rtl8366_smi *smi) |
{ |
int err; |
|
REG_WR(smi, RTL8367_LED_SYS_CONFIG_REG, 0x1472); |
return 0; |
} |
|
static int rtl8367_led_blinkrate_set(struct rtl8366_smi *smi, unsigned int rate) |
{ |
u16 mask; |
u16 set; |
int err; |
|
mask = RTL8367_LED_MODE_RATE_M << RTL8367_LED_MODE_RATE_S; |
set = (rate & RTL8367_LED_MODE_RATE_M) << RTL8367_LED_MODE_RATE_S; |
REG_RMW(smi, RTL8367_LED_MODE_REG, mask, set); |
|
return 0; |
} |
|
#ifdef CONFIG_OF |
static int rtl8367_extif_init_of(struct rtl8366_smi *smi, int id, |
const char *name) |
{ |
struct rtl8367_extif_config *cfg; |
const __be32 *prop; |
int size; |
int err; |
|
prop = of_get_property(smi->parent->of_node, name, &size); |
if (!prop) |
return rtl8367_extif_init(smi, id, NULL); |
|
if (size != (9 * sizeof(*prop))) { |
dev_err(smi->parent, "%s property is invalid\n", name); |
return -EINVAL; |
} |
|
cfg = kzalloc(sizeof(struct rtl8367_extif_config), GFP_KERNEL); |
if (!cfg) |
return -ENOMEM; |
|
cfg->txdelay = be32_to_cpup(prop++); |
cfg->rxdelay = be32_to_cpup(prop++); |
cfg->mode = be32_to_cpup(prop++); |
cfg->ability.force_mode = be32_to_cpup(prop++); |
cfg->ability.txpause = be32_to_cpup(prop++); |
cfg->ability.rxpause = be32_to_cpup(prop++); |
cfg->ability.link = be32_to_cpup(prop++); |
cfg->ability.duplex = be32_to_cpup(prop++); |
cfg->ability.speed = be32_to_cpup(prop++); |
|
err = rtl8367_extif_init(smi, id, cfg); |
kfree(cfg); |
|
return err; |
} |
#else |
static int rtl8367_extif_init_of(struct rtl8366_smi *smi, int id, |
const char *name) |
{ |
return -EINVAL; |
} |
#endif |
|
static int rtl8367_setup(struct rtl8366_smi *smi) |
{ |
struct rtl8367_platform_data *pdata; |
int err; |
int i; |
|
pdata = smi->parent->platform_data; |
|
err = rtl8367_init_regs(smi); |
if (err) |
return err; |
|
/* initialize external interfaces */ |
if (smi->parent->of_node) { |
err = rtl8367_extif_init_of(smi, 0, "realtek,extif0"); |
if (err) |
return err; |
|
err = rtl8367_extif_init_of(smi, 1, "realtek,extif1"); |
if (err) |
return err; |
} else { |
err = rtl8367_extif_init(smi, 0, pdata->extif0_cfg); |
if (err) |
return err; |
|
err = rtl8367_extif_init(smi, 1, pdata->extif1_cfg); |
if (err) |
return err; |
} |
|
/* set maximum packet length to 1536 bytes */ |
REG_RMW(smi, RTL8367_SWC0_REG, RTL8367_SWC0_MAX_LENGTH_MASK, |
RTL8367_SWC0_MAX_LENGTH_1536); |
|
/* |
* discard VLAN tagged packets if the port is not a member of |
* the VLAN with which the packets is associated. |
*/ |
REG_WR(smi, RTL8367_VLAN_INGRESS_REG, RTL8367_PORTS_ALL); |
|
/* |
* Setup egress tag mode for each port. |
*/ |
for (i = 0; i < RTL8367_NUM_PORTS; i++) |
REG_RMW(smi, |
RTL8367_PORT_CFG_REG(i), |
RTL8367_PORT_CFG_EGRESS_MODE_MASK << |
RTL8367_PORT_CFG_EGRESS_MODE_SHIFT, |
RTL8367_PORT_CFG_EGRESS_MODE_ORIGINAL << |
RTL8367_PORT_CFG_EGRESS_MODE_SHIFT); |
|
/* setup LEDs */ |
err = rtl8367_led_group_set_ports(smi, 0, RTL8367_PORTS_ALL); |
if (err) |
return err; |
|
err = rtl8367_led_group_set_mode(smi, 0); |
if (err) |
return err; |
|
err = rtl8367_led_op_select_parallel(smi); |
if (err) |
return err; |
|
err = rtl8367_led_blinkrate_set(smi, 1); |
if (err) |
return err; |
|
err = rtl8367_led_group_set_config(smi, 0, 2); |
if (err) |
return err; |
|
return 0; |
} |
|
static int rtl8367_get_mib_counter(struct rtl8366_smi *smi, int counter, |
int port, unsigned long long *val) |
{ |
struct rtl8366_mib_counter *mib; |
int offset; |
int i; |
int err; |
u32 addr, data; |
u64 mibvalue; |
|
if (port > RTL8367_NUM_PORTS || counter >= RTL8367_MIB_COUNT) |
return -EINVAL; |
|
mib = &rtl8367_mib_counters[counter]; |
addr = RTL8367_MIB_COUNTER_PORT_OFFSET * port + mib->offset; |
|
/* |
* Writing access counter address first |
* then ASIC will prepare 64bits counter wait for being retrived |
*/ |
REG_WR(smi, RTL8367_MIB_ADDRESS_REG, addr >> 2); |
|
/* read MIB control register */ |
REG_RD(smi, RTL8367_MIB_CTRL_REG(0), &data); |
|
if (data & RTL8367_MIB_CTRL_BUSY_MASK) |
return -EBUSY; |
|
if (data & RTL8367_MIB_CTRL_RESET_MASK) |
return -EIO; |
|
if (mib->length == 4) |
offset = 3; |
else |
offset = (mib->offset + 1) % 4; |
|
mibvalue = 0; |
for (i = 0; i < mib->length; i++) { |
REG_RD(smi, RTL8367_MIB_COUNTER_REG(offset - i), &data); |
mibvalue = (mibvalue << 16) | (data & 0xFFFF); |
} |
|
*val = mibvalue; |
return 0; |
} |
|
static int rtl8367_get_vlan_4k(struct rtl8366_smi *smi, u32 vid, |
struct rtl8366_vlan_4k *vlan4k) |
{ |
u32 data[RTL8367_TA_VLAN_DATA_SIZE]; |
int err; |
int i; |
|
memset(vlan4k, '\0', sizeof(struct rtl8366_vlan_4k)); |
|
if (vid >= RTL8367_NUM_VIDS) |
return -EINVAL; |
|
/* write VID */ |
REG_WR(smi, RTL8367_TA_ADDR_REG, vid); |
|
/* write table access control word */ |
REG_WR(smi, RTL8367_TA_CTRL_REG, RTL8367_TA_CTRL_CVLAN_READ); |
|
for (i = 0; i < ARRAY_SIZE(data); i++) |
REG_RD(smi, RTL8367_TA_DATA_REG(i), &data[i]); |
|
vlan4k->vid = vid; |
vlan4k->member = (data[0] >> RTL8367_TA_VLAN_MEMBER_SHIFT) & |
RTL8367_TA_VLAN_MEMBER_MASK; |
vlan4k->fid = (data[1] >> RTL8367_TA_VLAN_FID_SHIFT) & |
RTL8367_TA_VLAN_FID_MASK; |
vlan4k->untag = (data[2] >> RTL8367_TA_VLAN_UNTAG1_SHIFT) & |
RTL8367_TA_VLAN_UNTAG1_MASK; |
vlan4k->untag |= ((data[3] >> RTL8367_TA_VLAN_UNTAG2_SHIFT) & |
RTL8367_TA_VLAN_UNTAG2_MASK) << 2; |
|
return 0; |
} |
|
static int rtl8367_set_vlan_4k(struct rtl8366_smi *smi, |
const struct rtl8366_vlan_4k *vlan4k) |
{ |
u32 data[RTL8367_TA_VLAN_DATA_SIZE]; |
int err; |
int i; |
|
if (vlan4k->vid >= RTL8367_NUM_VIDS || |
vlan4k->member > RTL8367_TA_VLAN_MEMBER_MASK || |
vlan4k->untag > RTL8367_UNTAG_MASK || |
vlan4k->fid > RTL8367_FIDMAX) |
return -EINVAL; |
|
data[0] = (vlan4k->member & RTL8367_TA_VLAN_MEMBER_MASK) << |
RTL8367_TA_VLAN_MEMBER_SHIFT; |
data[1] = (vlan4k->fid & RTL8367_TA_VLAN_FID_MASK) << |
RTL8367_TA_VLAN_FID_SHIFT; |
data[2] = (vlan4k->untag & RTL8367_TA_VLAN_UNTAG1_MASK) << |
RTL8367_TA_VLAN_UNTAG1_SHIFT; |
data[3] = ((vlan4k->untag >> 2) & RTL8367_TA_VLAN_UNTAG2_MASK) << |
RTL8367_TA_VLAN_UNTAG2_SHIFT; |
|
for (i = 0; i < ARRAY_SIZE(data); i++) |
REG_WR(smi, RTL8367_TA_DATA_REG(i), data[i]); |
|
/* write VID */ |
REG_WR(smi, RTL8367_TA_ADDR_REG, |
vlan4k->vid & RTL8367_TA_VLAN_VID_MASK); |
|
/* write table access control word */ |
REG_WR(smi, RTL8367_TA_CTRL_REG, RTL8367_TA_CTRL_CVLAN_WRITE); |
|
return 0; |
} |
|
static int rtl8367_get_vlan_mc(struct rtl8366_smi *smi, u32 index, |
struct rtl8366_vlan_mc *vlanmc) |
{ |
u32 data[RTL8367_VLAN_MC_DATA_SIZE]; |
int err; |
int i; |
|
memset(vlanmc, '\0', sizeof(struct rtl8366_vlan_mc)); |
|
if (index >= RTL8367_NUM_VLANS) |
return -EINVAL; |
|
for (i = 0; i < ARRAY_SIZE(data); i++) |
REG_RD(smi, RTL8367_VLAN_MC_BASE(index) + i, &data[i]); |
|
vlanmc->member = (data[0] >> RTL8367_VLAN_MC_MEMBER_SHIFT) & |
RTL8367_VLAN_MC_MEMBER_MASK; |
vlanmc->fid = (data[1] >> RTL8367_VLAN_MC_FID_SHIFT) & |
RTL8367_VLAN_MC_FID_MASK; |
vlanmc->vid = (data[3] >> RTL8367_VLAN_MC_EVID_SHIFT) & |
RTL8367_VLAN_MC_EVID_MASK; |
|
return 0; |
} |
|
static int rtl8367_set_vlan_mc(struct rtl8366_smi *smi, u32 index, |
const struct rtl8366_vlan_mc *vlanmc) |
{ |
u32 data[RTL8367_VLAN_MC_DATA_SIZE]; |
int err; |
int i; |
|
if (index >= RTL8367_NUM_VLANS || |
vlanmc->vid >= RTL8367_NUM_VIDS || |
vlanmc->priority > RTL8367_PRIORITYMAX || |
vlanmc->member > RTL8367_VLAN_MC_MEMBER_MASK || |
vlanmc->untag > RTL8367_UNTAG_MASK || |
vlanmc->fid > RTL8367_FIDMAX) |
return -EINVAL; |
|
data[0] = (vlanmc->member & RTL8367_VLAN_MC_MEMBER_MASK) << |
RTL8367_VLAN_MC_MEMBER_SHIFT; |
data[1] = (vlanmc->fid & RTL8367_VLAN_MC_FID_MASK) << |
RTL8367_VLAN_MC_FID_SHIFT; |
data[2] = 0; |
data[3] = (vlanmc->vid & RTL8367_VLAN_MC_EVID_MASK) << |
RTL8367_VLAN_MC_EVID_SHIFT; |
|
for (i = 0; i < ARRAY_SIZE(data); i++) |
REG_WR(smi, RTL8367_VLAN_MC_BASE(index) + i, data[i]); |
|
return 0; |
} |
|
static int rtl8367_get_mc_index(struct rtl8366_smi *smi, int port, int *val) |
{ |
u32 data; |
int err; |
|
if (port >= RTL8367_NUM_PORTS) |
return -EINVAL; |
|
REG_RD(smi, RTL8367_VLAN_PVID_CTRL_REG(port), &data); |
|
*val = (data >> RTL8367_VLAN_PVID_CTRL_SHIFT(port)) & |
RTL8367_VLAN_PVID_CTRL_MASK; |
|
return 0; |
} |
|
static int rtl8367_set_mc_index(struct rtl8366_smi *smi, int port, int index) |
{ |
if (port >= RTL8367_NUM_PORTS || index >= RTL8367_NUM_VLANS) |
return -EINVAL; |
|
return rtl8366_smi_rmwr(smi, RTL8367_VLAN_PVID_CTRL_REG(port), |
RTL8367_VLAN_PVID_CTRL_MASK << |
RTL8367_VLAN_PVID_CTRL_SHIFT(port), |
(index & RTL8367_VLAN_PVID_CTRL_MASK) << |
RTL8367_VLAN_PVID_CTRL_SHIFT(port)); |
} |
|
static int rtl8367_enable_vlan(struct rtl8366_smi *smi, int enable) |
{ |
return rtl8366_smi_rmwr(smi, RTL8367_VLAN_CTRL_REG, |
RTL8367_VLAN_CTRL_ENABLE, |
(enable) ? RTL8367_VLAN_CTRL_ENABLE : 0); |
} |
|
static int rtl8367_enable_vlan4k(struct rtl8366_smi *smi, int enable) |
{ |
return 0; |
} |
|
static int rtl8367_is_vlan_valid(struct rtl8366_smi *smi, unsigned vlan) |
{ |
unsigned max = RTL8367_NUM_VLANS; |
|
if (smi->vlan4k_enabled) |
max = RTL8367_NUM_VIDS - 1; |
|
if (vlan == 0 || vlan >= max) |
return 0; |
|
return 1; |
} |
|
static int rtl8367_enable_port(struct rtl8366_smi *smi, int port, int enable) |
{ |
int err; |
|
REG_WR(smi, RTL8367_PORT_ISOLATION_REG(port), |
(enable) ? RTL8367_PORTS_ALL : 0); |
|
return 0; |
} |
|
static int rtl8367_sw_reset_mibs(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
|
return rtl8366_smi_rmwr(smi, RTL8367_MIB_CTRL_REG(0), 0, |
RTL8367_MIB_CTRL_GLOBAL_RESET_MASK); |
} |
|
static int rtl8367_sw_get_port_link(struct switch_dev *dev, |
int port, |
struct switch_port_link *link) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
u32 data = 0; |
u32 speed; |
|
if (port >= RTL8367_NUM_PORTS) |
return -EINVAL; |
|
rtl8366_smi_read_reg(smi, RTL8367_PORT_STATUS_REG(port), &data); |
|
link->link = !!(data & RTL8367_PORT_STATUS_LINK); |
if (!link->link) |
return 0; |
|
link->duplex = !!(data & RTL8367_PORT_STATUS_DUPLEX); |
link->rx_flow = !!(data & RTL8367_PORT_STATUS_RXPAUSE); |
link->tx_flow = !!(data & RTL8367_PORT_STATUS_TXPAUSE); |
link->aneg = !!(data & RTL8367_PORT_STATUS_NWAY); |
|
speed = (data & RTL8367_PORT_STATUS_SPEED_MASK); |
switch (speed) { |
case 0: |
link->speed = SWITCH_PORT_SPEED_10; |
break; |
case 1: |
link->speed = SWITCH_PORT_SPEED_100; |
break; |
case 2: |
link->speed = SWITCH_PORT_SPEED_1000; |
break; |
default: |
link->speed = SWITCH_PORT_SPEED_UNKNOWN; |
break; |
} |
|
return 0; |
} |
|
static int rtl8367_sw_get_max_length(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
u32 data; |
|
rtl8366_smi_read_reg(smi, RTL8367_SWC0_REG, &data); |
val->value.i = (data & RTL8367_SWC0_MAX_LENGTH_MASK) >> |
RTL8367_SWC0_MAX_LENGTH_SHIFT; |
|
return 0; |
} |
|
static int rtl8367_sw_set_max_length(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
u32 max_len; |
|
switch (val->value.i) { |
case 0: |
max_len = RTL8367_SWC0_MAX_LENGTH_1522; |
break; |
case 1: |
max_len = RTL8367_SWC0_MAX_LENGTH_1536; |
break; |
case 2: |
max_len = RTL8367_SWC0_MAX_LENGTH_1552; |
break; |
case 3: |
max_len = RTL8367_SWC0_MAX_LENGTH_16000; |
break; |
default: |
return -EINVAL; |
} |
|
return rtl8366_smi_rmwr(smi, RTL8367_SWC0_REG, |
RTL8367_SWC0_MAX_LENGTH_MASK, max_len); |
} |
|
|
static int rtl8367_sw_reset_port_mibs(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
int port; |
|
port = val->port_vlan; |
if (port >= RTL8367_NUM_PORTS) |
return -EINVAL; |
|
return rtl8366_smi_rmwr(smi, RTL8367_MIB_CTRL_REG(port / 8), 0, |
RTL8367_MIB_CTRL_PORT_RESET_MASK(port % 8)); |
} |
|
static int rtl8367_sw_get_port_stats(struct switch_dev *dev, int port, |
struct switch_port_stats *stats) |
{ |
return (rtl8366_sw_get_port_stats(dev, port, stats, |
RTL8367_MIB_TXB_ID, RTL8367_MIB_RXB_ID)); |
} |
|
static struct switch_attr rtl8367_globals[] = { |
{ |
.type = SWITCH_TYPE_INT, |
.name = "enable_vlan", |
.description = "Enable VLAN mode", |
.set = rtl8366_sw_set_vlan_enable, |
.get = rtl8366_sw_get_vlan_enable, |
.max = 1, |
.ofs = 1 |
}, { |
.type = SWITCH_TYPE_INT, |
.name = "enable_vlan4k", |
.description = "Enable VLAN 4K mode", |
.set = rtl8366_sw_set_vlan_enable, |
.get = rtl8366_sw_get_vlan_enable, |
.max = 1, |
.ofs = 2 |
}, { |
.type = SWITCH_TYPE_NOVAL, |
.name = "reset_mibs", |
.description = "Reset all MIB counters", |
.set = rtl8367_sw_reset_mibs, |
}, { |
.type = SWITCH_TYPE_INT, |
.name = "max_length", |
.description = "Get/Set the maximum length of valid packets" |
"(0:1522, 1:1536, 2:1552, 3:16000)", |
.set = rtl8367_sw_set_max_length, |
.get = rtl8367_sw_get_max_length, |
.max = 3, |
} |
}; |
|
static struct switch_attr rtl8367_port[] = { |
{ |
.type = SWITCH_TYPE_NOVAL, |
.name = "reset_mib", |
.description = "Reset single port MIB counters", |
.set = rtl8367_sw_reset_port_mibs, |
}, { |
.type = SWITCH_TYPE_STRING, |
.name = "mib", |
.description = "Get MIB counters for port", |
.max = 33, |
.set = NULL, |
.get = rtl8366_sw_get_port_mib, |
}, |
}; |
|
static struct switch_attr rtl8367_vlan[] = { |
{ |
.type = SWITCH_TYPE_STRING, |
.name = "info", |
.description = "Get vlan information", |
.max = 1, |
.set = NULL, |
.get = rtl8366_sw_get_vlan_info, |
}, { |
.type = SWITCH_TYPE_INT, |
.name = "fid", |
.description = "Get/Set vlan FID", |
.max = RTL8367_FIDMAX, |
.set = rtl8366_sw_set_vlan_fid, |
.get = rtl8366_sw_get_vlan_fid, |
}, |
}; |
|
static const struct switch_dev_ops rtl8367_sw_ops = { |
.attr_global = { |
.attr = rtl8367_globals, |
.n_attr = ARRAY_SIZE(rtl8367_globals), |
}, |
.attr_port = { |
.attr = rtl8367_port, |
.n_attr = ARRAY_SIZE(rtl8367_port), |
}, |
.attr_vlan = { |
.attr = rtl8367_vlan, |
.n_attr = ARRAY_SIZE(rtl8367_vlan), |
}, |
|
.get_vlan_ports = rtl8366_sw_get_vlan_ports, |
.set_vlan_ports = rtl8366_sw_set_vlan_ports, |
.get_port_pvid = rtl8366_sw_get_port_pvid, |
.set_port_pvid = rtl8366_sw_set_port_pvid, |
.reset_switch = rtl8366_sw_reset_switch, |
.get_port_link = rtl8367_sw_get_port_link, |
.get_port_stats = rtl8367_sw_get_port_stats, |
}; |
|
static int rtl8367_switch_init(struct rtl8366_smi *smi) |
{ |
struct switch_dev *dev = &smi->sw_dev; |
int err; |
|
dev->name = "RTL8367"; |
dev->cpu_port = RTL8367_CPU_PORT_NUM; |
dev->ports = RTL8367_NUM_PORTS; |
dev->vlans = RTL8367_NUM_VIDS; |
dev->ops = &rtl8367_sw_ops; |
dev->alias = dev_name(smi->parent); |
|
err = register_switch(dev, NULL); |
if (err) |
dev_err(smi->parent, "switch registration failed\n"); |
|
return err; |
} |
|
static void rtl8367_switch_cleanup(struct rtl8366_smi *smi) |
{ |
unregister_switch(&smi->sw_dev); |
} |
|
static int rtl8367_mii_read(struct mii_bus *bus, int addr, int reg) |
{ |
struct rtl8366_smi *smi = bus->priv; |
u32 val = 0; |
int err; |
|
err = rtl8367_read_phy_reg(smi, addr, reg, &val); |
if (err) |
return 0xffff; |
|
return val; |
} |
|
static int rtl8367_mii_write(struct mii_bus *bus, int addr, int reg, u16 val) |
{ |
struct rtl8366_smi *smi = bus->priv; |
u32 t; |
int err; |
|
err = rtl8367_write_phy_reg(smi, addr, reg, val); |
if (err) |
return err; |
|
/* flush write */ |
(void) rtl8367_read_phy_reg(smi, addr, reg, &t); |
|
return err; |
} |
|
static int rtl8367_detect(struct rtl8366_smi *smi) |
{ |
u32 rtl_no = 0; |
u32 rtl_ver = 0; |
char *chip_name; |
int ret; |
|
ret = rtl8366_smi_read_reg(smi, RTL8367_RTL_NO_REG, &rtl_no); |
if (ret) { |
dev_err(smi->parent, "unable to read chip number\n"); |
return ret; |
} |
|
switch (rtl_no) { |
case RTL8367_RTL_NO_8367R: |
chip_name = "8367R"; |
break; |
case RTL8367_RTL_NO_8367M: |
chip_name = "8367M"; |
break; |
default: |
dev_err(smi->parent, "unknown chip number (%04x)\n", rtl_no); |
return -ENODEV; |
} |
|
ret = rtl8366_smi_read_reg(smi, RTL8367_RTL_VER_REG, &rtl_ver); |
if (ret) { |
dev_err(smi->parent, "unable to read chip version\n"); |
return ret; |
} |
|
dev_info(smi->parent, "RTL%s ver. %u chip found\n", |
chip_name, rtl_ver & RTL8367_RTL_VER_MASK); |
|
return 0; |
} |
|
static struct rtl8366_smi_ops rtl8367_smi_ops = { |
.detect = rtl8367_detect, |
.reset_chip = rtl8367_reset_chip, |
.setup = rtl8367_setup, |
|
.mii_read = rtl8367_mii_read, |
.mii_write = rtl8367_mii_write, |
|
.get_vlan_mc = rtl8367_get_vlan_mc, |
.set_vlan_mc = rtl8367_set_vlan_mc, |
.get_vlan_4k = rtl8367_get_vlan_4k, |
.set_vlan_4k = rtl8367_set_vlan_4k, |
.get_mc_index = rtl8367_get_mc_index, |
.set_mc_index = rtl8367_set_mc_index, |
.get_mib_counter = rtl8367_get_mib_counter, |
.is_vlan_valid = rtl8367_is_vlan_valid, |
.enable_vlan = rtl8367_enable_vlan, |
.enable_vlan4k = rtl8367_enable_vlan4k, |
.enable_port = rtl8367_enable_port, |
}; |
|
static int rtl8367_probe(struct platform_device *pdev) |
{ |
struct rtl8366_smi *smi; |
int err; |
|
smi = rtl8366_smi_probe(pdev); |
if (!smi) |
return -ENODEV; |
|
smi->clk_delay = 1500; |
smi->cmd_read = 0xb9; |
smi->cmd_write = 0xb8; |
smi->ops = &rtl8367_smi_ops; |
smi->cpu_port = RTL8367_CPU_PORT_NUM; |
smi->num_ports = RTL8367_NUM_PORTS; |
smi->num_vlan_mc = RTL8367_NUM_VLANS; |
smi->mib_counters = rtl8367_mib_counters; |
smi->num_mib_counters = ARRAY_SIZE(rtl8367_mib_counters); |
|
err = rtl8366_smi_init(smi); |
if (err) |
goto err_free_smi; |
|
platform_set_drvdata(pdev, smi); |
|
err = rtl8367_switch_init(smi); |
if (err) |
goto err_clear_drvdata; |
|
return 0; |
|
err_clear_drvdata: |
platform_set_drvdata(pdev, NULL); |
rtl8366_smi_cleanup(smi); |
err_free_smi: |
kfree(smi); |
return err; |
} |
|
static int rtl8367_remove(struct platform_device *pdev) |
{ |
struct rtl8366_smi *smi = platform_get_drvdata(pdev); |
|
if (smi) { |
rtl8367_switch_cleanup(smi); |
platform_set_drvdata(pdev, NULL); |
rtl8366_smi_cleanup(smi); |
kfree(smi); |
} |
|
return 0; |
} |
|
static void rtl8367_shutdown(struct platform_device *pdev) |
{ |
struct rtl8366_smi *smi = platform_get_drvdata(pdev); |
|
if (smi) |
rtl8367_reset_chip(smi); |
} |
|
#ifdef CONFIG_OF |
static const struct of_device_id rtl8367_match[] = { |
{ .compatible = "realtek,rtl8367" }, |
{}, |
}; |
MODULE_DEVICE_TABLE(of, rtl8367_match); |
#endif |
|
static struct platform_driver rtl8367_driver = { |
.driver = { |
.name = RTL8367_DRIVER_NAME, |
.owner = THIS_MODULE, |
#ifdef CONFIG_OF |
.of_match_table = of_match_ptr(rtl8367_match), |
#endif |
}, |
.probe = rtl8367_probe, |
.remove = rtl8367_remove, |
.shutdown = rtl8367_shutdown, |
}; |
|
static int __init rtl8367_module_init(void) |
{ |
return platform_driver_register(&rtl8367_driver); |
} |
module_init(rtl8367_module_init); |
|
static void __exit rtl8367_module_exit(void) |
{ |
platform_driver_unregister(&rtl8367_driver); |
} |
module_exit(rtl8367_module_exit); |
|
MODULE_DESCRIPTION("Realtek RTL8367 ethernet switch driver"); |
MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>"); |
MODULE_LICENSE("GPL v2"); |
MODULE_ALIAS("platform:" RTL8367_DRIVER_NAME); |
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/rtl8367b.c |
@@ -0,0 +1,1613 @@ |
/* |
* Platform driver for the Realtek RTL8367R-VB ethernet switches |
* |
* Copyright (C) 2012 Gabor Juhos <juhosg@openwrt.org> |
* |
* This program is free software; you can redistribute it and/or modify it |
* under the terms of the GNU General Public License version 2 as published |
* by the Free Software Foundation. |
*/ |
|
#include <linux/kernel.h> |
#include <linux/module.h> |
#include <linux/init.h> |
#include <linux/device.h> |
#include <linux/of.h> |
#include <linux/of_platform.h> |
#include <linux/delay.h> |
#include <linux/skbuff.h> |
#include <linux/rtl8367.h> |
|
#include "rtl8366_smi.h" |
|
#define RTL8367B_RESET_DELAY 1000 /* msecs*/ |
|
#define RTL8367B_PHY_ADDR_MAX 8 |
#define RTL8367B_PHY_REG_MAX 31 |
|
#define RTL8367B_VID_MASK 0x3fff |
#define RTL8367B_FID_MASK 0xf |
#define RTL8367B_UNTAG_MASK 0xff |
#define RTL8367B_MEMBER_MASK 0xff |
|
#define RTL8367B_PORT_MISC_CFG_REG(_p) (0x000e + 0x20 * (_p)) |
#define RTL8367B_PORT_MISC_CFG_EGRESS_MODE_SHIFT 4 |
#define RTL8367B_PORT_MISC_CFG_EGRESS_MODE_MASK 0x3 |
#define RTL8367B_PORT_MISC_CFG_EGRESS_MODE_ORIGINAL 0 |
#define RTL8367B_PORT_MISC_CFG_EGRESS_MODE_KEEP 1 |
#define RTL8367B_PORT_MISC_CFG_EGRESS_MODE_PRI 2 |
#define RTL8367B_PORT_MISC_CFG_EGRESS_MODE_REAL 3 |
|
#define RTL8367B_BYPASS_LINE_RATE_REG 0x03f7 |
|
#define RTL8367B_TA_CTRL_REG 0x0500 /*GOOD*/ |
#define RTL8367B_TA_CTRL_SPA_SHIFT 8 |
#define RTL8367B_TA_CTRL_SPA_MASK 0x7 |
#define RTL8367B_TA_CTRL_METHOD BIT(4)/*GOOD*/ |
#define RTL8367B_TA_CTRL_CMD_SHIFT 3 |
#define RTL8367B_TA_CTRL_CMD_READ 0 |
#define RTL8367B_TA_CTRL_CMD_WRITE 1 |
#define RTL8367B_TA_CTRL_TABLE_SHIFT 0 /*GOOD*/ |
#define RTL8367B_TA_CTRL_TABLE_ACLRULE 1 |
#define RTL8367B_TA_CTRL_TABLE_ACLACT 2 |
#define RTL8367B_TA_CTRL_TABLE_CVLAN 3 |
#define RTL8367B_TA_CTRL_TABLE_L2 4 |
#define RTL8367B_TA_CTRL_CVLAN_READ \ |
((RTL8367B_TA_CTRL_CMD_READ << RTL8367B_TA_CTRL_CMD_SHIFT) | \ |
RTL8367B_TA_CTRL_TABLE_CVLAN) |
#define RTL8367B_TA_CTRL_CVLAN_WRITE \ |
((RTL8367B_TA_CTRL_CMD_WRITE << RTL8367B_TA_CTRL_CMD_SHIFT) | \ |
RTL8367B_TA_CTRL_TABLE_CVLAN) |
|
#define RTL8367B_TA_ADDR_REG 0x0501/*GOOD*/ |
#define RTL8367B_TA_ADDR_MASK 0x3fff/*GOOD*/ |
|
#define RTL8367B_TA_LUT_REG 0x0502/*GOOD*/ |
|
#define RTL8367B_TA_WRDATA_REG(_x) (0x0510 + (_x))/*GOOD*/ |
#define RTL8367B_TA_VLAN_NUM_WORDS 2 |
#define RTL8367B_TA_VLAN_VID_MASK RTL8367B_VID_MASK |
#define RTL8367B_TA_VLAN0_MEMBER_SHIFT 0 |
#define RTL8367B_TA_VLAN0_MEMBER_MASK RTL8367B_MEMBER_MASK |
#define RTL8367B_TA_VLAN0_UNTAG_SHIFT 8 |
#define RTL8367B_TA_VLAN0_UNTAG_MASK RTL8367B_MEMBER_MASK |
#define RTL8367B_TA_VLAN1_FID_SHIFT 0 |
#define RTL8367B_TA_VLAN1_FID_MASK RTL8367B_FID_MASK |
|
#define RTL8367B_TA_RDDATA_REG(_x) (0x0520 + (_x))/*GOOD*/ |
|
#define RTL8367B_VLAN_PVID_CTRL_REG(_p) (0x0700 + (_p) / 2) /*GOOD*/ |
#define RTL8367B_VLAN_PVID_CTRL_MASK 0x1f /*GOOD*/ |
#define RTL8367B_VLAN_PVID_CTRL_SHIFT(_p) (8 * ((_p) % 2)) /*GOOD*/ |
|
#define RTL8367B_VLAN_MC_BASE(_x) (0x0728 + (_x) * 4) /*GOOD*/ |
#define RTL8367B_VLAN_MC_NUM_WORDS 4 /*GOOD*/ |
#define RTL8367B_VLAN_MC0_MEMBER_SHIFT 0/*GOOD*/ |
#define RTL8367B_VLAN_MC0_MEMBER_MASK RTL8367B_MEMBER_MASK/*GOOD*/ |
#define RTL8367B_VLAN_MC1_FID_SHIFT 0/*GOOD*/ |
#define RTL8367B_VLAN_MC1_FID_MASK RTL8367B_FID_MASK/*GOOD*/ |
#define RTL8367B_VLAN_MC3_EVID_SHIFT 0/*GOOD*/ |
#define RTL8367B_VLAN_MC3_EVID_MASK RTL8367B_VID_MASK/*GOOD*/ |
|
#define RTL8367B_VLAN_CTRL_REG 0x07a8 /*GOOD*/ |
#define RTL8367B_VLAN_CTRL_ENABLE BIT(0) |
|
#define RTL8367B_VLAN_INGRESS_REG 0x07a9 /*GOOD*/ |
|
#define RTL8367B_PORT_ISOLATION_REG(_p) (0x08a2 + (_p)) /*GOOD*/ |
|
#define RTL8367B_MIB_COUNTER_REG(_x) (0x1000 + (_x)) /*GOOD*/ |
#define RTL8367B_MIB_COUNTER_PORT_OFFSET 0x007c /*GOOD*/ |
|
#define RTL8367B_MIB_ADDRESS_REG 0x1004 /*GOOD*/ |
|
#define RTL8367B_MIB_CTRL0_REG(_x) (0x1005 + (_x)) /*GOOD*/ |
#define RTL8367B_MIB_CTRL0_GLOBAL_RESET_MASK BIT(11) /*GOOD*/ |
#define RTL8367B_MIB_CTRL0_QM_RESET_MASK BIT(10) /*GOOD*/ |
#define RTL8367B_MIB_CTRL0_PORT_RESET_MASK(_p) BIT(2 + (_p)) /*GOOD*/ |
#define RTL8367B_MIB_CTRL0_RESET_MASK BIT(1) /*GOOD*/ |
#define RTL8367B_MIB_CTRL0_BUSY_MASK BIT(0) /*GOOD*/ |
|
#define RTL8367B_SWC0_REG 0x1200/*GOOD*/ |
#define RTL8367B_SWC0_MAX_LENGTH_SHIFT 13/*GOOD*/ |
#define RTL8367B_SWC0_MAX_LENGTH(_x) ((_x) << 13) /*GOOD*/ |
#define RTL8367B_SWC0_MAX_LENGTH_MASK RTL8367B_SWC0_MAX_LENGTH(0x3) |
#define RTL8367B_SWC0_MAX_LENGTH_1522 RTL8367B_SWC0_MAX_LENGTH(0) |
#define RTL8367B_SWC0_MAX_LENGTH_1536 RTL8367B_SWC0_MAX_LENGTH(1) |
#define RTL8367B_SWC0_MAX_LENGTH_1552 RTL8367B_SWC0_MAX_LENGTH(2) |
#define RTL8367B_SWC0_MAX_LENGTH_16000 RTL8367B_SWC0_MAX_LENGTH(3) |
|
#define RTL8367B_CHIP_NUMBER_REG 0x1300/*GOOD*/ |
|
#define RTL8367B_CHIP_VER_REG 0x1301/*GOOD*/ |
#define RTL8367B_CHIP_VER_RLVID_SHIFT 12/*GOOD*/ |
#define RTL8367B_CHIP_VER_RLVID_MASK 0xf/*GOOD*/ |
#define RTL8367B_CHIP_VER_MCID_SHIFT 8/*GOOD*/ |
#define RTL8367B_CHIP_VER_MCID_MASK 0xf/*GOOD*/ |
#define RTL8367B_CHIP_VER_BOID_SHIFT 4/*GOOD*/ |
#define RTL8367B_CHIP_VER_BOID_MASK 0xf/*GOOD*/ |
#define RTL8367B_CHIP_VER_AFE_SHIFT 0/*GOOD*/ |
#define RTL8367B_CHIP_VER_AFE_MASK 0x1/*GOOD*/ |
|
#define RTL8367B_CHIP_MODE_REG 0x1302 |
#define RTL8367B_CHIP_MODE_MASK 0x7 |
|
#define RTL8367B_CHIP_DEBUG0_REG 0x1303 |
#define RTL8367B_CHIP_DEBUG0_DUMMY0(_x) BIT(8 + (_x)) |
|
#define RTL8367B_CHIP_DEBUG1_REG 0x1304 |
|
#define RTL8367B_DIS_REG 0x1305 |
#define RTL8367B_DIS_SKIP_MII_RXER(_x) BIT(12 + (_x)) |
#define RTL8367B_DIS_RGMII_SHIFT(_x) (4 * (_x)) |
#define RTL8367B_DIS_RGMII_MASK 0x7 |
|
#define RTL8367B_EXT_RGMXF_REG(_x) (0x1306 + (_x)) |
#define RTL8367B_EXT_RGMXF_DUMMY0_SHIFT 5 |
#define RTL8367B_EXT_RGMXF_DUMMY0_MASK 0x7ff |
#define RTL8367B_EXT_RGMXF_TXDELAY_SHIFT 3 |
#define RTL8367B_EXT_RGMXF_TXDELAY_MASK 1 |
#define RTL8367B_EXT_RGMXF_RXDELAY_MASK 0x7 |
|
#define RTL8367B_DI_FORCE_REG(_x) (0x1310 + (_x)) |
#define RTL8367B_DI_FORCE_MODE BIT(12) |
#define RTL8367B_DI_FORCE_NWAY BIT(7) |
#define RTL8367B_DI_FORCE_TXPAUSE BIT(6) |
#define RTL8367B_DI_FORCE_RXPAUSE BIT(5) |
#define RTL8367B_DI_FORCE_LINK BIT(4) |
#define RTL8367B_DI_FORCE_DUPLEX BIT(2) |
#define RTL8367B_DI_FORCE_SPEED_MASK 3 |
#define RTL8367B_DI_FORCE_SPEED_10 0 |
#define RTL8367B_DI_FORCE_SPEED_100 1 |
#define RTL8367B_DI_FORCE_SPEED_1000 2 |
|
#define RTL8367B_MAC_FORCE_REG(_x) (0x1312 + (_x)) |
|
#define RTL8367B_CHIP_RESET_REG 0x1322 /*GOOD*/ |
#define RTL8367B_CHIP_RESET_SW BIT(1) /*GOOD*/ |
#define RTL8367B_CHIP_RESET_HW BIT(0) /*GOOD*/ |
|
#define RTL8367B_PORT_STATUS_REG(_p) (0x1352 + (_p)) /*GOOD*/ |
#define RTL8367B_PORT_STATUS_EN_1000_SPI BIT(11) /*GOOD*/ |
#define RTL8367B_PORT_STATUS_EN_100_SPI BIT(10)/*GOOD*/ |
#define RTL8367B_PORT_STATUS_NWAY_FAULT BIT(9)/*GOOD*/ |
#define RTL8367B_PORT_STATUS_LINK_MASTER BIT(8)/*GOOD*/ |
#define RTL8367B_PORT_STATUS_NWAY BIT(7)/*GOOD*/ |
#define RTL8367B_PORT_STATUS_TXPAUSE BIT(6)/*GOOD*/ |
#define RTL8367B_PORT_STATUS_RXPAUSE BIT(5)/*GOOD*/ |
#define RTL8367B_PORT_STATUS_LINK BIT(4)/*GOOD*/ |
#define RTL8367B_PORT_STATUS_DUPLEX BIT(2)/*GOOD*/ |
#define RTL8367B_PORT_STATUS_SPEED_MASK 0x0003/*GOOD*/ |
#define RTL8367B_PORT_STATUS_SPEED_10 0/*GOOD*/ |
#define RTL8367B_PORT_STATUS_SPEED_100 1/*GOOD*/ |
#define RTL8367B_PORT_STATUS_SPEED_1000 2/*GOOD*/ |
|
#define RTL8367B_RTL_MAGIC_ID_REG 0x13c2 |
#define RTL8367B_RTL_MAGIC_ID_VAL 0x0249 |
|
#define RTL8367B_IA_CTRL_REG 0x1f00 |
#define RTL8367B_IA_CTRL_RW(_x) ((_x) << 1) |
#define RTL8367B_IA_CTRL_RW_READ RTL8367B_IA_CTRL_RW(0) |
#define RTL8367B_IA_CTRL_RW_WRITE RTL8367B_IA_CTRL_RW(1) |
#define RTL8367B_IA_CTRL_CMD_MASK BIT(0) |
|
#define RTL8367B_IA_STATUS_REG 0x1f01 |
#define RTL8367B_IA_STATUS_PHY_BUSY BIT(2) |
#define RTL8367B_IA_STATUS_SDS_BUSY BIT(1) |
#define RTL8367B_IA_STATUS_MDX_BUSY BIT(0) |
|
#define RTL8367B_IA_ADDRESS_REG 0x1f02 |
#define RTL8367B_IA_WRITE_DATA_REG 0x1f03 |
#define RTL8367B_IA_READ_DATA_REG 0x1f04 |
|
#define RTL8367B_INTERNAL_PHY_REG(_a, _r) (0x2000 + 32 * (_a) + (_r)) |
|
#define RTL8367B_NUM_MIB_COUNTERS 58 |
|
#define RTL8367B_CPU_PORT_NUM 5 |
#define RTL8367B_NUM_PORTS 8 |
#define RTL8367B_NUM_VLANS 32 |
#define RTL8367B_NUM_VIDS 4096 |
#define RTL8367B_PRIORITYMAX 7 |
#define RTL8367B_FIDMAX 7 |
|
#define RTL8367B_PORT_0 BIT(0) |
#define RTL8367B_PORT_1 BIT(1) |
#define RTL8367B_PORT_2 BIT(2) |
#define RTL8367B_PORT_3 BIT(3) |
#define RTL8367B_PORT_4 BIT(4) |
#define RTL8367B_PORT_E0 BIT(5) /* External port 0 */ |
#define RTL8367B_PORT_E1 BIT(6) /* External port 1 */ |
#define RTL8367B_PORT_E2 BIT(7) /* External port 2 */ |
|
#define RTL8367B_PORTS_ALL \ |
(RTL8367B_PORT_0 | RTL8367B_PORT_1 | RTL8367B_PORT_2 | \ |
RTL8367B_PORT_3 | RTL8367B_PORT_4 | RTL8367B_PORT_E0 | \ |
RTL8367B_PORT_E1 | RTL8367B_PORT_E2) |
|
#define RTL8367B_PORTS_ALL_BUT_CPU \ |
(RTL8367B_PORT_0 | RTL8367B_PORT_1 | RTL8367B_PORT_2 | \ |
RTL8367B_PORT_3 | RTL8367B_PORT_4 | RTL8367B_PORT_E1 | \ |
RTL8367B_PORT_E2) |
|
struct rtl8367b_initval { |
u16 reg; |
u16 val; |
}; |
|
#define RTL8367B_MIB_RXB_ID 0 /* IfInOctets */ |
#define RTL8367B_MIB_TXB_ID 28 /* IfOutOctets */ |
|
static struct rtl8366_mib_counter |
rtl8367b_mib_counters[RTL8367B_NUM_MIB_COUNTERS] = { |
{0, 0, 4, "ifInOctets" }, |
{0, 4, 2, "dot3StatsFCSErrors" }, |
{0, 6, 2, "dot3StatsSymbolErrors" }, |
{0, 8, 2, "dot3InPauseFrames" }, |
{0, 10, 2, "dot3ControlInUnknownOpcodes" }, |
{0, 12, 2, "etherStatsFragments" }, |
{0, 14, 2, "etherStatsJabbers" }, |
{0, 16, 2, "ifInUcastPkts" }, |
{0, 18, 2, "etherStatsDropEvents" }, |
{0, 20, 2, "ifInMulticastPkts" }, |
{0, 22, 2, "ifInBroadcastPkts" }, |
{0, 24, 2, "inMldChecksumError" }, |
{0, 26, 2, "inIgmpChecksumError" }, |
{0, 28, 2, "inMldSpecificQuery" }, |
{0, 30, 2, "inMldGeneralQuery" }, |
{0, 32, 2, "inIgmpSpecificQuery" }, |
{0, 34, 2, "inIgmpGeneralQuery" }, |
{0, 36, 2, "inMldLeaves" }, |
{0, 38, 2, "inIgmpLeaves" }, |
|
{0, 40, 4, "etherStatsOctets" }, |
{0, 44, 2, "etherStatsUnderSizePkts" }, |
{0, 46, 2, "etherOversizeStats" }, |
{0, 48, 2, "etherStatsPkts64Octets" }, |
{0, 50, 2, "etherStatsPkts65to127Octets" }, |
{0, 52, 2, "etherStatsPkts128to255Octets" }, |
{0, 54, 2, "etherStatsPkts256to511Octets" }, |
{0, 56, 2, "etherStatsPkts512to1023Octets" }, |
{0, 58, 2, "etherStatsPkts1024to1518Octets" }, |
|
{0, 60, 4, "ifOutOctets" }, |
{0, 64, 2, "dot3StatsSingleCollisionFrames" }, |
{0, 66, 2, "dot3StatMultipleCollisionFrames" }, |
{0, 68, 2, "dot3sDeferredTransmissions" }, |
{0, 70, 2, "dot3StatsLateCollisions" }, |
{0, 72, 2, "etherStatsCollisions" }, |
{0, 74, 2, "dot3StatsExcessiveCollisions" }, |
{0, 76, 2, "dot3OutPauseFrames" }, |
{0, 78, 2, "ifOutDiscards" }, |
{0, 80, 2, "dot1dTpPortInDiscards" }, |
{0, 82, 2, "ifOutUcastPkts" }, |
{0, 84, 2, "ifOutMulticastPkts" }, |
{0, 86, 2, "ifOutBroadcastPkts" }, |
{0, 88, 2, "outOampduPkts" }, |
{0, 90, 2, "inOampduPkts" }, |
{0, 92, 2, "inIgmpJoinsSuccess" }, |
{0, 94, 2, "inIgmpJoinsFail" }, |
{0, 96, 2, "inMldJoinsSuccess" }, |
{0, 98, 2, "inMldJoinsFail" }, |
{0, 100, 2, "inReportSuppressionDrop" }, |
{0, 102, 2, "inLeaveSuppressionDrop" }, |
{0, 104, 2, "outIgmpReports" }, |
{0, 106, 2, "outIgmpLeaves" }, |
{0, 108, 2, "outIgmpGeneralQuery" }, |
{0, 110, 2, "outIgmpSpecificQuery" }, |
{0, 112, 2, "outMldReports" }, |
{0, 114, 2, "outMldLeaves" }, |
{0, 116, 2, "outMldGeneralQuery" }, |
{0, 118, 2, "outMldSpecificQuery" }, |
{0, 120, 2, "inKnownMulticastPkts" }, |
}; |
|
#define REG_RD(_smi, _reg, _val) \ |
do { \ |
err = rtl8366_smi_read_reg(_smi, _reg, _val); \ |
if (err) \ |
return err; \ |
} while (0) |
|
#define REG_WR(_smi, _reg, _val) \ |
do { \ |
err = rtl8366_smi_write_reg(_smi, _reg, _val); \ |
if (err) \ |
return err; \ |
} while (0) |
|
#define REG_RMW(_smi, _reg, _mask, _val) \ |
do { \ |
err = rtl8366_smi_rmwr(_smi, _reg, _mask, _val); \ |
if (err) \ |
return err; \ |
} while (0) |
|
static const struct rtl8367b_initval rtl8367r_vb_initvals_0[] = { |
{0x1B03, 0x0876}, {0x1200, 0x7FC4}, {0x0301, 0x0026}, {0x1722, 0x0E14}, |
{0x205F, 0x0002}, {0x2059, 0x1A00}, {0x205F, 0x0000}, {0x207F, 0x0002}, |
{0x2077, 0x0000}, {0x2078, 0x0000}, {0x2079, 0x0000}, {0x207A, 0x0000}, |
{0x207B, 0x0000}, {0x207F, 0x0000}, {0x205F, 0x0002}, {0x2053, 0x0000}, |
{0x2054, 0x0000}, {0x2055, 0x0000}, {0x2056, 0x0000}, {0x2057, 0x0000}, |
{0x205F, 0x0000}, {0x12A4, 0x110A}, {0x12A6, 0x150A}, {0x13F1, 0x0013}, |
{0x13F4, 0x0010}, {0x13F5, 0x0000}, {0x0018, 0x0F00}, {0x0038, 0x0F00}, |
{0x0058, 0x0F00}, {0x0078, 0x0F00}, {0x0098, 0x0F00}, {0x12B6, 0x0C02}, |
{0x12B7, 0x030F}, {0x12B8, 0x11FF}, {0x12BC, 0x0004}, {0x1362, 0x0115}, |
{0x1363, 0x0002}, {0x1363, 0x0000}, {0x133F, 0x0030}, {0x133E, 0x000E}, |
{0x221F, 0x0007}, {0x221E, 0x002D}, {0x2218, 0xF030}, {0x221F, 0x0007}, |
{0x221E, 0x0023}, {0x2216, 0x0005}, {0x2215, 0x00B9}, {0x2219, 0x0044}, |
{0x2215, 0x00BA}, {0x2219, 0x0020}, {0x2215, 0x00BB}, {0x2219, 0x00C1}, |
{0x2215, 0x0148}, {0x2219, 0x0096}, {0x2215, 0x016E}, {0x2219, 0x0026}, |
{0x2216, 0x0000}, {0x2216, 0x0000}, {0x221E, 0x002D}, {0x2218, 0xF010}, |
{0x221F, 0x0007}, {0x221E, 0x0020}, {0x2215, 0x0D00}, {0x221F, 0x0000}, |
{0x221F, 0x0000}, {0x2217, 0x2160}, {0x221F, 0x0001}, {0x2210, 0xF25E}, |
{0x221F, 0x0007}, {0x221E, 0x0042}, {0x2215, 0x0F00}, {0x2215, 0x0F00}, |
{0x2216, 0x7408}, {0x2215, 0x0E00}, {0x2215, 0x0F00}, {0x2215, 0x0F01}, |
{0x2216, 0x4000}, {0x2215, 0x0E01}, {0x2215, 0x0F01}, {0x2215, 0x0F02}, |
{0x2216, 0x9400}, {0x2215, 0x0E02}, {0x2215, 0x0F02}, {0x2215, 0x0F03}, |
{0x2216, 0x7408}, {0x2215, 0x0E03}, {0x2215, 0x0F03}, {0x2215, 0x0F04}, |
{0x2216, 0x4008}, {0x2215, 0x0E04}, {0x2215, 0x0F04}, {0x2215, 0x0F05}, |
{0x2216, 0x9400}, {0x2215, 0x0E05}, {0x2215, 0x0F05}, {0x2215, 0x0F06}, |
{0x2216, 0x0803}, {0x2215, 0x0E06}, {0x2215, 0x0F06}, {0x2215, 0x0D00}, |
{0x2215, 0x0100}, {0x221F, 0x0001}, {0x2210, 0xF05E}, {0x221F, 0x0000}, |
{0x2217, 0x2100}, {0x221F, 0x0000}, {0x220D, 0x0003}, {0x220E, 0x0015}, |
{0x220D, 0x4003}, {0x220E, 0x0006}, {0x221F, 0x0000}, {0x2200, 0x1340}, |
{0x133F, 0x0010}, {0x12A0, 0x0058}, {0x12A1, 0x0058}, {0x133E, 0x000E}, |
{0x133F, 0x0030}, {0x221F, 0x0000}, {0x2210, 0x0166}, {0x221F, 0x0000}, |
{0x133E, 0x000E}, {0x133F, 0x0010}, {0x133F, 0x0030}, {0x133E, 0x000E}, |
{0x221F, 0x0005}, {0x2205, 0xFFF6}, {0x2206, 0x0080}, {0x2205, 0x8B6E}, |
{0x2206, 0x0000}, {0x220F, 0x0100}, {0x2205, 0x8000}, {0x2206, 0x0280}, |
{0x2206, 0x28F7}, {0x2206, 0x00E0}, {0x2206, 0xFFF7}, {0x2206, 0xA080}, |
{0x2206, 0x02AE}, {0x2206, 0xF602}, {0x2206, 0x0153}, {0x2206, 0x0201}, |
{0x2206, 0x6602}, {0x2206, 0x80B9}, {0x2206, 0xE08B}, {0x2206, 0x8CE1}, |
{0x2206, 0x8B8D}, {0x2206, 0x1E01}, {0x2206, 0xE18B}, {0x2206, 0x8E1E}, |
{0x2206, 0x01A0}, {0x2206, 0x00E7}, {0x2206, 0xAEDB}, {0x2206, 0xEEE0}, |
{0x2206, 0x120E}, {0x2206, 0xEEE0}, {0x2206, 0x1300}, {0x2206, 0xEEE0}, |
{0x2206, 0x2001}, {0x2206, 0xEEE0}, {0x2206, 0x2166}, {0x2206, 0xEEE0}, |
{0x2206, 0xC463}, {0x2206, 0xEEE0}, {0x2206, 0xC5E8}, {0x2206, 0xEEE0}, |
{0x2206, 0xC699}, {0x2206, 0xEEE0}, {0x2206, 0xC7C2}, {0x2206, 0xEEE0}, |
{0x2206, 0xC801}, {0x2206, 0xEEE0}, {0x2206, 0xC913}, {0x2206, 0xEEE0}, |
{0x2206, 0xCA30}, {0x2206, 0xEEE0}, {0x2206, 0xCB3E}, {0x2206, 0xEEE0}, |
{0x2206, 0xDCE1}, {0x2206, 0xEEE0}, {0x2206, 0xDD00}, {0x2206, 0xEEE2}, |
{0x2206, 0x0001}, {0x2206, 0xEEE2}, {0x2206, 0x0100}, {0x2206, 0xEEE4}, |
{0x2206, 0x8860}, {0x2206, 0xEEE4}, {0x2206, 0x8902}, {0x2206, 0xEEE4}, |
{0x2206, 0x8C00}, {0x2206, 0xEEE4}, {0x2206, 0x8D30}, {0x2206, 0xEEEA}, |
{0x2206, 0x1480}, {0x2206, 0xEEEA}, {0x2206, 0x1503}, {0x2206, 0xEEEA}, |
{0x2206, 0xC600}, {0x2206, 0xEEEA}, {0x2206, 0xC706}, {0x2206, 0xEE85}, |
{0x2206, 0xEE00}, {0x2206, 0xEE85}, {0x2206, 0xEF00}, {0x2206, 0xEE8B}, |
{0x2206, 0x6750}, {0x2206, 0xEE8B}, {0x2206, 0x6632}, {0x2206, 0xEE8A}, |
{0x2206, 0xD448}, {0x2206, 0xEE8A}, {0x2206, 0xD548}, {0x2206, 0xEE8A}, |
{0x2206, 0xD649}, {0x2206, 0xEE8A}, {0x2206, 0xD7F8}, {0x2206, 0xEE8B}, |
{0x2206, 0x85E2}, {0x2206, 0xEE8B}, {0x2206, 0x8700}, {0x2206, 0xEEFF}, |
{0x2206, 0xF600}, {0x2206, 0xEEFF}, {0x2206, 0xF7FC}, {0x2206, 0x04F8}, |
{0x2206, 0xE08B}, {0x2206, 0x8EAD}, {0x2206, 0x2023}, {0x2206, 0xF620}, |
{0x2206, 0xE48B}, {0x2206, 0x8E02}, {0x2206, 0x2877}, {0x2206, 0x0225}, |
{0x2206, 0xC702}, {0x2206, 0x26A1}, {0x2206, 0x0281}, {0x2206, 0xB302}, |
{0x2206, 0x8496}, {0x2206, 0x0202}, {0x2206, 0xA102}, {0x2206, 0x27F1}, |
{0x2206, 0x0228}, {0x2206, 0xF902}, {0x2206, 0x2AA0}, {0x2206, 0x0282}, |
{0x2206, 0xB8E0}, {0x2206, 0x8B8E}, {0x2206, 0xAD21}, {0x2206, 0x08F6}, |
{0x2206, 0x21E4}, {0x2206, 0x8B8E}, {0x2206, 0x0202}, {0x2206, 0x80E0}, |
{0x2206, 0x8B8E}, {0x2206, 0xAD22}, {0x2206, 0x05F6}, {0x2206, 0x22E4}, |
{0x2206, 0x8B8E}, {0x2206, 0xE08B}, {0x2206, 0x8EAD}, {0x2206, 0x2305}, |
{0x2206, 0xF623}, {0x2206, 0xE48B}, {0x2206, 0x8EE0}, {0x2206, 0x8B8E}, |
{0x2206, 0xAD24}, {0x2206, 0x08F6}, {0x2206, 0x24E4}, {0x2206, 0x8B8E}, |
{0x2206, 0x0227}, {0x2206, 0x6AE0}, {0x2206, 0x8B8E}, {0x2206, 0xAD25}, |
{0x2206, 0x05F6}, {0x2206, 0x25E4}, {0x2206, 0x8B8E}, {0x2206, 0xE08B}, |
{0x2206, 0x8EAD}, {0x2206, 0x260B}, {0x2206, 0xF626}, {0x2206, 0xE48B}, |
{0x2206, 0x8E02}, {0x2206, 0x830D}, {0x2206, 0x021D}, {0x2206, 0x6BE0}, |
{0x2206, 0x8B8E}, {0x2206, 0xAD27}, {0x2206, 0x05F6}, {0x2206, 0x27E4}, |
{0x2206, 0x8B8E}, {0x2206, 0x0281}, {0x2206, 0x4402}, {0x2206, 0x045C}, |
{0x2206, 0xFC04}, {0x2206, 0xF8E0}, {0x2206, 0x8B83}, {0x2206, 0xAD23}, |
{0x2206, 0x30E0}, {0x2206, 0xE022}, {0x2206, 0xE1E0}, {0x2206, 0x2359}, |
{0x2206, 0x02E0}, {0x2206, 0x85EF}, {0x2206, 0xE585}, {0x2206, 0xEFAC}, |
{0x2206, 0x2907}, {0x2206, 0x1F01}, {0x2206, 0x9E51}, {0x2206, 0xAD29}, |
{0x2206, 0x20E0}, {0x2206, 0x8B83}, {0x2206, 0xAD21}, {0x2206, 0x06E1}, |
{0x2206, 0x8B84}, {0x2206, 0xAD28}, {0x2206, 0x42E0}, {0x2206, 0x8B85}, |
{0x2206, 0xAD21}, {0x2206, 0x06E1}, {0x2206, 0x8B84}, {0x2206, 0xAD29}, |
{0x2206, 0x36BF}, {0x2206, 0x34BF}, {0x2206, 0x022C}, {0x2206, 0x31AE}, |
{0x2206, 0x2EE0}, {0x2206, 0x8B83}, {0x2206, 0xAD21}, {0x2206, 0x10E0}, |
{0x2206, 0x8B84}, {0x2206, 0xF620}, {0x2206, 0xE48B}, {0x2206, 0x84EE}, |
{0x2206, 0x8ADA}, {0x2206, 0x00EE}, {0x2206, 0x8ADB}, {0x2206, 0x00E0}, |
{0x2206, 0x8B85}, {0x2206, 0xAD21}, {0x2206, 0x0CE0}, {0x2206, 0x8B84}, |
{0x2206, 0xF621}, {0x2206, 0xE48B}, {0x2206, 0x84EE}, {0x2206, 0x8B72}, |
{0x2206, 0xFFBF}, {0x2206, 0x34C2}, {0x2206, 0x022C}, {0x2206, 0x31FC}, |
{0x2206, 0x04F8}, {0x2206, 0xFAEF}, {0x2206, 0x69E0}, {0x2206, 0x8B85}, |
{0x2206, 0xAD21}, {0x2206, 0x42E0}, {0x2206, 0xE022}, {0x2206, 0xE1E0}, |
{0x2206, 0x2358}, {0x2206, 0xC059}, {0x2206, 0x021E}, {0x2206, 0x01E1}, |
{0x2206, 0x8B72}, {0x2206, 0x1F10}, {0x2206, 0x9E2F}, {0x2206, 0xE48B}, |
{0x2206, 0x72AD}, {0x2206, 0x2123}, {0x2206, 0xE18B}, {0x2206, 0x84F7}, |
{0x2206, 0x29E5}, {0x2206, 0x8B84}, {0x2206, 0xAC27}, {0x2206, 0x10AC}, |
{0x2206, 0x2605}, {0x2206, 0x0205}, {0x2206, 0x23AE}, {0x2206, 0x1602}, |
{0x2206, 0x0535}, {0x2206, 0x0282}, {0x2206, 0x30AE}, {0x2206, 0x0E02}, |
{0x2206, 0x056A}, {0x2206, 0x0282}, {0x2206, 0x75AE}, {0x2206, 0x0602}, |
{0x2206, 0x04DC}, {0x2206, 0x0282}, {0x2206, 0x04EF}, {0x2206, 0x96FE}, |
{0x2206, 0xFC04}, {0x2206, 0xF8F9}, {0x2206, 0xE08B}, {0x2206, 0x87AD}, |
{0x2206, 0x2321}, {0x2206, 0xE0EA}, {0x2206, 0x14E1}, {0x2206, 0xEA15}, |
{0x2206, 0xAD26}, {0x2206, 0x18F6}, {0x2206, 0x27E4}, {0x2206, 0xEA14}, |
{0x2206, 0xE5EA}, {0x2206, 0x15F6}, {0x2206, 0x26E4}, {0x2206, 0xEA14}, |
{0x2206, 0xE5EA}, {0x2206, 0x15F7}, {0x2206, 0x27E4}, {0x2206, 0xEA14}, |
{0x2206, 0xE5EA}, {0x2206, 0x15FD}, {0x2206, 0xFC04}, {0x2206, 0xF8F9}, |
{0x2206, 0xE08B}, {0x2206, 0x87AD}, {0x2206, 0x233A}, {0x2206, 0xAD22}, |
{0x2206, 0x37E0}, {0x2206, 0xE020}, {0x2206, 0xE1E0}, {0x2206, 0x21AC}, |
{0x2206, 0x212E}, {0x2206, 0xE0EA}, {0x2206, 0x14E1}, {0x2206, 0xEA15}, |
{0x2206, 0xF627}, {0x2206, 0xE4EA}, {0x2206, 0x14E5}, {0x2206, 0xEA15}, |
{0x2206, 0xE2EA}, {0x2206, 0x12E3}, {0x2206, 0xEA13}, {0x2206, 0x5A8F}, |
{0x2206, 0x6A20}, {0x2206, 0xE6EA}, {0x2206, 0x12E7}, {0x2206, 0xEA13}, |
{0x2206, 0xF726}, {0x2206, 0xE4EA}, {0x2206, 0x14E5}, {0x2206, 0xEA15}, |
{0x2206, 0xF727}, {0x2206, 0xE4EA}, {0x2206, 0x14E5}, {0x2206, 0xEA15}, |
{0x2206, 0xFDFC}, {0x2206, 0x04F8}, {0x2206, 0xF9E0}, {0x2206, 0x8B87}, |
{0x2206, 0xAD23}, {0x2206, 0x38AD}, {0x2206, 0x2135}, {0x2206, 0xE0E0}, |
{0x2206, 0x20E1}, {0x2206, 0xE021}, {0x2206, 0xAC21}, {0x2206, 0x2CE0}, |
{0x2206, 0xEA14}, {0x2206, 0xE1EA}, {0x2206, 0x15F6}, {0x2206, 0x27E4}, |
{0x2206, 0xEA14}, {0x2206, 0xE5EA}, {0x2206, 0x15E2}, {0x2206, 0xEA12}, |
{0x2206, 0xE3EA}, {0x2206, 0x135A}, {0x2206, 0x8FE6}, {0x2206, 0xEA12}, |
{0x2206, 0xE7EA}, {0x2206, 0x13F7}, {0x2206, 0x26E4}, {0x2206, 0xEA14}, |
{0x2206, 0xE5EA}, {0x2206, 0x15F7}, {0x2206, 0x27E4}, {0x2206, 0xEA14}, |
{0x2206, 0xE5EA}, {0x2206, 0x15FD}, {0x2206, 0xFC04}, {0x2206, 0xF8FA}, |
{0x2206, 0xEF69}, {0x2206, 0xE08B}, {0x2206, 0x86AD}, {0x2206, 0x2146}, |
{0x2206, 0xE0E0}, {0x2206, 0x22E1}, {0x2206, 0xE023}, {0x2206, 0x58C0}, |
{0x2206, 0x5902}, {0x2206, 0x1E01}, {0x2206, 0xE18B}, {0x2206, 0x651F}, |
{0x2206, 0x109E}, {0x2206, 0x33E4}, {0x2206, 0x8B65}, {0x2206, 0xAD21}, |
{0x2206, 0x22AD}, {0x2206, 0x272A}, {0x2206, 0xD400}, {0x2206, 0x01BF}, |
{0x2206, 0x34F2}, {0x2206, 0x022C}, {0x2206, 0xA2BF}, {0x2206, 0x34F5}, |
{0x2206, 0x022C}, {0x2206, 0xE0E0}, {0x2206, 0x8B67}, {0x2206, 0x1B10}, |
{0x2206, 0xAA14}, {0x2206, 0xE18B}, {0x2206, 0x660D}, {0x2206, 0x1459}, |
{0x2206, 0x0FAE}, {0x2206, 0x05E1}, {0x2206, 0x8B66}, {0x2206, 0x590F}, |
{0x2206, 0xBF85}, {0x2206, 0x6102}, {0x2206, 0x2CA2}, {0x2206, 0xEF96}, |
{0x2206, 0xFEFC}, {0x2206, 0x04F8}, {0x2206, 0xF9FA}, {0x2206, 0xFBEF}, |
{0x2206, 0x79E2}, {0x2206, 0x8AD2}, {0x2206, 0xAC19}, {0x2206, 0x2DE0}, |
{0x2206, 0xE036}, {0x2206, 0xE1E0}, {0x2206, 0x37EF}, {0x2206, 0x311F}, |
{0x2206, 0x325B}, {0x2206, 0x019E}, {0x2206, 0x1F7A}, {0x2206, 0x0159}, |
{0x2206, 0x019F}, {0x2206, 0x0ABF}, {0x2206, 0x348E}, {0x2206, 0x022C}, |
{0x2206, 0x31F6}, {0x2206, 0x06AE}, {0x2206, 0x0FF6}, {0x2206, 0x0302}, |
{0x2206, 0x0470}, {0x2206, 0xF703}, {0x2206, 0xF706}, {0x2206, 0xBF34}, |
{0x2206, 0x9302}, {0x2206, 0x2C31}, {0x2206, 0xAC1A}, {0x2206, 0x25E0}, |
{0x2206, 0xE022}, {0x2206, 0xE1E0}, {0x2206, 0x23EF}, {0x2206, 0x300D}, |
{0x2206, 0x311F}, {0x2206, 0x325B}, {0x2206, 0x029E}, {0x2206, 0x157A}, |
{0x2206, 0x0258}, {0x2206, 0xC4A0}, {0x2206, 0x0408}, {0x2206, 0xBF34}, |
{0x2206, 0x9E02}, {0x2206, 0x2C31}, {0x2206, 0xAE06}, {0x2206, 0xBF34}, |
{0x2206, 0x9C02}, {0x2206, 0x2C31}, {0x2206, 0xAC1B}, {0x2206, 0x4AE0}, |
{0x2206, 0xE012}, {0x2206, 0xE1E0}, {0x2206, 0x13EF}, {0x2206, 0x300D}, |
{0x2206, 0x331F}, {0x2206, 0x325B}, {0x2206, 0x1C9E}, {0x2206, 0x3AEF}, |
{0x2206, 0x325B}, {0x2206, 0x1C9F}, {0x2206, 0x09BF}, {0x2206, 0x3498}, |
{0x2206, 0x022C}, {0x2206, 0x3102}, {0x2206, 0x83C5}, {0x2206, 0x5A03}, |
{0x2206, 0x0D03}, {0x2206, 0x581C}, {0x2206, 0x1E20}, {0x2206, 0x0207}, |
{0x2206, 0xA0A0}, {0x2206, 0x000E}, {0x2206, 0x0284}, {0x2206, 0x17AD}, |
{0x2206, 0x1817}, {0x2206, 0xBF34}, {0x2206, 0x9A02}, {0x2206, 0x2C31}, |
{0x2206, 0xAE0F}, {0x2206, 0xBF34}, {0x2206, 0xC802}, {0x2206, 0x2C31}, |
{0x2206, 0xBF34}, {0x2206, 0xC502}, {0x2206, 0x2C31}, {0x2206, 0x0284}, |
{0x2206, 0x52E6}, {0x2206, 0x8AD2}, {0x2206, 0xEF97}, {0x2206, 0xFFFE}, |
{0x2206, 0xFDFC}, {0x2206, 0x04F8}, {0x2206, 0xBF34}, {0x2206, 0xDA02}, |
{0x2206, 0x2CE0}, {0x2206, 0xE58A}, {0x2206, 0xD3BF}, {0x2206, 0x34D4}, |
{0x2206, 0x022C}, {0x2206, 0xE00C}, {0x2206, 0x1159}, {0x2206, 0x02E0}, |
{0x2206, 0x8AD3}, {0x2206, 0x1E01}, {0x2206, 0xE48A}, {0x2206, 0xD3D1}, |
{0x2206, 0x00BF}, {0x2206, 0x34DA}, {0x2206, 0x022C}, {0x2206, 0xA2D1}, |
{0x2206, 0x01BF}, {0x2206, 0x34D4}, {0x2206, 0x022C}, {0x2206, 0xA2BF}, |
{0x2206, 0x34CB}, {0x2206, 0x022C}, {0x2206, 0xE0E5}, {0x2206, 0x8ACE}, |
{0x2206, 0xBF85}, {0x2206, 0x6702}, {0x2206, 0x2CE0}, {0x2206, 0xE58A}, |
{0x2206, 0xCFBF}, {0x2206, 0x8564}, {0x2206, 0x022C}, {0x2206, 0xE0E5}, |
{0x2206, 0x8AD0}, {0x2206, 0xBF85}, {0x2206, 0x6A02}, {0x2206, 0x2CE0}, |
{0x2206, 0xE58A}, {0x2206, 0xD1FC}, {0x2206, 0x04F8}, {0x2206, 0xE18A}, |
{0x2206, 0xD1BF}, {0x2206, 0x856A}, {0x2206, 0x022C}, {0x2206, 0xA2E1}, |
{0x2206, 0x8AD0}, {0x2206, 0xBF85}, {0x2206, 0x6402}, {0x2206, 0x2CA2}, |
{0x2206, 0xE18A}, {0x2206, 0xCFBF}, {0x2206, 0x8567}, {0x2206, 0x022C}, |
{0x2206, 0xA2E1}, {0x2206, 0x8ACE}, {0x2206, 0xBF34}, {0x2206, 0xCB02}, |
{0x2206, 0x2CA2}, {0x2206, 0xE18A}, {0x2206, 0xD3BF}, {0x2206, 0x34DA}, |
{0x2206, 0x022C}, {0x2206, 0xA2E1}, {0x2206, 0x8AD3}, {0x2206, 0x0D11}, |
{0x2206, 0xBF34}, {0x2206, 0xD402}, {0x2206, 0x2CA2}, {0x2206, 0xFC04}, |
{0x2206, 0xF9A0}, {0x2206, 0x0405}, {0x2206, 0xE38A}, {0x2206, 0xD4AE}, |
{0x2206, 0x13A0}, {0x2206, 0x0805}, {0x2206, 0xE38A}, {0x2206, 0xD5AE}, |
{0x2206, 0x0BA0}, {0x2206, 0x0C05}, {0x2206, 0xE38A}, {0x2206, 0xD6AE}, |
{0x2206, 0x03E3}, {0x2206, 0x8AD7}, {0x2206, 0xEF13}, {0x2206, 0xBF34}, |
{0x2206, 0xCB02}, {0x2206, 0x2CA2}, {0x2206, 0xEF13}, {0x2206, 0x0D11}, |
{0x2206, 0xBF85}, {0x2206, 0x6702}, {0x2206, 0x2CA2}, {0x2206, 0xEF13}, |
{0x2206, 0x0D14}, {0x2206, 0xBF85}, {0x2206, 0x6402}, {0x2206, 0x2CA2}, |
{0x2206, 0xEF13}, {0x2206, 0x0D17}, {0x2206, 0xBF85}, {0x2206, 0x6A02}, |
{0x2206, 0x2CA2}, {0x2206, 0xFD04}, {0x2206, 0xF8E0}, {0x2206, 0x8B85}, |
{0x2206, 0xAD27}, {0x2206, 0x2DE0}, {0x2206, 0xE036}, {0x2206, 0xE1E0}, |
{0x2206, 0x37E1}, {0x2206, 0x8B73}, {0x2206, 0x1F10}, {0x2206, 0x9E20}, |
{0x2206, 0xE48B}, {0x2206, 0x73AC}, {0x2206, 0x200B}, {0x2206, 0xAC21}, |
{0x2206, 0x0DAC}, {0x2206, 0x250F}, {0x2206, 0xAC27}, {0x2206, 0x0EAE}, |
{0x2206, 0x0F02}, {0x2206, 0x84CC}, {0x2206, 0xAE0A}, {0x2206, 0x0284}, |
{0x2206, 0xD1AE}, {0x2206, 0x05AE}, {0x2206, 0x0302}, {0x2206, 0x84D8}, |
{0x2206, 0xFC04}, {0x2206, 0xEE8B}, {0x2206, 0x6800}, {0x2206, 0x0402}, |
{0x2206, 0x84E5}, {0x2206, 0x0285}, {0x2206, 0x2804}, {0x2206, 0x0285}, |
{0x2206, 0x4904}, {0x2206, 0xEE8B}, {0x2206, 0x6800}, {0x2206, 0xEE8B}, |
{0x2206, 0x6902}, {0x2206, 0x04F8}, {0x2206, 0xF9E0}, {0x2206, 0x8B85}, |
{0x2206, 0xAD26}, {0x2206, 0x38D0}, {0x2206, 0x0B02}, {0x2206, 0x2B4D}, |
{0x2206, 0x5882}, {0x2206, 0x7882}, {0x2206, 0x9F2D}, {0x2206, 0xE08B}, |
{0x2206, 0x68E1}, {0x2206, 0x8B69}, {0x2206, 0x1F10}, {0x2206, 0x9EC8}, |
{0x2206, 0x10E4}, {0x2206, 0x8B68}, {0x2206, 0xE0E0}, {0x2206, 0x00E1}, |
{0x2206, 0xE001}, {0x2206, 0xF727}, {0x2206, 0xE4E0}, {0x2206, 0x00E5}, |
{0x2206, 0xE001}, {0x2206, 0xE2E0}, {0x2206, 0x20E3}, {0x2206, 0xE021}, |
{0x2206, 0xAD30}, {0x2206, 0xF7F6}, {0x2206, 0x27E4}, {0x2206, 0xE000}, |
{0x2206, 0xE5E0}, {0x2206, 0x01FD}, {0x2206, 0xFC04}, {0x2206, 0xF8FA}, |
{0x2206, 0xEF69}, {0x2206, 0xE08B}, {0x2206, 0x86AD}, {0x2206, 0x2212}, |
{0x2206, 0xE0E0}, {0x2206, 0x14E1}, {0x2206, 0xE015}, {0x2206, 0xAD26}, |
{0x2206, 0x9CE1}, {0x2206, 0x85E0}, {0x2206, 0xBF85}, {0x2206, 0x6D02}, |
{0x2206, 0x2CA2}, {0x2206, 0xEF96}, {0x2206, 0xFEFC}, {0x2206, 0x04F8}, |
{0x2206, 0xFAEF}, {0x2206, 0x69E0}, {0x2206, 0x8B86}, {0x2206, 0xAD22}, |
{0x2206, 0x09E1}, {0x2206, 0x85E1}, {0x2206, 0xBF85}, {0x2206, 0x6D02}, |
{0x2206, 0x2CA2}, {0x2206, 0xEF96}, {0x2206, 0xFEFC}, {0x2206, 0x0464}, |
{0x2206, 0xE48C}, {0x2206, 0xFDE4}, {0x2206, 0x80CA}, {0x2206, 0xE480}, |
{0x2206, 0x66E0}, {0x2206, 0x8E70}, {0x2206, 0xE076}, {0x2205, 0xE142}, |
{0x2206, 0x0701}, {0x2205, 0xE140}, {0x2206, 0x0405}, {0x220F, 0x0000}, |
{0x221F, 0x0000}, {0x2200, 0x1340}, {0x133E, 0x000E}, {0x133F, 0x0010}, |
{0x13EB, 0x11BB} |
}; |
|
static const struct rtl8367b_initval rtl8367r_vb_initvals_1[] = { |
{0x1B03, 0x0876}, {0x1200, 0x7FC4}, {0x1305, 0xC000}, {0x121E, 0x03CA}, |
{0x1233, 0x0352}, {0x1234, 0x0064}, {0x1237, 0x0096}, {0x1238, 0x0078}, |
{0x1239, 0x0084}, {0x123A, 0x0030}, {0x205F, 0x0002}, {0x2059, 0x1A00}, |
{0x205F, 0x0000}, {0x207F, 0x0002}, {0x2077, 0x0000}, {0x2078, 0x0000}, |
{0x2079, 0x0000}, {0x207A, 0x0000}, {0x207B, 0x0000}, {0x207F, 0x0000}, |
{0x205F, 0x0002}, {0x2053, 0x0000}, {0x2054, 0x0000}, {0x2055, 0x0000}, |
{0x2056, 0x0000}, {0x2057, 0x0000}, {0x205F, 0x0000}, {0x133F, 0x0030}, |
{0x133E, 0x000E}, {0x221F, 0x0005}, {0x2205, 0x8B86}, {0x2206, 0x800E}, |
{0x221F, 0x0000}, {0x133F, 0x0010}, {0x12A3, 0x2200}, {0x6107, 0xE58B}, |
{0x6103, 0xA970}, {0x0018, 0x0F00}, {0x0038, 0x0F00}, {0x0058, 0x0F00}, |
{0x0078, 0x0F00}, {0x0098, 0x0F00}, {0x133F, 0x0030}, {0x133E, 0x000E}, |
{0x221F, 0x0005}, {0x2205, 0x8B6E}, {0x2206, 0x0000}, {0x220F, 0x0100}, |
{0x2205, 0xFFF6}, {0x2206, 0x0080}, {0x2205, 0x8000}, {0x2206, 0x0280}, |
{0x2206, 0x2BF7}, {0x2206, 0x00E0}, {0x2206, 0xFFF7}, {0x2206, 0xA080}, |
{0x2206, 0x02AE}, {0x2206, 0xF602}, {0x2206, 0x0153}, {0x2206, 0x0201}, |
{0x2206, 0x6602}, {0x2206, 0x8044}, {0x2206, 0x0201}, {0x2206, 0x7CE0}, |
{0x2206, 0x8B8C}, {0x2206, 0xE18B}, {0x2206, 0x8D1E}, {0x2206, 0x01E1}, |
{0x2206, 0x8B8E}, {0x2206, 0x1E01}, {0x2206, 0xA000}, {0x2206, 0xE4AE}, |
{0x2206, 0xD8EE}, {0x2206, 0x85C0}, {0x2206, 0x00EE}, {0x2206, 0x85C1}, |
{0x2206, 0x00EE}, {0x2206, 0x8AFC}, {0x2206, 0x07EE}, {0x2206, 0x8AFD}, |
{0x2206, 0x73EE}, {0x2206, 0xFFF6}, {0x2206, 0x00EE}, {0x2206, 0xFFF7}, |
{0x2206, 0xFC04}, {0x2206, 0xF8E0}, {0x2206, 0x8B8E}, {0x2206, 0xAD20}, |
{0x2206, 0x0302}, {0x2206, 0x8050}, {0x2206, 0xFC04}, {0x2206, 0xF8F9}, |
{0x2206, 0xE08B}, {0x2206, 0x85AD}, {0x2206, 0x2548}, {0x2206, 0xE08A}, |
{0x2206, 0xE4E1}, {0x2206, 0x8AE5}, {0x2206, 0x7C00}, {0x2206, 0x009E}, |
{0x2206, 0x35EE}, {0x2206, 0x8AE4}, {0x2206, 0x00EE}, {0x2206, 0x8AE5}, |
{0x2206, 0x00E0}, {0x2206, 0x8AFC}, {0x2206, 0xE18A}, {0x2206, 0xFDE2}, |
{0x2206, 0x85C0}, {0x2206, 0xE385}, {0x2206, 0xC102}, {0x2206, 0x2DAC}, |
{0x2206, 0xAD20}, {0x2206, 0x12EE}, {0x2206, 0x8AE4}, {0x2206, 0x03EE}, |
{0x2206, 0x8AE5}, {0x2206, 0xB7EE}, {0x2206, 0x85C0}, {0x2206, 0x00EE}, |
{0x2206, 0x85C1}, {0x2206, 0x00AE}, {0x2206, 0x1115}, {0x2206, 0xE685}, |
{0x2206, 0xC0E7}, {0x2206, 0x85C1}, {0x2206, 0xAE08}, {0x2206, 0xEE85}, |
{0x2206, 0xC000}, {0x2206, 0xEE85}, {0x2206, 0xC100}, {0x2206, 0xFDFC}, |
{0x2206, 0x0400}, {0x2205, 0xE142}, {0x2206, 0x0701}, {0x2205, 0xE140}, |
{0x2206, 0x0405}, {0x220F, 0x0000}, {0x221F, 0x0000}, {0x133E, 0x000E}, |
{0x133F, 0x0010}, {0x13EB, 0x11BB}, {0x207F, 0x0002}, {0x2073, 0x1D22}, |
{0x207F, 0x0000}, {0x133F, 0x0030}, {0x133E, 0x000E}, {0x2200, 0x1340}, |
{0x133E, 0x000E}, {0x133F, 0x0010}, |
}; |
|
static int rtl8367b_write_initvals(struct rtl8366_smi *smi, |
const struct rtl8367b_initval *initvals, |
int count) |
{ |
int err; |
int i; |
|
for (i = 0; i < count; i++) |
REG_WR(smi, initvals[i].reg, initvals[i].val); |
|
return 0; |
} |
|
static int rtl8367b_read_phy_reg(struct rtl8366_smi *smi, |
u32 phy_addr, u32 phy_reg, u32 *val) |
{ |
int timeout; |
u32 data; |
int err; |
|
if (phy_addr > RTL8367B_PHY_ADDR_MAX) |
return -EINVAL; |
|
if (phy_reg > RTL8367B_PHY_REG_MAX) |
return -EINVAL; |
|
REG_RD(smi, RTL8367B_IA_STATUS_REG, &data); |
if (data & RTL8367B_IA_STATUS_PHY_BUSY) |
return -ETIMEDOUT; |
|
/* prepare address */ |
REG_WR(smi, RTL8367B_IA_ADDRESS_REG, |
RTL8367B_INTERNAL_PHY_REG(phy_addr, phy_reg)); |
|
/* send read command */ |
REG_WR(smi, RTL8367B_IA_CTRL_REG, |
RTL8367B_IA_CTRL_CMD_MASK | RTL8367B_IA_CTRL_RW_READ); |
|
timeout = 5; |
do { |
REG_RD(smi, RTL8367B_IA_STATUS_REG, &data); |
if ((data & RTL8367B_IA_STATUS_PHY_BUSY) == 0) |
break; |
|
if (timeout--) { |
dev_err(smi->parent, "phy read timed out\n"); |
return -ETIMEDOUT; |
} |
|
udelay(1); |
} while (1); |
|
/* read data */ |
REG_RD(smi, RTL8367B_IA_READ_DATA_REG, val); |
|
dev_dbg(smi->parent, "phy_read: addr:%02x, reg:%02x, val:%04x\n", |
phy_addr, phy_reg, *val); |
return 0; |
} |
|
static int rtl8367b_write_phy_reg(struct rtl8366_smi *smi, |
u32 phy_addr, u32 phy_reg, u32 val) |
{ |
int timeout; |
u32 data; |
int err; |
|
dev_dbg(smi->parent, "phy_write: addr:%02x, reg:%02x, val:%04x\n", |
phy_addr, phy_reg, val); |
|
if (phy_addr > RTL8367B_PHY_ADDR_MAX) |
return -EINVAL; |
|
if (phy_reg > RTL8367B_PHY_REG_MAX) |
return -EINVAL; |
|
REG_RD(smi, RTL8367B_IA_STATUS_REG, &data); |
if (data & RTL8367B_IA_STATUS_PHY_BUSY) |
return -ETIMEDOUT; |
|
/* preapre data */ |
REG_WR(smi, RTL8367B_IA_WRITE_DATA_REG, val); |
|
/* prepare address */ |
REG_WR(smi, RTL8367B_IA_ADDRESS_REG, |
RTL8367B_INTERNAL_PHY_REG(phy_addr, phy_reg)); |
|
/* send write command */ |
REG_WR(smi, RTL8367B_IA_CTRL_REG, |
RTL8367B_IA_CTRL_CMD_MASK | RTL8367B_IA_CTRL_RW_WRITE); |
|
timeout = 5; |
do { |
REG_RD(smi, RTL8367B_IA_STATUS_REG, &data); |
if ((data & RTL8367B_IA_STATUS_PHY_BUSY) == 0) |
break; |
|
if (timeout--) { |
dev_err(smi->parent, "phy write timed out\n"); |
return -ETIMEDOUT; |
} |
|
udelay(1); |
} while (1); |
|
return 0; |
} |
|
static int rtl8367b_init_regs(struct rtl8366_smi *smi) |
{ |
const struct rtl8367b_initval *initvals; |
u32 chip_ver; |
u32 rlvid; |
int count; |
int err; |
|
REG_WR(smi, RTL8367B_RTL_MAGIC_ID_REG, RTL8367B_RTL_MAGIC_ID_VAL); |
REG_RD(smi, RTL8367B_CHIP_VER_REG, &chip_ver); |
|
rlvid = (chip_ver >> RTL8367B_CHIP_VER_RLVID_SHIFT) & |
RTL8367B_CHIP_VER_RLVID_MASK; |
|
switch (rlvid) { |
case 0: |
initvals = rtl8367r_vb_initvals_0; |
count = ARRAY_SIZE(rtl8367r_vb_initvals_0); |
break; |
|
case 1: |
initvals = rtl8367r_vb_initvals_1; |
count = ARRAY_SIZE(rtl8367r_vb_initvals_1); |
break; |
|
default: |
dev_err(smi->parent, "unknow rlvid %u\n", rlvid); |
return -ENODEV; |
} |
|
/* TODO: disable RLTP */ |
|
return rtl8367b_write_initvals(smi, initvals, count); |
} |
|
static int rtl8367b_reset_chip(struct rtl8366_smi *smi) |
{ |
int timeout = 10; |
int err; |
u32 data; |
|
REG_WR(smi, RTL8367B_CHIP_RESET_REG, RTL8367B_CHIP_RESET_HW); |
msleep(RTL8367B_RESET_DELAY); |
|
do { |
REG_RD(smi, RTL8367B_CHIP_RESET_REG, &data); |
if (!(data & RTL8367B_CHIP_RESET_HW)) |
break; |
|
msleep(1); |
} while (--timeout); |
|
if (!timeout) { |
dev_err(smi->parent, "chip reset timed out\n"); |
return -ETIMEDOUT; |
} |
|
return 0; |
} |
|
static int rtl8367b_extif_set_mode(struct rtl8366_smi *smi, int id, |
enum rtl8367_extif_mode mode) |
{ |
int err; |
|
/* set port mode */ |
switch (mode) { |
case RTL8367_EXTIF_MODE_RGMII: |
case RTL8367_EXTIF_MODE_RGMII_33V: |
REG_WR(smi, RTL8367B_CHIP_DEBUG0_REG, 0x0367); |
REG_WR(smi, RTL8367B_CHIP_DEBUG1_REG, 0x7777); |
break; |
|
case RTL8367_EXTIF_MODE_TMII_MAC: |
case RTL8367_EXTIF_MODE_TMII_PHY: |
REG_RMW(smi, RTL8367B_BYPASS_LINE_RATE_REG, |
BIT((id + 1) % 2), BIT((id + 1) % 2)); |
break; |
|
case RTL8367_EXTIF_MODE_GMII: |
REG_RMW(smi, RTL8367B_CHIP_DEBUG0_REG, |
RTL8367B_CHIP_DEBUG0_DUMMY0(id), |
RTL8367B_CHIP_DEBUG0_DUMMY0(id)); |
REG_RMW(smi, RTL8367B_EXT_RGMXF_REG(id), BIT(6), BIT(6)); |
break; |
|
case RTL8367_EXTIF_MODE_MII_MAC: |
case RTL8367_EXTIF_MODE_MII_PHY: |
case RTL8367_EXTIF_MODE_DISABLED: |
REG_RMW(smi, RTL8367B_BYPASS_LINE_RATE_REG, |
BIT((id + 1) % 2), 0); |
REG_RMW(smi, RTL8367B_EXT_RGMXF_REG(id), BIT(6), 0); |
break; |
|
default: |
dev_err(smi->parent, |
"invalid mode for external interface %d\n", id); |
return -EINVAL; |
} |
|
REG_RMW(smi, RTL8367B_DIS_REG, |
RTL8367B_DIS_RGMII_MASK << RTL8367B_DIS_RGMII_SHIFT(id), |
mode << RTL8367B_DIS_RGMII_SHIFT(id)); |
|
return 0; |
} |
|
static int rtl8367b_extif_set_force(struct rtl8366_smi *smi, int id, |
struct rtl8367_port_ability *pa) |
{ |
u32 mask; |
u32 val; |
int err; |
|
mask = (RTL8367B_DI_FORCE_MODE | |
RTL8367B_DI_FORCE_NWAY | |
RTL8367B_DI_FORCE_TXPAUSE | |
RTL8367B_DI_FORCE_RXPAUSE | |
RTL8367B_DI_FORCE_LINK | |
RTL8367B_DI_FORCE_DUPLEX | |
RTL8367B_DI_FORCE_SPEED_MASK); |
|
val = pa->speed; |
val |= pa->force_mode ? RTL8367B_DI_FORCE_MODE : 0; |
val |= pa->nway ? RTL8367B_DI_FORCE_NWAY : 0; |
val |= pa->txpause ? RTL8367B_DI_FORCE_TXPAUSE : 0; |
val |= pa->rxpause ? RTL8367B_DI_FORCE_RXPAUSE : 0; |
val |= pa->link ? RTL8367B_DI_FORCE_LINK : 0; |
val |= pa->duplex ? RTL8367B_DI_FORCE_DUPLEX : 0; |
|
REG_RMW(smi, RTL8367B_DI_FORCE_REG(id), mask, val); |
|
return 0; |
} |
|
static int rtl8367b_extif_set_rgmii_delay(struct rtl8366_smi *smi, int id, |
unsigned txdelay, unsigned rxdelay) |
{ |
u32 mask; |
u32 val; |
int err; |
|
mask = (RTL8367B_EXT_RGMXF_RXDELAY_MASK | |
(RTL8367B_EXT_RGMXF_TXDELAY_MASK << |
RTL8367B_EXT_RGMXF_TXDELAY_SHIFT)); |
|
val = rxdelay; |
val |= txdelay << RTL8367B_EXT_RGMXF_TXDELAY_SHIFT; |
|
REG_RMW(smi, RTL8367B_EXT_RGMXF_REG(id), mask, val); |
|
return 0; |
} |
|
static int rtl8367b_extif_init(struct rtl8366_smi *smi, int id, |
struct rtl8367_extif_config *cfg) |
{ |
enum rtl8367_extif_mode mode; |
int err; |
|
mode = (cfg) ? cfg->mode : RTL8367_EXTIF_MODE_DISABLED; |
|
err = rtl8367b_extif_set_mode(smi, id, mode); |
if (err) |
return err; |
|
if (mode != RTL8367_EXTIF_MODE_DISABLED) { |
err = rtl8367b_extif_set_force(smi, id, &cfg->ability); |
if (err) |
return err; |
|
err = rtl8367b_extif_set_rgmii_delay(smi, id, cfg->txdelay, |
cfg->rxdelay); |
if (err) |
return err; |
} |
|
return 0; |
} |
|
#ifdef CONFIG_OF |
static int rtl8367b_extif_init_of(struct rtl8366_smi *smi, int id, |
const char *name) |
{ |
struct rtl8367_extif_config *cfg; |
const __be32 *prop; |
int size; |
int err; |
|
prop = of_get_property(smi->parent->of_node, name, &size); |
if (!prop) |
return rtl8367b_extif_init(smi, id, NULL); |
|
if (size != (9 * sizeof(*prop))) { |
dev_err(smi->parent, "%s property is invalid\n", name); |
return -EINVAL; |
} |
|
cfg = kzalloc(sizeof(struct rtl8367_extif_config), GFP_KERNEL); |
if (!cfg) |
return -ENOMEM; |
|
cfg->txdelay = be32_to_cpup(prop++); |
cfg->rxdelay = be32_to_cpup(prop++); |
cfg->mode = be32_to_cpup(prop++); |
cfg->ability.force_mode = be32_to_cpup(prop++); |
cfg->ability.txpause = be32_to_cpup(prop++); |
cfg->ability.rxpause = be32_to_cpup(prop++); |
cfg->ability.link = be32_to_cpup(prop++); |
cfg->ability.duplex = be32_to_cpup(prop++); |
cfg->ability.speed = be32_to_cpup(prop++); |
|
err = rtl8367b_extif_init(smi, id, cfg); |
kfree(cfg); |
|
return err; |
} |
#else |
static int rtl8367b_extif_init_of(struct rtl8366_smi *smi, int id, |
const char *name) |
{ |
return -EINVAL; |
} |
#endif |
|
static int rtl8367b_setup(struct rtl8366_smi *smi) |
{ |
struct rtl8367_platform_data *pdata; |
int err; |
int i; |
|
pdata = smi->parent->platform_data; |
|
err = rtl8367b_init_regs(smi); |
if (err) |
return err; |
|
/* initialize external interfaces */ |
if (smi->parent->of_node) { |
err = rtl8367b_extif_init_of(smi, 0, "realtek,extif0"); |
if (err) |
return err; |
|
err = rtl8367b_extif_init_of(smi, 1, "realtek,extif1"); |
if (err) |
return err; |
} else { |
err = rtl8367b_extif_init(smi, 0, pdata->extif0_cfg); |
if (err) |
return err; |
|
err = rtl8367b_extif_init(smi, 1, pdata->extif1_cfg); |
if (err) |
return err; |
} |
|
/* set maximum packet length to 1536 bytes */ |
REG_RMW(smi, RTL8367B_SWC0_REG, RTL8367B_SWC0_MAX_LENGTH_MASK, |
RTL8367B_SWC0_MAX_LENGTH_1536); |
|
/* |
* discard VLAN tagged packets if the port is not a member of |
* the VLAN with which the packets is associated. |
*/ |
REG_WR(smi, RTL8367B_VLAN_INGRESS_REG, RTL8367B_PORTS_ALL); |
|
/* |
* Setup egress tag mode for each port. |
*/ |
for (i = 0; i < RTL8367B_NUM_PORTS; i++) |
REG_RMW(smi, |
RTL8367B_PORT_MISC_CFG_REG(i), |
RTL8367B_PORT_MISC_CFG_EGRESS_MODE_MASK << |
RTL8367B_PORT_MISC_CFG_EGRESS_MODE_SHIFT, |
RTL8367B_PORT_MISC_CFG_EGRESS_MODE_ORIGINAL << |
RTL8367B_PORT_MISC_CFG_EGRESS_MODE_SHIFT); |
|
return 0; |
} |
|
static int rtl8367b_get_mib_counter(struct rtl8366_smi *smi, int counter, |
int port, unsigned long long *val) |
{ |
struct rtl8366_mib_counter *mib; |
int offset; |
int i; |
int err; |
u32 addr, data; |
u64 mibvalue; |
|
if (port > RTL8367B_NUM_PORTS || |
counter >= RTL8367B_NUM_MIB_COUNTERS) |
return -EINVAL; |
|
mib = &rtl8367b_mib_counters[counter]; |
addr = RTL8367B_MIB_COUNTER_PORT_OFFSET * port + mib->offset; |
|
/* |
* Writing access counter address first |
* then ASIC will prepare 64bits counter wait for being retrived |
*/ |
REG_WR(smi, RTL8367B_MIB_ADDRESS_REG, addr >> 2); |
|
/* read MIB control register */ |
REG_RD(smi, RTL8367B_MIB_CTRL0_REG(0), &data); |
|
if (data & RTL8367B_MIB_CTRL0_BUSY_MASK) |
return -EBUSY; |
|
if (data & RTL8367B_MIB_CTRL0_RESET_MASK) |
return -EIO; |
|
if (mib->length == 4) |
offset = 3; |
else |
offset = (mib->offset + 1) % 4; |
|
mibvalue = 0; |
for (i = 0; i < mib->length; i++) { |
REG_RD(smi, RTL8367B_MIB_COUNTER_REG(offset - i), &data); |
mibvalue = (mibvalue << 16) | (data & 0xFFFF); |
} |
|
*val = mibvalue; |
return 0; |
} |
|
static int rtl8367b_get_vlan_4k(struct rtl8366_smi *smi, u32 vid, |
struct rtl8366_vlan_4k *vlan4k) |
{ |
u32 data[RTL8367B_TA_VLAN_NUM_WORDS]; |
int err; |
int i; |
|
memset(vlan4k, '\0', sizeof(struct rtl8366_vlan_4k)); |
|
if (vid >= RTL8367B_NUM_VIDS) |
return -EINVAL; |
|
/* write VID */ |
REG_WR(smi, RTL8367B_TA_ADDR_REG, vid); |
|
/* write table access control word */ |
REG_WR(smi, RTL8367B_TA_CTRL_REG, RTL8367B_TA_CTRL_CVLAN_READ); |
|
for (i = 0; i < ARRAY_SIZE(data); i++) |
REG_RD(smi, RTL8367B_TA_RDDATA_REG(i), &data[i]); |
|
vlan4k->vid = vid; |
vlan4k->member = (data[0] >> RTL8367B_TA_VLAN0_MEMBER_SHIFT) & |
RTL8367B_TA_VLAN0_MEMBER_MASK; |
vlan4k->untag = (data[0] >> RTL8367B_TA_VLAN0_UNTAG_SHIFT) & |
RTL8367B_TA_VLAN0_UNTAG_MASK; |
vlan4k->fid = (data[1] >> RTL8367B_TA_VLAN1_FID_SHIFT) & |
RTL8367B_TA_VLAN1_FID_MASK; |
|
return 0; |
} |
|
static int rtl8367b_set_vlan_4k(struct rtl8366_smi *smi, |
const struct rtl8366_vlan_4k *vlan4k) |
{ |
u32 data[RTL8367B_TA_VLAN_NUM_WORDS]; |
int err; |
int i; |
|
if (vlan4k->vid >= RTL8367B_NUM_VIDS || |
vlan4k->member > RTL8367B_TA_VLAN0_MEMBER_MASK || |
vlan4k->untag > RTL8367B_UNTAG_MASK || |
vlan4k->fid > RTL8367B_FIDMAX) |
return -EINVAL; |
|
memset(data, 0, sizeof(data)); |
|
data[0] = (vlan4k->member & RTL8367B_TA_VLAN0_MEMBER_MASK) << |
RTL8367B_TA_VLAN0_MEMBER_SHIFT; |
data[0] |= (vlan4k->untag & RTL8367B_TA_VLAN0_UNTAG_MASK) << |
RTL8367B_TA_VLAN0_UNTAG_SHIFT; |
data[1] = (vlan4k->fid & RTL8367B_TA_VLAN1_FID_MASK) << |
RTL8367B_TA_VLAN1_FID_SHIFT; |
|
for (i = 0; i < ARRAY_SIZE(data); i++) |
REG_WR(smi, RTL8367B_TA_WRDATA_REG(i), data[i]); |
|
/* write VID */ |
REG_WR(smi, RTL8367B_TA_ADDR_REG, |
vlan4k->vid & RTL8367B_TA_VLAN_VID_MASK); |
|
/* write table access control word */ |
REG_WR(smi, RTL8367B_TA_CTRL_REG, RTL8367B_TA_CTRL_CVLAN_WRITE); |
|
return 0; |
} |
|
static int rtl8367b_get_vlan_mc(struct rtl8366_smi *smi, u32 index, |
struct rtl8366_vlan_mc *vlanmc) |
{ |
u32 data[RTL8367B_VLAN_MC_NUM_WORDS]; |
int err; |
int i; |
|
memset(vlanmc, '\0', sizeof(struct rtl8366_vlan_mc)); |
|
if (index >= RTL8367B_NUM_VLANS) |
return -EINVAL; |
|
for (i = 0; i < ARRAY_SIZE(data); i++) |
REG_RD(smi, RTL8367B_VLAN_MC_BASE(index) + i, &data[i]); |
|
vlanmc->member = (data[0] >> RTL8367B_VLAN_MC0_MEMBER_SHIFT) & |
RTL8367B_VLAN_MC0_MEMBER_MASK; |
vlanmc->fid = (data[1] >> RTL8367B_VLAN_MC1_FID_SHIFT) & |
RTL8367B_VLAN_MC1_FID_MASK; |
vlanmc->vid = (data[3] >> RTL8367B_VLAN_MC3_EVID_SHIFT) & |
RTL8367B_VLAN_MC3_EVID_MASK; |
|
return 0; |
} |
|
static int rtl8367b_set_vlan_mc(struct rtl8366_smi *smi, u32 index, |
const struct rtl8366_vlan_mc *vlanmc) |
{ |
u32 data[RTL8367B_VLAN_MC_NUM_WORDS]; |
int err; |
int i; |
|
if (index >= RTL8367B_NUM_VLANS || |
vlanmc->vid >= RTL8367B_NUM_VIDS || |
vlanmc->priority > RTL8367B_PRIORITYMAX || |
vlanmc->member > RTL8367B_VLAN_MC0_MEMBER_MASK || |
vlanmc->untag > RTL8367B_UNTAG_MASK || |
vlanmc->fid > RTL8367B_FIDMAX) |
return -EINVAL; |
|
data[0] = (vlanmc->member & RTL8367B_VLAN_MC0_MEMBER_MASK) << |
RTL8367B_VLAN_MC0_MEMBER_SHIFT; |
data[1] = (vlanmc->fid & RTL8367B_VLAN_MC1_FID_MASK) << |
RTL8367B_VLAN_MC1_FID_SHIFT; |
data[2] = 0; |
data[3] = (vlanmc->vid & RTL8367B_VLAN_MC3_EVID_MASK) << |
RTL8367B_VLAN_MC3_EVID_SHIFT; |
|
for (i = 0; i < ARRAY_SIZE(data); i++) |
REG_WR(smi, RTL8367B_VLAN_MC_BASE(index) + i, data[i]); |
|
return 0; |
} |
|
static int rtl8367b_get_mc_index(struct rtl8366_smi *smi, int port, int *val) |
{ |
u32 data; |
int err; |
|
if (port >= RTL8367B_NUM_PORTS) |
return -EINVAL; |
|
REG_RD(smi, RTL8367B_VLAN_PVID_CTRL_REG(port), &data); |
|
*val = (data >> RTL8367B_VLAN_PVID_CTRL_SHIFT(port)) & |
RTL8367B_VLAN_PVID_CTRL_MASK; |
|
return 0; |
} |
|
static int rtl8367b_set_mc_index(struct rtl8366_smi *smi, int port, int index) |
{ |
if (port >= RTL8367B_NUM_PORTS || index >= RTL8367B_NUM_VLANS) |
return -EINVAL; |
|
return rtl8366_smi_rmwr(smi, RTL8367B_VLAN_PVID_CTRL_REG(port), |
RTL8367B_VLAN_PVID_CTRL_MASK << |
RTL8367B_VLAN_PVID_CTRL_SHIFT(port), |
(index & RTL8367B_VLAN_PVID_CTRL_MASK) << |
RTL8367B_VLAN_PVID_CTRL_SHIFT(port)); |
} |
|
static int rtl8367b_enable_vlan(struct rtl8366_smi *smi, int enable) |
{ |
return rtl8366_smi_rmwr(smi, RTL8367B_VLAN_CTRL_REG, |
RTL8367B_VLAN_CTRL_ENABLE, |
(enable) ? RTL8367B_VLAN_CTRL_ENABLE : 0); |
} |
|
static int rtl8367b_enable_vlan4k(struct rtl8366_smi *smi, int enable) |
{ |
return 0; |
} |
|
static int rtl8367b_is_vlan_valid(struct rtl8366_smi *smi, unsigned vlan) |
{ |
unsigned max = RTL8367B_NUM_VLANS; |
|
if (smi->vlan4k_enabled) |
max = RTL8367B_NUM_VIDS - 1; |
|
if (vlan == 0 || vlan >= max) |
return 0; |
|
return 1; |
} |
|
static int rtl8367b_enable_port(struct rtl8366_smi *smi, int port, int enable) |
{ |
int err; |
|
REG_WR(smi, RTL8367B_PORT_ISOLATION_REG(port), |
(enable) ? RTL8367B_PORTS_ALL : 0); |
|
return 0; |
} |
|
static int rtl8367b_sw_reset_mibs(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
|
return rtl8366_smi_rmwr(smi, RTL8367B_MIB_CTRL0_REG(0), 0, |
RTL8367B_MIB_CTRL0_GLOBAL_RESET_MASK); |
} |
|
static int rtl8367b_sw_get_port_link(struct switch_dev *dev, |
int port, |
struct switch_port_link *link) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
u32 data = 0; |
u32 speed; |
|
if (port >= RTL8367B_NUM_PORTS) |
return -EINVAL; |
|
rtl8366_smi_read_reg(smi, RTL8367B_PORT_STATUS_REG(port), &data); |
|
link->link = !!(data & RTL8367B_PORT_STATUS_LINK); |
if (!link->link) |
return 0; |
|
link->duplex = !!(data & RTL8367B_PORT_STATUS_DUPLEX); |
link->rx_flow = !!(data & RTL8367B_PORT_STATUS_RXPAUSE); |
link->tx_flow = !!(data & RTL8367B_PORT_STATUS_TXPAUSE); |
link->aneg = !!(data & RTL8367B_PORT_STATUS_NWAY); |
|
speed = (data & RTL8367B_PORT_STATUS_SPEED_MASK); |
switch (speed) { |
case 0: |
link->speed = SWITCH_PORT_SPEED_10; |
break; |
case 1: |
link->speed = SWITCH_PORT_SPEED_100; |
break; |
case 2: |
link->speed = SWITCH_PORT_SPEED_1000; |
break; |
default: |
link->speed = SWITCH_PORT_SPEED_UNKNOWN; |
break; |
} |
|
return 0; |
} |
|
static int rtl8367b_sw_get_max_length(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
u32 data; |
|
rtl8366_smi_read_reg(smi, RTL8367B_SWC0_REG, &data); |
val->value.i = (data & RTL8367B_SWC0_MAX_LENGTH_MASK) >> |
RTL8367B_SWC0_MAX_LENGTH_SHIFT; |
|
return 0; |
} |
|
static int rtl8367b_sw_set_max_length(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
u32 max_len; |
|
switch (val->value.i) { |
case 0: |
max_len = RTL8367B_SWC0_MAX_LENGTH_1522; |
break; |
case 1: |
max_len = RTL8367B_SWC0_MAX_LENGTH_1536; |
break; |
case 2: |
max_len = RTL8367B_SWC0_MAX_LENGTH_1552; |
break; |
case 3: |
max_len = RTL8367B_SWC0_MAX_LENGTH_16000; |
break; |
default: |
return -EINVAL; |
} |
|
return rtl8366_smi_rmwr(smi, RTL8367B_SWC0_REG, |
RTL8367B_SWC0_MAX_LENGTH_MASK, max_len); |
} |
|
|
static int rtl8367b_sw_reset_port_mibs(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); |
int port; |
|
port = val->port_vlan; |
if (port >= RTL8367B_NUM_PORTS) |
return -EINVAL; |
|
return rtl8366_smi_rmwr(smi, RTL8367B_MIB_CTRL0_REG(port / 8), 0, |
RTL8367B_MIB_CTRL0_PORT_RESET_MASK(port % 8)); |
} |
|
static int rtl8367b_sw_get_port_stats(struct switch_dev *dev, int port, |
struct switch_port_stats *stats) |
{ |
return (rtl8366_sw_get_port_stats(dev, port, stats, |
RTL8367B_MIB_TXB_ID, RTL8367B_MIB_RXB_ID)); |
} |
|
static struct switch_attr rtl8367b_globals[] = { |
{ |
.type = SWITCH_TYPE_INT, |
.name = "enable_vlan", |
.description = "Enable VLAN mode", |
.set = rtl8366_sw_set_vlan_enable, |
.get = rtl8366_sw_get_vlan_enable, |
.max = 1, |
.ofs = 1 |
}, { |
.type = SWITCH_TYPE_INT, |
.name = "enable_vlan4k", |
.description = "Enable VLAN 4K mode", |
.set = rtl8366_sw_set_vlan_enable, |
.get = rtl8366_sw_get_vlan_enable, |
.max = 1, |
.ofs = 2 |
}, { |
.type = SWITCH_TYPE_NOVAL, |
.name = "reset_mibs", |
.description = "Reset all MIB counters", |
.set = rtl8367b_sw_reset_mibs, |
}, { |
.type = SWITCH_TYPE_INT, |
.name = "max_length", |
.description = "Get/Set the maximum length of valid packets" |
"(0:1522, 1:1536, 2:1552, 3:16000)", |
.set = rtl8367b_sw_set_max_length, |
.get = rtl8367b_sw_get_max_length, |
.max = 3, |
} |
}; |
|
static struct switch_attr rtl8367b_port[] = { |
{ |
.type = SWITCH_TYPE_NOVAL, |
.name = "reset_mib", |
.description = "Reset single port MIB counters", |
.set = rtl8367b_sw_reset_port_mibs, |
}, { |
.type = SWITCH_TYPE_STRING, |
.name = "mib", |
.description = "Get MIB counters for port", |
.max = 33, |
.set = NULL, |
.get = rtl8366_sw_get_port_mib, |
}, |
}; |
|
static struct switch_attr rtl8367b_vlan[] = { |
{ |
.type = SWITCH_TYPE_STRING, |
.name = "info", |
.description = "Get vlan information", |
.max = 1, |
.set = NULL, |
.get = rtl8366_sw_get_vlan_info, |
}, |
}; |
|
static const struct switch_dev_ops rtl8367b_sw_ops = { |
.attr_global = { |
.attr = rtl8367b_globals, |
.n_attr = ARRAY_SIZE(rtl8367b_globals), |
}, |
.attr_port = { |
.attr = rtl8367b_port, |
.n_attr = ARRAY_SIZE(rtl8367b_port), |
}, |
.attr_vlan = { |
.attr = rtl8367b_vlan, |
.n_attr = ARRAY_SIZE(rtl8367b_vlan), |
}, |
|
.get_vlan_ports = rtl8366_sw_get_vlan_ports, |
.set_vlan_ports = rtl8366_sw_set_vlan_ports, |
.get_port_pvid = rtl8366_sw_get_port_pvid, |
.set_port_pvid = rtl8366_sw_set_port_pvid, |
.reset_switch = rtl8366_sw_reset_switch, |
.get_port_link = rtl8367b_sw_get_port_link, |
.get_port_stats = rtl8367b_sw_get_port_stats, |
}; |
|
static int rtl8367b_switch_init(struct rtl8366_smi *smi) |
{ |
struct switch_dev *dev = &smi->sw_dev; |
int err; |
|
dev->name = "RTL8367B"; |
dev->cpu_port = RTL8367B_CPU_PORT_NUM; |
dev->ports = RTL8367B_NUM_PORTS; |
dev->vlans = RTL8367B_NUM_VIDS; |
dev->ops = &rtl8367b_sw_ops; |
dev->alias = dev_name(smi->parent); |
|
err = register_switch(dev, NULL); |
if (err) |
dev_err(smi->parent, "switch registration failed\n"); |
|
return err; |
} |
|
static void rtl8367b_switch_cleanup(struct rtl8366_smi *smi) |
{ |
unregister_switch(&smi->sw_dev); |
} |
|
static int rtl8367b_mii_read(struct mii_bus *bus, int addr, int reg) |
{ |
struct rtl8366_smi *smi = bus->priv; |
u32 val = 0; |
int err; |
|
err = rtl8367b_read_phy_reg(smi, addr, reg, &val); |
if (err) |
return 0xffff; |
|
return val; |
} |
|
static int rtl8367b_mii_write(struct mii_bus *bus, int addr, int reg, u16 val) |
{ |
struct rtl8366_smi *smi = bus->priv; |
u32 t; |
int err; |
|
err = rtl8367b_write_phy_reg(smi, addr, reg, val); |
if (err) |
return err; |
|
/* flush write */ |
(void) rtl8367b_read_phy_reg(smi, addr, reg, &t); |
|
return err; |
} |
|
static int rtl8367b_detect(struct rtl8366_smi *smi) |
{ |
const char *chip_name; |
u32 chip_num; |
u32 chip_ver; |
u32 chip_mode; |
int ret; |
|
/* TODO: improve chip detection */ |
rtl8366_smi_write_reg(smi, RTL8367B_RTL_MAGIC_ID_REG, |
RTL8367B_RTL_MAGIC_ID_VAL); |
|
ret = rtl8366_smi_read_reg(smi, RTL8367B_CHIP_NUMBER_REG, &chip_num); |
if (ret) { |
dev_err(smi->parent, "unable to read %s register\n", |
"chip number"); |
return ret; |
} |
|
ret = rtl8366_smi_read_reg(smi, RTL8367B_CHIP_VER_REG, &chip_ver); |
if (ret) { |
dev_err(smi->parent, "unable to read %s register\n", |
"chip version"); |
return ret; |
} |
|
ret = rtl8366_smi_read_reg(smi, RTL8367B_CHIP_MODE_REG, &chip_mode); |
if (ret) { |
dev_err(smi->parent, "unable to read %s register\n", |
"chip mode"); |
return ret; |
} |
|
switch (chip_ver) { |
case 0x1000: |
chip_name = "8367RB"; |
break; |
case 0x1010: |
chip_name = "8367R-VB"; |
break; |
default: |
dev_err(smi->parent, |
"unknown chip num:%04x ver:%04x, mode:%04x\n", |
chip_num, chip_ver, chip_mode); |
return -ENODEV; |
} |
|
dev_info(smi->parent, "RTL%s chip found\n", chip_name); |
|
return 0; |
} |
|
static struct rtl8366_smi_ops rtl8367b_smi_ops = { |
.detect = rtl8367b_detect, |
.reset_chip = rtl8367b_reset_chip, |
.setup = rtl8367b_setup, |
|
.mii_read = rtl8367b_mii_read, |
.mii_write = rtl8367b_mii_write, |
|
.get_vlan_mc = rtl8367b_get_vlan_mc, |
.set_vlan_mc = rtl8367b_set_vlan_mc, |
.get_vlan_4k = rtl8367b_get_vlan_4k, |
.set_vlan_4k = rtl8367b_set_vlan_4k, |
.get_mc_index = rtl8367b_get_mc_index, |
.set_mc_index = rtl8367b_set_mc_index, |
.get_mib_counter = rtl8367b_get_mib_counter, |
.is_vlan_valid = rtl8367b_is_vlan_valid, |
.enable_vlan = rtl8367b_enable_vlan, |
.enable_vlan4k = rtl8367b_enable_vlan4k, |
.enable_port = rtl8367b_enable_port, |
}; |
|
static int rtl8367b_probe(struct platform_device *pdev) |
{ |
struct rtl8366_smi *smi; |
int err; |
|
smi = rtl8366_smi_probe(pdev); |
if (!smi) |
return -ENODEV; |
|
smi->clk_delay = 1500; |
smi->cmd_read = 0xb9; |
smi->cmd_write = 0xb8; |
smi->ops = &rtl8367b_smi_ops; |
smi->cpu_port = RTL8367B_CPU_PORT_NUM; |
smi->num_ports = RTL8367B_NUM_PORTS; |
smi->num_vlan_mc = RTL8367B_NUM_VLANS; |
smi->mib_counters = rtl8367b_mib_counters; |
smi->num_mib_counters = ARRAY_SIZE(rtl8367b_mib_counters); |
|
err = rtl8366_smi_init(smi); |
if (err) |
goto err_free_smi; |
|
platform_set_drvdata(pdev, smi); |
|
err = rtl8367b_switch_init(smi); |
if (err) |
goto err_clear_drvdata; |
|
return 0; |
|
err_clear_drvdata: |
platform_set_drvdata(pdev, NULL); |
rtl8366_smi_cleanup(smi); |
err_free_smi: |
kfree(smi); |
return err; |
} |
|
static int rtl8367b_remove(struct platform_device *pdev) |
{ |
struct rtl8366_smi *smi = platform_get_drvdata(pdev); |
|
if (smi) { |
rtl8367b_switch_cleanup(smi); |
platform_set_drvdata(pdev, NULL); |
rtl8366_smi_cleanup(smi); |
kfree(smi); |
} |
|
return 0; |
} |
|
static void rtl8367b_shutdown(struct platform_device *pdev) |
{ |
struct rtl8366_smi *smi = platform_get_drvdata(pdev); |
|
if (smi) |
rtl8367b_reset_chip(smi); |
} |
|
#ifdef CONFIG_OF |
static const struct of_device_id rtl8367b_match[] = { |
{ .compatible = "realtek,rtl8367b" }, |
{ .compatible = "rtl8367b" }, |
{}, |
}; |
MODULE_DEVICE_TABLE(of, rtl8367b_match); |
#endif |
|
static struct platform_driver rtl8367b_driver = { |
.driver = { |
.name = RTL8367B_DRIVER_NAME, |
.owner = THIS_MODULE, |
#ifdef CONFIG_OF |
.of_match_table = of_match_ptr(rtl8367b_match), |
#endif |
}, |
.probe = rtl8367b_probe, |
.remove = rtl8367b_remove, |
.shutdown = rtl8367b_shutdown, |
}; |
|
module_platform_driver(rtl8367b_driver); |
|
MODULE_DESCRIPTION("Realtek RTL8367B ethernet switch driver"); |
MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>"); |
MODULE_LICENSE("GPL v2"); |
MODULE_ALIAS("platform:" RTL8367B_DRIVER_NAME); |
|
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/swconfig.c |
@@ -0,0 +1,1256 @@ |
/* |
* swconfig.c: Switch configuration API |
* |
* Copyright (C) 2008 Felix Fietkau <nbd@nbd.name> |
* |
* 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; either version 2 |
* of the License, or (at your option) any later version. |
* |
* 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. |
*/ |
|
#include <linux/types.h> |
#include <linux/module.h> |
#include <linux/init.h> |
#include <linux/list.h> |
#include <linux/if.h> |
#include <linux/if_ether.h> |
#include <linux/capability.h> |
#include <linux/skbuff.h> |
#include <linux/switch.h> |
#include <linux/of.h> |
#include <linux/version.h> |
#include <uapi/linux/mii.h> |
|
#define SWCONFIG_DEVNAME "switch%d" |
|
#include "swconfig_leds.c" |
|
MODULE_AUTHOR("Felix Fietkau <nbd@nbd.name>"); |
MODULE_LICENSE("GPL"); |
|
static int swdev_id; |
static struct list_head swdevs; |
static DEFINE_MUTEX(swdevs_lock); |
struct swconfig_callback; |
|
struct swconfig_callback { |
struct sk_buff *msg; |
struct genlmsghdr *hdr; |
struct genl_info *info; |
int cmd; |
|
/* callback for filling in the message data */ |
int (*fill)(struct swconfig_callback *cb, void *arg); |
|
/* callback for closing the message before sending it */ |
int (*close)(struct swconfig_callback *cb, void *arg); |
|
struct nlattr *nest[4]; |
int args[4]; |
}; |
|
/* defaults */ |
|
static int |
swconfig_get_vlan_ports(struct switch_dev *dev, const struct switch_attr *attr, |
struct switch_val *val) |
{ |
int ret; |
if (val->port_vlan >= dev->vlans) |
return -EINVAL; |
|
if (!dev->ops->get_vlan_ports) |
return -EOPNOTSUPP; |
|
ret = dev->ops->get_vlan_ports(dev, val); |
return ret; |
} |
|
static int |
swconfig_set_vlan_ports(struct switch_dev *dev, const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct switch_port *ports = val->value.ports; |
const struct switch_dev_ops *ops = dev->ops; |
int i; |
|
if (val->port_vlan >= dev->vlans) |
return -EINVAL; |
|
/* validate ports */ |
if (val->len > dev->ports) |
return -EINVAL; |
|
if (!ops->set_vlan_ports) |
return -EOPNOTSUPP; |
|
for (i = 0; i < val->len; i++) { |
if (ports[i].id >= dev->ports) |
return -EINVAL; |
|
if (ops->set_port_pvid && |
!(ports[i].flags & (1 << SWITCH_PORT_FLAG_TAGGED))) |
ops->set_port_pvid(dev, ports[i].id, val->port_vlan); |
} |
|
return ops->set_vlan_ports(dev, val); |
} |
|
static int |
swconfig_set_pvid(struct switch_dev *dev, const struct switch_attr *attr, |
struct switch_val *val) |
{ |
if (val->port_vlan >= dev->ports) |
return -EINVAL; |
|
if (!dev->ops->set_port_pvid) |
return -EOPNOTSUPP; |
|
return dev->ops->set_port_pvid(dev, val->port_vlan, val->value.i); |
} |
|
static int |
swconfig_get_pvid(struct switch_dev *dev, const struct switch_attr *attr, |
struct switch_val *val) |
{ |
if (val->port_vlan >= dev->ports) |
return -EINVAL; |
|
if (!dev->ops->get_port_pvid) |
return -EOPNOTSUPP; |
|
return dev->ops->get_port_pvid(dev, val->port_vlan, &val->value.i); |
} |
|
static int |
swconfig_set_link(struct switch_dev *dev, const struct switch_attr *attr, |
struct switch_val *val) |
{ |
if (!dev->ops->set_port_link) |
return -EOPNOTSUPP; |
|
return dev->ops->set_port_link(dev, val->port_vlan, val->value.link); |
} |
|
static int |
swconfig_get_link(struct switch_dev *dev, const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct switch_port_link *link = val->value.link; |
|
if (val->port_vlan >= dev->ports) |
return -EINVAL; |
|
if (!dev->ops->get_port_link) |
return -EOPNOTSUPP; |
|
memset(link, 0, sizeof(*link)); |
return dev->ops->get_port_link(dev, val->port_vlan, link); |
} |
|
static int |
swconfig_apply_config(struct switch_dev *dev, const struct switch_attr *attr, |
struct switch_val *val) |
{ |
/* don't complain if not supported by the switch driver */ |
if (!dev->ops->apply_config) |
return 0; |
|
return dev->ops->apply_config(dev); |
} |
|
static int |
swconfig_reset_switch(struct switch_dev *dev, const struct switch_attr *attr, |
struct switch_val *val) |
{ |
/* don't complain if not supported by the switch driver */ |
if (!dev->ops->reset_switch) |
return 0; |
|
return dev->ops->reset_switch(dev); |
} |
|
enum global_defaults { |
GLOBAL_APPLY, |
GLOBAL_RESET, |
}; |
|
enum vlan_defaults { |
VLAN_PORTS, |
}; |
|
enum port_defaults { |
PORT_PVID, |
PORT_LINK, |
}; |
|
static struct switch_attr default_global[] = { |
[GLOBAL_APPLY] = { |
.type = SWITCH_TYPE_NOVAL, |
.name = "apply", |
.description = "Activate changes in the hardware", |
.set = swconfig_apply_config, |
}, |
[GLOBAL_RESET] = { |
.type = SWITCH_TYPE_NOVAL, |
.name = "reset", |
.description = "Reset the switch", |
.set = swconfig_reset_switch, |
} |
}; |
|
static struct switch_attr default_port[] = { |
[PORT_PVID] = { |
.type = SWITCH_TYPE_INT, |
.name = "pvid", |
.description = "Primary VLAN ID", |
.set = swconfig_set_pvid, |
.get = swconfig_get_pvid, |
}, |
[PORT_LINK] = { |
.type = SWITCH_TYPE_LINK, |
.name = "link", |
.description = "Get port link information", |
.set = swconfig_set_link, |
.get = swconfig_get_link, |
} |
}; |
|
static struct switch_attr default_vlan[] = { |
[VLAN_PORTS] = { |
.type = SWITCH_TYPE_PORTS, |
.name = "ports", |
.description = "VLAN port mapping", |
.set = swconfig_set_vlan_ports, |
.get = swconfig_get_vlan_ports, |
}, |
}; |
|
static const struct switch_attr * |
swconfig_find_attr_by_name(const struct switch_attrlist *alist, |
const char *name) |
{ |
int i; |
|
for (i = 0; i < alist->n_attr; i++) |
if (strcmp(name, alist->attr[i].name) == 0) |
return &alist->attr[i]; |
|
return NULL; |
} |
|
static void swconfig_defaults_init(struct switch_dev *dev) |
{ |
const struct switch_dev_ops *ops = dev->ops; |
|
dev->def_global = 0; |
dev->def_vlan = 0; |
dev->def_port = 0; |
|
if (ops->get_vlan_ports || ops->set_vlan_ports) |
set_bit(VLAN_PORTS, &dev->def_vlan); |
|
if (ops->get_port_pvid || ops->set_port_pvid) |
set_bit(PORT_PVID, &dev->def_port); |
|
if (ops->get_port_link && |
!swconfig_find_attr_by_name(&ops->attr_port, "link")) |
set_bit(PORT_LINK, &dev->def_port); |
|
/* always present, can be no-op */ |
set_bit(GLOBAL_APPLY, &dev->def_global); |
set_bit(GLOBAL_RESET, &dev->def_global); |
} |
|
|
static struct genl_family switch_fam; |
|
static const struct nla_policy switch_policy[SWITCH_ATTR_MAX+1] = { |
[SWITCH_ATTR_ID] = { .type = NLA_U32 }, |
[SWITCH_ATTR_OP_ID] = { .type = NLA_U32 }, |
[SWITCH_ATTR_OP_PORT] = { .type = NLA_U32 }, |
[SWITCH_ATTR_OP_VLAN] = { .type = NLA_U32 }, |
[SWITCH_ATTR_OP_VALUE_INT] = { .type = NLA_U32 }, |
[SWITCH_ATTR_OP_VALUE_STR] = { .type = NLA_NUL_STRING }, |
[SWITCH_ATTR_OP_VALUE_PORTS] = { .type = NLA_NESTED }, |
[SWITCH_ATTR_TYPE] = { .type = NLA_U32 }, |
}; |
|
static const struct nla_policy port_policy[SWITCH_PORT_ATTR_MAX+1] = { |
[SWITCH_PORT_ID] = { .type = NLA_U32 }, |
[SWITCH_PORT_FLAG_TAGGED] = { .type = NLA_FLAG }, |
}; |
|
static struct nla_policy link_policy[SWITCH_LINK_ATTR_MAX] = { |
[SWITCH_LINK_FLAG_DUPLEX] = { .type = NLA_FLAG }, |
[SWITCH_LINK_FLAG_ANEG] = { .type = NLA_FLAG }, |
[SWITCH_LINK_SPEED] = { .type = NLA_U32 }, |
}; |
|
static inline void |
swconfig_lock(void) |
{ |
mutex_lock(&swdevs_lock); |
} |
|
static inline void |
swconfig_unlock(void) |
{ |
mutex_unlock(&swdevs_lock); |
} |
|
static struct switch_dev * |
swconfig_get_dev(struct genl_info *info) |
{ |
struct switch_dev *dev = NULL; |
struct switch_dev *p; |
int id; |
|
if (!info->attrs[SWITCH_ATTR_ID]) |
goto done; |
|
id = nla_get_u32(info->attrs[SWITCH_ATTR_ID]); |
swconfig_lock(); |
list_for_each_entry(p, &swdevs, dev_list) { |
if (id != p->id) |
continue; |
|
dev = p; |
break; |
} |
if (dev) |
mutex_lock(&dev->sw_mutex); |
else |
pr_debug("device %d not found\n", id); |
swconfig_unlock(); |
done: |
return dev; |
} |
|
static inline void |
swconfig_put_dev(struct switch_dev *dev) |
{ |
mutex_unlock(&dev->sw_mutex); |
} |
|
static int |
swconfig_dump_attr(struct swconfig_callback *cb, void *arg) |
{ |
struct switch_attr *op = arg; |
struct genl_info *info = cb->info; |
struct sk_buff *msg = cb->msg; |
int id = cb->args[0]; |
void *hdr; |
|
hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq, &switch_fam, |
NLM_F_MULTI, SWITCH_CMD_NEW_ATTR); |
if (IS_ERR(hdr)) |
return -1; |
|
if (nla_put_u32(msg, SWITCH_ATTR_OP_ID, id)) |
goto nla_put_failure; |
if (nla_put_u32(msg, SWITCH_ATTR_OP_TYPE, op->type)) |
goto nla_put_failure; |
if (nla_put_string(msg, SWITCH_ATTR_OP_NAME, op->name)) |
goto nla_put_failure; |
if (op->description) |
if (nla_put_string(msg, SWITCH_ATTR_OP_DESCRIPTION, |
op->description)) |
goto nla_put_failure; |
|
genlmsg_end(msg, hdr); |
return msg->len; |
nla_put_failure: |
genlmsg_cancel(msg, hdr); |
return -EMSGSIZE; |
} |
|
/* spread multipart messages across multiple message buffers */ |
static int |
swconfig_send_multipart(struct swconfig_callback *cb, void *arg) |
{ |
struct genl_info *info = cb->info; |
int restart = 0; |
int err; |
|
do { |
if (!cb->msg) { |
cb->msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); |
if (cb->msg == NULL) |
goto error; |
} |
|
if (!(cb->fill(cb, arg) < 0)) |
break; |
|
/* fill failed, check if this was already the second attempt */ |
if (restart) |
goto error; |
|
/* try again in a new message, send the current one */ |
restart = 1; |
if (cb->close) { |
if (cb->close(cb, arg) < 0) |
goto error; |
} |
err = genlmsg_reply(cb->msg, info); |
cb->msg = NULL; |
if (err < 0) |
goto error; |
|
} while (restart); |
|
return 0; |
|
error: |
if (cb->msg) |
nlmsg_free(cb->msg); |
return -1; |
} |
|
static int |
swconfig_list_attrs(struct sk_buff *skb, struct genl_info *info) |
{ |
struct genlmsghdr *hdr = nlmsg_data(info->nlhdr); |
const struct switch_attrlist *alist; |
struct switch_dev *dev; |
struct swconfig_callback cb; |
int err = -EINVAL; |
int i; |
|
/* defaults */ |
struct switch_attr *def_list; |
unsigned long *def_active; |
int n_def; |
|
dev = swconfig_get_dev(info); |
if (!dev) |
return -EINVAL; |
|
switch (hdr->cmd) { |
case SWITCH_CMD_LIST_GLOBAL: |
alist = &dev->ops->attr_global; |
def_list = default_global; |
def_active = &dev->def_global; |
n_def = ARRAY_SIZE(default_global); |
break; |
case SWITCH_CMD_LIST_VLAN: |
alist = &dev->ops->attr_vlan; |
def_list = default_vlan; |
def_active = &dev->def_vlan; |
n_def = ARRAY_SIZE(default_vlan); |
break; |
case SWITCH_CMD_LIST_PORT: |
alist = &dev->ops->attr_port; |
def_list = default_port; |
def_active = &dev->def_port; |
n_def = ARRAY_SIZE(default_port); |
break; |
default: |
WARN_ON(1); |
goto out; |
} |
|
memset(&cb, 0, sizeof(cb)); |
cb.info = info; |
cb.fill = swconfig_dump_attr; |
for (i = 0; i < alist->n_attr; i++) { |
if (alist->attr[i].disabled) |
continue; |
cb.args[0] = i; |
err = swconfig_send_multipart(&cb, (void *) &alist->attr[i]); |
if (err < 0) |
goto error; |
} |
|
/* defaults */ |
for (i = 0; i < n_def; i++) { |
if (!test_bit(i, def_active)) |
continue; |
cb.args[0] = SWITCH_ATTR_DEFAULTS_OFFSET + i; |
err = swconfig_send_multipart(&cb, (void *) &def_list[i]); |
if (err < 0) |
goto error; |
} |
swconfig_put_dev(dev); |
|
if (!cb.msg) |
return 0; |
|
return genlmsg_reply(cb.msg, info); |
|
error: |
if (cb.msg) |
nlmsg_free(cb.msg); |
out: |
swconfig_put_dev(dev); |
return err; |
} |
|
static const struct switch_attr * |
swconfig_lookup_attr(struct switch_dev *dev, struct genl_info *info, |
struct switch_val *val) |
{ |
struct genlmsghdr *hdr = nlmsg_data(info->nlhdr); |
const struct switch_attrlist *alist; |
const struct switch_attr *attr = NULL; |
unsigned int attr_id; |
|
/* defaults */ |
struct switch_attr *def_list; |
unsigned long *def_active; |
int n_def; |
|
if (!info->attrs[SWITCH_ATTR_OP_ID]) |
goto done; |
|
switch (hdr->cmd) { |
case SWITCH_CMD_SET_GLOBAL: |
case SWITCH_CMD_GET_GLOBAL: |
alist = &dev->ops->attr_global; |
def_list = default_global; |
def_active = &dev->def_global; |
n_def = ARRAY_SIZE(default_global); |
break; |
case SWITCH_CMD_SET_VLAN: |
case SWITCH_CMD_GET_VLAN: |
alist = &dev->ops->attr_vlan; |
def_list = default_vlan; |
def_active = &dev->def_vlan; |
n_def = ARRAY_SIZE(default_vlan); |
if (!info->attrs[SWITCH_ATTR_OP_VLAN]) |
goto done; |
val->port_vlan = nla_get_u32(info->attrs[SWITCH_ATTR_OP_VLAN]); |
if (val->port_vlan >= dev->vlans) |
goto done; |
break; |
case SWITCH_CMD_SET_PORT: |
case SWITCH_CMD_GET_PORT: |
alist = &dev->ops->attr_port; |
def_list = default_port; |
def_active = &dev->def_port; |
n_def = ARRAY_SIZE(default_port); |
if (!info->attrs[SWITCH_ATTR_OP_PORT]) |
goto done; |
val->port_vlan = nla_get_u32(info->attrs[SWITCH_ATTR_OP_PORT]); |
if (val->port_vlan >= dev->ports) |
goto done; |
break; |
default: |
WARN_ON(1); |
goto done; |
} |
|
if (!alist) |
goto done; |
|
attr_id = nla_get_u32(info->attrs[SWITCH_ATTR_OP_ID]); |
if (attr_id >= SWITCH_ATTR_DEFAULTS_OFFSET) { |
attr_id -= SWITCH_ATTR_DEFAULTS_OFFSET; |
if (attr_id >= n_def) |
goto done; |
if (!test_bit(attr_id, def_active)) |
goto done; |
attr = &def_list[attr_id]; |
} else { |
if (attr_id >= alist->n_attr) |
goto done; |
attr = &alist->attr[attr_id]; |
} |
|
if (attr->disabled) |
attr = NULL; |
|
done: |
if (!attr) |
pr_debug("attribute lookup failed\n"); |
val->attr = attr; |
return attr; |
} |
|
static int |
swconfig_parse_ports(struct sk_buff *msg, struct nlattr *head, |
struct switch_val *val, int max) |
{ |
struct nlattr *nla; |
int rem; |
|
val->len = 0; |
nla_for_each_nested(nla, head, rem) { |
struct nlattr *tb[SWITCH_PORT_ATTR_MAX+1]; |
struct switch_port *port; |
|
if (val->len >= max) |
return -EINVAL; |
|
port = &val->value.ports[val->len]; |
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,12,0) |
if (nla_parse_nested(tb, SWITCH_PORT_ATTR_MAX, nla, |
port_policy)) |
#else |
if (nla_parse_nested(tb, SWITCH_PORT_ATTR_MAX, nla, |
port_policy, NULL)) |
#endif |
return -EINVAL; |
|
if (!tb[SWITCH_PORT_ID]) |
return -EINVAL; |
|
port->id = nla_get_u32(tb[SWITCH_PORT_ID]); |
if (tb[SWITCH_PORT_FLAG_TAGGED]) |
port->flags |= (1 << SWITCH_PORT_FLAG_TAGGED); |
val->len++; |
} |
|
return 0; |
} |
|
static int |
swconfig_parse_link(struct sk_buff *msg, struct nlattr *nla, |
struct switch_port_link *link) |
{ |
struct nlattr *tb[SWITCH_LINK_ATTR_MAX + 1]; |
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,12,0) |
if (nla_parse_nested(tb, SWITCH_LINK_ATTR_MAX, nla, link_policy)) |
#else |
if (nla_parse_nested(tb, SWITCH_LINK_ATTR_MAX, nla, link_policy, NULL)) |
#endif |
return -EINVAL; |
|
link->duplex = !!tb[SWITCH_LINK_FLAG_DUPLEX]; |
link->aneg = !!tb[SWITCH_LINK_FLAG_ANEG]; |
link->speed = nla_get_u32(tb[SWITCH_LINK_SPEED]); |
|
return 0; |
} |
|
static int |
swconfig_set_attr(struct sk_buff *skb, struct genl_info *info) |
{ |
const struct switch_attr *attr; |
struct switch_dev *dev; |
struct switch_val val; |
int err = -EINVAL; |
|
if (!capable(CAP_NET_ADMIN)) |
return -EPERM; |
|
dev = swconfig_get_dev(info); |
if (!dev) |
return -EINVAL; |
|
memset(&val, 0, sizeof(val)); |
attr = swconfig_lookup_attr(dev, info, &val); |
if (!attr || !attr->set) |
goto error; |
|
val.attr = attr; |
switch (attr->type) { |
case SWITCH_TYPE_NOVAL: |
break; |
case SWITCH_TYPE_INT: |
if (!info->attrs[SWITCH_ATTR_OP_VALUE_INT]) |
goto error; |
val.value.i = |
nla_get_u32(info->attrs[SWITCH_ATTR_OP_VALUE_INT]); |
break; |
case SWITCH_TYPE_STRING: |
if (!info->attrs[SWITCH_ATTR_OP_VALUE_STR]) |
goto error; |
val.value.s = |
nla_data(info->attrs[SWITCH_ATTR_OP_VALUE_STR]); |
break; |
case SWITCH_TYPE_PORTS: |
val.value.ports = dev->portbuf; |
memset(dev->portbuf, 0, |
sizeof(struct switch_port) * dev->ports); |
|
/* TODO: implement multipart? */ |
if (info->attrs[SWITCH_ATTR_OP_VALUE_PORTS]) { |
err = swconfig_parse_ports(skb, |
info->attrs[SWITCH_ATTR_OP_VALUE_PORTS], |
&val, dev->ports); |
if (err < 0) |
goto error; |
} else { |
val.len = 0; |
err = 0; |
} |
break; |
case SWITCH_TYPE_LINK: |
val.value.link = &dev->linkbuf; |
memset(&dev->linkbuf, 0, sizeof(struct switch_port_link)); |
|
if (info->attrs[SWITCH_ATTR_OP_VALUE_LINK]) { |
err = swconfig_parse_link(skb, |
info->attrs[SWITCH_ATTR_OP_VALUE_LINK], |
val.value.link); |
if (err < 0) |
goto error; |
} else { |
val.len = 0; |
err = 0; |
} |
break; |
default: |
goto error; |
} |
|
err = attr->set(dev, attr, &val); |
error: |
swconfig_put_dev(dev); |
return err; |
} |
|
static int |
swconfig_close_portlist(struct swconfig_callback *cb, void *arg) |
{ |
if (cb->nest[0]) |
nla_nest_end(cb->msg, cb->nest[0]); |
return 0; |
} |
|
static int |
swconfig_send_port(struct swconfig_callback *cb, void *arg) |
{ |
const struct switch_port *port = arg; |
struct nlattr *p = NULL; |
|
if (!cb->nest[0]) { |
cb->nest[0] = nla_nest_start(cb->msg, cb->cmd); |
if (!cb->nest[0]) |
return -1; |
} |
|
p = nla_nest_start(cb->msg, SWITCH_ATTR_PORT); |
if (!p) |
goto error; |
|
if (nla_put_u32(cb->msg, SWITCH_PORT_ID, port->id)) |
goto nla_put_failure; |
if (port->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) { |
if (nla_put_flag(cb->msg, SWITCH_PORT_FLAG_TAGGED)) |
goto nla_put_failure; |
} |
|
nla_nest_end(cb->msg, p); |
return 0; |
|
nla_put_failure: |
nla_nest_cancel(cb->msg, p); |
error: |
nla_nest_cancel(cb->msg, cb->nest[0]); |
return -1; |
} |
|
static int |
swconfig_send_ports(struct sk_buff **msg, struct genl_info *info, int attr, |
const struct switch_val *val) |
{ |
struct swconfig_callback cb; |
int err = 0; |
int i; |
|
if (!val->value.ports) |
return -EINVAL; |
|
memset(&cb, 0, sizeof(cb)); |
cb.cmd = attr; |
cb.msg = *msg; |
cb.info = info; |
cb.fill = swconfig_send_port; |
cb.close = swconfig_close_portlist; |
|
cb.nest[0] = nla_nest_start(cb.msg, cb.cmd); |
for (i = 0; i < val->len; i++) { |
err = swconfig_send_multipart(&cb, &val->value.ports[i]); |
if (err) |
goto done; |
} |
err = val->len; |
swconfig_close_portlist(&cb, NULL); |
*msg = cb.msg; |
|
done: |
return err; |
} |
|
static int |
swconfig_send_link(struct sk_buff *msg, struct genl_info *info, int attr, |
const struct switch_port_link *link) |
{ |
struct nlattr *p = NULL; |
int err = 0; |
|
p = nla_nest_start(msg, attr); |
if (link->link) { |
if (nla_put_flag(msg, SWITCH_LINK_FLAG_LINK)) |
goto nla_put_failure; |
} |
if (link->duplex) { |
if (nla_put_flag(msg, SWITCH_LINK_FLAG_DUPLEX)) |
goto nla_put_failure; |
} |
if (link->aneg) { |
if (nla_put_flag(msg, SWITCH_LINK_FLAG_ANEG)) |
goto nla_put_failure; |
} |
if (link->tx_flow) { |
if (nla_put_flag(msg, SWITCH_LINK_FLAG_TX_FLOW)) |
goto nla_put_failure; |
} |
if (link->rx_flow) { |
if (nla_put_flag(msg, SWITCH_LINK_FLAG_RX_FLOW)) |
goto nla_put_failure; |
} |
if (nla_put_u32(msg, SWITCH_LINK_SPEED, link->speed)) |
goto nla_put_failure; |
if (link->eee & ADVERTISED_100baseT_Full) { |
if (nla_put_flag(msg, SWITCH_LINK_FLAG_EEE_100BASET)) |
goto nla_put_failure; |
} |
if (link->eee & ADVERTISED_1000baseT_Full) { |
if (nla_put_flag(msg, SWITCH_LINK_FLAG_EEE_1000BASET)) |
goto nla_put_failure; |
} |
nla_nest_end(msg, p); |
|
return err; |
|
nla_put_failure: |
nla_nest_cancel(msg, p); |
return -1; |
} |
|
static int |
swconfig_get_attr(struct sk_buff *skb, struct genl_info *info) |
{ |
struct genlmsghdr *hdr = nlmsg_data(info->nlhdr); |
const struct switch_attr *attr; |
struct switch_dev *dev; |
struct sk_buff *msg = NULL; |
struct switch_val val; |
int err = -EINVAL; |
int cmd = hdr->cmd; |
|
dev = swconfig_get_dev(info); |
if (!dev) |
return -EINVAL; |
|
memset(&val, 0, sizeof(val)); |
attr = swconfig_lookup_attr(dev, info, &val); |
if (!attr || !attr->get) |
goto error; |
|
if (attr->type == SWITCH_TYPE_PORTS) { |
val.value.ports = dev->portbuf; |
memset(dev->portbuf, 0, |
sizeof(struct switch_port) * dev->ports); |
} else if (attr->type == SWITCH_TYPE_LINK) { |
val.value.link = &dev->linkbuf; |
memset(&dev->linkbuf, 0, sizeof(struct switch_port_link)); |
} |
|
err = attr->get(dev, attr, &val); |
if (err) |
goto error; |
|
msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); |
if (!msg) |
goto error; |
|
hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq, &switch_fam, |
0, cmd); |
if (IS_ERR(hdr)) |
goto nla_put_failure; |
|
switch (attr->type) { |
case SWITCH_TYPE_INT: |
if (nla_put_u32(msg, SWITCH_ATTR_OP_VALUE_INT, val.value.i)) |
goto nla_put_failure; |
break; |
case SWITCH_TYPE_STRING: |
if (nla_put_string(msg, SWITCH_ATTR_OP_VALUE_STR, val.value.s)) |
goto nla_put_failure; |
break; |
case SWITCH_TYPE_PORTS: |
err = swconfig_send_ports(&msg, info, |
SWITCH_ATTR_OP_VALUE_PORTS, &val); |
if (err < 0) |
goto nla_put_failure; |
break; |
case SWITCH_TYPE_LINK: |
err = swconfig_send_link(msg, info, |
SWITCH_ATTR_OP_VALUE_LINK, val.value.link); |
if (err < 0) |
goto nla_put_failure; |
break; |
default: |
pr_debug("invalid type in attribute\n"); |
err = -EINVAL; |
goto nla_put_failure; |
} |
genlmsg_end(msg, hdr); |
err = msg->len; |
if (err < 0) |
goto nla_put_failure; |
|
swconfig_put_dev(dev); |
return genlmsg_reply(msg, info); |
|
nla_put_failure: |
if (msg) |
nlmsg_free(msg); |
error: |
swconfig_put_dev(dev); |
if (!err) |
err = -ENOMEM; |
return err; |
} |
|
static int |
swconfig_send_switch(struct sk_buff *msg, u32 pid, u32 seq, int flags, |
const struct switch_dev *dev) |
{ |
struct nlattr *p = NULL, *m = NULL; |
void *hdr; |
int i; |
|
hdr = genlmsg_put(msg, pid, seq, &switch_fam, flags, |
SWITCH_CMD_NEW_ATTR); |
if (IS_ERR(hdr)) |
return -1; |
|
if (nla_put_u32(msg, SWITCH_ATTR_ID, dev->id)) |
goto nla_put_failure; |
if (nla_put_string(msg, SWITCH_ATTR_DEV_NAME, dev->devname)) |
goto nla_put_failure; |
if (nla_put_string(msg, SWITCH_ATTR_ALIAS, dev->alias)) |
goto nla_put_failure; |
if (nla_put_string(msg, SWITCH_ATTR_NAME, dev->name)) |
goto nla_put_failure; |
if (nla_put_u32(msg, SWITCH_ATTR_VLANS, dev->vlans)) |
goto nla_put_failure; |
if (nla_put_u32(msg, SWITCH_ATTR_PORTS, dev->ports)) |
goto nla_put_failure; |
if (nla_put_u32(msg, SWITCH_ATTR_CPU_PORT, dev->cpu_port)) |
goto nla_put_failure; |
|
m = nla_nest_start(msg, SWITCH_ATTR_PORTMAP); |
if (!m) |
goto nla_put_failure; |
for (i = 0; i < dev->ports; i++) { |
p = nla_nest_start(msg, SWITCH_ATTR_PORTS); |
if (!p) |
continue; |
if (dev->portmap[i].s) { |
if (nla_put_string(msg, SWITCH_PORTMAP_SEGMENT, |
dev->portmap[i].s)) |
goto nla_put_failure; |
if (nla_put_u32(msg, SWITCH_PORTMAP_VIRT, |
dev->portmap[i].virt)) |
goto nla_put_failure; |
} |
nla_nest_end(msg, p); |
} |
nla_nest_end(msg, m); |
genlmsg_end(msg, hdr); |
return msg->len; |
nla_put_failure: |
genlmsg_cancel(msg, hdr); |
return -EMSGSIZE; |
} |
|
static int swconfig_dump_switches(struct sk_buff *skb, |
struct netlink_callback *cb) |
{ |
struct switch_dev *dev; |
int start = cb->args[0]; |
int idx = 0; |
|
swconfig_lock(); |
list_for_each_entry(dev, &swdevs, dev_list) { |
if (++idx <= start) |
continue; |
if (swconfig_send_switch(skb, NETLINK_CB(cb->skb).portid, |
cb->nlh->nlmsg_seq, NLM_F_MULTI, |
dev) < 0) |
break; |
} |
swconfig_unlock(); |
cb->args[0] = idx; |
|
return skb->len; |
} |
|
static int |
swconfig_done(struct netlink_callback *cb) |
{ |
return 0; |
} |
|
static struct genl_ops swconfig_ops[] = { |
{ |
.cmd = SWITCH_CMD_LIST_GLOBAL, |
.doit = swconfig_list_attrs, |
.policy = switch_policy, |
}, |
{ |
.cmd = SWITCH_CMD_LIST_VLAN, |
.doit = swconfig_list_attrs, |
.policy = switch_policy, |
}, |
{ |
.cmd = SWITCH_CMD_LIST_PORT, |
.doit = swconfig_list_attrs, |
.policy = switch_policy, |
}, |
{ |
.cmd = SWITCH_CMD_GET_GLOBAL, |
.doit = swconfig_get_attr, |
.policy = switch_policy, |
}, |
{ |
.cmd = SWITCH_CMD_GET_VLAN, |
.doit = swconfig_get_attr, |
.policy = switch_policy, |
}, |
{ |
.cmd = SWITCH_CMD_GET_PORT, |
.doit = swconfig_get_attr, |
.policy = switch_policy, |
}, |
{ |
.cmd = SWITCH_CMD_SET_GLOBAL, |
.flags = GENL_ADMIN_PERM, |
.doit = swconfig_set_attr, |
.policy = switch_policy, |
}, |
{ |
.cmd = SWITCH_CMD_SET_VLAN, |
.flags = GENL_ADMIN_PERM, |
.doit = swconfig_set_attr, |
.policy = switch_policy, |
}, |
{ |
.cmd = SWITCH_CMD_SET_PORT, |
.flags = GENL_ADMIN_PERM, |
.doit = swconfig_set_attr, |
.policy = switch_policy, |
}, |
{ |
.cmd = SWITCH_CMD_GET_SWITCH, |
.dumpit = swconfig_dump_switches, |
.policy = switch_policy, |
.done = swconfig_done, |
} |
}; |
|
static struct genl_family switch_fam = { |
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,10,0) |
.id = GENL_ID_GENERATE, |
#endif |
.name = "switch", |
.hdrsize = 0, |
.version = 1, |
.maxattr = SWITCH_ATTR_MAX, |
.module = THIS_MODULE, |
.ops = swconfig_ops, |
.n_ops = ARRAY_SIZE(swconfig_ops), |
}; |
|
#ifdef CONFIG_OF |
void |
of_switch_load_portmap(struct switch_dev *dev) |
{ |
struct device_node *port; |
|
if (!dev->of_node) |
return; |
|
for_each_child_of_node(dev->of_node, port) { |
const __be32 *prop; |
const char *segment; |
int size, phys; |
|
if (!of_device_is_compatible(port, "swconfig,port")) |
continue; |
|
if (of_property_read_string(port, "swconfig,segment", &segment)) |
continue; |
|
prop = of_get_property(port, "swconfig,portmap", &size); |
if (!prop) |
continue; |
|
if (size != (2 * sizeof(*prop))) { |
pr_err("%s: failed to parse port mapping\n", |
port->name); |
continue; |
} |
|
phys = be32_to_cpup(prop++); |
if ((phys < 0) | (phys >= dev->ports)) { |
pr_err("%s: physical port index out of range\n", |
port->name); |
continue; |
} |
|
dev->portmap[phys].s = kstrdup(segment, GFP_KERNEL); |
dev->portmap[phys].virt = be32_to_cpup(prop); |
pr_debug("Found port: %s, physical: %d, virtual: %d\n", |
segment, phys, dev->portmap[phys].virt); |
} |
} |
#endif |
|
int |
register_switch(struct switch_dev *dev, struct net_device *netdev) |
{ |
struct switch_dev *sdev; |
const int max_switches = 8 * sizeof(unsigned long); |
unsigned long in_use = 0; |
int err; |
int i; |
|
INIT_LIST_HEAD(&dev->dev_list); |
if (netdev) { |
dev->netdev = netdev; |
if (!dev->alias) |
dev->alias = netdev->name; |
} |
BUG_ON(!dev->alias); |
|
/* Make sure swdev_id doesn't overflow */ |
if (swdev_id == INT_MAX) { |
return -ENOMEM; |
} |
|
if (dev->ports > 0) { |
dev->portbuf = kzalloc(sizeof(struct switch_port) * |
dev->ports, GFP_KERNEL); |
if (!dev->portbuf) |
return -ENOMEM; |
dev->portmap = kzalloc(sizeof(struct switch_portmap) * |
dev->ports, GFP_KERNEL); |
if (!dev->portmap) { |
kfree(dev->portbuf); |
return -ENOMEM; |
} |
} |
swconfig_defaults_init(dev); |
mutex_init(&dev->sw_mutex); |
swconfig_lock(); |
dev->id = ++swdev_id; |
|
list_for_each_entry(sdev, &swdevs, dev_list) { |
if (!sscanf(sdev->devname, SWCONFIG_DEVNAME, &i)) |
continue; |
if (i < 0 || i > max_switches) |
continue; |
|
set_bit(i, &in_use); |
} |
i = find_first_zero_bit(&in_use, max_switches); |
|
if (i == max_switches) { |
swconfig_unlock(); |
return -ENFILE; |
} |
|
#ifdef CONFIG_OF |
if (dev->ports) |
of_switch_load_portmap(dev); |
#endif |
|
/* fill device name */ |
snprintf(dev->devname, IFNAMSIZ, SWCONFIG_DEVNAME, i); |
|
list_add_tail(&dev->dev_list, &swdevs); |
swconfig_unlock(); |
|
err = swconfig_create_led_trigger(dev); |
if (err) |
return err; |
|
return 0; |
} |
EXPORT_SYMBOL_GPL(register_switch); |
|
void |
unregister_switch(struct switch_dev *dev) |
{ |
swconfig_destroy_led_trigger(dev); |
kfree(dev->portbuf); |
mutex_lock(&dev->sw_mutex); |
swconfig_lock(); |
list_del(&dev->dev_list); |
swconfig_unlock(); |
mutex_unlock(&dev->sw_mutex); |
} |
EXPORT_SYMBOL_GPL(unregister_switch); |
|
int |
switch_generic_set_link(struct switch_dev *dev, int port, |
struct switch_port_link *link) |
{ |
if (WARN_ON(!dev->ops->phy_write16)) |
return -ENOTSUPP; |
|
/* Generic implementation */ |
if (link->aneg) { |
dev->ops->phy_write16(dev, port, MII_BMCR, 0x0000); |
dev->ops->phy_write16(dev, port, MII_BMCR, BMCR_ANENABLE | BMCR_ANRESTART); |
} else { |
u16 bmcr = 0; |
|
if (link->duplex) |
bmcr |= BMCR_FULLDPLX; |
|
switch (link->speed) { |
case SWITCH_PORT_SPEED_10: |
break; |
case SWITCH_PORT_SPEED_100: |
bmcr |= BMCR_SPEED100; |
break; |
case SWITCH_PORT_SPEED_1000: |
bmcr |= BMCR_SPEED1000; |
break; |
default: |
return -ENOTSUPP; |
} |
|
dev->ops->phy_write16(dev, port, MII_BMCR, bmcr); |
} |
|
return 0; |
} |
|
static int __init |
swconfig_init(void) |
{ |
INIT_LIST_HEAD(&swdevs); |
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,10,0) |
return genl_register_family_with_ops(&switch_fam, swconfig_ops); |
#else |
return genl_register_family(&switch_fam); |
#endif |
} |
|
static void __exit |
swconfig_exit(void) |
{ |
genl_unregister_family(&switch_fam); |
} |
|
module_init(swconfig_init); |
module_exit(swconfig_exit); |