| /* |
| * 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 = ðernet_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"); |