blob: be5af4154c61979ed103f5d79e7757985e350db6 [file] [log] [blame]
/*
* Platform driver for the Realtek RTL8367RB-VB ethernet switches
*
* Copyright (C) 2021 Fei Lv <feilv@asrmicro.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/delay.h>
#include <linux/phy.h>
#include <linux/of_mdio.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/skbuff.h>
#include <linux/switch.h>
#include <linux/uaccess.h>
#include <linux/trace_seq.h>
#include <linux/seq_file.h>
#include <linux/u64_stats_sync.h>
#include <net/netevent.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/version.h>
#include "../../../../net/8021q/vlan.h"
#ifdef CONFIG_DEBUG_FS
#include <linux/debugfs.h>
#define CONFIG_JL5106_SMI_DEBUG_FS
#endif
#include "./libswitch/inc/jl_base.h"
#include "./libswitch/inc/port.h"
#include "./libswitch/inc/vlan.h"
#include "./libswitch/inc/mib.h"
#include "./libswitch/inc/cpu.h"
#define JL5106_NUM_PORTS 6
#define JL5106_NUM_VIDS 16
#define JL5106_SW_CPU_PORT 5
#define JL5106_MAX_QUEUE_NUM 4
//#define WAN_LAN_AUTO_ADAPT 1
struct jl5106_priv {
struct switch_dev sw_dev;
struct device *parent;
/*
* Reference to a Linux network interface that connects
* to the root switch chip of the tree.
*/
struct device *netdev;
/*
* Reference to network device to use
*/
struct net_device *master_netdev;
struct notifier_block netdev_notifier;
struct mii_bus *ext_mbus;
struct mii_bus *mii_bus;
u32 cpu_port;
u32 num_ports;
bool vlan_enable;
char buf[4096];
#ifdef CONFIG_JL5106_SMI_DEBUG_FS
struct dentry *debugfs_root;
u16 dbg_port;
u16 dbg_reg;
#endif
};
#define sw_to_jl5106_priv(sw) container_of(sw, struct jl5106_priv, sw_dev)
struct jl5106_mib_counter {
unsigned offset;
unsigned length;
const char *name;
};
struct jl5106_vlan_info {
u16 vid;
u32 untag;
u32 member;
u8 fid;
};
static struct jl5106_priv *g_jl5106_priv;
static struct jl5106_mib_counter jl5106_mib_counters[] = {
{ PORT_MIB_TX_PKT_CNT, 8, "TransmitPacketCounter" },
{ PORT_MIB_TX_PKT_BYTE_CNT, 8, "TransmitPacketByteCounter" },
{ PORT_MIB_TX_PKT_UC_CNT, 8, "TransmitPacketUnicastCounter" },
{ PORT_MIB_TX_PKT_UC_BYTE_CNT, 8, "TransmitPacketUnicastByteCounter" },
{ PORT_MIB_TX_PKT_MC_CNT, 8, "TransmitPacketMulticastCounter" },
{ PORT_MIB_TX_PKT_MC_BYTE_CNT, 8, "TransmitPacketMulticastByteCounter" },
{ PORT_MIB_TX_PKT_BC_CNT, 8, "TransmitPacketBroadcastCounter" },
{ PORT_MIB_TX_PKT_BC_BYTE_CNT, 8, "TransmitPacketBroadcastByteCounter" },
{ PORT_MIB_TX_PKT_DRP_CNT, 4, "TransmitDropPacketCounter" },
{ PORT_MIB_TX_PKT_ERR_CNT, 4, "TransmitErrorPacketCounter" },
{ PORT_MIB_RX_PKT_CNT, 8, "ReveivePacketCounter" },
{ PORT_MIB_RX_PKT_BYTE_CNT, 8, "ReveivePacketByteCounter" },
{ PORT_MIB_RX_PKT_UC_CNT, 8, "ReveivePacketUnicastCounter" },
{ PORT_MIB_RX_PKT_UC_BYTE_CNT, 8, "ReveivePacketUnicastByteCounter" },
{ PORT_MIB_RX_PKT_MC_CNT, 8, "ReveivePacketMulticastCounter" },
{ PORT_MIB_RX_PKT_MC_BYTE_CNT, 8, "ReveivePacketMulticastByteCounter" },
{ PORT_MIB_RX_PKT_BC_CNT, 8, "ReveivePacketBroadcastCounter" },
{ PORT_MIB_RX_PKT_BC_BYTE_CNT, 8, "ReveivePacketBroadcastByteCounter" },
{ PORT_MIB_RX_PKT_DRP_CNT, 4, "ReveiveDropPacketCounter" },
{ PORT_MIB_RX_PKT_CRC_ERR_CNT, 4, "ReveiveCRCErrorPacketCounter" },
{ PORT_MIB_RX_FRAG_ERR_CNT, 4, "ReveiveErrorFragmentCounter" },
};
struct jl5106_event {
const char *name;
char *action;
int port;
struct sk_buff *skb;
struct work_struct work;
struct delayed_work delay_work;
int flag_start;
int port_link[JL5106_NUM_PORTS];
};
struct jl5106_event g_jl5105_event;
static int jl5106_port_map[] =
{
UTP_PORT0,
UTP_PORT1,
UTP_PORT2,
UTP_PORT3,
UTP_PORT4,
EXT_PORT0,
};
#define jl5106_phy_port(port) jl5106_port_map[port]
u32 jl5106_mdio_read(u32 phy_addr, u32 reg, u32 *data)
{
struct mii_bus *bus = g_jl5106_priv->ext_mbus;
mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
*data = bus->read(bus, phy_addr, reg);
mutex_unlock(&bus->mdio_lock);
return 0;
}
u32 jl5106_mdio_write(u32 phy_addr, u32 reg, u32 data)
{
struct mii_bus *bus = g_jl5106_priv->ext_mbus;
mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
bus->write(bus, phy_addr, reg, data);
mutex_unlock(&bus->mdio_lock);
return 0;
}
int jl5106_smi_read_reg(struct jl5106_priv *priv, u32 port,
u32 addr, u32 *data)
{
return jl5106_mdio_read(jl5106_phy_port(port), addr, data);
}
int jl5106_smi_write_reg(struct jl5106_priv *priv, u32 port,
u32 addr, u32 data)
{
return jl5106_mdio_write(jl5106_phy_port(port), addr, data);
}
static int jl5106_mii_phy_read(struct mii_bus *bus, int addr, int reg)
{
int val;
jl_port_phy_reg_get(addr, 0x0, reg, &val);
return val;
}
static int jl5106_mii_phy_write(struct mii_bus *bus, int addr, int reg, u16 val)
{
jl_port_phy_reg_set(addr, 0x0, reg, val);
return 0;
}
static inline u32 jl5106_portmask_phy_to_sw(jl_uint32 portmask)
{
u32 member;
int i;
member = 0;
for (i = 0; i < JL5106_NUM_PORTS; i++) {
if (portmask & (1 << jl5106_phy_port(i)))
member |= 1 << i;
}
return member;
}
static inline int jl5106_is_vlan_valid(u32 vlan)
{
return (vlan == 0 || vlan >= JL5106_NUM_VIDS) ? 0 : 1;
}
static int jl5106_get_vlan(u16 vid, struct jl5106_vlan_info *vlan)
{
struct vlan_config vconf;
enum vlan_operation opt;
int i;
memset(vlan, '\0', sizeof(struct jl5106_vlan_info));
if (vid >= JL5106_NUM_VIDS)
return -EINVAL;
jl_vlan_get(vid, &vconf);
vlan->vid = vconf.vid;
vlan->member = jl5106_portmask_phy_to_sw(vconf.port_mask);
for (i = 0; i < JL5106_NUM_PORTS; i++) {
if (vlan->member & (1 << i)) {
jl_port_vlan_operation_get(jl5106_phy_port(i),
PORT_DIR_EGRESS, &opt);
if (opt == VLAN_OPERATION_REMOVE_ALL)
vlan->untag |= 1 << i;
}
}
vlan->fid = vconf.fid;
return 0;
}
static int jl5106_set_vlan(u16 vid, u32 mbr, u32 untag, u8 fid)
{
struct vlan_config vconf;
jl_uint32 portmask;
jl_port_t port;
int i;
portmask = 0x0;
for (i = 0; i < JL5106_NUM_PORTS; i++) {
if (!(mbr & (1 << i)))
continue;
port = jl5106_phy_port(i);
portmask |= 1 << port;
if (untag & (1 << i)) {
jl_port_vlan_assignment_set(port, VLAN_ASSIGN_PORT);
jl_vlan_acpt_frmt_set(port,
VLAN_ACCEPT_FRAME_TYPE_UNTAG_ONLY);
jl_port_vlan_operation_set(port, PORT_DIR_INGRESS,
VLAN_OPERATION_PUSH);
jl_port_vlan_operation_set(port, PORT_DIR_EGRESS,
VLAN_OPERATION_REMOVE_ALL);
} else {
jl_port_vlan_assignment_set(port, VLAN_ASSIGN_PKT);
jl_vlan_acpt_frmt_set(port, VLAN_ACCEPT_FRAME_TYPE_ALL);
jl_port_vlan_operation_set(port, PORT_DIR_INGRESS,
VLAN_OPERATION_POP);
jl_port_vlan_operation_set(port, PORT_DIR_EGRESS,
VLAN_OPERATION_NONE);
}
}
vconf.valid = 1;
vconf.vid = vid;
vconf.port_mask = portmask;
vconf.fid = 0;
jl_vlan_set(vid, &vconf);
return 0;
}
static int jl5106_get_pvid(int port, int *pvid)
{
jl_api_ret_t ret;
if (port >= JL5106_NUM_PORTS)
return -EINVAL;
ret = jl_port_vlan_get(jl5106_phy_port(port), pvid);
return ret;
}
static int jl5106_set_pvid(int port, int pvid)
{
if (port >= JL5106_NUM_PORTS)
return -EINVAL;
jl_port_vlan_set(jl5106_phy_port(port), pvid);
return 0;
}
static int jl5106_get_port_link(struct jl5106_priv *priv, int port,
int *link, int *speed, int *duplex)
{
jl_port_speed_t port_speed;
jl_port_duplex_t port_duplex;
jl_port_link_status_t port_linkst;
if (port == priv->cpu_port) {
jl_port_ext_mac_ability_t ability;
memset(&ability, 0x00, sizeof(jl_port_ext_mac_ability_t));
jl_port_mac_force_link_ext_get(EXT_PORT0, &ability);
port_speed = ability.speed;
port_duplex = ability.duplex;
port_linkst = ability.link;
} else {
port_speed = 0;
port_duplex = 0;
port_linkst = 0;
jl_port_phy_link_status_get(jl5106_phy_port(port), &port_linkst,
&port_duplex, &port_speed);
}
if (port_linkst == PORT_LINKUP)
*link = 1;
else
*link = 0;
if (port_speed == PORT_SPEED_10M)
*speed = SWITCH_PORT_SPEED_10;
else if (port_speed == PORT_SPEED_100M)
*speed = SWITCH_PORT_SPEED_100;
else
*speed = SWITCH_PORT_SPEED_UNKNOWN;
if (port_duplex == PORT_FULL_DUPLEX)
*duplex = 1;
else
*duplex = 0;
return 0;
}
static void jl5106_cpu_port_set_rmii(struct jl5106_priv *priv)
{
jl_port_ext_mac_ability_t ability;
memset(&ability, 0x00, sizeof(jl_port_ext_mac_ability_t));
jl_port_mac_force_link_ext_get(jl5106_phy_port(priv->cpu_port), &ability);
ability.force_mode = 1;
ability.speed = 1;
ability.duplex = 1;
ability.link = 1;
ability.tx_pause = 1;
ability.rx_pause = 1;
jl_port_mac_force_link_ext_set(jl5106_phy_port(priv->cpu_port), &ability);
}
static void jl5106_port_phy_enable_all(struct jl5106_priv *priv)
{
jl_port_phy_ability_t ability;
int i;
memset(&ability, 0x00, sizeof(jl_port_phy_ability_t));
ability.full_duplex_100 = 1;
ability.full_duplex_10 = 1;
ability.half_duplex_100 = 0;
ability.half_duplex_10 = 0;
ability.flow_control = 1;
ability.asym_flow_control = 1;
/* Enable all ports */
for (i = 0; i < JL5106_NUM_PORTS - 1; i++) {
jl_port_phy_autoneg_ability_set(jl5106_phy_port(i), &ability);
jl_mib_port_rc_mode_set(jl5106_phy_port(i), 0);
}
jl_mib_port_rc_mode_set(jl5106_phy_port(priv->cpu_port), 0);
jl_cpu_tag_enable_set(DISABLED);
}
static int jl5106_hw_init(struct jl5106_priv *priv)
{
jl_error_code_t ret;
jl_port_phy_data_t id1;
jl_port_phy_data_t id2;
ret = jl_switch_init();
if (ret) {
dev_err(priv->parent, "JL SWITCH init fail\n");
return -1;
}
/* init vlan */
ret = jl_vlan_init();
if (ret) {
dev_err(priv->parent, "JL vlan init fail\n");
priv->vlan_enable = 0;
return -1;
}
priv->vlan_enable = 1;
jl_port_phy_reg_get(0x0, 0x0, PHY_ID1_REG, &id1);
jl_port_phy_reg_get(0x0, 0x0, PHY_ID2_REG, &id2);
dev_err(priv->parent, "phyid: 0x%x 0x%x\n", id1, id2);
jl5106_cpu_port_set_rmii(priv);
jl5106_port_phy_enable_all(priv);
return 0;
}
static int jl5106_sw_set_vlan_enable(struct switch_dev *dev,
const struct switch_attr *attr,
struct switch_val *val)
{
struct jl5106_priv *priv = sw_to_jl5106_priv(dev);
jl_api_ret_t ret;
bool enable;
enable = val->value.i;
if (enable)
ret = jl_vlan_init();
else
ret = jl_vlan_deinit();
if (ret) {
dev_err(priv->parent, "vlan %s failed\n",
enable ? "enabled" : "disabled");
return -1;
}
priv->vlan_enable = enable;
return 0;
}
static int jl5106_sw_get_vlan_enable(struct switch_dev *dev,
const struct switch_attr *attr,
struct switch_val *val)
{
struct jl5106_priv *priv = sw_to_jl5106_priv(dev);
val->value.i = priv->vlan_enable;
return 0;
}
static int jl5106_sw_reset_mibs(struct switch_dev *dev,
const struct switch_attr *attr,
struct switch_val *val)
{
int i;
for (i = 0; i < JL5106_NUM_PORTS; i++)
jl_mib_port_clear_all(jl5106_phy_port(i));
return 0;
}
static int jl5106_sw_reset_port_mibs(struct switch_dev *dev,
const struct switch_attr *attr,
struct switch_val *val)
{
int port = val->port_vlan;
if (port >= JL5106_NUM_PORTS)
return -EINVAL;
return jl_mib_port_clear_all(jl5106_phy_port(port));
}
static int jl5106_sw_get_port_mib(struct switch_dev *dev,
const struct switch_attr *attr,
struct switch_val *val)
{
struct jl5106_priv *priv = sw_to_jl5106_priv(dev);
jl_uint64 counter = 0;
int i, len = 0;
char *buf = priv->buf;
jl_ret_t ret;
if (val->port_vlan >= priv->num_ports)
return -EINVAL;
len += snprintf(buf + len, sizeof(priv->buf) - len,
"Port %d MIB counters\n", val->port_vlan);
for (i = 0; i < ARRAY_SIZE(jl5106_mib_counters); ++i) {
len += snprintf(buf + len, sizeof(priv->buf) - len,
"\t%-36s: ", jl5106_mib_counters[i].name);
ret = jl_mib_port_get(jl5106_phy_port(val->port_vlan),
jl5106_mib_counters[i].offset, &counter);
if (jl5106_mib_counters[i].length == 4)
counter &= 0xFFFFFFFF;
if (ret) {
len += snprintf(buf + len, sizeof(priv->buf) - len,
"%s\n", "N/A");
} else {
len += snprintf(buf + len, sizeof(priv->buf) - len,
"%llu\n", counter);
}
}
val->value.s = buf;
val->len = len;
return 0;
}
static int jl5106_sw_get_vlan_info(struct switch_dev *dev,
const struct switch_attr *attr,
struct switch_val *val)
{
struct jl5106_priv *priv = sw_to_jl5106_priv(dev);
struct jl5106_vlan_info vlan;
char *buf = priv->buf;
u32 len = 0;
int i;
int err;
if (!jl5106_is_vlan_valid(val->port_vlan))
return -EINVAL;
memset(buf, '\0', sizeof(priv->buf));
err = jl5106_get_vlan(val->port_vlan, &vlan);
if (err)
return err;
len += snprintf(buf + len, sizeof(priv->buf) - len,
"VLAN %d: Ports: '", vlan.vid);
for (i = 0; i < priv->num_ports; i++) {
if (!(vlan.member & (1 << i)))
continue;
len += snprintf(buf + len, sizeof(priv->buf) - len, "%d%s",
i, (vlan.untag & (1 << i)) ? "" : "t");
}
len += snprintf(buf + len, sizeof(priv->buf) - len,
"', members=%04x, untag=%04x, fid=%u",
vlan.member, vlan.untag, vlan.fid);
val->value.s = buf;
val->len = len;
return 0;
}
static int jl5106_sw_get_vlan_ports(struct switch_dev *dev,
struct switch_val *val)
{
struct jl5106_priv *priv = sw_to_jl5106_priv(dev);
struct switch_port *port;
struct jl5106_vlan_info vlan;
int i;
if (!jl5106_is_vlan_valid(val->port_vlan))
return -EINVAL;
if(jl5106_get_vlan(val->port_vlan, &vlan))
return -EINVAL;
port = &val->value.ports[0];
val->len = 0;
for (i = 0; i < priv->num_ports; i++) {
if (!(vlan.member & BIT(i)))
continue;
port->id = i;
port->flags = (vlan.untag & BIT(i)) ? 0 :
BIT(SWITCH_PORT_FLAG_TAGGED);
val->len++;
port++;
}
return 0;
}
static int jl5106_sw_set_vlan_ports(struct switch_dev *dev,
struct switch_val *val)
{
struct jl5106_priv *priv = sw_to_jl5106_priv(dev);
struct switch_port *port;
u32 member = 0;
u32 untag = 0;
u8 fid = 0;
int err;
int i;
if (!jl5106_is_vlan_valid(val->port_vlan))
return -EINVAL;
port = &val->value.ports[0];
for (i = 0; i < val->len; i++, port++) {
member |= BIT(port->id);
if (!(port->flags & BIT(SWITCH_PORT_FLAG_TAGGED)))
untag |= BIT(port->id);
err = jl5106_set_pvid(port->id, val->port_vlan);
if (err < 0)
return err;
}
dev_err(priv->parent, "==> [%s] vid=%d, mem=%x, untag=%x, fid=%d \n",
__func__, val->port_vlan, member, untag,fid);
return jl5106_set_vlan(val->port_vlan, member, untag, fid);
}
static int jl5106_sw_get_port_pvid(struct switch_dev *dev, int port, int *val)
{
return jl5106_get_pvid(port, val);
}
static int jl5106_sw_set_port_pvid(struct switch_dev *dev, int port, int val)
{
return jl5106_set_pvid(port, val);
}
static int jl5106_sw_reset_switch(struct switch_dev *dev)
{
struct jl5106_priv *priv = sw_to_jl5106_priv(dev);
if(g_jl5105_event.flag_start)
cancel_delayed_work_sync(&g_jl5105_event.delay_work);
jl5106_hw_init(priv);
if(g_jl5105_event.flag_start)
schedule_delayed_work(&g_jl5105_event.delay_work, msecs_to_jiffies(3000));
return 0;
}
static int jl5106_sw_get_port_link(struct switch_dev *dev, int port,
struct switch_port_link *link)
{
struct jl5106_priv *priv = sw_to_jl5106_priv(dev);
if (port >= priv->num_ports)
return -EINVAL;
if(jl5106_get_port_link(priv, port, (int *)&link->link,
(int *)&link->speed, (int *)&link->duplex))
return -EINVAL;
return 0;
}
static struct switch_attr jl5106_globals[] = {
{
.type = SWITCH_TYPE_INT,
.name = "enable_vlan",
.description = "Enable VLAN mode",
.set = jl5106_sw_set_vlan_enable,
.get = jl5106_sw_get_vlan_enable,
.max = 1,
}, {
.type = SWITCH_TYPE_NOVAL,
.name = "reset_mibs",
.description = "Reset all MIB counters",
.set = jl5106_sw_reset_mibs,
}
};
static struct switch_attr jl5106_port[] = {
{
.type = SWITCH_TYPE_NOVAL,
.name = "reset_mib",
.description = "Reset single port MIB counters",
.set = jl5106_sw_reset_port_mibs,
}, {
.type = SWITCH_TYPE_STRING,
.name = "mib",
.description = "Get MIB counters for port",
.set = NULL,
.get = jl5106_sw_get_port_mib,
},
};
static struct switch_attr jl5106_vlan[] = {
{
.type = SWITCH_TYPE_STRING,
.name = "info",
.description = "Get vlan information",
.max = 1,
.set = NULL,
.get = jl5106_sw_get_vlan_info,
},
};
static const struct switch_dev_ops jl5106_sw_ops = {
.attr_global = {
.attr = jl5106_globals,
.n_attr = ARRAY_SIZE(jl5106_globals),
},
.attr_port = {
.attr = jl5106_port,
.n_attr = ARRAY_SIZE(jl5106_port),
},
.attr_vlan = {
.attr = jl5106_vlan,
.n_attr = ARRAY_SIZE(jl5106_vlan),
},
.get_vlan_ports = jl5106_sw_get_vlan_ports,
.set_vlan_ports = jl5106_sw_set_vlan_ports,
.get_port_pvid = jl5106_sw_get_port_pvid,
.set_port_pvid = jl5106_sw_set_port_pvid,
.reset_switch = jl5106_sw_reset_switch,
.get_port_link = jl5106_sw_get_port_link,
};
int jl5106_switch_init(struct jl5106_priv *priv)
{
struct switch_dev *dev = &priv->sw_dev;
int err;
dev->name = "JL5106";
dev->cpu_port = priv->cpu_port;
dev->ports = JL5106_NUM_PORTS;
dev->vlans = JL5106_NUM_VIDS;
dev->ops = &jl5106_sw_ops;
dev->alias = dev_name(priv->parent);
err = register_switch(dev, NULL);
if (err)
dev_err(priv->parent, "switch registration failed\n");
return err;
}
static void jl5106_switch_cleanup(struct jl5106_priv *priv)
{
unregister_switch(&priv->sw_dev);
}
#ifdef CONFIG_JL5106_SMI_DEBUG_FS
int jl5106_debugfs_open(struct inode *inode, struct file *file)
{
file->private_data = inode->i_private;
return 0;
}
EXPORT_SYMBOL_GPL(jl5106_debugfs_open);
static ssize_t jl5106_read_debugfs_pvid(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
struct jl5106_priv *priv = (struct jl5106_priv *)file->private_data;
char *buf = priv->buf;
int len = 0;
int i;
len += snprintf(buf + len, sizeof(priv->buf) - len, "%4s %4s\n",
"port", "pvid");
for (i = 0; i < priv->num_ports; i++) {
int pvid;
int err;
err = jl5106_get_pvid(i, &pvid);
if (err < 0)
len += snprintf(buf + len, sizeof(priv->buf) - len,
"%4d error\n", i);
else
len += snprintf(buf + len, sizeof(priv->buf) - len,
"%4d %4d\n", i, pvid);
}
return simple_read_from_buffer(user_buf, count, ppos, buf, len);
}
static ssize_t jl5106_read_debugfs_reg(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
struct jl5106_priv *priv = (struct jl5106_priv *)file->private_data;
u32 t, reg = priv->dbg_reg;
int err, len = 0;
char *buf = priv->buf;
memset(buf, '\0', sizeof(priv->buf));
err = jl5106_smi_read_reg(priv, priv->dbg_port, reg, &t);
if (err) {
len += snprintf(buf, sizeof(priv->buf),
"Read failed (reg: 0x%04x)\n", reg);
return simple_read_from_buffer(user_buf, count, ppos, buf, len);
}
len += snprintf(buf, sizeof(priv->buf), "reg = 0x%04x, val = 0x%04x\n",
reg, t);
return simple_read_from_buffer(user_buf, count, ppos, buf, len);
}
static ssize_t jl5106_write_debugfs_reg(struct file *file,
const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct jl5106_priv *priv = (struct jl5106_priv *)file->private_data;
unsigned long data;
u32 reg = priv->dbg_reg;
int err;
size_t len;
char *buf = priv->buf;
len = min(count, sizeof(priv->buf) - 1);
if (copy_from_user(buf, user_buf, len)) {
dev_err(priv->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(priv->parent, "Invalid reg value %s\n", buf);
} else {
err = jl5106_smi_write_reg(priv, priv->dbg_port, reg, data);
if (err) {
dev_err(priv->parent,
"writing reg 0x%04x val 0x%04lx failed\n",
reg, data);
}
}
return count;
}
static ssize_t jl5106_read_debugfs_mibs(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
struct jl5106_priv *priv = file->private_data;
int i, j, len = 0;
char *buf = priv->buf;
jl_ret_t ret;
len += snprintf(buf + len, sizeof(priv->buf) - len, "%-36s",
"Counter");
for (i = 0; i < priv->num_ports; i++) {
char port_buf[10];
snprintf(port_buf, sizeof(port_buf), "Port %d", i);
len += snprintf(buf + len, sizeof(priv->buf) - len, " %12s",
port_buf);
}
len += snprintf(buf + len, sizeof(priv->buf) - len, "\n");
for (i = 0; i < ARRAY_SIZE(jl5106_mib_counters); i++) {
len += snprintf(buf + len, sizeof(priv->buf) - len, "%-36s ",
jl5106_mib_counters[i].name);
for (j = 0; j < priv->num_ports; j++) {
jl_uint64 counter = 0;
ret = jl_mib_port_get(jl5106_phy_port(j),
jl5106_mib_counters[i].offset, &counter);
if (jl5106_mib_counters[i].length == 4)
counter &= 0xFFFFFFFF;
if (ret) {
len += snprintf(buf + len,
sizeof(priv->buf) - len,
"%12s ", "error");
} else {
len += snprintf(buf + len,
sizeof(priv->buf) - len,
"%12llu ", counter);
}
}
len += snprintf(buf + len, sizeof(priv->buf) - len, "\n");
}
return simple_read_from_buffer(user_buf, count, ppos, buf, len);
}
static const struct file_operations fops_jl5106_regs = {
.read = jl5106_read_debugfs_reg,
.write = jl5106_write_debugfs_reg,
.open = jl5106_debugfs_open,
.owner = THIS_MODULE
};
static const struct file_operations fops_jl5106_pvid = {
.read = jl5106_read_debugfs_pvid,
.open = jl5106_debugfs_open,
.owner = THIS_MODULE
};
static const struct file_operations fops_jl5106_mibs = {
.read = jl5106_read_debugfs_mibs,
.open = jl5106_debugfs_open,
.owner = THIS_MODULE
};
static void jl5106_debugfs_init(struct jl5106_priv *priv)
{
struct dentry *node;
struct dentry *root;
if (!priv->debugfs_root)
priv->debugfs_root = debugfs_create_dir(dev_name(priv->parent),
NULL);
if (!priv->debugfs_root) {
dev_err(priv->parent, "Unable to create debugfs dir\n");
return;
}
root = priv->debugfs_root;
node = debugfs_create_x16("port", S_IRUGO | S_IWUSR, root,
&priv->dbg_port);
if (!node) {
dev_err(priv->parent, "Creating debugfs file '%s' failed\n",
"reg");
return;
}
node = debugfs_create_x16("reg", S_IRUGO | S_IWUSR, root,
&priv->dbg_reg);
if (!node) {
dev_err(priv->parent, "Creating debugfs file '%s' failed\n",
"reg");
return;
}
node = debugfs_create_file("val", S_IRUGO | S_IWUSR, root, priv,
&fops_jl5106_regs);
if (!node) {
dev_err(priv->parent, "Creating debugfs file '%s' failed\n",
"val");
return;
}
node = debugfs_create_file("pvid", S_IRUSR, root, priv,
&fops_jl5106_pvid);
if (!node) {
dev_err(priv->parent, "Creating debugfs file '%s' failed\n",
"pvid");
return;
}
node = debugfs_create_file("mibs", S_IRUSR, priv->debugfs_root, priv,
&fops_jl5106_mibs);
if (!node)
dev_err(priv->parent, "Creating debugfs file '%s' failed\n",
"mibs");
}
static void jl5106_debugfs_remove(struct jl5106_priv *priv)
{
if (priv->debugfs_root) {
debugfs_remove_recursive(priv->debugfs_root);
priv->debugfs_root = NULL;
}
}
#else
static inline void jl5106_debugfs_init(struct jl5106_priv *priv) {}
static inline void jl5106_debugfs_remove(struct jl5106_priv *priv) {}
#endif /* CONFIG_JL5106_SMI_DEBUG_FS */
/* platform driver init and cleanup *****************************************/
static int dev_is_class(struct device *dev, void *class)
{
if (dev->class != NULL && !strcmp(dev->class->name, class))
return 1;
return 0;
}
static struct device *dev_find_class(struct device *parent, char *class)
{
if (dev_is_class(parent, class)) {
get_device(parent);
return parent;
}
return device_find_child(parent, class, dev_is_class);
}
static struct net_device *dev_to_net_device(struct device *dev)
{
struct device *d;
d = dev_find_class(dev, "net");
if (d != NULL) {
struct net_device *nd;
nd = to_net_dev(d);
dev_hold(nd);
put_device(d);
return nd;
}
return NULL;
}
static int jl5106_smi_mii_init(struct jl5106_priv *priv)
{
int ret;
priv->mii_bus = mdiobus_alloc();
if (priv->mii_bus == NULL) {
ret = -ENOMEM;
goto err;
}
priv->mii_bus->priv = (void *) priv;
priv->mii_bus->name = dev_name(priv->parent);
priv->mii_bus->read = jl5106_mii_phy_read;
priv->mii_bus->write = jl5106_mii_phy_write;
snprintf(priv->mii_bus->id, MII_BUS_ID_SIZE, "%s",
dev_name(priv->parent));
priv->mii_bus->parent = priv->parent;
priv->mii_bus->phy_mask = ~(0x1f);
ret = mdiobus_register(priv->mii_bus);
if (ret)
goto err_free;
return 0;
err_free:
mdiobus_free(priv->mii_bus);
err:
return ret;
}
static void jl5106_smi_mii_cleanup(struct jl5106_priv *priv)
{
mdiobus_unregister(priv->mii_bus);
mdiobus_free(priv->mii_bus);
}
#define JL5106_SKB_SIZE 2048
extern u64 uevent_next_seqnum(void);
static int jl5106_event_add_var(struct jl5106_event *event, int argv,
const char *format, ...)
{
static char buf[128];
char *s;
va_list args;
int len;
if (argv)
return 0;
va_start(args, format);
len = vsnprintf(buf, sizeof(buf), format, args);
va_end(args);
if (len >= sizeof(buf)) {
printk("buffer size too small\n");
WARN_ON(1);
return -ENOMEM;
}
s = skb_put(event->skb, len + 1);
strcpy(s, buf);
return 0;
}
static int jl5106_hotplug_fill_event(struct jl5106_event *event)
{
int ret;
ret = jl5106_event_add_var(event, 0, "HOME=%s", "/");
if (ret)
return ret;
ret = jl5106_event_add_var(event, 0, "PATH=%s",
"/sbin:/bin:/usr/sbin:/usr/bin");
if (ret)
return ret;
ret = jl5106_event_add_var(event, 0, "SUBSYSTEM=%s", "ethernet");
if (ret)
return ret;
ret = jl5106_event_add_var(event, 0, "ACTION=%s", event->action);
if (ret)
return ret;
ret = jl5106_event_add_var(event, 0, "ETH=%s", event->name);
if (ret)
return ret;
ret = jl5106_event_add_var(event, 0, "PORT=%d", event->port);
if (ret)
return ret;
ret = jl5106_event_add_var(event, 0, "SEQNUM=%llu", uevent_next_seqnum());
return ret;
}
static void jl5106_hotplug_work(struct work_struct *work)
{
struct jl5106_event *event = &g_jl5105_event;
int ret = 0;
event->skb = alloc_skb(JL5106_SKB_SIZE, GFP_KERNEL);
if (!event->skb)
return;
ret = jl5106_event_add_var(event, 0, "%s@", event->action);
if (ret)
goto out_free_skb;
ret = jl5106_hotplug_fill_event(event);
if (ret)
goto out_free_skb;
NETLINK_CB(event->skb).dst_group = 1;
broadcast_uevent(event->skb, 0, 1, GFP_KERNEL);
out_free_skb:
if (ret) {
pr_err("work error %d\n", ret);
kfree_skb(event->skb);
}
}
static int jl5106_sig_workq(int event, int port)
{
#ifdef WAN_LAN_AUTO_ADAPT
struct jl5106_event *u_event = &g_jl5105_event;
u_event->name = "jl5106";
if(event == 0)
u_event->action = "JL5106_LINKDW";
else if(event == 1)
u_event->action = "JL5106_LINKUP";
else if(event == 2)
u_event->action = "JL5106_CONNECT";
u_event->port = port;
INIT_WORK(&u_event->work, (void *)jl5106_hotplug_work);
schedule_work(&u_event->work);
return 0;
#endif
return 0;
}
static void jl5106_port_link_work(struct work_struct *work)
{
int port = 0;
int link = 0;
int speed = 0;
int duplex = 0;
if(0 == g_jl5105_event.flag_start)
goto schedule_next;
for(port = 0; port < JL5106_NUM_PORTS; port++) {
if(JL5106_SW_CPU_PORT == port)
continue;
jl5106_get_port_link(g_jl5106_priv, port, &link, &speed, &duplex);
if(link != g_jl5105_event.port_link[port]) {
g_jl5105_event.port_link[port] = link;
jl5106_sig_workq(link, port);
pr_info("jl5106 port %d link %s\n", port, 1 == link ? "up" : "down");
break;
}
}
schedule_next:
schedule_delayed_work(&g_jl5105_event.delay_work, msecs_to_jiffies(1000));
return;
}
static int jl5106_start_after_bridge_event(struct notifier_block *nb,
unsigned long event, void *ptr)
{
struct jl5106_priv *priv = container_of(nb, struct jl5106_priv,
netdev_notifier);
struct net_device *dev;
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0))
dev = ptr;
#else
dev = netdev_notifier_info_to_dev(ptr);
#endif
if(g_jl5105_event.flag_start)
return NOTIFY_DONE;
if(netif_is_bridge_master(dev) && NETDEV_CHANGE == event) {
memset(g_jl5105_event.port_link, 0, JL5106_NUM_PORTS*sizeof(int));
jl5106_sig_workq(2,0);
g_jl5105_event.flag_start = 1;
schedule_delayed_work(&g_jl5105_event.delay_work, msecs_to_jiffies(3000));
}
return NOTIFY_DONE;
}
static int jl5106_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct jl5106_priv *priv;
struct device_node *mdio_node, *ethernet;
struct mii_bus *mdio_bus;
struct platform_device *ethernet_dev;
int err;
mdio_node = of_parse_phandle(np, "mii-bus", 0);
if (!mdio_node)
return -EINVAL;
mdio_bus = of_mdio_find_bus(mdio_node);
if (!mdio_bus)
return -EPROBE_DEFER;
ethernet = of_parse_phandle(np, "jlsemi,ethernet", 0);
if (!ethernet) {
dev_err(&pdev->dev, "not find dts node jlsemi,ethernet\n");
return -EINVAL;
}
ethernet_dev = of_find_device_by_node(ethernet);
if (!ethernet_dev) {
dev_err(&pdev->dev, "not find ethernet device\n");
return -ENODEV;
}
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv) {
dev_err(&pdev->dev, "no memory for private data\n");
return -ENOMEM;
}
g_jl5106_priv = priv;
priv->parent = &pdev->dev;
priv->ext_mbus = mdio_bus;
priv->vlan_enable = 0;
priv->num_ports = JL5106_NUM_PORTS;
if (of_property_read_u32(np, "cpu_port", &priv->cpu_port)
|| priv->cpu_port >= priv->num_ports)
priv->cpu_port = JL5106_SW_CPU_PORT;
priv->netdev = &ethernet_dev->dev;
priv->master_netdev = dev_to_net_device(priv->netdev);
if (priv->master_netdev == NULL) {
err = -EINVAL;
goto err_clear_drvdata;
}
err = jl5106_hw_init(priv);
if (err)
goto err_hw_init;
err = jl5106_smi_mii_init(priv);
if (err)
goto err_hw_init;
INIT_DELAYED_WORK(&g_jl5105_event.delay_work, jl5106_port_link_work);
priv->netdev_notifier.notifier_call = jl5106_start_after_bridge_event;
err = register_netdevice_notifier(&priv->netdev_notifier);
if (err < 0) {
dev_err(&pdev->dev, "register_netdev_notifier failed (%d)\n", err);
goto err_smi_cleaup;
}
platform_set_drvdata(pdev, priv);
err = jl5106_switch_init(priv);
if (err)
goto err_clear_drvdata;
jl5106_debugfs_init(priv);
return 0;
err_clear_drvdata:
platform_set_drvdata(pdev, NULL);
err_smi_cleaup:
jl5106_smi_mii_cleanup(priv);
err_hw_init:
unregister_netdevice_notifier(&priv->netdev_notifier);
kfree(priv);
return err;
}
static int jl5106_remove(struct platform_device *pdev)
{
struct jl5106_priv *priv = platform_get_drvdata(pdev);
if (priv) {
jl5106_switch_cleanup(priv);
platform_set_drvdata(pdev, NULL);
jl5106_debugfs_remove(priv);
g_jl5105_event.flag_start = 0;
cancel_delayed_work_sync(&g_jl5105_event.delay_work);
unregister_netdevice_notifier(&priv->netdev_notifier);
jl5106_smi_mii_cleanup(priv);
kfree(priv);
g_jl5106_priv = NULL;
}
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id jl5106_match[] = {
{ .compatible = "jlsemi,jl5106" },
{},
};
MODULE_DEVICE_TABLE(of, jl5106_match);
#endif
static struct platform_driver jl5106_driver = {
.probe = jl5106_probe,
.remove = jl5106_remove,
.driver = {
.name = "jl5106",
.owner = THIS_MODULE,
#ifdef CONFIG_OF
.of_match_table = jl5106_match,
#endif
},
};
module_platform_driver(jl5106_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Fei Lv <felv@asimicro.com>");
MODULE_DESCRIPTION("jl5106 switch driver for ASR platform");