Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Tristram Ha | 3127 | 45.73% | 9 | 10.71% |
Woojung Huh | 1464 | 21.41% | 1 | 1.19% |
Oleksij Rempel | 717 | 10.49% | 14 | 16.67% |
Arun Ramadoss | 706 | 10.32% | 26 | 30.95% |
Lukasz Majewski | 227 | 3.32% | 1 | 1.19% |
Enguerrand de Ribaucourt | 143 | 2.09% | 2 | 2.38% |
Vladimir Oltean | 119 | 1.74% | 6 | 7.14% |
Marek Vašut | 96 | 1.40% | 8 | 9.52% |
Rasmus Villemoes | 65 | 0.95% | 1 | 1.19% |
Robert Hancock | 42 | 0.61% | 4 | 4.76% |
Paul Barker | 40 | 0.58% | 1 | 1.19% |
Helmut Grohne | 30 | 0.44% | 1 | 1.19% |
Arkadi Sharshevsky | 15 | 0.22% | 2 | 2.38% |
Andrew Lunn | 15 | 0.22% | 1 | 1.19% |
Michael Grzeschik | 10 | 0.15% | 2 | 2.38% |
Russell King | 9 | 0.13% | 1 | 1.19% |
Rakesh Sankaranarayanan | 8 | 0.12% | 2 | 2.38% |
Florian Fainelli | 4 | 0.06% | 1 | 1.19% |
Colin Ian King | 1 | 0.01% | 1 | 1.19% |
Total | 6838 | 84 |
// SPDX-License-Identifier: GPL-2.0 /* * Microchip KSZ9477 switch driver main logic * * Copyright (C) 2017-2019 Microchip Technology Inc. */ #include <linux/kernel.h> #include <linux/module.h> #include <linux/iopoll.h> #include <linux/platform_data/microchip-ksz.h> #include <linux/phy.h> #include <linux/if_bridge.h> #include <linux/if_vlan.h> #include <net/dsa.h> #include <net/switchdev.h> #include "ksz9477_reg.h" #include "ksz_common.h" #include "ksz9477.h" static void ksz_cfg(struct ksz_device *dev, u32 addr, u8 bits, bool set) { regmap_update_bits(ksz_regmap_8(dev), addr, bits, set ? bits : 0); } static void ksz_port_cfg(struct ksz_device *dev, int port, int offset, u8 bits, bool set) { regmap_update_bits(ksz_regmap_8(dev), PORT_CTRL_ADDR(port, offset), bits, set ? bits : 0); } static void ksz9477_cfg32(struct ksz_device *dev, u32 addr, u32 bits, bool set) { regmap_update_bits(ksz_regmap_32(dev), addr, bits, set ? bits : 0); } static void ksz9477_port_cfg32(struct ksz_device *dev, int port, int offset, u32 bits, bool set) { regmap_update_bits(ksz_regmap_32(dev), PORT_CTRL_ADDR(port, offset), bits, set ? bits : 0); } int ksz9477_change_mtu(struct ksz_device *dev, int port, int mtu) { u16 frame_size; if (!dsa_is_cpu_port(dev->ds, port)) return 0; frame_size = mtu + VLAN_ETH_HLEN + ETH_FCS_LEN; return regmap_update_bits(ksz_regmap_16(dev), REG_SW_MTU__2, REG_SW_MTU_MASK, frame_size); } /** * ksz9477_handle_wake_reason - Handle wake reason on a specified port. * @dev: The device structure. * @port: The port number. * * This function reads the PME (Power Management Event) status register of a * specified port to determine the wake reason. If there is no wake event, it * returns early. Otherwise, it logs the wake reason which could be due to a * "Magic Packet", "Link Up", or "Energy Detect" event. The PME status register * is then cleared to acknowledge the handling of the wake event. * * Return: 0 on success, or an error code on failure. */ static int ksz9477_handle_wake_reason(struct ksz_device *dev, int port) { u8 pme_status; int ret; ret = ksz_pread8(dev, port, REG_PORT_PME_STATUS, &pme_status); if (ret) return ret; if (!pme_status) return 0; dev_dbg(dev->dev, "Wake event on port %d due to:%s%s%s\n", port, pme_status & PME_WOL_MAGICPKT ? " \"Magic Packet\"" : "", pme_status & PME_WOL_LINKUP ? " \"Link Up\"" : "", pme_status & PME_WOL_ENERGY ? " \"Energy detect\"" : ""); return ksz_pwrite8(dev, port, REG_PORT_PME_STATUS, pme_status); } /** * ksz9477_get_wol - Get Wake-on-LAN settings for a specified port. * @dev: The device structure. * @port: The port number. * @wol: Pointer to ethtool Wake-on-LAN settings structure. * * This function checks the PME Pin Control Register to see if PME Pin Output * Enable is set, indicating PME is enabled. If enabled, it sets the supported * and active WoL flags. */ void ksz9477_get_wol(struct ksz_device *dev, int port, struct ethtool_wolinfo *wol) { u8 pme_ctrl; int ret; if (!dev->wakeup_source) return; wol->supported = WAKE_PHY; /* Check if the current MAC address on this port can be set * as global for WAKE_MAGIC support. The result may vary * dynamically based on other ports configurations. */ if (ksz_is_port_mac_global_usable(dev->ds, port)) wol->supported |= WAKE_MAGIC; ret = ksz_pread8(dev, port, REG_PORT_PME_CTRL, &pme_ctrl); if (ret) return; if (pme_ctrl & PME_WOL_MAGICPKT) wol->wolopts |= WAKE_MAGIC; if (pme_ctrl & (PME_WOL_LINKUP | PME_WOL_ENERGY)) wol->wolopts |= WAKE_PHY; } /** * ksz9477_set_wol - Set Wake-on-LAN settings for a specified port. * @dev: The device structure. * @port: The port number. * @wol: Pointer to ethtool Wake-on-LAN settings structure. * * This function configures Wake-on-LAN (WoL) settings for a specified port. * It validates the provided WoL options, checks if PME is enabled via the * switch's PME Pin Control Register, clears any previous wake reasons, * and sets the Magic Packet flag in the port's PME control register if * specified. * * Return: 0 on success, or other error codes on failure. */ int ksz9477_set_wol(struct ksz_device *dev, int port, struct ethtool_wolinfo *wol) { u8 pme_ctrl = 0, pme_ctrl_old = 0; bool magic_switched_off; bool magic_switched_on; int ret; if (wol->wolopts & ~(WAKE_PHY | WAKE_MAGIC)) return -EINVAL; if (!dev->wakeup_source) return -EOPNOTSUPP; ret = ksz9477_handle_wake_reason(dev, port); if (ret) return ret; if (wol->wolopts & WAKE_MAGIC) pme_ctrl |= PME_WOL_MAGICPKT; if (wol->wolopts & WAKE_PHY) pme_ctrl |= PME_WOL_LINKUP | PME_WOL_ENERGY; ret = ksz_pread8(dev, port, REG_PORT_PME_CTRL, &pme_ctrl_old); if (ret) return ret; if (pme_ctrl_old == pme_ctrl) return 0; magic_switched_off = (pme_ctrl_old & PME_WOL_MAGICPKT) && !(pme_ctrl & PME_WOL_MAGICPKT); magic_switched_on = !(pme_ctrl_old & PME_WOL_MAGICPKT) && (pme_ctrl & PME_WOL_MAGICPKT); /* To keep reference count of MAC address, we should do this * operation only on change of WOL settings. */ if (magic_switched_on) { ret = ksz_switch_macaddr_get(dev->ds, port, NULL); if (ret) return ret; } else if (magic_switched_off) { ksz_switch_macaddr_put(dev->ds); } ret = ksz_pwrite8(dev, port, REG_PORT_PME_CTRL, pme_ctrl); if (ret) { if (magic_switched_on) ksz_switch_macaddr_put(dev->ds); return ret; } return 0; } /** * ksz9477_wol_pre_shutdown - Prepares the switch device for shutdown while * considering Wake-on-LAN (WoL) settings. * @dev: The switch device structure. * @wol_enabled: Pointer to a boolean which will be set to true if WoL is * enabled on any port. * * This function prepares the switch device for a safe shutdown while taking * into account the Wake-on-LAN (WoL) settings on the user ports. It updates * the wol_enabled flag accordingly to reflect whether WoL is active on any * port. */ void ksz9477_wol_pre_shutdown(struct ksz_device *dev, bool *wol_enabled) { struct dsa_port *dp; int ret; *wol_enabled = false; if (!dev->wakeup_source) return; dsa_switch_for_each_user_port(dp, dev->ds) { u8 pme_ctrl = 0; ret = ksz_pread8(dev, dp->index, REG_PORT_PME_CTRL, &pme_ctrl); if (!ret && pme_ctrl) *wol_enabled = true; /* make sure there are no pending wake events which would * prevent the device from going to sleep/shutdown. */ ksz9477_handle_wake_reason(dev, dp->index); } /* Now we are save to enable PME pin. */ if (*wol_enabled) ksz_write8(dev, REG_SW_PME_CTRL, PME_ENABLE); } static int ksz9477_wait_vlan_ctrl_ready(struct ksz_device *dev) { unsigned int val; return regmap_read_poll_timeout(ksz_regmap_8(dev), REG_SW_VLAN_CTRL, val, !(val & VLAN_START), 10, 1000); } static int ksz9477_get_vlan_table(struct ksz_device *dev, u16 vid, u32 *vlan_table) { int ret; mutex_lock(&dev->vlan_mutex); ksz_write16(dev, REG_SW_VLAN_ENTRY_INDEX__2, vid & VLAN_INDEX_M); ksz_write8(dev, REG_SW_VLAN_CTRL, VLAN_READ | VLAN_START); /* wait to be cleared */ ret = ksz9477_wait_vlan_ctrl_ready(dev); if (ret) { dev_dbg(dev->dev, "Failed to read vlan table\n"); goto exit; } ksz_read32(dev, REG_SW_VLAN_ENTRY__4, &vlan_table[0]); ksz_read32(dev, REG_SW_VLAN_ENTRY_UNTAG__4, &vlan_table[1]); ksz_read32(dev, REG_SW_VLAN_ENTRY_PORTS__4, &vlan_table[2]); ksz_write8(dev, REG_SW_VLAN_CTRL, 0); exit: mutex_unlock(&dev->vlan_mutex); return ret; } static int ksz9477_set_vlan_table(struct ksz_device *dev, u16 vid, u32 *vlan_table) { int ret; mutex_lock(&dev->vlan_mutex); ksz_write32(dev, REG_SW_VLAN_ENTRY__4, vlan_table[0]); ksz_write32(dev, REG_SW_VLAN_ENTRY_UNTAG__4, vlan_table[1]); ksz_write32(dev, REG_SW_VLAN_ENTRY_PORTS__4, vlan_table[2]); ksz_write16(dev, REG_SW_VLAN_ENTRY_INDEX__2, vid & VLAN_INDEX_M); ksz_write8(dev, REG_SW_VLAN_CTRL, VLAN_START | VLAN_WRITE); /* wait to be cleared */ ret = ksz9477_wait_vlan_ctrl_ready(dev); if (ret) { dev_dbg(dev->dev, "Failed to write vlan table\n"); goto exit; } ksz_write8(dev, REG_SW_VLAN_CTRL, 0); /* update vlan cache table */ dev->vlan_cache[vid].table[0] = vlan_table[0]; dev->vlan_cache[vid].table[1] = vlan_table[1]; dev->vlan_cache[vid].table[2] = vlan_table[2]; exit: mutex_unlock(&dev->vlan_mutex); return ret; } static void ksz9477_read_table(struct ksz_device *dev, u32 *table) { ksz_read32(dev, REG_SW_ALU_VAL_A, &table[0]); ksz_read32(dev, REG_SW_ALU_VAL_B, &table[1]); ksz_read32(dev, REG_SW_ALU_VAL_C, &table[2]); ksz_read32(dev, REG_SW_ALU_VAL_D, &table[3]); } static void ksz9477_write_table(struct ksz_device *dev, u32 *table) { ksz_write32(dev, REG_SW_ALU_VAL_A, table[0]); ksz_write32(dev, REG_SW_ALU_VAL_B, table[1]); ksz_write32(dev, REG_SW_ALU_VAL_C, table[2]); ksz_write32(dev, REG_SW_ALU_VAL_D, table[3]); } static int ksz9477_wait_alu_ready(struct ksz_device *dev) { unsigned int val; return regmap_read_poll_timeout(ksz_regmap_32(dev), REG_SW_ALU_CTRL__4, val, !(val & ALU_START), 10, 1000); } static int ksz9477_wait_alu_sta_ready(struct ksz_device *dev) { unsigned int val; return regmap_read_poll_timeout(ksz_regmap_32(dev), REG_SW_ALU_STAT_CTRL__4, val, !(val & ALU_STAT_START), 10, 1000); } int ksz9477_reset_switch(struct ksz_device *dev) { u8 data8; u32 data32; /* reset switch */ ksz_cfg(dev, REG_SW_OPERATION, SW_RESET, true); /* turn off SPI DO Edge select */ regmap_update_bits(ksz_regmap_8(dev), REG_SW_GLOBAL_SERIAL_CTRL_0, SPI_AUTO_EDGE_DETECTION, 0); /* default configuration */ ksz_write8(dev, REG_SW_LUE_CTRL_1, SW_AGING_ENABLE | SW_LINK_AUTO_AGING | SW_SRC_ADDR_FILTER); /* disable interrupts */ ksz_write32(dev, REG_SW_INT_MASK__4, SWITCH_INT_MASK); ksz_write32(dev, REG_SW_PORT_INT_MASK__4, 0x7F); ksz_read32(dev, REG_SW_PORT_INT_STATUS__4, &data32); /* KSZ9893 compatible chips do not support refclk configuration */ if (dev->chip_id == KSZ9893_CHIP_ID || dev->chip_id == KSZ8563_CHIP_ID || dev->chip_id == KSZ9563_CHIP_ID) return 0; data8 = SW_ENABLE_REFCLKO; if (dev->synclko_disable) data8 = 0; else if (dev->synclko_125) data8 = SW_ENABLE_REFCLKO | SW_REFCLKO_IS_125MHZ; ksz_write8(dev, REG_SW_GLOBAL_OUTPUT_CTRL__1, data8); return 0; } void ksz9477_r_mib_cnt(struct ksz_device *dev, int port, u16 addr, u64 *cnt) { struct ksz_port *p = &dev->ports[port]; unsigned int val; u32 data; int ret; /* retain the flush/freeze bit */ data = p->freeze ? MIB_COUNTER_FLUSH_FREEZE : 0; data |= MIB_COUNTER_READ; data |= (addr << MIB_COUNTER_INDEX_S); ksz_pwrite32(dev, port, REG_PORT_MIB_CTRL_STAT__4, data); ret = regmap_read_poll_timeout(ksz_regmap_32(dev), PORT_CTRL_ADDR(port, REG_PORT_MIB_CTRL_STAT__4), val, !(val & MIB_COUNTER_READ), 10, 1000); /* failed to read MIB. get out of loop */ if (ret) { dev_dbg(dev->dev, "Failed to get MIB\n"); return; } /* count resets upon read */ ksz_pread32(dev, port, REG_PORT_MIB_DATA, &data); *cnt += data; } void ksz9477_r_mib_pkt(struct ksz_device *dev, int port, u16 addr, u64 *dropped, u64 *cnt) { addr = dev->info->mib_names[addr].index; ksz9477_r_mib_cnt(dev, port, addr, cnt); } void ksz9477_freeze_mib(struct ksz_device *dev, int port, bool freeze) { u32 val = freeze ? MIB_COUNTER_FLUSH_FREEZE : 0; struct ksz_port *p = &dev->ports[port]; /* enable/disable the port for flush/freeze function */ mutex_lock(&p->mib.cnt_mutex); ksz_pwrite32(dev, port, REG_PORT_MIB_CTRL_STAT__4, val); /* used by MIB counter reading code to know freeze is enabled */ p->freeze = freeze; mutex_unlock(&p->mib.cnt_mutex); } int ksz9477_errata_monitor(struct ksz_device *dev, int port, u64 tx_late_col) { u32 pmavbc; u8 status; u16 pqm; int ret; ret = ksz_pread8(dev, port, REG_PORT_STATUS_0, &status); if (ret) return ret; if (!(FIELD_GET(PORT_INTF_SPEED_MASK, status) == PORT_INTF_SPEED_NONE) && !(status & PORT_INTF_FULL_DUPLEX)) { /* Errata DS80000754 recommends monitoring potential faults in * half-duplex mode. The switch might not be able to communicate anymore * in these states. * If you see this message, please read the errata-sheet for more information: * https://ww1.microchip.com/downloads/aemDocuments/documents/UNG/ProductDocuments/Errata/KSZ9477S-Errata-DS80000754.pdf * To workaround this issue, half-duplex mode should be avoided. * A software reset could be implemented to recover from this state. */ dev_warn_once(dev->dev, "Half-duplex detected on port %d, transmission halt may occur\n", port); if (tx_late_col != 0) { /* Transmission halt with late collisions */ dev_crit_once(dev->dev, "TX late collisions detected, transmission may be halted on port %d\n", port); } ret = ksz_read8(dev, REG_SW_LUE_CTRL_0, &status); if (ret) return ret; if (status & SW_VLAN_ENABLE) { ret = ksz_pread16(dev, port, REG_PORT_QM_TX_CNT_0__4, &pqm); if (ret) return ret; ret = ksz_read32(dev, REG_PMAVBC, &pmavbc); if (ret) return ret; if ((FIELD_GET(PMAVBC_MASK, pmavbc) <= PMAVBC_MIN) || (FIELD_GET(PORT_QM_TX_CNT_M, pqm) >= PORT_QM_TX_CNT_MAX)) { /* Transmission halt with Half-Duplex and VLAN */ dev_crit_once(dev->dev, "resources out of limits, transmission may be halted\n"); } } } return ret; } void ksz9477_port_init_cnt(struct ksz_device *dev, int port) { struct ksz_port_mib *mib = &dev->ports[port].mib; /* flush all enabled port MIB counters */ mutex_lock(&mib->cnt_mutex); ksz_pwrite32(dev, port, REG_PORT_MIB_CTRL_STAT__4, MIB_COUNTER_FLUSH_FREEZE); ksz_write8(dev, REG_SW_MAC_CTRL_6, SW_MIB_COUNTER_FLUSH); ksz_pwrite32(dev, port, REG_PORT_MIB_CTRL_STAT__4, 0); mutex_unlock(&mib->cnt_mutex); } static void ksz9477_r_phy_quirks(struct ksz_device *dev, u16 addr, u16 reg, u16 *data) { /* KSZ8563R do not have extended registers but BMSR_ESTATEN and * BMSR_ERCAP bits are set. */ if (dev->chip_id == KSZ8563_CHIP_ID && reg == MII_BMSR) *data &= ~(BMSR_ESTATEN | BMSR_ERCAP); } int ksz9477_r_phy(struct ksz_device *dev, u16 addr, u16 reg, u16 *data) { u16 val = 0xffff; int ret; /* No real PHY after this. Simulate the PHY. * A fixed PHY can be setup in the device tree, but this function is * still called for that port during initialization. * For RGMII PHY there is no way to access it so the fixed PHY should * be used. For SGMII PHY the supporting code will be added later. */ if (!dev->info->internal_phy[addr]) { struct ksz_port *p = &dev->ports[addr]; switch (reg) { case MII_BMCR: val = 0x1140; break; case MII_BMSR: val = 0x796d; break; case MII_PHYSID1: val = 0x0022; break; case MII_PHYSID2: val = 0x1631; break; case MII_ADVERTISE: val = 0x05e1; break; case MII_LPA: val = 0xc5e1; break; case MII_CTRL1000: val = 0x0700; break; case MII_STAT1000: if (p->phydev.speed == SPEED_1000) val = 0x3800; else val = 0; break; } } else { ret = ksz_pread16(dev, addr, 0x100 + (reg << 1), &val); if (ret) return ret; ksz9477_r_phy_quirks(dev, addr, reg, &val); } *data = val; return 0; } int ksz9477_w_phy(struct ksz_device *dev, u16 addr, u16 reg, u16 val) { u32 mask, val32; /* No real PHY after this. */ if (!dev->info->internal_phy[addr]) return 0; if (reg < 0x10) return ksz_pwrite16(dev, addr, 0x100 + (reg << 1), val); /* Errata: When using SPI, I2C, or in-band register access, * writes to certain PHY registers should be performed as * 32-bit writes instead of 16-bit writes. */ val32 = val; mask = 0xffff; if ((reg & 1) == 0) { val32 <<= 16; mask <<= 16; } reg &= ~1; return ksz_prmw32(dev, addr, 0x100 + (reg << 1), mask, val32); } void ksz9477_cfg_port_member(struct ksz_device *dev, int port, u8 member) { ksz_pwrite32(dev, port, REG_PORT_VLAN_MEMBERSHIP__4, member); } void ksz9477_flush_dyn_mac_table(struct ksz_device *dev, int port) { const u16 *regs = dev->info->regs; u8 data; regmap_update_bits(ksz_regmap_8(dev), REG_SW_LUE_CTRL_2, SW_FLUSH_OPTION_M << SW_FLUSH_OPTION_S, SW_FLUSH_OPTION_DYN_MAC << SW_FLUSH_OPTION_S); if (port < dev->info->port_cnt) { /* flush individual port */ ksz_pread8(dev, port, regs[P_STP_CTRL], &data); if (!(data & PORT_LEARN_DISABLE)) ksz_pwrite8(dev, port, regs[P_STP_CTRL], data | PORT_LEARN_DISABLE); ksz_cfg(dev, S_FLUSH_TABLE_CTRL, SW_FLUSH_DYN_MAC_TABLE, true); ksz_pwrite8(dev, port, regs[P_STP_CTRL], data); } else { /* flush all */ ksz_cfg(dev, S_FLUSH_TABLE_CTRL, SW_FLUSH_STP_TABLE, true); } } int ksz9477_port_vlan_filtering(struct ksz_device *dev, int port, bool flag, struct netlink_ext_ack *extack) { if (flag) { ksz_port_cfg(dev, port, REG_PORT_LUE_CTRL, PORT_VLAN_LOOKUP_VID_0, true); ksz_cfg(dev, REG_SW_LUE_CTRL_0, SW_VLAN_ENABLE, true); } else { ksz_cfg(dev, REG_SW_LUE_CTRL_0, SW_VLAN_ENABLE, false); ksz_port_cfg(dev, port, REG_PORT_LUE_CTRL, PORT_VLAN_LOOKUP_VID_0, false); } return 0; } int ksz9477_port_vlan_add(struct ksz_device *dev, int port, const struct switchdev_obj_port_vlan *vlan, struct netlink_ext_ack *extack) { u32 vlan_table[3]; bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED; int err; err = ksz9477_get_vlan_table(dev, vlan->vid, vlan_table); if (err) { NL_SET_ERR_MSG_MOD(extack, "Failed to get vlan table"); return err; } vlan_table[0] = VLAN_VALID | (vlan->vid & VLAN_FID_M); if (untagged) vlan_table[1] |= BIT(port); else vlan_table[1] &= ~BIT(port); vlan_table[1] &= ~(BIT(dev->cpu_port)); vlan_table[2] |= BIT(port) | BIT(dev->cpu_port); err = ksz9477_set_vlan_table(dev, vlan->vid, vlan_table); if (err) { NL_SET_ERR_MSG_MOD(extack, "Failed to set vlan table"); return err; } /* change PVID */ if (vlan->flags & BRIDGE_VLAN_INFO_PVID) ksz_pwrite16(dev, port, REG_PORT_DEFAULT_VID, vlan->vid); return 0; } int ksz9477_port_vlan_del(struct ksz_device *dev, int port, const struct switchdev_obj_port_vlan *vlan) { bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED; u32 vlan_table[3]; u16 pvid; ksz_pread16(dev, port, REG_PORT_DEFAULT_VID, &pvid); pvid = pvid & 0xFFF; if (ksz9477_get_vlan_table(dev, vlan->vid, vlan_table)) { dev_dbg(dev->dev, "Failed to get vlan table\n"); return -ETIMEDOUT; } vlan_table[2] &= ~BIT(port); if (pvid == vlan->vid) pvid = 1; if (untagged) vlan_table[1] &= ~BIT(port); if (ksz9477_set_vlan_table(dev, vlan->vid, vlan_table)) { dev_dbg(dev->dev, "Failed to set vlan table\n"); return -ETIMEDOUT; } ksz_pwrite16(dev, port, REG_PORT_DEFAULT_VID, pvid); return 0; } int ksz9477_fdb_add(struct ksz_device *dev, int port, const unsigned char *addr, u16 vid, struct dsa_db db) { u32 alu_table[4]; u32 data; int ret = 0; mutex_lock(&dev->alu_mutex); /* find any entry with mac & vid */ data = vid << ALU_FID_INDEX_S; data |= ((addr[0] << 8) | addr[1]); ksz_write32(dev, REG_SW_ALU_INDEX_0, data); data = ((addr[2] << 24) | (addr[3] << 16)); data |= ((addr[4] << 8) | addr[5]); ksz_write32(dev, REG_SW_ALU_INDEX_1, data); /* start read operation */ ksz_write32(dev, REG_SW_ALU_CTRL__4, ALU_READ | ALU_START); /* wait to be finished */ ret = ksz9477_wait_alu_ready(dev); if (ret) { dev_dbg(dev->dev, "Failed to read ALU\n"); goto exit; } /* read ALU entry */ ksz9477_read_table(dev, alu_table); /* update ALU entry */ alu_table[0] = ALU_V_STATIC_VALID; alu_table[1] |= BIT(port); if (vid) alu_table[1] |= ALU_V_USE_FID; alu_table[2] = (vid << ALU_V_FID_S); alu_table[2] |= ((addr[0] << 8) | addr[1]); alu_table[3] = ((addr[2] << 24) | (addr[3] << 16)); alu_table[3] |= ((addr[4] << 8) | addr[5]); ksz9477_write_table(dev, alu_table); ksz_write32(dev, REG_SW_ALU_CTRL__4, ALU_WRITE | ALU_START); /* wait to be finished */ ret = ksz9477_wait_alu_ready(dev); if (ret) dev_dbg(dev->dev, "Failed to write ALU\n"); exit: mutex_unlock(&dev->alu_mutex); return ret; } int ksz9477_fdb_del(struct ksz_device *dev, int port, const unsigned char *addr, u16 vid, struct dsa_db db) { u32 alu_table[4]; u32 data; int ret = 0; mutex_lock(&dev->alu_mutex); /* read any entry with mac & vid */ data = vid << ALU_FID_INDEX_S; data |= ((addr[0] << 8) | addr[1]); ksz_write32(dev, REG_SW_ALU_INDEX_0, data); data = ((addr[2] << 24) | (addr[3] << 16)); data |= ((addr[4] << 8) | addr[5]); ksz_write32(dev, REG_SW_ALU_INDEX_1, data); /* start read operation */ ksz_write32(dev, REG_SW_ALU_CTRL__4, ALU_READ | ALU_START); /* wait to be finished */ ret = ksz9477_wait_alu_ready(dev); if (ret) { dev_dbg(dev->dev, "Failed to read ALU\n"); goto exit; } ksz_read32(dev, REG_SW_ALU_VAL_A, &alu_table[0]); if (alu_table[0] & ALU_V_STATIC_VALID) { ksz_read32(dev, REG_SW_ALU_VAL_B, &alu_table[1]); ksz_read32(dev, REG_SW_ALU_VAL_C, &alu_table[2]); ksz_read32(dev, REG_SW_ALU_VAL_D, &alu_table[3]); /* clear forwarding port */ alu_table[1] &= ~BIT(port); /* if there is no port to forward, clear table */ if ((alu_table[1] & ALU_V_PORT_MAP) == 0) { alu_table[0] = 0; alu_table[1] = 0; alu_table[2] = 0; alu_table[3] = 0; } } else { alu_table[0] = 0; alu_table[1] = 0; alu_table[2] = 0; alu_table[3] = 0; } ksz9477_write_table(dev, alu_table); ksz_write32(dev, REG_SW_ALU_CTRL__4, ALU_WRITE | ALU_START); /* wait to be finished */ ret = ksz9477_wait_alu_ready(dev); if (ret) dev_dbg(dev->dev, "Failed to write ALU\n"); exit: mutex_unlock(&dev->alu_mutex); return ret; } static void ksz9477_convert_alu(struct alu_struct *alu, u32 *alu_table) { alu->is_static = !!(alu_table[0] & ALU_V_STATIC_VALID); alu->is_src_filter = !!(alu_table[0] & ALU_V_SRC_FILTER); alu->is_dst_filter = !!(alu_table[0] & ALU_V_DST_FILTER); alu->prio_age = (alu_table[0] >> ALU_V_PRIO_AGE_CNT_S) & ALU_V_PRIO_AGE_CNT_M; alu->mstp = alu_table[0] & ALU_V_MSTP_M; alu->is_override = !!(alu_table[1] & ALU_V_OVERRIDE); alu->is_use_fid = !!(alu_table[1] & ALU_V_USE_FID); alu->port_forward = alu_table[1] & ALU_V_PORT_MAP; alu->fid = (alu_table[2] >> ALU_V_FID_S) & ALU_V_FID_M; alu->mac[0] = (alu_table[2] >> 8) & 0xFF; alu->mac[1] = alu_table[2] & 0xFF; alu->mac[2] = (alu_table[3] >> 24) & 0xFF; alu->mac[3] = (alu_table[3] >> 16) & 0xFF; alu->mac[4] = (alu_table[3] >> 8) & 0xFF; alu->mac[5] = alu_table[3] & 0xFF; } int ksz9477_fdb_dump(struct ksz_device *dev, int port, dsa_fdb_dump_cb_t *cb, void *data) { int ret = 0; u32 ksz_data; u32 alu_table[4]; struct alu_struct alu; int timeout; mutex_lock(&dev->alu_mutex); /* start ALU search */ ksz_write32(dev, REG_SW_ALU_CTRL__4, ALU_START | ALU_SEARCH); do { timeout = 1000; do { ksz_read32(dev, REG_SW_ALU_CTRL__4, &ksz_data); if ((ksz_data & ALU_VALID) || !(ksz_data & ALU_START)) break; usleep_range(1, 10); } while (timeout-- > 0); if (!timeout) { dev_dbg(dev->dev, "Failed to search ALU\n"); ret = -ETIMEDOUT; goto exit; } if (!(ksz_data & ALU_VALID)) continue; /* read ALU table */ ksz9477_read_table(dev, alu_table); ksz9477_convert_alu(&alu, alu_table); if (alu.port_forward & BIT(port)) { ret = cb(alu.mac, alu.fid, alu.is_static, data); if (ret) goto exit; } } while (ksz_data & ALU_START); exit: /* stop ALU search */ ksz_write32(dev, REG_SW_ALU_CTRL__4, 0); mutex_unlock(&dev->alu_mutex); return ret; } int ksz9477_mdb_add(struct ksz_device *dev, int port, const struct switchdev_obj_port_mdb *mdb, struct dsa_db db) { u32 static_table[4]; const u8 *shifts; const u32 *masks; u32 data; int index; u32 mac_hi, mac_lo; int err = 0; shifts = dev->info->shifts; masks = dev->info->masks; mac_hi = ((mdb->addr[0] << 8) | mdb->addr[1]); mac_lo = ((mdb->addr[2] << 24) | (mdb->addr[3] << 16)); mac_lo |= ((mdb->addr[4] << 8) | mdb->addr[5]); mutex_lock(&dev->alu_mutex); for (index = 0; index < dev->info->num_statics; index++) { /* find empty slot first */ data = (index << shifts[ALU_STAT_INDEX]) | masks[ALU_STAT_READ] | ALU_STAT_START; ksz_write32(dev, REG_SW_ALU_STAT_CTRL__4, data); /* wait to be finished */ err = ksz9477_wait_alu_sta_ready(dev); if (err) { dev_dbg(dev->dev, "Failed to read ALU STATIC\n"); goto exit; } /* read ALU static table */ ksz9477_read_table(dev, static_table); if (static_table[0] & ALU_V_STATIC_VALID) { /* check this has same vid & mac address */ if (((static_table[2] >> ALU_V_FID_S) == mdb->vid) && ((static_table[2] & ALU_V_MAC_ADDR_HI) == mac_hi) && static_table[3] == mac_lo) { /* found matching one */ break; } } else { /* found empty one */ break; } } /* no available entry */ if (index == dev->info->num_statics) { err = -ENOSPC; goto exit; } /* add entry */ static_table[0] = ALU_V_STATIC_VALID; static_table[1] |= BIT(port); if (mdb->vid) static_table[1] |= ALU_V_USE_FID; static_table[2] = (mdb->vid << ALU_V_FID_S); static_table[2] |= mac_hi; static_table[3] = mac_lo; ksz9477_write_table(dev, static_table); data = (index << shifts[ALU_STAT_INDEX]) | ALU_STAT_START; ksz_write32(dev, REG_SW_ALU_STAT_CTRL__4, data); /* wait to be finished */ if (ksz9477_wait_alu_sta_ready(dev)) dev_dbg(dev->dev, "Failed to read ALU STATIC\n"); exit: mutex_unlock(&dev->alu_mutex); return err; } int ksz9477_mdb_del(struct ksz_device *dev, int port, const struct switchdev_obj_port_mdb *mdb, struct dsa_db db) { u32 static_table[4]; const u8 *shifts; const u32 *masks; u32 data; int index; int ret = 0; u32 mac_hi, mac_lo; shifts = dev->info->shifts; masks = dev->info->masks; mac_hi = ((mdb->addr[0] << 8) | mdb->addr[1]); mac_lo = ((mdb->addr[2] << 24) | (mdb->addr[3] << 16)); mac_lo |= ((mdb->addr[4] << 8) | mdb->addr[5]); mutex_lock(&dev->alu_mutex); for (index = 0; index < dev->info->num_statics; index++) { /* find empty slot first */ data = (index << shifts[ALU_STAT_INDEX]) | masks[ALU_STAT_READ] | ALU_STAT_START; ksz_write32(dev, REG_SW_ALU_STAT_CTRL__4, data); /* wait to be finished */ ret = ksz9477_wait_alu_sta_ready(dev); if (ret) { dev_dbg(dev->dev, "Failed to read ALU STATIC\n"); goto exit; } /* read ALU static table */ ksz9477_read_table(dev, static_table); if (static_table[0] & ALU_V_STATIC_VALID) { /* check this has same vid & mac address */ if (((static_table[2] >> ALU_V_FID_S) == mdb->vid) && ((static_table[2] & ALU_V_MAC_ADDR_HI) == mac_hi) && static_table[3] == mac_lo) { /* found matching one */ break; } } } /* no available entry */ if (index == dev->info->num_statics) goto exit; /* clear port */ static_table[1] &= ~BIT(port); if ((static_table[1] & ALU_V_PORT_MAP) == 0) { /* delete entry */ static_table[0] = 0; static_table[1] = 0; static_table[2] = 0; static_table[3] = 0; } ksz9477_write_table(dev, static_table); data = (index << shifts[ALU_STAT_INDEX]) | ALU_STAT_START; ksz_write32(dev, REG_SW_ALU_STAT_CTRL__4, data); /* wait to be finished */ ret = ksz9477_wait_alu_sta_ready(dev); if (ret) dev_dbg(dev->dev, "Failed to read ALU STATIC\n"); exit: mutex_unlock(&dev->alu_mutex); return ret; } int ksz9477_port_mirror_add(struct ksz_device *dev, int port, struct dsa_mall_mirror_tc_entry *mirror, bool ingress, struct netlink_ext_ack *extack) { u8 data; int p; /* Limit to one sniffer port * Check if any of the port is already set for sniffing * If yes, instruct the user to remove the previous entry & exit */ for (p = 0; p < dev->info->port_cnt; p++) { /* Skip the current sniffing port */ if (p == mirror->to_local_port) continue; ksz_pread8(dev, p, P_MIRROR_CTRL, &data); if (data & PORT_MIRROR_SNIFFER) { NL_SET_ERR_MSG_MOD(extack, "Sniffer port is already configured, delete existing rules & retry"); return -EBUSY; } } if (ingress) ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_RX, true); else ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_TX, true); /* configure mirror port */ ksz_port_cfg(dev, mirror->to_local_port, P_MIRROR_CTRL, PORT_MIRROR_SNIFFER, true); ksz_cfg(dev, S_MIRROR_CTRL, SW_MIRROR_RX_TX, false); return 0; } void ksz9477_port_mirror_del(struct ksz_device *dev, int port, struct dsa_mall_mirror_tc_entry *mirror) { bool in_use = false; u8 data; int p; if (mirror->ingress) ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_RX, false); else ksz_port_cfg(dev, port, P_MIRROR_CTRL, PORT_MIRROR_TX, false); /* Check if any of the port is still referring to sniffer port */ for (p = 0; p < dev->info->port_cnt; p++) { ksz_pread8(dev, p, P_MIRROR_CTRL, &data); if ((data & (PORT_MIRROR_RX | PORT_MIRROR_TX))) { in_use = true; break; } } /* delete sniffing if there are no other mirroring rules */ if (!in_use) ksz_port_cfg(dev, mirror->to_local_port, P_MIRROR_CTRL, PORT_MIRROR_SNIFFER, false); } static phy_interface_t ksz9477_get_interface(struct ksz_device *dev, int port) { phy_interface_t interface; bool gbit; if (dev->info->internal_phy[port]) return PHY_INTERFACE_MODE_NA; gbit = ksz_get_gbit(dev, port); interface = ksz_get_xmii(dev, port, gbit); return interface; } void ksz9477_get_caps(struct ksz_device *dev, int port, struct phylink_config *config) { config->mac_capabilities = MAC_10 | MAC_100 | MAC_ASYM_PAUSE | MAC_SYM_PAUSE; if (dev->info->gbit_capable[port]) config->mac_capabilities |= MAC_1000FD; } int ksz9477_set_ageing_time(struct ksz_device *dev, unsigned int msecs) { u32 secs = msecs / 1000; u8 value; u8 data; int ret; value = FIELD_GET(SW_AGE_PERIOD_7_0_M, secs); ret = ksz_write8(dev, REG_SW_LUE_CTRL_3, value); if (ret < 0) return ret; data = FIELD_GET(SW_AGE_PERIOD_10_8_M, secs); ret = ksz_read8(dev, REG_SW_LUE_CTRL_0, &value); if (ret < 0) return ret; value &= ~SW_AGE_CNT_M; value |= FIELD_PREP(SW_AGE_CNT_M, data); return ksz_write8(dev, REG_SW_LUE_CTRL_0, value); } void ksz9477_port_queue_split(struct ksz_device *dev, int port) { u8 data; if (dev->info->num_tx_queues == 8) data = PORT_EIGHT_QUEUE; else if (dev->info->num_tx_queues == 4) data = PORT_FOUR_QUEUE; else if (dev->info->num_tx_queues == 2) data = PORT_TWO_QUEUE; else data = PORT_SINGLE_QUEUE; ksz_prmw8(dev, port, REG_PORT_CTRL_0, PORT_QUEUE_SPLIT_MASK, data); } void ksz9477_port_setup(struct ksz_device *dev, int port, bool cpu_port) { struct dsa_switch *ds = dev->ds; u16 data16; u8 member; /* enable tag tail for host port */ if (cpu_port) ksz_port_cfg(dev, port, REG_PORT_CTRL_0, PORT_TAIL_TAG_ENABLE, true); ksz9477_port_queue_split(dev, port); ksz_port_cfg(dev, port, REG_PORT_CTRL_0, PORT_MAC_LOOPBACK, false); /* set back pressure */ ksz_port_cfg(dev, port, REG_PORT_MAC_CTRL_1, PORT_BACK_PRESSURE, true); /* enable broadcast storm limit */ ksz_port_cfg(dev, port, P_BCAST_STORM_CTRL, PORT_BROADCAST_STORM, true); /* replace priority */ ksz_port_cfg(dev, port, REG_PORT_MRI_MAC_CTRL, PORT_USER_PRIO_CEILING, false); ksz9477_port_cfg32(dev, port, REG_PORT_MTI_QUEUE_CTRL_0__4, MTI_PVID_REPLACE, false); /* force flow control for non-PHY ports only */ ksz_port_cfg(dev, port, REG_PORT_CTRL_0, PORT_FORCE_TX_FLOW_CTRL | PORT_FORCE_RX_FLOW_CTRL, !dev->info->internal_phy[port]); if (cpu_port) member = dsa_user_ports(ds); else member = BIT(dsa_upstream_port(ds, port)); ksz9477_cfg_port_member(dev, port, member); /* clear pending interrupts */ if (dev->info->internal_phy[port]) ksz_pread16(dev, port, REG_PORT_PHY_INT_ENABLE, &data16); ksz9477_port_acl_init(dev, port); /* clear pending wake flags */ ksz9477_handle_wake_reason(dev, port); /* Disable all WoL options by default. Otherwise * ksz_switch_macaddr_get/put logic will not work properly. */ ksz_pwrite8(dev, port, REG_PORT_PME_CTRL, 0); } void ksz9477_config_cpu_port(struct dsa_switch *ds) { struct ksz_device *dev = ds->priv; struct ksz_port *p; int i; for (i = 0; i < dev->info->port_cnt; i++) { if (dsa_is_cpu_port(ds, i) && (dev->info->cpu_ports & (1 << i))) { phy_interface_t interface; const char *prev_msg; const char *prev_mode; dev->cpu_port = i; p = &dev->ports[i]; /* Read from XMII register to determine host port * interface. If set specifically in device tree * note the difference to help debugging. */ interface = ksz9477_get_interface(dev, i); if (!p->interface) { if (dev->compat_interface) { dev_warn(dev->dev, "Using legacy switch \"phy-mode\" property, because it is missing on port %d node. " "Please update your device tree.\n", i); p->interface = dev->compat_interface; } else { p->interface = interface; } } if (interface && interface != p->interface) { prev_msg = " instead of "; prev_mode = phy_modes(interface); } else { prev_msg = ""; prev_mode = ""; } dev_info(dev->dev, "Port%d: using phy mode %s%s%s\n", i, phy_modes(p->interface), prev_msg, prev_mode); /* enable cpu port */ ksz9477_port_setup(dev, i, true); } } for (i = 0; i < dev->info->port_cnt; i++) { if (i == dev->cpu_port) continue; ksz_port_stp_state_set(ds, i, BR_STATE_DISABLED); } } int ksz9477_enable_stp_addr(struct ksz_device *dev) { const u32 *masks; u32 data; int ret; masks = dev->info->masks; /* Enable Reserved multicast table */ ksz_cfg(dev, REG_SW_LUE_CTRL_0, SW_RESV_MCAST_ENABLE, true); /* Set the Override bit for forwarding BPDU packet to CPU */ ret = ksz_write32(dev, REG_SW_ALU_VAL_B, ALU_V_OVERRIDE | BIT(dev->cpu_port)); if (ret < 0) return ret; data = ALU_STAT_START | ALU_RESV_MCAST_ADDR | masks[ALU_STAT_WRITE]; ret = ksz_write32(dev, REG_SW_ALU_STAT_CTRL__4, data); if (ret < 0) return ret; /* wait to be finished */ ret = ksz9477_wait_alu_sta_ready(dev); if (ret < 0) { dev_err(dev->dev, "Failed to update Reserved Multicast table\n"); return ret; } return 0; } int ksz9477_setup(struct dsa_switch *ds) { struct ksz_device *dev = ds->priv; int ret = 0; ds->mtu_enforcement_ingress = true; /* Required for port partitioning. */ ksz9477_cfg32(dev, REG_SW_QM_CTRL__4, UNICAST_VLAN_BOUNDARY, true); /* Do not work correctly with tail tagging. */ ksz_cfg(dev, REG_SW_MAC_CTRL_0, SW_CHECK_LENGTH, false); /* Enable REG_SW_MTU__2 reg by setting SW_JUMBO_PACKET */ ksz_cfg(dev, REG_SW_MAC_CTRL_1, SW_JUMBO_PACKET, true); /* Use collision based back pressure mode. */ ksz_cfg(dev, REG_SW_MAC_CTRL_1, SW_BACK_PRESSURE, SW_BACK_PRESSURE_COLLISION); /* Now we can configure default MTU value */ ret = regmap_update_bits(ksz_regmap_16(dev), REG_SW_MTU__2, REG_SW_MTU_MASK, VLAN_ETH_FRAME_LEN + ETH_FCS_LEN); if (ret) return ret; /* queue based egress rate limit */ ksz_cfg(dev, REG_SW_MAC_CTRL_5, SW_OUT_RATE_LIMIT_QUEUE_BASED, true); /* enable global MIB counter freeze function */ ksz_cfg(dev, REG_SW_MAC_CTRL_6, SW_MIB_COUNTER_FREEZE, true); /* Make sure PME (WoL) is not enabled. If requested, it will be * enabled by ksz9477_wol_pre_shutdown(). Otherwise, some PMICs do not * like PME events changes before shutdown. */ ksz_write8(dev, REG_SW_PME_CTRL, 0); return 0; } u32 ksz9477_get_port_addr(int port, int offset) { return PORT_CTRL_ADDR(port, offset); } int ksz9477_tc_cbs_set_cinc(struct ksz_device *dev, int port, u32 val) { val = val >> 8; return ksz_pwrite16(dev, port, REG_PORT_MTI_CREDIT_INCREMENT, val); } /* The KSZ9477 provides following HW features to accelerate * HSR frames handling: * * 1. TX PACKET DUPLICATION FROM HOST TO SWITCH * 2. RX PACKET DUPLICATION DISCARDING * 3. PREVENTING PACKET LOOP IN THE RING BY SELF-ADDRESS FILTERING * * Only one from point 1. has the NETIF_F* flag available. * * Ones from point 2 and 3 are "best effort" - i.e. those will * work correctly most of the time, but it may happen that some * frames will not be caught - to be more specific; there is a race * condition in hardware such that, when duplicate packets are received * on member ports very close in time to each other, the hardware fails * to detect that they are duplicates. * * Hence, the SW needs to handle those special cases. However, the speed * up gain is considerable when above features are used. * * Moreover, the NETIF_F_HW_HSR_FWD feature is also enabled, as HSR frames * can be forwarded in the switch fabric between HSR ports. */ #define KSZ9477_SUPPORTED_HSR_FEATURES (NETIF_F_HW_HSR_DUP | NETIF_F_HW_HSR_FWD) void ksz9477_hsr_join(struct dsa_switch *ds, int port, struct net_device *hsr) { struct ksz_device *dev = ds->priv; struct net_device *user; struct dsa_port *hsr_dp; u8 data, hsr_ports = 0; /* Program which port(s) shall support HSR */ ksz_rmw32(dev, REG_HSR_PORT_MAP__4, BIT(port), BIT(port)); /* Forward frames between HSR ports (i.e. bridge together HSR ports) */ if (dev->hsr_ports) { dsa_hsr_foreach_port(hsr_dp, ds, hsr) hsr_ports |= BIT(hsr_dp->index); hsr_ports |= BIT(dsa_upstream_port(ds, port)); dsa_hsr_foreach_port(hsr_dp, ds, hsr) ksz9477_cfg_port_member(dev, hsr_dp->index, hsr_ports); } if (!dev->hsr_ports) { /* Enable discarding of received HSR frames */ ksz_read8(dev, REG_HSR_ALU_CTRL_0__1, &data); data |= HSR_DUPLICATE_DISCARD; data &= ~HSR_NODE_UNICAST; ksz_write8(dev, REG_HSR_ALU_CTRL_0__1, data); } /* Enable per port self-address filtering. * The global self-address filtering has already been enabled in the * ksz9477_reset_switch() function. */ ksz_port_cfg(dev, port, REG_PORT_LUE_CTRL, PORT_SRC_ADDR_FILTER, true); /* Setup HW supported features for lan HSR ports */ user = dsa_to_port(ds, port)->user; user->features |= KSZ9477_SUPPORTED_HSR_FEATURES; } void ksz9477_hsr_leave(struct dsa_switch *ds, int port, struct net_device *hsr) { struct ksz_device *dev = ds->priv; /* Clear port HSR support */ ksz_rmw32(dev, REG_HSR_PORT_MAP__4, BIT(port), 0); /* Disable forwarding frames between HSR ports */ ksz9477_cfg_port_member(dev, port, BIT(dsa_upstream_port(ds, port))); /* Disable per port self-address filtering */ ksz_port_cfg(dev, port, REG_PORT_LUE_CTRL, PORT_SRC_ADDR_FILTER, false); } int ksz9477_switch_init(struct ksz_device *dev) { u8 data8; int ret; dev->port_mask = (1 << dev->info->port_cnt) - 1; /* turn off SPI DO Edge select */ ret = ksz_read8(dev, REG_SW_GLOBAL_SERIAL_CTRL_0, &data8); if (ret) return ret; data8 &= ~SPI_AUTO_EDGE_DETECTION; ret = ksz_write8(dev, REG_SW_GLOBAL_SERIAL_CTRL_0, data8); if (ret) return ret; return 0; } void ksz9477_switch_exit(struct ksz_device *dev) { ksz9477_reset_switch(dev); } MODULE_AUTHOR("Woojung Huh <Woojung.Huh@microchip.com>"); MODULE_DESCRIPTION("Microchip KSZ9477 Series Switch DSA Driver"); MODULE_LICENSE("GPL");
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1