blob: e3a7dc0c956eea9000fe97917db59f2383c90f2a [file] [log] [blame]
/*
* Copyright (c) 2013 Broadcom Corporation
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/wait.h>
#include <linux/mii.h>
#include <linux/ethtool.h>
#include <linux/bitops.h>
#include <linux/netdevice.h>
#include <linux/proc_fs.h>
#include <linux/ctype.h>
#include <linux/stddef.h>
#include <linux/phy.h>
#include <linux/brcmphy.h>
#include <linux/module.h>
#define BCM53125_PHY_ID 0x03625f20
#define BCM53101_PHY_ID 0x03625ed0
static unsigned char swdata[16];
static struct proc_dir_entry *p;
#define REG_PSEUDO_PHY_MII_REG16 0x10
#define REG_PPM_REG16_SWITCH_PAGE_NUMBER_SHIFT 8
#define REG_PPM_REG16_MDIO_ENABLE 0x01
#define REG_PSEUDO_PHY_MII_REG17 0x11
#define REG_PPM_REG17_REG_NUMBER_SHIFT 8
#define REG_PPM_REG17_OP_DONE 0x00
#define REG_PPM_REG17_OP_WRITE 0x01
#define REG_PPM_REG17_OP_READ 0x02
#define REG_PSEUDO_PHY_MII_REG24 0x18
#define REG_PSEUDO_PHY_MII_REG25 0x19
#define REG_PSEUDO_PHY_MII_REG26 0x1a
#define REG_PSEUDO_PHY_MII_REG27 0x1b
#define PAGE_CONTROL 0x00
#define REG_PORT_CTRL 0x00
#define REG_IMP_PORT_CONTROL 0x08
#define REG_IMP_PORT_CONTROL_RX_UCST_EN 0x10
#define REG_IMP_PORT_CONTROL_RX_MCST_EN 0x08
#define REG_IMP_PORT_CONTROL_RX_BCST_EN 0x04
#define REG_SWITCH_MODE 0x0b
#define REG_SWITCH_MODE_FRAME_MANAGE_MODE 0x01
#define REG_SWITCH_MODE_SW_FWDG_EN 0x02
#define REG_CONTROL_IMP_PORT_STATE_OVERRIDE 0x0e
#define REG_CONTROL_MPSO_MII_SW_OVERRIDE 0x80
#define REG_CONTROL_MPSO_TX_FLOW_CONTROL 0x20
#define REG_CONTROL_MPSO_RX_FLOW_CONTROL 0x10
#define REG_CONTROL_MPSO_SPEED1000 0x08
#define REG_CONTROL_MPSO_SPEED100 0x04
#define REG_CONTROL_MPSO_FDX 0x02
#define REG_CONTROL_MPSO_LINKPASS 0x01
#define REG_SWITCH_CTRL 0x20
#define REG_MII_DUMB_FWD_EN 0x01 /* 53101 only */
#define REG_PORT_FORWARD 0x21 /* 5397 only */
#define REG_PORT_FORWARD_MCST 0x80
#define REG_PORT_FORWARD_UCST 0x40
#define REG_PORT_FORWARD_IPMC 0x01
#define REG_UCST_LOOKUP_FAIL 0x32
#define REG_RGMII_CTRL_IMP 0x60
#define REG_RGMII_CTRL_P5 0x65
#define REG_RGMII_CTRL_ENABLE_GMII 0x80
#define REG_RGMII_CTRL_TIMING_SEL 0x04
#define REG_RGMII_CTRL_DLL_RXC 0x02
#define REG_RGMII_CTRL_DLL_TXC 0x01
#define SWITCH_PORT_MAP_IMP 0x0100
#define PAGE_MANAGEMENT 0x02
#define REG_GLOBAL_CONFIG 0x00
#define PAGE_PORT_BASED_VLAN 0x31
#define REG_VLAN_CTRL_P0 0x00
#define REG_VLAN_CTRL_P1 0x02
#define REG_VLAN_CTRL_P2 0x04
#define REG_VLAN_CTRL_P3 0x06
#define REG_VLAN_CTRL_P4 0x08
#define REG_VLAN_CTRL_P5 0x0a
#define REG_VLAN_CTRL_P6 0x0c
#define REG_VLAN_CTRL_P7 0x0e
#define REG_VLAN_CTRL_P8 0x10
#define BE64(x) \
({ \
uint64_t __x = (x); \
((uint64_t)( \
(((uint64_t)(__x) & (uint64_t)0x00000000000000ffUL) << 56) | \
(((uint64_t)(__x) & (uint64_t)0x000000000000ff00UL) << 40) | \
(((uint64_t)(__x) & (uint64_t)0x0000000000ff0000UL) << 24) | \
(((uint64_t)(__x) & (uint64_t)0x00000000ff000000UL) << 8) | \
(((uint64_t)(__x) & (uint64_t)0x000000ff00000000UL) >> 8) | \
(((uint64_t)(__x) & (uint64_t)0x0000ff0000000000UL) >> 24) | \
(((uint64_t)(__x) & (uint64_t)0x00ff000000000000UL) >> 40) | \
(((uint64_t)(__x) & (uint64_t)0xff00000000000000UL) >> 56) )); \
})
#define BE32(x) \
({ \
uint32_t __x = (x); \
((uint32_t)( \
(((uint32_t)(__x) & (uint32_t)0x000000ffUL) << 24) | \
(((uint32_t)(__x) & (uint32_t)0x0000ff00UL) << 8) | \
(((uint32_t)(__x) & (uint32_t)0x00ff0000UL) >> 8) | \
(((uint32_t)(__x) & (uint32_t)0xff000000UL) >> 24) )); \
})
#define BE16(x) \
({ \
uint16_t __x = (x); \
((uint16_t)( \
(((uint16_t)(__x) & (uint16_t)0x00ffUL) << 8) | \
(((uint16_t)(__x) & (uint16_t)0xff00UL) >> 8) )); \
})
static void ethsw_mdio_rreg(struct phy_device *phydev, int page, int reg,
unsigned char *data, int len)
{
int phy_id;
int v, vm[4];
int i;
phy_id = BRCM_PSEUDO_PHY_ADDR;
v = (page << REG_PPM_REG16_SWITCH_PAGE_NUMBER_SHIFT) | REG_PPM_REG16_MDIO_ENABLE;
mdiobus_write(phydev->bus, phy_id, REG_PSEUDO_PHY_MII_REG16, v);
v = (reg << REG_PPM_REG17_REG_NUMBER_SHIFT) | REG_PPM_REG17_OP_READ;
mdiobus_write(phydev->bus, phy_id, REG_PSEUDO_PHY_MII_REG17, v);
for (i = 0; i < 5; i++)
{
v = mdiobus_read(phydev->bus, phy_id, REG_PSEUDO_PHY_MII_REG17);
if ((v & (REG_PPM_REG17_OP_WRITE | REG_PPM_REG17_OP_READ)) == REG_PPM_REG17_OP_DONE)
break;
udelay(10);
}
if (i == 5)
{
printk("ethsw_mdio_rreg: timeout!\n");
return;
}
switch (len)
{
case 1:
v = mdiobus_read(phydev->bus, phy_id, REG_PSEUDO_PHY_MII_REG24);
data[0] = (unsigned char)v;
break;
case 2:
v = mdiobus_read(phydev->bus, phy_id, REG_PSEUDO_PHY_MII_REG24);
((unsigned short *)data)[0] = (unsigned short)v;
break;
case 4:
vm[0] = mdiobus_read(phydev->bus, phy_id, REG_PSEUDO_PHY_MII_REG24);
vm[1] = mdiobus_read(phydev->bus, phy_id, REG_PSEUDO_PHY_MII_REG25);
((unsigned short *)data)[0] = (unsigned short)vm[0];
((unsigned short *)data)[1] = (unsigned short)vm[1];
break;
case 6:
vm[0] = mdiobus_read(phydev->bus, phy_id, REG_PSEUDO_PHY_MII_REG26);
vm[1] = mdiobus_read(phydev->bus, phy_id, REG_PSEUDO_PHY_MII_REG25);
vm[2] = mdiobus_read(phydev->bus, phy_id, REG_PSEUDO_PHY_MII_REG24);
((unsigned short *)data)[0] = (unsigned short)vm[2];
((unsigned short *)data)[1] = (unsigned short)vm[1];
((unsigned short *)data)[2] = (unsigned short)vm[0];
break;
case 8:
vm[0] = mdiobus_read(phydev->bus, phy_id, REG_PSEUDO_PHY_MII_REG27);
vm[1] = mdiobus_read(phydev->bus, phy_id, REG_PSEUDO_PHY_MII_REG26);
vm[2] = mdiobus_read(phydev->bus, phy_id, REG_PSEUDO_PHY_MII_REG25);
vm[3] = mdiobus_read(phydev->bus, phy_id, REG_PSEUDO_PHY_MII_REG24);
((unsigned short *)data)[0] = (unsigned short)vm[3];
((unsigned short *)data)[1] = (unsigned short)vm[2];
((unsigned short *)data)[2] = (unsigned short)vm[1];
((unsigned short *)data)[3] = (unsigned short)vm[0];
break;
}
v = page << REG_PPM_REG16_SWITCH_PAGE_NUMBER_SHIFT;
mdiobus_write(phydev->bus, phy_id, REG_PSEUDO_PHY_MII_REG16, v);
}
static void ethsw_mdio_wreg(struct phy_device *phydev, int page, int reg,
unsigned char *data, int len)
{
int phy_id;
int v;
int i;
phy_id = BRCM_PSEUDO_PHY_ADDR;
v = (page << REG_PPM_REG16_SWITCH_PAGE_NUMBER_SHIFT) | REG_PPM_REG16_MDIO_ENABLE;
mdiobus_write(phydev->bus, phy_id, REG_PSEUDO_PHY_MII_REG16, v);
switch (len)
{
case 1:
v = data[0];
mdiobus_write(phydev->bus, phy_id, REG_PSEUDO_PHY_MII_REG24, v);
break;
case 2:
v = ((unsigned short *)data)[0];
mdiobus_write(phydev->bus, phy_id, REG_PSEUDO_PHY_MII_REG24, v);
break;
case 4:
v = ((unsigned short *)data)[0];
mdiobus_write(phydev->bus, phy_id, REG_PSEUDO_PHY_MII_REG24, v);
v = ((unsigned short *)data)[1];
mdiobus_write(phydev->bus, phy_id, REG_PSEUDO_PHY_MII_REG25, v);
break;
case 6:
v = ((unsigned short *)data)[0];
mdiobus_write(phydev->bus, phy_id, REG_PSEUDO_PHY_MII_REG24, v);
v = ((unsigned short *)data)[1];
mdiobus_write(phydev->bus, phy_id, REG_PSEUDO_PHY_MII_REG25, v);
v = ((unsigned short *)data)[2];
mdiobus_write(phydev->bus, phy_id, REG_PSEUDO_PHY_MII_REG26, v);
break;
case 8:
v = ((unsigned short *)data)[0];
mdiobus_write(phydev->bus, phy_id, REG_PSEUDO_PHY_MII_REG24, v);
v = ((unsigned short *)data)[1];
mdiobus_write(phydev->bus, phy_id, REG_PSEUDO_PHY_MII_REG25, v);
v = ((unsigned short *)data)[2];
mdiobus_write(phydev->bus, phy_id, REG_PSEUDO_PHY_MII_REG26, v);
v = ((unsigned short *)data)[3];
mdiobus_write(phydev->bus, phy_id, REG_PSEUDO_PHY_MII_REG27, v);
break;
}
v = (reg << REG_PPM_REG17_REG_NUMBER_SHIFT) | REG_PPM_REG17_OP_WRITE;
mdiobus_write(phydev->bus, phy_id, REG_PSEUDO_PHY_MII_REG17, v);
for (i = 0; i < 5; i++)
{
v = mdiobus_read(phydev->bus, phy_id, REG_PSEUDO_PHY_MII_REG17);
if ((v & (REG_PPM_REG17_OP_WRITE | REG_PPM_REG17_OP_READ)) == REG_PPM_REG17_OP_DONE)
break;
udelay(10);
}
if (i == 5)
printk("ethsw_mdio_wreg: timeout!\n");
v = page << REG_PPM_REG16_SWITCH_PAGE_NUMBER_SHIFT;
mdiobus_write(phydev->bus, phy_id, REG_PSEUDO_PHY_MII_REG16, v);
}
static void ethsw_rreg(struct phy_device *phydev, int page, int reg,
unsigned char *data, int len)
{
if (((len != 1) && (len % 2) != 0) || len > 8)
panic("ethsw_rreg: wrong length!\n");
ethsw_mdio_rreg(phydev, page, reg, data, len);
}
static void ethsw_wreg(struct phy_device *phydev, int page, int reg,
unsigned char *data, int len)
{
if (((len != 1) && (len % 2) != 0) || len > 8)
panic("ethsw_wreg: wrong length!\n");
ethsw_mdio_wreg(phydev, page, reg, data, len);
}
static int ethsw_config_learning(struct phy_device *phydev)
{
unsigned char v8;
unsigned short v16;
// Forward lookup failure to CPU
v8 = (REG_PORT_FORWARD_MCST | REG_PORT_FORWARD_UCST | REG_PORT_FORWARD_IPMC);
ethsw_wreg(phydev, PAGE_CONTROL, REG_PORT_FORWARD, (unsigned char *)&v8, sizeof(v8));
// Forward unlearned unicast frames to the MIPS
v16 = SWITCH_PORT_MAP_IMP;
ethsw_wreg(phydev, PAGE_CONTROL, REG_UCST_LOOKUP_FAIL, (unsigned char *)&v16, sizeof(v16));
return 0;
}
static void ethsw_switch_unmanage_mode(struct phy_device *phydev)
{
unsigned char v8;
unsigned short v16;
ethsw_rreg(phydev, PAGE_CONTROL, REG_SWITCH_MODE, &v8, sizeof(v8));
v8 &= ~REG_SWITCH_MODE_FRAME_MANAGE_MODE;
v8 |= REG_SWITCH_MODE_SW_FWDG_EN;
ethsw_wreg(phydev, PAGE_CONTROL, REG_SWITCH_MODE, &v8, sizeof(v8));
/* BCM53101 requires this MII_DUMB_FWD_EN to be set to forward frames
* towards the host CPU
*/
if ((phydev->drv->phy_id & phydev->drv->phy_id_mask) == BCM53101_PHY_ID) {
v8 = REG_MII_DUMB_FWD_EN;
ethsw_wreg(phydev, PAGE_CONTROL, REG_SWITCH_CTRL, &v8, sizeof(v8));
}
v8 = 0;
ethsw_wreg(phydev, PAGE_MANAGEMENT, REG_GLOBAL_CONFIG, &v8, sizeof(v8));
/* Delete port-based VLAN */
v16 = 0x01ff;
ethsw_wreg(phydev, PAGE_PORT_BASED_VLAN, REG_VLAN_CTRL_P0, (unsigned char *)&v16, sizeof(v16));
ethsw_wreg(phydev, PAGE_PORT_BASED_VLAN, REG_VLAN_CTRL_P1, (unsigned char *)&v16, sizeof(v16));
ethsw_wreg(phydev, PAGE_PORT_BASED_VLAN, REG_VLAN_CTRL_P2, (unsigned char *)&v16, sizeof(v16));
ethsw_wreg(phydev, PAGE_PORT_BASED_VLAN, REG_VLAN_CTRL_P3, (unsigned char *)&v16, sizeof(v16));
ethsw_wreg(phydev, PAGE_PORT_BASED_VLAN, REG_VLAN_CTRL_P4, (unsigned char *)&v16, sizeof(v16));
ethsw_wreg(phydev, PAGE_PORT_BASED_VLAN, REG_VLAN_CTRL_P8, (unsigned char *)&v16, sizeof(v16));
}
static int ethsw_force_speed(struct phy_device *phydev)
{
unsigned char v8;
v8 = 0;
v8 |= (REG_CONTROL_MPSO_MII_SW_OVERRIDE|REG_CONTROL_MPSO_LINKPASS);
if (phydev->advertising & (SUPPORTED_1000baseT_Full | SUPPORTED_1000baseT_Half))
v8 |= REG_CONTROL_MPSO_SPEED1000;
else if (phydev->advertising & (SUPPORTED_100baseT_Full | SUPPORTED_100baseT_Half))
v8 |= REG_CONTROL_MPSO_SPEED100;
else
v8 &= ~(REG_CONTROL_MPSO_SPEED1000 | REG_CONTROL_MPSO_SPEED100);
v8 |= (REG_CONTROL_MPSO_RX_FLOW_CONTROL|REG_CONTROL_MPSO_FDX);
ethsw_wreg(phydev, PAGE_CONTROL, REG_CONTROL_IMP_PORT_STATE_OVERRIDE, &v8, sizeof(v8));
/* checking Switch functional */
v8 = 0;
ethsw_rreg(phydev, PAGE_CONTROL, REG_CONTROL_IMP_PORT_STATE_OVERRIDE, &v8, sizeof(v8));
if ((v8 & (REG_CONTROL_MPSO_MII_SW_OVERRIDE|REG_CONTROL_MPSO_LINKPASS)) !=
(REG_CONTROL_MPSO_MII_SW_OVERRIDE|REG_CONTROL_MPSO_LINKPASS) ||
(v8 == 0xff)) {
printk(KERN_ERR "error on Ethernet Switch setup 0x%x\n", v8);
return -1;
}
return 0;
}
static int ethsw_reset_ports(struct phy_device *phydev)
{
unsigned long flags;
int i;
unsigned char v8;
local_irq_save(flags);
/* MAC level reset */
v8 = 0; /* No spanning tree and tx/rx enable */
for (i = 0; i < 5; i++)
{
ethsw_wreg(phydev, PAGE_CONTROL, REG_PORT_CTRL + i, &v8, 1);
}
/* Config IMP port RGMII clock delay by DLL disabled and tx_clk aligned
* timing (restoring to reset defaults)
*/
ethsw_rreg(phydev, PAGE_CONTROL, REG_RGMII_CTRL_IMP, &v8, sizeof(v8));
v8 &= ~(REG_RGMII_CTRL_DLL_RXC | REG_RGMII_CTRL_DLL_TXC);
v8 &= ~REG_RGMII_CTRL_TIMING_SEL;
/* PHY_INTERFACE_MODE_RGMII_TXID means TX internal delay, make sure
* that we enable the IMP Port TX clock internal delay to account for
* this internal delay that is inserted, otherwise the switch won't be
* able to receive correctly.
*
* PHY_INTERFACE_MODE_RGMII means that we are not introducing any delay
* neither on transmission nor reception, so the BCM53125 must also be
* configured accordingly to account for the lack of delay and
* introduce
*
* The BCM53125 switch has its RX clock and TX clock control swapped,
* hence the reason why we modify the TX clock path in the "RGMII" case
*/
if (phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID)
v8 |= REG_RGMII_CTRL_DLL_TXC;
if (phydev->interface == PHY_INTERFACE_MODE_RGMII)
v8 |= REG_RGMII_CTRL_DLL_TXC | REG_RGMII_CTRL_DLL_RXC;
v8 |= REG_RGMII_CTRL_TIMING_SEL;
ethsw_wreg(phydev, PAGE_CONTROL, REG_RGMII_CTRL_IMP, &v8, sizeof(v8));
/* Set IMP port RMII mode */
v8 = 0;
v8 |= REG_IMP_PORT_CONTROL_RX_UCST_EN;
v8 |= REG_IMP_PORT_CONTROL_RX_MCST_EN;
v8 |= REG_IMP_PORT_CONTROL_RX_BCST_EN;
ethsw_wreg(phydev, PAGE_CONTROL, REG_IMP_PORT_CONTROL, &v8, 1);
local_irq_restore(flags);
return 0;
}
static void ethsw_print_reg(struct phy_device *phydev, int reg_page, int reg_addr, int reg_len, char *reg_name)
{
unsigned char swdata[8];
unsigned int val;
int i;
ethsw_rreg(phydev, reg_page, reg_addr, swdata, reg_len);
printk("%s [%02x:%02x] = ", reg_name, reg_page, reg_addr);
switch(reg_len) {
case 2:
*(uint16_t *)&swdata[0] = BE16(*(uint16_t *)&swdata[0]);
break;
case 4:
*(uint32_t *)&swdata[0] = BE32(*(uint32_t *)&swdata[0]);
break;
case 8:
val = BE32(*(uint32_t *)&swdata[0]);
*(uint32_t *)&swdata[0] = BE32(*(uint32_t *)&swdata[4]);
*(uint32_t *)&swdata[4] = val;
break;
}
for (i = 0; i < reg_len; i ++)
printk("%02x ", swdata[i]);
printk("\n");
}
static int ethsw_dump_mib(struct phy_device *phydev, int port)
{
int reg_page;
if (port > 8) {
printk("Invalid port number \n");
return -1;
}
printk("\n");
printk("dump port %d MIB counter\n", port);
reg_page = 0x20 + port;
/* Display Tx statistics */
ethsw_print_reg(phydev, reg_page, 0, 8, "TxOctets");
ethsw_print_reg(phydev, reg_page, 8, 4, "TxDropPkts");
ethsw_print_reg(phydev, reg_page, 0x10, 4, "TxBroadcastPkts");
ethsw_print_reg(phydev, reg_page, 0x14, 4, "TxMulticastPkts");
ethsw_print_reg(phydev, reg_page, 0x18, 4, "TxUnicastPkts");
ethsw_print_reg(phydev, reg_page, 0x1c, 4, "TxCollisions");
ethsw_print_reg(phydev, reg_page, 0x58, 4, "RxUndersizePkts");
ethsw_print_reg(phydev, reg_page, 0x60, 4, "Pkt64Octets");
ethsw_print_reg(phydev, reg_page, 0x64, 4, "Pkt65to127Octets");
ethsw_print_reg(phydev, reg_page, 0x68, 4, "Pkt128to255Octets");
ethsw_print_reg(phydev, reg_page, 0x6C, 4, "Pkt256to511Octets");
ethsw_print_reg(phydev, reg_page, 0x70, 4, "Pkt512to1023Octets");
ethsw_print_reg(phydev, reg_page, 0x74, 4, "Pkts1024toMaxPktOctets");
ethsw_print_reg(phydev, reg_page, 0x78, 4, "RxOversizePkts");
ethsw_print_reg(phydev, reg_page, 0x80, 4, "RxAlignmentErros");
ethsw_print_reg(phydev, reg_page, 0x84, 4, "RXFCSErrors");
ethsw_print_reg(phydev, reg_page, 0x88, 4, "RxGoodOctets");
ethsw_print_reg(phydev, reg_page, 0x90, 4, "RxDropPkts");
ethsw_print_reg(phydev, reg_page, 0x94, 4, "RxUnicastPkts");
ethsw_print_reg(phydev, reg_page, 0x98, 4, "RxMulticastPkts");
ethsw_print_reg(phydev, reg_page, 0x9C, 4, "RxBroadcastPkts");
ethsw_print_reg(phydev, reg_page, 0xA8, 4, "JumboPkt");
ethsw_print_reg(phydev, reg_page, 0xAC, 4, "RXSymbolError");
ethsw_print_reg(phydev, reg_page, 0xB4, 4, "OutofRangeError");
return 0;
}
static void str_to_num(char* in, char* out, int len)
{
int i;
memset(out, 0, len);
for (i = 0; i < len * 2; i ++)
{
if ((*in >= '0') && (*in <= '9'))
*out += (*in - '0');
else if ((*in >= 'a') && (*in <= 'f'))
*out += (*in - 'a') + 10;
else if ((*in >= 'A') && (*in <= 'F'))
*out += (*in - 'A') + 10;
else
*out += 0;
if ((i % 2) == 0)
*out *= 16;
else
out ++;
in ++;
}
return;
}
static int proc_get_sw_param(struct seq_file *m, void *v)
{
struct phy_device *phydev = m->private;
int reg_page = swdata[0];
int reg_addr = swdata[1];
int reg_len = swdata[2];
int i = 0;
int r = 0;
unsigned int val;
if (reg_len == 0)
return 0;
if (!v)
return 0;
#if 0
if (reg_page == 0xfe) {
if (reg_len == 6)
show_arl((struct phy_device *)data, swdata[1], swdata + 3);
else
show_arl((struct phy_device *)data, swdata[1], NULL);
return 0;
}
#endif
if (reg_page == 0xff) {
for (i = 0; i < 8; i++) {
if ((0x01 << i) & reg_addr)
ethsw_dump_mib(phydev, i);
}
ethsw_dump_mib(phydev, 8);
return 0;
}
ethsw_rreg(phydev, reg_page, reg_addr, swdata + 3, reg_len);
seq_printf(m, "[%02x:%02x] = ", swdata[0], swdata[1]);
switch(reg_len) {
case 2:
*(uint16_t *)&swdata[3] = BE16(*(uint16_t *)&swdata[3]);
break;
case 4:
*(uint32_t *)&swdata[3] = BE32(*(uint32_t *)&swdata[3]);
break;
case 6:
val = BE32(*(uint32_t *)&swdata[5]);
*(uint16_t *)&swdata[7] = BE16(*(uint32_t *)&swdata[3]);
*(uint32_t *)&swdata[3] = val;
break;
case 8:
val = BE32(*(uint32_t *)&swdata[3]);
*(uint32_t *)&swdata[3] = BE32(*(uint32_t *)&swdata[7]);
*(uint32_t *)&swdata[7] = val;
break;
}
for (i = 0; i < reg_len; i ++)
r += seq_printf(m, "%02x ", swdata[3 + i]);
seq_printf(m, "\n");
return 0;
}
static ssize_t proc_set_sw_param(struct file *f, const char *buf, size_t cnt, loff_t *data)
{
struct phy_device *phydev = PDE_DATA(file_inode(f));
char input[32];
int i;
int r;
int num_of_octets;
int reg_page;
int reg_addr;
int reg_len;
unsigned int val;
if (cnt > 32)
cnt = 32;
if (copy_from_user(input, buf, cnt) != 0)
return -EFAULT;
r = cnt;
for (i = 0; i < r; i ++)
{
if (!isxdigit(input[i]))
{
memmove(&input[i], &input[i + 1], r - i - 1);
r --;
i --;
}
}
num_of_octets = r / 2;
if (num_of_octets < 3) // page, addr, len
return -EFAULT;
str_to_num(input, swdata, num_of_octets);
reg_page = swdata[0];
reg_addr = swdata[1];
reg_len = swdata[2];
if (((reg_len != 1) && (reg_len % 2) != 0) || reg_len > 8)
{
memset(swdata, 0, sizeof(swdata));
return -EFAULT;
}
if ((num_of_octets > 3) && (num_of_octets != reg_len + 3))
{
memset(swdata, 0, sizeof(swdata));
return -EFAULT;
}
if ((num_of_octets > 3) && (reg_page < 0xfe)) {
switch(reg_len) {
case 2:
*(uint16_t *)&swdata[3] = BE16(*(uint16_t *)&swdata[3]);
break;
case 4:
*(uint32_t *)&swdata[3] = BE32(*(uint32_t *)&swdata[3]);
break;
case 6:
val = BE32(*(uint32_t *)&swdata[5]);
*(uint16_t *)&swdata[7] = BE16(*(uint32_t *)&swdata[3]);
*(uint32_t *)&swdata[3] = val;
break;
case 8:
val = BE32(*(uint32_t *)&swdata[3]);
*(uint32_t *)&swdata[3] = BE32(*(uint32_t *)&swdata[7]);
*(uint32_t *)&swdata[7] = val;
break;
}
ethsw_wreg(phydev, reg_page, reg_addr, swdata + 3, reg_len);
}
return cnt;
}
static int ethsw_open(struct inode *inode, struct file *file)
{
return single_open(file, proc_get_sw_param, PDE_DATA(inode));
}
static struct file_operations ethsw_fops = {
.open = ethsw_open,
.read = seq_read,
.write = proc_set_sw_param,
.llseek = seq_lseek,
.release = seq_release,
};
static int ethsw_add_proc_files(struct phy_device *phydev)
{
p = proc_create_data("switch", 0644, NULL, &ethsw_fops, phydev);
if (p == NULL) {
pr_err("%s: unable to create proc entry\n", __func__);
return -1;
}
memset(swdata, 0, sizeof(swdata));
return 0;
}
static int bcm53x25_ethsw_init(struct phy_device *phydev)
{
ethsw_reset_ports(phydev);
ethsw_switch_unmanage_mode(phydev);
ethsw_config_learning(phydev);
return 0;
}
static int bcm53x25_ethsw_resume(struct phy_device *phydev)
{
bcm53x25_ethsw_init(phydev);
return ethsw_force_speed(phydev);
}
static int bcm53x25_config_aneg(struct phy_device *phydev)
{
return ethsw_force_speed(phydev);
}
static int bcm53x25_read_status(struct phy_device *phydev)
{
phydev->duplex = DUPLEX_FULL;
if (phydev->advertising & (SUPPORTED_1000baseT_Half |
SUPPORTED_1000baseT_Full))
phydev->speed = SPEED_1000;
else if (phydev->advertising & (SUPPORTED_100baseT_Half |
SUPPORTED_100baseT_Full))
phydev->speed = SPEED_100;
else
phydev->speed = SPEED_10;
phydev->link = 1;
phydev->state = PHY_RUNNING;
netif_carrier_on(phydev->attached_dev);
phydev->adjust_link(phydev->attached_dev);
return 0;
}
static int bcm53x25_probe(struct phy_device *phydev)
{
/* Make sure that we match either the pseudo-PHY addreess or
* address 0 as this is the default PHY addressing supported
* for BCM53xxx switches
*/
if (phydev->addr != BRCM_PSEUDO_PHY_ADDR && phydev->addr != 0) {
dev_err(&phydev->dev, "invalid PHY address %d\n",
phydev->addr);
return -ENODEV;
}
if (p) {
remove_proc_entry("switch", NULL);
p = NULL;
}
ethsw_add_proc_files(phydev);
/* Tell the MDIO bus controller that this switch's pseudo-PHY has a
* broken turn-around and it should ignore read failures. See ING JIRA
* SF-357.
*/
phydev->bus->phy_ignore_ta_mask |= 1 << BRCM_PSEUDO_PHY_ADDR;
return 0;
}
static void bcm53x25_remove(struct phy_device *phydev)
{
remove_proc_entry("switch", NULL);
}
static struct phy_driver bcm53x25_driver[] = {
{
/* Match all BCM53125 revisions */
.phy_id = BCM53125_PHY_ID,
.phy_id_mask = 0x1ffffff0,
.name = "Broadcom BCM53125",
.features = PHY_GBIT_FEATURES,
.probe = bcm53x25_probe,
.remove = bcm53x25_remove,
.config_init = bcm53x25_ethsw_init,
.config_aneg = bcm53x25_config_aneg,
.read_status = bcm53x25_read_status,
.resume = bcm53x25_ethsw_resume,
.driver = { .owner = THIS_MODULE },
},
{
.phy_id = BCM53101_PHY_ID,
.phy_id_mask = 0x1ffffff0,
.name = "Broadcom BCM53101",
.features = PHY_BASIC_FEATURES,
.probe = bcm53x25_probe,
.remove = bcm53x25_remove,
.config_init = bcm53x25_ethsw_init,
.config_aneg = bcm53x25_config_aneg,
.read_status = bcm53x25_read_status,
.resume = bcm53x25_ethsw_resume,
.driver = { .owner = THIS_MODULE },
},
};
static int __init bcm53x25_init(void)
{
return phy_drivers_register(bcm53x25_driver,
ARRAY_SIZE(bcm53x25_driver));
}
module_init(bcm53x25_init);
MODULE_DESCRIPTION("Broadcom BCM53x25 switch driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Broadcom Corporation");