| /* niu.c: Neptune ethernet driver. |
| * |
| * Copyright (C) 2007, 2008 David S. Miller (davem@davemloft.net) |
| */ |
| |
| #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
| |
| #include <linux/module.h> |
| #include <linux/init.h> |
| #include <linux/pci.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/netdevice.h> |
| #include <linux/ethtool.h> |
| #include <linux/etherdevice.h> |
| #include <linux/platform_device.h> |
| #include <linux/delay.h> |
| #include <linux/bitops.h> |
| #include <linux/mii.h> |
| #include <linux/if_ether.h> |
| #include <linux/if_vlan.h> |
| #include <linux/ip.h> |
| #include <linux/in.h> |
| #include <linux/ipv6.h> |
| #include <linux/log2.h> |
| #include <linux/jiffies.h> |
| #include <linux/crc32.h> |
| #include <linux/list.h> |
| #include <linux/slab.h> |
| |
| #include <linux/io.h> |
| #include <linux/of_device.h> |
| |
| #include "niu.h" |
| |
| #define DRV_MODULE_NAME "niu" |
| #define DRV_MODULE_VERSION "1.1" |
| #define DRV_MODULE_RELDATE "Apr 22, 2010" |
| |
| static char version[] __devinitdata = |
| DRV_MODULE_NAME ".c:v" DRV_MODULE_VERSION " (" DRV_MODULE_RELDATE ")\n"; |
| |
| MODULE_AUTHOR("David S. Miller (davem@davemloft.net)"); |
| MODULE_DESCRIPTION("NIU ethernet driver"); |
| MODULE_LICENSE("GPL"); |
| MODULE_VERSION(DRV_MODULE_VERSION); |
| |
| #ifndef readq |
| static u64 readq(void __iomem *reg) |
| { |
| return ((u64) readl(reg)) | (((u64) readl(reg + 4UL)) << 32); |
| } |
| |
| static void writeq(u64 val, void __iomem *reg) |
| { |
| writel(val & 0xffffffff, reg); |
| writel(val >> 32, reg + 0x4UL); |
| } |
| #endif |
| |
| static DEFINE_PCI_DEVICE_TABLE(niu_pci_tbl) = { |
| {PCI_DEVICE(PCI_VENDOR_ID_SUN, 0xabcd)}, |
| {} |
| }; |
| |
| MODULE_DEVICE_TABLE(pci, niu_pci_tbl); |
| |
| #define NIU_TX_TIMEOUT (5 * HZ) |
| |
| #define nr64(reg) readq(np->regs + (reg)) |
| #define nw64(reg, val) writeq((val), np->regs + (reg)) |
| |
| #define nr64_mac(reg) readq(np->mac_regs + (reg)) |
| #define nw64_mac(reg, val) writeq((val), np->mac_regs + (reg)) |
| |
| #define nr64_ipp(reg) readq(np->regs + np->ipp_off + (reg)) |
| #define nw64_ipp(reg, val) writeq((val), np->regs + np->ipp_off + (reg)) |
| |
| #define nr64_pcs(reg) readq(np->regs + np->pcs_off + (reg)) |
| #define nw64_pcs(reg, val) writeq((val), np->regs + np->pcs_off + (reg)) |
| |
| #define nr64_xpcs(reg) readq(np->regs + np->xpcs_off + (reg)) |
| #define nw64_xpcs(reg, val) writeq((val), np->regs + np->xpcs_off + (reg)) |
| |
| #define NIU_MSG_DEFAULT (NETIF_MSG_DRV | NETIF_MSG_PROBE | NETIF_MSG_LINK) |
| |
| static int niu_debug; |
| static int debug = -1; |
| module_param(debug, int, 0); |
| MODULE_PARM_DESC(debug, "NIU debug level"); |
| |
| #define niu_lock_parent(np, flags) \ |
| spin_lock_irqsave(&np->parent->lock, flags) |
| #define niu_unlock_parent(np, flags) \ |
| spin_unlock_irqrestore(&np->parent->lock, flags) |
| |
| static int serdes_init_10g_serdes(struct niu *np); |
| |
| static int __niu_wait_bits_clear_mac(struct niu *np, unsigned long reg, |
| u64 bits, int limit, int delay) |
| { |
| while (--limit >= 0) { |
| u64 val = nr64_mac(reg); |
| |
| if (!(val & bits)) |
| break; |
| udelay(delay); |
| } |
| if (limit < 0) |
| return -ENODEV; |
| return 0; |
| } |
| |
| static int __niu_set_and_wait_clear_mac(struct niu *np, unsigned long reg, |
| u64 bits, int limit, int delay, |
| const char *reg_name) |
| { |
| int err; |
| |
| nw64_mac(reg, bits); |
| err = __niu_wait_bits_clear_mac(np, reg, bits, limit, delay); |
| if (err) |
| netdev_err(np->dev, "bits (%llx) of register %s would not clear, val[%llx]\n", |
| (unsigned long long)bits, reg_name, |
| (unsigned long long)nr64_mac(reg)); |
| return err; |
| } |
| |
| #define niu_set_and_wait_clear_mac(NP, REG, BITS, LIMIT, DELAY, REG_NAME) \ |
| ({ BUILD_BUG_ON(LIMIT <= 0 || DELAY < 0); \ |
| __niu_set_and_wait_clear_mac(NP, REG, BITS, LIMIT, DELAY, REG_NAME); \ |
| }) |
| |
| static int __niu_wait_bits_clear_ipp(struct niu *np, unsigned long reg, |
| u64 bits, int limit, int delay) |
| { |
| while (--limit >= 0) { |
| u64 val = nr64_ipp(reg); |
| |
| if (!(val & bits)) |
| break; |
| udelay(delay); |
| } |
| if (limit < 0) |
| return -ENODEV; |
| return 0; |
| } |
| |
| static int __niu_set_and_wait_clear_ipp(struct niu *np, unsigned long reg, |
| u64 bits, int limit, int delay, |
| const char *reg_name) |
| { |
| int err; |
| u64 val; |
| |
| val = nr64_ipp(reg); |
| val |= bits; |
| nw64_ipp(reg, val); |
| |
| err = __niu_wait_bits_clear_ipp(np, reg, bits, limit, delay); |
| if (err) |
| netdev_err(np->dev, "bits (%llx) of register %s would not clear, val[%llx]\n", |
| (unsigned long long)bits, reg_name, |
| (unsigned long long)nr64_ipp(reg)); |
| return err; |
| } |
| |
| #define niu_set_and_wait_clear_ipp(NP, REG, BITS, LIMIT, DELAY, REG_NAME) \ |
| ({ BUILD_BUG_ON(LIMIT <= 0 || DELAY < 0); \ |
| __niu_set_and_wait_clear_ipp(NP, REG, BITS, LIMIT, DELAY, REG_NAME); \ |
| }) |
| |
| static int __niu_wait_bits_clear(struct niu *np, unsigned long reg, |
| u64 bits, int limit, int delay) |
| { |
| while (--limit >= 0) { |
| u64 val = nr64(reg); |
| |
| if (!(val & bits)) |
| break; |
| udelay(delay); |
| } |
| if (limit < 0) |
| return -ENODEV; |
| return 0; |
| } |
| |
| #define niu_wait_bits_clear(NP, REG, BITS, LIMIT, DELAY) \ |
| ({ BUILD_BUG_ON(LIMIT <= 0 || DELAY < 0); \ |
| __niu_wait_bits_clear(NP, REG, BITS, LIMIT, DELAY); \ |
| }) |
| |
| static int __niu_set_and_wait_clear(struct niu *np, unsigned long reg, |
| u64 bits, int limit, int delay, |
| const char *reg_name) |
| { |
| int err; |
| |
| nw64(reg, bits); |
| err = __niu_wait_bits_clear(np, reg, bits, limit, delay); |
| if (err) |
| netdev_err(np->dev, "bits (%llx) of register %s would not clear, val[%llx]\n", |
| (unsigned long long)bits, reg_name, |
| (unsigned long long)nr64(reg)); |
| return err; |
| } |
| |
| #define niu_set_and_wait_clear(NP, REG, BITS, LIMIT, DELAY, REG_NAME) \ |
| ({ BUILD_BUG_ON(LIMIT <= 0 || DELAY < 0); \ |
| __niu_set_and_wait_clear(NP, REG, BITS, LIMIT, DELAY, REG_NAME); \ |
| }) |
| |
| static void niu_ldg_rearm(struct niu *np, struct niu_ldg *lp, int on) |
| { |
| u64 val = (u64) lp->timer; |
| |
| if (on) |
| val |= LDG_IMGMT_ARM; |
| |
| nw64(LDG_IMGMT(lp->ldg_num), val); |
| } |
| |
| static int niu_ldn_irq_enable(struct niu *np, int ldn, int on) |
| { |
| unsigned long mask_reg, bits; |
| u64 val; |
| |
| if (ldn < 0 || ldn > LDN_MAX) |
| return -EINVAL; |
| |
| if (ldn < 64) { |
| mask_reg = LD_IM0(ldn); |
| bits = LD_IM0_MASK; |
| } else { |
| mask_reg = LD_IM1(ldn - 64); |
| bits = LD_IM1_MASK; |
| } |
| |
| val = nr64(mask_reg); |
| if (on) |
| val &= ~bits; |
| else |
| val |= bits; |
| nw64(mask_reg, val); |
| |
| return 0; |
| } |
| |
| static int niu_enable_ldn_in_ldg(struct niu *np, struct niu_ldg *lp, int on) |
| { |
| struct niu_parent *parent = np->parent; |
| int i; |
| |
| for (i = 0; i <= LDN_MAX; i++) { |
| int err; |
| |
| if (parent->ldg_map[i] != lp->ldg_num) |
| continue; |
| |
| err = niu_ldn_irq_enable(np, i, on); |
| if (err) |
| return err; |
| } |
| return 0; |
| } |
| |
| static int niu_enable_interrupts(struct niu *np, int on) |
| { |
| int i; |
| |
| for (i = 0; i < np->num_ldg; i++) { |
| struct niu_ldg *lp = &np->ldg[i]; |
| int err; |
| |
| err = niu_enable_ldn_in_ldg(np, lp, on); |
| if (err) |
| return err; |
| } |
| for (i = 0; i < np->num_ldg; i++) |
| niu_ldg_rearm(np, &np->ldg[i], on); |
| |
| return 0; |
| } |
| |
| static u32 phy_encode(u32 type, int port) |
| { |
| return type << (port * 2); |
| } |
| |
| static u32 phy_decode(u32 val, int port) |
| { |
| return (val >> (port * 2)) & PORT_TYPE_MASK; |
| } |
| |
| static int mdio_wait(struct niu *np) |
| { |
| int limit = 1000; |
| u64 val; |
| |
| while (--limit > 0) { |
| val = nr64(MIF_FRAME_OUTPUT); |
| if ((val >> MIF_FRAME_OUTPUT_TA_SHIFT) & 0x1) |
| return val & MIF_FRAME_OUTPUT_DATA; |
| |
| udelay(10); |
| } |
| |
| return -ENODEV; |
| } |
| |
| static int mdio_read(struct niu *np, int port, int dev, int reg) |
| { |
| int err; |
| |
| nw64(MIF_FRAME_OUTPUT, MDIO_ADDR_OP(port, dev, reg)); |
| err = mdio_wait(np); |
| if (err < 0) |
| return err; |
| |
| nw64(MIF_FRAME_OUTPUT, MDIO_READ_OP(port, dev)); |
| return mdio_wait(np); |
| } |
| |
| static int mdio_write(struct niu *np, int port, int dev, int reg, int data) |
| { |
| int err; |
| |
| nw64(MIF_FRAME_OUTPUT, MDIO_ADDR_OP(port, dev, reg)); |
| err = mdio_wait(np); |
| if (err < 0) |
| return err; |
| |
| nw64(MIF_FRAME_OUTPUT, MDIO_WRITE_OP(port, dev, data)); |
| err = mdio_wait(np); |
| if (err < 0) |
| return err; |
| |
| return 0; |
| } |
| |
| static int mii_read(struct niu *np, int port, int reg) |
| { |
| nw64(MIF_FRAME_OUTPUT, MII_READ_OP(port, reg)); |
| return mdio_wait(np); |
| } |
| |
| static int mii_write(struct niu *np, int port, int reg, int data) |
| { |
| int err; |
| |
| nw64(MIF_FRAME_OUTPUT, MII_WRITE_OP(port, reg, data)); |
| err = mdio_wait(np); |
| if (err < 0) |
| return err; |
| |
| return 0; |
| } |
| |
| static int esr2_set_tx_cfg(struct niu *np, unsigned long channel, u32 val) |
| { |
| int err; |
| |
| err = mdio_write(np, np->port, NIU_ESR2_DEV_ADDR, |
| ESR2_TI_PLL_TX_CFG_L(channel), |
| val & 0xffff); |
| if (!err) |
| err = mdio_write(np, np->port, NIU_ESR2_DEV_ADDR, |
| ESR2_TI_PLL_TX_CFG_H(channel), |
| val >> 16); |
| return err; |
| } |
| |
| static int esr2_set_rx_cfg(struct niu *np, unsigned long channel, u32 val) |
| { |
| int err; |
| |
| err = mdio_write(np, np->port, NIU_ESR2_DEV_ADDR, |
| ESR2_TI_PLL_RX_CFG_L(channel), |
| val & 0xffff); |
| if (!err) |
| err = mdio_write(np, np->port, NIU_ESR2_DEV_ADDR, |
| ESR2_TI_PLL_RX_CFG_H(channel), |
| val >> 16); |
| return err; |
| } |
| |
| /* Mode is always 10G fiber. */ |
| static int serdes_init_niu_10g_fiber(struct niu *np) |
| { |
| struct niu_link_config *lp = &np->link_config; |
| u32 tx_cfg, rx_cfg; |
| unsigned long i; |
| |
| tx_cfg = (PLL_TX_CFG_ENTX | PLL_TX_CFG_SWING_1375MV); |
| rx_cfg = (PLL_RX_CFG_ENRX | PLL_RX_CFG_TERM_0P8VDDT | |
| PLL_RX_CFG_ALIGN_ENA | PLL_RX_CFG_LOS_LTHRESH | |
| PLL_RX_CFG_EQ_LP_ADAPTIVE); |
| |
| if (lp->loopback_mode == LOOPBACK_PHY) { |
| u16 test_cfg = PLL_TEST_CFG_LOOPBACK_CML_DIS; |
| |
| mdio_write(np, np->port, NIU_ESR2_DEV_ADDR, |
| ESR2_TI_PLL_TEST_CFG_L, test_cfg); |
| |
| tx_cfg |= PLL_TX_CFG_ENTEST; |
| rx_cfg |= PLL_RX_CFG_ENTEST; |
| } |
| |
| /* Initialize all 4 lanes of the SERDES. */ |
| for (i = 0; i < 4; i++) { |
| int err = esr2_set_tx_cfg(np, i, tx_cfg); |
| if (err) |
| return err; |
| } |
| |
| for (i = 0; i < 4; i++) { |
| int err = esr2_set_rx_cfg(np, i, rx_cfg); |
| if (err) |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| static int serdes_init_niu_1g_serdes(struct niu *np) |
| { |
| struct niu_link_config *lp = &np->link_config; |
| u16 pll_cfg, pll_sts; |
| int max_retry = 100; |
| u64 uninitialized_var(sig), mask, val; |
| u32 tx_cfg, rx_cfg; |
| unsigned long i; |
| int err; |
| |
| tx_cfg = (PLL_TX_CFG_ENTX | PLL_TX_CFG_SWING_1375MV | |
| PLL_TX_CFG_RATE_HALF); |
| rx_cfg = (PLL_RX_CFG_ENRX | PLL_RX_CFG_TERM_0P8VDDT | |
| PLL_RX_CFG_ALIGN_ENA | PLL_RX_CFG_LOS_LTHRESH | |
| PLL_RX_CFG_RATE_HALF); |
| |
| if (np->port == 0) |
| rx_cfg |= PLL_RX_CFG_EQ_LP_ADAPTIVE; |
| |
| if (lp->loopback_mode == LOOPBACK_PHY) { |
| u16 test_cfg = PLL_TEST_CFG_LOOPBACK_CML_DIS; |
| |
| mdio_write(np, np->port, NIU_ESR2_DEV_ADDR, |
| ESR2_TI_PLL_TEST_CFG_L, test_cfg); |
| |
| tx_cfg |= PLL_TX_CFG_ENTEST; |
| rx_cfg |= PLL_RX_CFG_ENTEST; |
| } |
| |
| /* Initialize PLL for 1G */ |
| pll_cfg = (PLL_CFG_ENPLL | PLL_CFG_MPY_8X); |
| |
| err = mdio_write(np, np->port, NIU_ESR2_DEV_ADDR, |
| ESR2_TI_PLL_CFG_L, pll_cfg); |
| if (err) { |
| netdev_err(np->dev, "NIU Port %d %s() mdio write to ESR2_TI_PLL_CFG_L failed\n", |
| np->port, __func__); |
| return err; |
| } |
| |
| pll_sts = PLL_CFG_ENPLL; |
| |
| err = mdio_write(np, np->port, NIU_ESR2_DEV_ADDR, |
| ESR2_TI_PLL_STS_L, pll_sts); |
| if (err) { |
| netdev_err(np->dev, "NIU Port %d %s() mdio write to ESR2_TI_PLL_STS_L failed\n", |
| np->port, __func__); |
| return err; |
| } |
| |
| udelay(200); |
| |
| /* Initialize all 4 lanes of the SERDES. */ |
| for (i = 0; i < 4; i++) { |
| err = esr2_set_tx_cfg(np, i, tx_cfg); |
| if (err) |
| return err; |
| } |
| |
| for (i = 0; i < 4; i++) { |
| err = esr2_set_rx_cfg(np, i, rx_cfg); |
| if (err) |
| return err; |
| } |
| |
| switch (np->port) { |
| case 0: |
| val = (ESR_INT_SRDY0_P0 | ESR_INT_DET0_P0); |
| mask = val; |
| break; |
| |
| case 1: |
| val = (ESR_INT_SRDY0_P1 | ESR_INT_DET0_P1); |
| mask = val; |
| break; |
| |
| default: |
| return -EINVAL; |
| } |
| |
| while (max_retry--) { |
| sig = nr64(ESR_INT_SIGNALS); |
| if ((sig & mask) == val) |
| break; |
| |
| mdelay(500); |
| } |
| |
| if ((sig & mask) != val) { |
| netdev_err(np->dev, "Port %u signal bits [%08x] are not [%08x]\n", |
| np->port, (int)(sig & mask), (int)val); |
| return -ENODEV; |
| } |
| |
| return 0; |
| } |
| |
| static int serdes_init_niu_10g_serdes(struct niu *np) |
| { |
| struct niu_link_config *lp = &np->link_config; |
| u32 tx_cfg, rx_cfg, pll_cfg, pll_sts; |
| int max_retry = 100; |
| u64 uninitialized_var(sig), mask, val; |
| unsigned long i; |
| int err; |
| |
| tx_cfg = (PLL_TX_CFG_ENTX | PLL_TX_CFG_SWING_1375MV); |
| rx_cfg = (PLL_RX_CFG_ENRX | PLL_RX_CFG_TERM_0P8VDDT | |
| PLL_RX_CFG_ALIGN_ENA | PLL_RX_CFG_LOS_LTHRESH | |
| PLL_RX_CFG_EQ_LP_ADAPTIVE); |
| |
| if (lp->loopback_mode == LOOPBACK_PHY) { |
| u16 test_cfg = PLL_TEST_CFG_LOOPBACK_CML_DIS; |
| |
| mdio_write(np, np->port, NIU_ESR2_DEV_ADDR, |
| ESR2_TI_PLL_TEST_CFG_L, test_cfg); |
| |
| tx_cfg |= PLL_TX_CFG_ENTEST; |
| rx_cfg |= PLL_RX_CFG_ENTEST; |
| } |
| |
| /* Initialize PLL for 10G */ |
| pll_cfg = (PLL_CFG_ENPLL | PLL_CFG_MPY_10X); |
| |
| err = mdio_write(np, np->port, NIU_ESR2_DEV_ADDR, |
| ESR2_TI_PLL_CFG_L, pll_cfg & 0xffff); |
| if (err) { |
| netdev_err(np->dev, "NIU Port %d %s() mdio write to ESR2_TI_PLL_CFG_L failed\n", |
| np->port, __func__); |
| return err; |
| } |
| |
| pll_sts = PLL_CFG_ENPLL; |
| |
| err = mdio_write(np, np->port, NIU_ESR2_DEV_ADDR, |
| ESR2_TI_PLL_STS_L, pll_sts & 0xffff); |
| if (err) { |
| netdev_err(np->dev, "NIU Port %d %s() mdio write to ESR2_TI_PLL_STS_L failed\n", |
| np->port, __func__); |
| return err; |
| } |
| |
| udelay(200); |
| |
| /* Initialize all 4 lanes of the SERDES. */ |
| for (i = 0; i < 4; i++) { |
| err = esr2_set_tx_cfg(np, i, tx_cfg); |
| if (err) |
| return err; |
| } |
| |
| for (i = 0; i < 4; i++) { |
| err = esr2_set_rx_cfg(np, i, rx_cfg); |
| if (err) |
| return err; |
| } |
| |
| /* check if serdes is ready */ |
| |
| switch (np->port) { |
| case 0: |
| mask = ESR_INT_SIGNALS_P0_BITS; |
| val = (ESR_INT_SRDY0_P0 | |
| ESR_INT_DET0_P0 | |
| ESR_INT_XSRDY_P0 | |
| ESR_INT_XDP_P0_CH3 | |
| ESR_INT_XDP_P0_CH2 | |
| ESR_INT_XDP_P0_CH1 | |
| ESR_INT_XDP_P0_CH0); |
| break; |
| |
| case 1: |
| mask = ESR_INT_SIGNALS_P1_BITS; |
| val = (ESR_INT_SRDY0_P1 | |
| ESR_INT_DET0_P1 | |
| ESR_INT_XSRDY_P1 | |
| ESR_INT_XDP_P1_CH3 | |
| ESR_INT_XDP_P1_CH2 | |
| ESR_INT_XDP_P1_CH1 | |
| ESR_INT_XDP_P1_CH0); |
| break; |
| |
| default: |
| return -EINVAL; |
| } |
| |
| while (max_retry--) { |
| sig = nr64(ESR_INT_SIGNALS); |
| if ((sig & mask) == val) |
| break; |
| |
| mdelay(500); |
| } |
| |
| if ((sig & mask) != val) { |
| pr_info("NIU Port %u signal bits [%08x] are not [%08x] for 10G...trying 1G\n", |
| np->port, (int)(sig & mask), (int)val); |
| |
| /* 10G failed, try initializing at 1G */ |
| err = serdes_init_niu_1g_serdes(np); |
| if (!err) { |
| np->flags &= ~NIU_FLAGS_10G; |
| np->mac_xcvr = MAC_XCVR_PCS; |
| } else { |
| netdev_err(np->dev, "Port %u 10G/1G SERDES Link Failed\n", |
| np->port); |
| return -ENODEV; |
| } |
| } |
| return 0; |
| } |
| |
| static int esr_read_rxtx_ctrl(struct niu *np, unsigned long chan, u32 *val) |
| { |
| int err; |
| |
| err = mdio_read(np, np->port, NIU_ESR_DEV_ADDR, ESR_RXTX_CTRL_L(chan)); |
| if (err >= 0) { |
| *val = (err & 0xffff); |
| err = mdio_read(np, np->port, NIU_ESR_DEV_ADDR, |
| ESR_RXTX_CTRL_H(chan)); |
| if (err >= 0) |
| *val |= ((err & 0xffff) << 16); |
| err = 0; |
| } |
| return err; |
| } |
| |
| static int esr_read_glue0(struct niu *np, unsigned long chan, u32 *val) |
| { |
| int err; |
| |
| err = mdio_read(np, np->port, NIU_ESR_DEV_ADDR, |
| ESR_GLUE_CTRL0_L(chan)); |
| if (err >= 0) { |
| *val = (err & 0xffff); |
| err = mdio_read(np, np->port, NIU_ESR_DEV_ADDR, |
| ESR_GLUE_CTRL0_H(chan)); |
| if (err >= 0) { |
| *val |= ((err & 0xffff) << 16); |
| err = 0; |
| } |
| } |
| return err; |
| } |
| |
| static int esr_read_reset(struct niu *np, u32 *val) |
| { |
| int err; |
| |
| err = mdio_read(np, np->port, NIU_ESR_DEV_ADDR, |
| ESR_RXTX_RESET_CTRL_L); |
| if (err >= 0) { |
| *val = (err & 0xffff); |
| err = mdio_read(np, np->port, NIU_ESR_DEV_ADDR, |
| ESR_RXTX_RESET_CTRL_H); |
| if (err >= 0) { |
| *val |= ((err & 0xffff) << 16); |
| err = 0; |
| } |
| } |
| return err; |
| } |
| |
| static int esr_write_rxtx_ctrl(struct niu *np, unsigned long chan, u32 val) |
| { |
| int err; |
| |
| err = mdio_write(np, np->port, NIU_ESR_DEV_ADDR, |
| ESR_RXTX_CTRL_L(chan), val & 0xffff); |
| if (!err) |
| err = mdio_write(np, np->port, NIU_ESR_DEV_ADDR, |
| ESR_RXTX_CTRL_H(chan), (val >> 16)); |
| return err; |
| } |
| |
| static int esr_write_glue0(struct niu *np, unsigned long chan, u32 val) |
| { |
| int err; |
| |
| err = mdio_write(np, np->port, NIU_ESR_DEV_ADDR, |
| ESR_GLUE_CTRL0_L(chan), val & 0xffff); |
| if (!err) |
| err = mdio_write(np, np->port, NIU_ESR_DEV_ADDR, |
| ESR_GLUE_CTRL0_H(chan), (val >> 16)); |
| return err; |
| } |
| |
| static int esr_reset(struct niu *np) |
| { |
| u32 uninitialized_var(reset); |
| int err; |
| |
| err = mdio_write(np, np->port, NIU_ESR_DEV_ADDR, |
| ESR_RXTX_RESET_CTRL_L, 0x0000); |
| if (err) |
| return err; |
| err = mdio_write(np, np->port, NIU_ESR_DEV_ADDR, |
| ESR_RXTX_RESET_CTRL_H, 0xffff); |
| if (err) |
| return err; |
| udelay(200); |
| |
| err = mdio_write(np, np->port, NIU_ESR_DEV_ADDR, |
| ESR_RXTX_RESET_CTRL_L, 0xffff); |
| if (err) |
| return err; |
| udelay(200); |
| |
| err = mdio_write(np, np->port, NIU_ESR_DEV_ADDR, |
| ESR_RXTX_RESET_CTRL_H, 0x0000); |
| if (err) |
| return err; |
| udelay(200); |
| |
| err = esr_read_reset(np, &reset); |
| if (err) |
| return err; |
| if (reset != 0) { |
| netdev_err(np->dev, "Port %u ESR_RESET did not clear [%08x]\n", |
| np->port, reset); |
| return -ENODEV; |
| } |
| |
| return 0; |
| } |
| |
| static int serdes_init_10g(struct niu *np) |
| { |
| struct niu_link_config *lp = &np->link_config; |
| unsigned long ctrl_reg, test_cfg_reg, i; |
| u64 ctrl_val, test_cfg_val, sig, mask, val; |
| int err; |
| |
| switch (np->port) { |
| case 0: |
| ctrl_reg = ENET_SERDES_0_CTRL_CFG; |
| test_cfg_reg = ENET_SERDES_0_TEST_CFG; |
| break; |
| case 1: |
| ctrl_reg = ENET_SERDES_1_CTRL_CFG; |
| test_cfg_reg = ENET_SERDES_1_TEST_CFG; |
| break; |
| |
| default: |
| return -EINVAL; |
| } |
| ctrl_val = (ENET_SERDES_CTRL_SDET_0 | |
| ENET_SERDES_CTRL_SDET_1 | |
| ENET_SERDES_CTRL_SDET_2 | |
| ENET_SERDES_CTRL_SDET_3 | |
| (0x5 << ENET_SERDES_CTRL_EMPH_0_SHIFT) | |
| (0x5 << ENET_SERDES_CTRL_EMPH_1_SHIFT) | |
| (0x5 << ENET_SERDES_CTRL_EMPH_2_SHIFT) | |
| (0x5 << ENET_SERDES_CTRL_EMPH_3_SHIFT) | |
| (0x1 << ENET_SERDES_CTRL_LADJ_0_SHIFT) | |
| (0x1 << ENET_SERDES_CTRL_LADJ_1_SHIFT) | |
| (0x1 << ENET_SERDES_CTRL_LADJ_2_SHIFT) | |
| (0x1 << ENET_SERDES_CTRL_LADJ_3_SHIFT)); |
| test_cfg_val = 0; |
| |
| if (lp->loopback_mode == LOOPBACK_PHY) { |
| test_cfg_val |= ((ENET_TEST_MD_PAD_LOOPBACK << |
| ENET_SERDES_TEST_MD_0_SHIFT) | |
| (ENET_TEST_MD_PAD_LOOPBACK << |
| ENET_SERDES_TEST_MD_1_SHIFT) | |
| (ENET_TEST_MD_PAD_LOOPBACK << |
| ENET_SERDES_TEST_MD_2_SHIFT) | |
| (ENET_TEST_MD_PAD_LOOPBACK << |
| ENET_SERDES_TEST_MD_3_SHIFT)); |
| } |
| |
| nw64(ctrl_reg, ctrl_val); |
| nw64(test_cfg_reg, test_cfg_val); |
| |
| /* Initialize all 4 lanes of the SERDES. */ |
| for (i = 0; i < 4; i++) { |
| u32 rxtx_ctrl, glue0; |
| |
| err = esr_read_rxtx_ctrl(np, i, &rxtx_ctrl); |
| if (err) |
| return err; |
| err = esr_read_glue0(np, i, &glue0); |
| if (err) |
| return err; |
| |
| rxtx_ctrl &= ~(ESR_RXTX_CTRL_VMUXLO); |
| rxtx_ctrl |= (ESR_RXTX_CTRL_ENSTRETCH | |
| (2 << ESR_RXTX_CTRL_VMUXLO_SHIFT)); |
| |
| glue0 &= ~(ESR_GLUE_CTRL0_SRATE | |
| ESR_GLUE_CTRL0_THCNT | |
| ESR_GLUE_CTRL0_BLTIME); |
| glue0 |= (ESR_GLUE_CTRL0_RXLOSENAB | |
| (0xf << ESR_GLUE_CTRL0_SRATE_SHIFT) | |
| (0xff << ESR_GLUE_CTRL0_THCNT_SHIFT) | |
| (BLTIME_300_CYCLES << |
| ESR_GLUE_CTRL0_BLTIME_SHIFT)); |
| |
| err = esr_write_rxtx_ctrl(np, i, rxtx_ctrl); |
| if (err) |
| return err; |
| err = esr_write_glue0(np, i, glue0); |
| if (err) |
| return err; |
| } |
| |
| err = esr_reset(np); |
| if (err) |
| return err; |
| |
| sig = nr64(ESR_INT_SIGNALS); |
| switch (np->port) { |
| case 0: |
| mask = ESR_INT_SIGNALS_P0_BITS; |
| val = (ESR_INT_SRDY0_P0 | |
| ESR_INT_DET0_P0 | |
| ESR_INT_XSRDY_P0 | |
| ESR_INT_XDP_P0_CH3 | |
| ESR_INT_XDP_P0_CH2 | |
| ESR_INT_XDP_P0_CH1 | |
| ESR_INT_XDP_P0_CH0); |
| break; |
| |
| case 1: |
| mask = ESR_INT_SIGNALS_P1_BITS; |
| val = (ESR_INT_SRDY0_P1 | |
| ESR_INT_DET0_P1 | |
| ESR_INT_XSRDY_P1 | |
| ESR_INT_XDP_P1_CH3 | |
| ESR_INT_XDP_P1_CH2 | |
| ESR_INT_XDP_P1_CH1 | |
| ESR_INT_XDP_P1_CH0); |
| break; |
| |
| default: |
| return -EINVAL; |
| } |
| |
| if ((sig & mask) != val) { |
| if (np->flags & NIU_FLAGS_HOTPLUG_PHY) { |
| np->flags &= ~NIU_FLAGS_HOTPLUG_PHY_PRESENT; |
| return 0; |
| } |
| netdev_err(np->dev, "Port %u signal bits [%08x] are not [%08x]\n", |
| np->port, (int)(sig & mask), (int)val); |
| return -ENODEV; |
| } |
| if (np->flags & NIU_FLAGS_HOTPLUG_PHY) |
| np->flags |= NIU_FLAGS_HOTPLUG_PHY_PRESENT; |
| return 0; |
| } |
| |
| static int serdes_init_1g(struct niu *np) |
| { |
| u64 val; |
| |
| val = nr64(ENET_SERDES_1_PLL_CFG); |
| val &= ~ENET_SERDES_PLL_FBDIV2; |
| switch (np->port) { |
| case 0: |
| val |= ENET_SERDES_PLL_HRATE0; |
| break; |
| case 1: |
| val |= ENET_SERDES_PLL_HRATE1; |
| break; |
| case 2: |
| val |= ENET_SERDES_PLL_HRATE2; |
| break; |
| case 3: |
| val |= ENET_SERDES_PLL_HRATE3; |
| break; |
| default: |
| return -EINVAL; |
| } |
| nw64(ENET_SERDES_1_PLL_CFG, val); |
| |
| return 0; |
| } |
| |
| static int serdes_init_1g_serdes(struct niu *np) |
| { |
| struct niu_link_config *lp = &np->link_config; |
| unsigned long ctrl_reg, test_cfg_reg, pll_cfg, i; |
| u64 ctrl_val, test_cfg_val, sig, mask, val; |
| int err; |
| u64 reset_val, val_rd; |
| |
| val = ENET_SERDES_PLL_HRATE0 | ENET_SERDES_PLL_HRATE1 | |
| ENET_SERDES_PLL_HRATE2 | ENET_SERDES_PLL_HRATE3 | |
| ENET_SERDES_PLL_FBDIV0; |
| switch (np->port) { |
| case 0: |
| reset_val = ENET_SERDES_RESET_0; |
| ctrl_reg = ENET_SERDES_0_CTRL_CFG; |
| test_cfg_reg = ENET_SERDES_0_TEST_CFG; |
| pll_cfg = ENET_SERDES_0_PLL_CFG; |
| break; |
| case 1: |
| reset_val = ENET_SERDES_RESET_1; |
| ctrl_reg = ENET_SERDES_1_CTRL_CFG; |
| test_cfg_reg = ENET_SERDES_1_TEST_CFG; |
| pll_cfg = ENET_SERDES_1_PLL_CFG; |
| break; |
| |
| default: |
| return -EINVAL; |
| } |
| ctrl_val = (ENET_SERDES_CTRL_SDET_0 | |
| ENET_SERDES_CTRL_SDET_1 | |
| ENET_SERDES_CTRL_SDET_2 | |
| ENET_SERDES_CTRL_SDET_3 | |
| (0x5 << ENET_SERDES_CTRL_EMPH_0_SHIFT) | |
| (0x5 << ENET_SERDES_CTRL_EMPH_1_SHIFT) | |
| (0x5 << ENET_SERDES_CTRL_EMPH_2_SHIFT) | |
| (0x5 << ENET_SERDES_CTRL_EMPH_3_SHIFT) | |
| (0x1 << ENET_SERDES_CTRL_LADJ_0_SHIFT) | |
| (0x1 << ENET_SERDES_CTRL_LADJ_1_SHIFT) | |
| (0x1 << ENET_SERDES_CTRL_LADJ_2_SHIFT) | |
| (0x1 << ENET_SERDES_CTRL_LADJ_3_SHIFT)); |
| test_cfg_val = 0; |
| |
| if (lp->loopback_mode == LOOPBACK_PHY) { |
| test_cfg_val |= ((ENET_TEST_MD_PAD_LOOPBACK << |
| ENET_SERDES_TEST_MD_0_SHIFT) | |
| (ENET_TEST_MD_PAD_LOOPBACK << |
| ENET_SERDES_TEST_MD_1_SHIFT) | |
| (ENET_TEST_MD_PAD_LOOPBACK << |
| ENET_SERDES_TEST_MD_2_SHIFT) | |
| (ENET_TEST_MD_PAD_LOOPBACK << |
| ENET_SERDES_TEST_MD_3_SHIFT)); |
| } |
| |
| nw64(ENET_SERDES_RESET, reset_val); |
| mdelay(20); |
| val_rd = nr64(ENET_SERDES_RESET); |
| val_rd &= ~reset_val; |
| nw64(pll_cfg, val); |
| nw64(ctrl_reg, ctrl_val); |
| nw64(test_cfg_reg, test_cfg_val); |
| nw64(ENET_SERDES_RESET, val_rd); |
| mdelay(2000); |
| |
| /* Initialize all 4 lanes of the SERDES. */ |
| for (i = 0; i < 4; i++) { |
| u32 rxtx_ctrl, glue0; |
| |
| err = esr_read_rxtx_ctrl(np, i, &rxtx_ctrl); |
| if (err) |
| return err; |
| err = esr_read_glue0(np, i, &glue0); |
| if (err) |
| return err; |
| |
| rxtx_ctrl &= ~(ESR_RXTX_CTRL_VMUXLO); |
| rxtx_ctrl |= (ESR_RXTX_CTRL_ENSTRETCH | |
| (2 << ESR_RXTX_CTRL_VMUXLO_SHIFT)); |
| |
| glue0 &= ~(ESR_GLUE_CTRL0_SRATE | |
| ESR_GLUE_CTRL0_THCNT | |
| ESR_GLUE_CTRL0_BLTIME); |
| glue0 |= (ESR_GLUE_CTRL0_RXLOSENAB | |
| (0xf << ESR_GLUE_CTRL0_SRATE_SHIFT) | |
| (0xff << ESR_GLUE_CTRL0_THCNT_SHIFT) | |
| (BLTIME_300_CYCLES << |
| ESR_GLUE_CTRL0_BLTIME_SHIFT)); |
| |
| err = esr_write_rxtx_ctrl(np, i, rxtx_ctrl); |
| if (err) |
| return err; |
| err = esr_write_glue0(np, i, glue0); |
| if (err) |
| return err; |
| } |
| |
| |
| sig = nr64(ESR_INT_SIGNALS); |
| switch (np->port) { |
| case 0: |
| val = (ESR_INT_SRDY0_P0 | ESR_INT_DET0_P0); |
| mask = val; |
| break; |
| |
| case 1: |
| val = (ESR_INT_SRDY0_P1 | ESR_INT_DET0_P1); |
| mask = val; |
| break; |
| |
| default: |
| return -EINVAL; |
| } |
| |
| if ((sig & mask) != val) { |
| netdev_err(np->dev, "Port %u signal bits [%08x] are not [%08x]\n", |
| np->port, (int)(sig & mask), (int)val); |
| return -ENODEV; |
| } |
| |
| return 0; |
| } |
| |
| static int link_status_1g_serdes(struct niu *np, int *link_up_p) |
| { |
| struct niu_link_config *lp = &np->link_config; |
| int link_up; |
| u64 val; |
| u16 current_speed; |
| unsigned long flags; |
| u8 current_duplex; |
| |
| link_up = 0; |
| current_speed = SPEED_INVALID; |
| current_duplex = DUPLEX_INVALID; |
| |
| spin_lock_irqsave(&np->lock, flags); |
| |
| val = nr64_pcs(PCS_MII_STAT); |
| |
| if (val & PCS_MII_STAT_LINK_STATUS) { |
| link_up = 1; |
| current_speed = SPEED_1000; |
| current_duplex = DUPLEX_FULL; |
| } |
| |
| lp->active_speed = current_speed; |
| lp->active_duplex = current_duplex; |
| spin_unlock_irqrestore(&np->lock, flags); |
| |
| *link_up_p = link_up; |
| return 0; |
| } |
| |
| static int link_status_10g_serdes(struct niu *np, int *link_up_p) |
| { |
| unsigned long flags; |
| struct niu_link_config *lp = &np->link_config; |
| int link_up = 0; |
| int link_ok = 1; |
| u64 val, val2; |
| u16 current_speed; |
| u8 current_duplex; |
| |
| if (!(np->flags & NIU_FLAGS_10G)) |
| return link_status_1g_serdes(np, link_up_p); |
| |
| current_speed = SPEED_INVALID; |
| current_duplex = DUPLEX_INVALID; |
| spin_lock_irqsave(&np->lock, flags); |
| |
| val = nr64_xpcs(XPCS_STATUS(0)); |
| val2 = nr64_mac(XMAC_INTER2); |
| if (val2 & 0x01000000) |
| link_ok = 0; |
| |
| if ((val & 0x1000ULL) && link_ok) { |
| link_up = 1; |
| current_speed = SPEED_10000; |
| current_duplex = DUPLEX_FULL; |
| } |
| lp->active_speed = current_speed; |
| lp->active_duplex = current_duplex; |
| spin_unlock_irqrestore(&np->lock, flags); |
| *link_up_p = link_up; |
| return 0; |
| } |
| |
| static int link_status_mii(struct niu *np, int *link_up_p) |
| { |
| struct niu_link_config *lp = &np->link_config; |
| int err; |
| int bmsr, advert, ctrl1000, stat1000, lpa, bmcr, estatus; |
| int supported, advertising, active_speed, active_duplex; |
| |
| err = mii_read(np, np->phy_addr, MII_BMCR); |
| if (unlikely(err < 0)) |
| return err; |
| bmcr = err; |
| |
| err = mii_read(np, np->phy_addr, MII_BMSR); |
| if (unlikely(err < 0)) |
| return err; |
| bmsr = err; |
| |
| err = mii_read(np, np->phy_addr, MII_ADVERTISE); |
| if (unlikely(err < 0)) |
| return err; |
| advert = err; |
| |
| err = mii_read(np, np->phy_addr, MII_LPA); |
| if (unlikely(err < 0)) |
| return err; |
| lpa = err; |
| |
| if (likely(bmsr & BMSR_ESTATEN)) { |
| err = mii_read(np, np->phy_addr, MII_ESTATUS); |
| if (unlikely(err < 0)) |
| return err; |
| estatus = err; |
| |
| err = mii_read(np, np->phy_addr, MII_CTRL1000); |
| if (unlikely(err < 0)) |
| return err; |
| ctrl1000 = err; |
| |
| err = mii_read(np, np->phy_addr, MII_STAT1000); |
| if (unlikely(err < 0)) |
| return err; |
| stat1000 = err; |
| } else |
| estatus = ctrl1000 = stat1000 = 0; |
| |
| supported = 0; |
| if (bmsr & BMSR_ANEGCAPABLE) |
| supported |= SUPPORTED_Autoneg; |
| if (bmsr & BMSR_10HALF) |
| supported |= SUPPORTED_10baseT_Half; |
| if (bmsr & BMSR_10FULL) |
| supported |= SUPPORTED_10baseT_Full; |
| if (bmsr & BMSR_100HALF) |
| supported |= SUPPORTED_100baseT_Half; |
| if (bmsr & BMSR_100FULL) |
| supported |= SUPPORTED_100baseT_Full; |
| if (estatus & ESTATUS_1000_THALF) |
| supported |= SUPPORTED_1000baseT_Half; |
| if (estatus & ESTATUS_1000_TFULL) |
| supported |= SUPPORTED_1000baseT_Full; |
| lp->supported = supported; |
| |
| advertising = 0; |
| if (advert & ADVERTISE_10HALF) |
| advertising |= ADVERTISED_10baseT_Half; |
| if (advert & ADVERTISE_10FULL) |
| advertising |= ADVERTISED_10baseT_Full; |
| if (advert & ADVERTISE_100HALF) |
| advertising |= ADVERTISED_100baseT_Half; |
| if (advert & ADVERTISE_100FULL) |
| advertising |= ADVERTISED_100baseT_Full; |
| if (ctrl1000 & ADVERTISE_1000HALF) |
| advertising |= ADVERTISED_1000baseT_Half; |
| if (ctrl1000 & ADVERTISE_1000FULL) |
| advertising |= ADVERTISED_1000baseT_Full; |
| |
| if (bmcr & BMCR_ANENABLE) { |
| int neg, neg1000; |
| |
| lp->active_autoneg = 1; |
| advertising |= ADVERTISED_Autoneg; |
| |
| neg = advert & lpa; |
| neg1000 = (ctrl1000 << 2) & stat1000; |
| |
| if (neg1000 & (LPA_1000FULL | LPA_1000HALF)) |
| active_speed = SPEED_1000; |
| else if (neg & LPA_100) |
| active_speed = SPEED_100; |
| else if (neg & (LPA_10HALF | LPA_10FULL)) |
| active_speed = SPEED_10; |
| else |
| active_speed = SPEED_INVALID; |
| |
| if ((neg1000 & LPA_1000FULL) || (neg & LPA_DUPLEX)) |
| active_duplex = DUPLEX_FULL; |
| else if (active_speed != SPEED_INVALID) |
| active_duplex = DUPLEX_HALF; |
| else |
| active_duplex = DUPLEX_INVALID; |
| } else { |
| lp->active_autoneg = 0; |
| |
| if ((bmcr & BMCR_SPEED1000) && !(bmcr & BMCR_SPEED100)) |
| active_speed = SPEED_1000; |
| else if (bmcr & BMCR_SPEED100) |
| active_speed = SPEED_100; |
| else |
| active_speed = SPEED_10; |
| |
| if (bmcr & BMCR_FULLDPLX) |
| active_duplex = DUPLEX_FULL; |
| else |
| active_duplex = DUPLEX_HALF; |
| } |
| |
| lp->active_advertising = advertising; |
| lp->active_speed = active_speed; |
| lp->active_duplex = active_duplex; |
| *link_up_p = !!(bmsr & BMSR_LSTATUS); |
| |
| return 0; |
| } |
| |
| static int link_status_1g_rgmii(struct niu *np, int *link_up_p) |
| { |
| struct niu_link_config *lp = &np->link_config; |
| u16 current_speed, bmsr; |
| unsigned long flags; |
| u8 current_duplex; |
| int err, link_up; |
| |
| link_up = 0; |
| current_speed = SPEED_INVALID; |
| current_duplex = DUPLEX_INVALID; |
| |
| spin_lock_irqsave(&np->lock, flags); |
| |
| err = -EINVAL; |
| |
| err = mii_read(np, np->phy_addr, MII_BMSR); |
| if (err < 0) |
| goto out; |
| |
| bmsr = err; |
| if (bmsr & BMSR_LSTATUS) { |
| u16 adv, lpa, common, estat; |
| |
| err = mii_read(np, np->phy_addr, MII_ADVERTISE); |
| if (err < 0) |
| goto out; |
| adv = err; |
| |
| err = mii_read(np, np->phy_addr, MII_LPA); |
| if (err < 0) |
| goto out; |
| lpa = err; |
| |
| common = adv & lpa; |
| |
| err = mii_read(np, np->phy_addr, MII_ESTATUS); |
| if (err < 0) |
| goto out; |
| estat = err; |
| link_up = 1; |
| current_speed = SPEED_1000; |
| current_duplex = DUPLEX_FULL; |
| |
| } |
| lp->active_speed = current_speed; |
| lp->active_duplex = current_duplex; |
| err = 0; |
| |
| out: |
| spin_unlock_irqrestore(&np->lock, flags); |
| |
| *link_up_p = link_up; |
| return err; |
| } |
| |
| static int link_status_1g(struct niu *np, int *link_up_p) |
| { |
| struct niu_link_config *lp = &np->link_config; |
| unsigned long flags; |
| int err; |
| |
| spin_lock_irqsave(&np->lock, flags); |
| |
| err = link_status_mii(np, link_up_p); |
| lp->supported |= SUPPORTED_TP; |
| lp->active_advertising |= ADVERTISED_TP; |
| |
| spin_unlock_irqrestore(&np->lock, flags); |
| return err; |
| } |
| |
| static int bcm8704_reset(struct niu *np) |
| { |
| int err, limit; |
| |
| err = mdio_read(np, np->phy_addr, |
| BCM8704_PHYXS_DEV_ADDR, MII_BMCR); |
| if (err < 0 || err == 0xffff) |
| return err; |
| err |= BMCR_RESET; |
| err = mdio_write(np, np->phy_addr, BCM8704_PHYXS_DEV_ADDR, |
| MII_BMCR, err); |
| if (err) |
| return err; |
| |
| limit = 1000; |
| while (--limit >= 0) { |
| err = mdio_read(np, np->phy_addr, |
| BCM8704_PHYXS_DEV_ADDR, MII_BMCR); |
| if (err < 0) |
| return err; |
| if (!(err & BMCR_RESET)) |
| break; |
| } |
| if (limit < 0) { |
| netdev_err(np->dev, "Port %u PHY will not reset (bmcr=%04x)\n", |
| np->port, (err & 0xffff)); |
| return -ENODEV; |
| } |
| return 0; |
| } |
| |
| /* When written, certain PHY registers need to be read back twice |
| * in order for the bits to settle properly. |
| */ |
| static int bcm8704_user_dev3_readback(struct niu *np, int reg) |
| { |
| int err = mdio_read(np, np->phy_addr, BCM8704_USER_DEV3_ADDR, reg); |
| if (err < 0) |
| return err; |
| err = mdio_read(np, np->phy_addr, BCM8704_USER_DEV3_ADDR, reg); |
| if (err < 0) |
| return err; |
| return 0; |
| } |
| |
| static int bcm8706_init_user_dev3(struct niu *np) |
| { |
| int err; |
| |
| |
| err = mdio_read(np, np->phy_addr, BCM8704_USER_DEV3_ADDR, |
| BCM8704_USER_OPT_DIGITAL_CTRL); |
| if (err < 0) |
| return err; |
| err &= ~USER_ODIG_CTRL_GPIOS; |
| err |= (0x3 << USER_ODIG_CTRL_GPIOS_SHIFT); |
| err |= USER_ODIG_CTRL_RESV2; |
| err = mdio_write(np, np->phy_addr, BCM8704_USER_DEV3_ADDR, |
| BCM8704_USER_OPT_DIGITAL_CTRL, err); |
| if (err) |
| return err; |
| |
| mdelay(1000); |
| |
| return 0; |
| } |
| |
| static int bcm8704_init_user_dev3(struct niu *np) |
| { |
| int err; |
| |
| err = mdio_write(np, np->phy_addr, |
| BCM8704_USER_DEV3_ADDR, BCM8704_USER_CONTROL, |
| (USER_CONTROL_OPTXRST_LVL | |
| USER_CONTROL_OPBIASFLT_LVL | |
| USER_CONTROL_OBTMPFLT_LVL | |
| USER_CONTROL_OPPRFLT_LVL | |
| USER_CONTROL_OPTXFLT_LVL | |
| USER_CONTROL_OPRXLOS_LVL | |
| USER_CONTROL_OPRXFLT_LVL | |
| USER_CONTROL_OPTXON_LVL | |
| (0x3f << USER_CONTROL_RES1_SHIFT))); |
| if (err) |
| return err; |
| |
| err = mdio_write(np, np->phy_addr, |
| BCM8704_USER_DEV3_ADDR, BCM8704_USER_PMD_TX_CONTROL, |
| (USER_PMD_TX_CTL_XFP_CLKEN | |
| (1 << USER_PMD_TX_CTL_TX_DAC_TXD_SH) | |
| (2 << USER_PMD_TX_CTL_TX_DAC_TXCK_SH) | |
| USER_PMD_TX_CTL_TSCK_LPWREN)); |
| if (err) |
| return err; |
| |
| err = bcm8704_user_dev3_readback(np, BCM8704_USER_CONTROL); |
| if (err) |
| return err; |
| err = bcm8704_user_dev3_readback(np, BCM8704_USER_PMD_TX_CONTROL); |
| if (err) |
| return err; |
| |
| err = mdio_read(np, np->phy_addr, BCM8704_USER_DEV3_ADDR, |
| BCM8704_USER_OPT_DIGITAL_CTRL); |
| if (err < 0) |
| return err; |
| err &= ~USER_ODIG_CTRL_GPIOS; |
| err |= (0x3 << USER_ODIG_CTRL_GPIOS_SHIFT); |
| err = mdio_write(np, np->phy_addr, BCM8704_USER_DEV3_ADDR, |
| BCM8704_USER_OPT_DIGITAL_CTRL, err); |
| if (err) |
| return err; |
| |
| mdelay(1000); |
| |
| return 0; |
| } |
| |
| static int mrvl88x2011_act_led(struct niu *np, int val) |
| { |
| int err; |
| |
| err = mdio_read(np, np->phy_addr, MRVL88X2011_USER_DEV2_ADDR, |
| MRVL88X2011_LED_8_TO_11_CTL); |
| if (err < 0) |
| return err; |
| |
| err &= ~MRVL88X2011_LED(MRVL88X2011_LED_ACT,MRVL88X2011_LED_CTL_MASK); |
| err |= MRVL88X2011_LED(MRVL88X2011_LED_ACT,val); |
| |
| return mdio_write(np, np->phy_addr, MRVL88X2011_USER_DEV2_ADDR, |
| MRVL88X2011_LED_8_TO_11_CTL, err); |
| } |
| |
| static int mrvl88x2011_led_blink_rate(struct niu *np, int rate) |
| { |
| int err; |
| |
| err = mdio_read(np, np->phy_addr, MRVL88X2011_USER_DEV2_ADDR, |
| MRVL88X2011_LED_BLINK_CTL); |
| if (err >= 0) { |
| err &= ~MRVL88X2011_LED_BLKRATE_MASK; |
| err |= (rate << 4); |
| |
| err = mdio_write(np, np->phy_addr, MRVL88X2011_USER_DEV2_ADDR, |
| MRVL88X2011_LED_BLINK_CTL, err); |
| } |
| |
| return err; |
| } |
| |
| static int xcvr_init_10g_mrvl88x2011(struct niu *np) |
| { |
| int err; |
| |
| /* Set LED functions */ |
| err = mrvl88x2011_led_blink_rate(np, MRVL88X2011_LED_BLKRATE_134MS); |
| if (err) |
| return err; |
| |
| /* led activity */ |
| err = mrvl88x2011_act_led(np, MRVL88X2011_LED_CTL_OFF); |
| if (err) |
| return err; |
| |
| err = mdio_read(np, np->phy_addr, MRVL88X2011_USER_DEV3_ADDR, |
| MRVL88X2011_GENERAL_CTL); |
| if (err < 0) |
| return err; |
| |
| err |= MRVL88X2011_ENA_XFPREFCLK; |
| |
| err = mdio_write(np, np->phy_addr, MRVL88X2011_USER_DEV3_ADDR, |
| MRVL88X2011_GENERAL_CTL, err); |
| if (err < 0) |
| return err; |
| |
| err = mdio_read(np, np->phy_addr, MRVL88X2011_USER_DEV1_ADDR, |
| MRVL88X2011_PMA_PMD_CTL_1); |
| if (err < 0) |
| return err; |
| |
| if (np->link_config.loopback_mode == LOOPBACK_MAC) |
| err |= MRVL88X2011_LOOPBACK; |
| else |
| err &= ~MRVL88X2011_LOOPBACK; |
| |
| err = mdio_write(np, np->phy_addr, MRVL88X2011_USER_DEV1_ADDR, |
| MRVL88X2011_PMA_PMD_CTL_1, err); |
| if (err < 0) |
| return err; |
| |
| /* Enable PMD */ |
| return mdio_write(np, np->phy_addr, MRVL88X2011_USER_DEV1_ADDR, |
| MRVL88X2011_10G_PMD_TX_DIS, MRVL88X2011_ENA_PMDTX); |
| } |
| |
| |
| static int xcvr_diag_bcm870x(struct niu *np) |
| { |
| u16 analog_stat0, tx_alarm_status; |
| int err = 0; |
| |
| #if 1 |
| err = mdio_read(np, np->phy_addr, BCM8704_PMA_PMD_DEV_ADDR, |
| MII_STAT1000); |
| if (err < 0) |
| return err; |
| pr_info("Port %u PMA_PMD(MII_STAT1000) [%04x]\n", np->port, err); |
| |
| err = mdio_read(np, np->phy_addr, BCM8704_USER_DEV3_ADDR, 0x20); |
| if (err < 0) |
| return err; |
| pr_info("Port %u USER_DEV3(0x20) [%04x]\n", np->port, err); |
| |
| err = mdio_read(np, np->phy_addr, BCM8704_PHYXS_DEV_ADDR, |
| MII_NWAYTEST); |
| if (err < 0) |
| return err; |
| pr_info("Port %u PHYXS(MII_NWAYTEST) [%04x]\n", np->port, err); |
| #endif |
| |
| /* XXX dig this out it might not be so useful XXX */ |
| err = mdio_read(np, np->phy_addr, BCM8704_USER_DEV3_ADDR, |
| BCM8704_USER_ANALOG_STATUS0); |
| if (err < 0) |
| return err; |
| err = mdio_read(np, np->phy_addr, BCM8704_USER_DEV3_ADDR, |
| BCM8704_USER_ANALOG_STATUS0); |
| if (err < 0) |
| return err; |
| analog_stat0 = err; |
| |
| err = mdio_read(np, np->phy_addr, BCM8704_USER_DEV3_ADDR, |
| BCM8704_USER_TX_ALARM_STATUS); |
| if (err < 0) |
| return err; |
| err = mdio_read(np, np->phy_addr, BCM8704_USER_DEV3_ADDR, |
| BCM8704_USER_TX_ALARM_STATUS); |
| if (err < 0) |
| return err; |
| tx_alarm_status = err; |
| |
| if (analog_stat0 != 0x03fc) { |
| if ((analog_stat0 == 0x43bc) && (tx_alarm_status != 0)) { |
| pr_info("Port %u cable not connected or bad cable\n", |
| np->port); |
| } else if (analog_stat0 == 0x639c) { |
| pr_info("Port %u optical module is bad or missing\n", |
| np->port); |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int xcvr_10g_set_lb_bcm870x(struct niu *np) |
| { |
| struct niu_link_config *lp = &np->link_config; |
| int err; |
| |
| err = mdio_read(np, np->phy_addr, BCM8704_PCS_DEV_ADDR, |
| MII_BMCR); |
| if (err < 0) |
| return err; |
| |
| err &= ~BMCR_LOOPBACK; |
| |
| if (lp->loopback_mode == LOOPBACK_MAC) |
| err |= BMCR_LOOPBACK; |
| |
| err = mdio_write(np, np->phy_addr, BCM8704_PCS_DEV_ADDR, |
| MII_BMCR, err); |
| if (err) |
| return err; |
| |
| return 0; |
| } |
| |
| static int xcvr_init_10g_bcm8706(struct niu *np) |
| { |
| int err = 0; |
| u64 val; |
| |
| if ((np->flags & NIU_FLAGS_HOTPLUG_PHY) && |
| (np->flags & NIU_FLAGS_HOTPLUG_PHY_PRESENT) == 0) |
| return err; |
| |
| val = nr64_mac(XMAC_CONFIG); |
| val &= ~XMAC_CONFIG_LED_POLARITY; |
| val |= XMAC_CONFIG_FORCE_LED_ON; |
| nw64_mac(XMAC_CONFIG, val); |
| |
| val = nr64(MIF_CONFIG); |
| val |= MIF_CONFIG_INDIRECT_MODE; |
| nw64(MIF_CONFIG, val); |
| |
| err = bcm8704_reset(np); |
| if (err) |
| return err; |
| |
| err = xcvr_10g_set_lb_bcm870x(np); |
| if (err) |
| return err; |
| |
| err = bcm8706_init_user_dev3(np); |
| if (err) |
| return err; |
| |
| err = xcvr_diag_bcm870x(np); |
| if (err) |
| return err; |
| |
| return 0; |
| } |
| |
| static int xcvr_init_10g_bcm8704(struct niu *np) |
| { |
| int err; |
| |
| err = bcm8704_reset(np); |
| if (err) |
| return err; |
| |
| err = bcm8704_init_user_dev3(np); |
| if (err) |
| return err; |
| |
| err = xcvr_10g_set_lb_bcm870x(np); |
| if (err) |
| return err; |
| |
| err = xcvr_diag_bcm870x(np); |
| if (err) |
| return err; |
| |
| return 0; |
| } |
| |
| static int xcvr_init_10g(struct niu *np) |
| { |
| int phy_id, err; |
| u64 val; |
| |
| val = nr64_mac(XMAC_CONFIG); |
| val &= ~XMAC_CONFIG_LED_POLARITY; |
| val |= XMAC_CONFIG_FORCE_LED_ON; |
| nw64_mac(XMAC_CONFIG, val); |
| |
| /* XXX shared resource, lock parent XXX */ |
| val = nr64(MIF_CONFIG); |
| val |= MIF_CONFIG_INDIRECT_MODE; |
| nw64(MIF_CONFIG, val); |
| |
| phy_id = phy_decode(np->parent->port_phy, np->port); |
| phy_id = np->parent->phy_probe_info.phy_id[phy_id][np->port]; |
| |
| /* handle different phy types */ |
| switch (phy_id & NIU_PHY_ID_MASK) { |
| case NIU_PHY_ID_MRVL88X2011: |
| err = xcvr_init_10g_mrvl88x2011(np); |
| break; |
| |
| default: /* bcom 8704 */ |
| err = xcvr_init_10g_bcm8704(np); |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static int mii_reset(struct niu *np) |
| { |
| int limit, err; |
| |
| err = mii_write(np, np->phy_addr, MII_BMCR, BMCR_RESET); |
| if (err) |
| return err; |
| |
| limit = 1000; |
| while (--limit >= 0) { |
| udelay(500); |
| err = mii_read(np, np->phy_addr, MII_BMCR); |
| if (err < 0) |
| return err; |
| if (!(err & BMCR_RESET)) |
| break; |
| } |
| if (limit < 0) { |
| netdev_err(np->dev, "Port %u MII would not reset, bmcr[%04x]\n", |
| np->port, err); |
| return -ENODEV; |
| } |
| |
| return 0; |
| } |
| |
| static int xcvr_init_1g_rgmii(struct niu *np) |
| { |
| int err; |
| u64 val; |
| u16 bmcr, bmsr, estat; |
| |
| val = nr64(MIF_CONFIG); |
| val &= ~MIF_CONFIG_INDIRECT_MODE; |
| nw64(MIF_CONFIG, val); |
| |
| err = mii_reset(np); |
| if (err) |
| return err; |
| |
| err = mii_read(np, np->phy_addr, MII_BMSR); |
| if (err < 0) |
| return err; |
| bmsr = err; |
| |
| estat = 0; |
| if (bmsr & BMSR_ESTATEN) { |
| err = mii_read(np, np->phy_addr, MII_ESTATUS); |
| if (err < 0) |
| return err; |
| estat = err; |
| } |
| |
| bmcr = 0; |
| err = mii_write(np, np->phy_addr, MII_BMCR, bmcr); |
| if (err) |
| return err; |
| |
| if (bmsr & BMSR_ESTATEN) { |
| u16 ctrl1000 = 0; |
| |
| if (estat & ESTATUS_1000_TFULL) |
| ctrl1000 |= ADVERTISE_1000FULL; |
| err = mii_write(np, np->phy_addr, MII_CTRL1000, ctrl1000); |
| if (err) |
| return err; |
| } |
| |
| bmcr = (BMCR_SPEED1000 | BMCR_FULLDPLX); |
| |
| err = mii_write(np, np->phy_addr, MII_BMCR, bmcr); |
| if (err) |
| return err; |
| |
| err = mii_read(np, np->phy_addr, MII_BMCR); |
| if (err < 0) |
| return err; |
| bmcr = mii_read(np, np->phy_addr, MII_BMCR); |
| |
| err = mii_read(np, np->phy_addr, MII_BMSR); |
| if (err < 0) |
| return err; |
| |
| return 0; |
| } |
| |
| static int mii_init_common(struct niu *np) |
| { |
| struct niu_link_config *lp = &np->link_config; |
| u16 bmcr, bmsr, adv, estat; |
| int err; |
| |
| err = mii_reset(np); |
| if (err) |
| return err; |
| |
| err = mii_read(np, np->phy_addr, MII_BMSR); |
| if (err < 0) |
| return err; |
| bmsr = err; |
| |
| estat = 0; |
| if (bmsr & BMSR_ESTATEN) { |
| err = mii_read(np, np->phy_addr, MII_ESTATUS); |
| if (err < 0) |
| return err; |
| estat = err; |
| } |
| |
| bmcr = 0; |
| err = mii_write(np, np->phy_addr, MII_BMCR, bmcr); |
| if (err) |
| return err; |
| |
| if (lp->loopback_mode == LOOPBACK_MAC) { |
| bmcr |= BMCR_LOOPBACK; |
| if (lp->active_speed == SPEED_1000) |
| bmcr |= BMCR_SPEED1000; |
| if (lp->active_duplex == DUPLEX_FULL) |
| bmcr |= BMCR_FULLDPLX; |
| } |
| |
| if (lp->loopback_mode == LOOPBACK_PHY) { |
| u16 aux; |
| |
| aux = (BCM5464R_AUX_CTL_EXT_LB | |
| BCM5464R_AUX_CTL_WRITE_1); |
| err = mii_write(np, np->phy_addr, BCM5464R_AUX_CTL, aux); |
| if (err) |
| return err; |
| } |
| |
| if (lp->autoneg) { |
| u16 ctrl1000; |
| |
| adv = ADVERTISE_CSMA | ADVERTISE_PAUSE_CAP; |
| if ((bmsr & BMSR_10HALF) && |
| (lp->advertising & ADVERTISED_10baseT_Half)) |
| adv |= ADVERTISE_10HALF; |
| if ((bmsr & BMSR_10FULL) && |
| (lp->advertising & ADVERTISED_10baseT_Full)) |
| adv |= ADVERTISE_10FULL; |
| if ((bmsr & BMSR_100HALF) && |
| (lp->advertising & ADVERTISED_100baseT_Half)) |
| adv |= ADVERTISE_100HALF; |
| if ((bmsr & BMSR_100FULL) && |
| (lp->advertising & ADVERTISED_100baseT_Full)) |
| adv |= ADVERTISE_100FULL; |
| err = mii_write(np, np->phy_addr, MII_ADVERTISE, adv); |
| if (err) |
| return err; |
| |
| if (likely(bmsr & BMSR_ESTATEN)) { |
| ctrl1000 = 0; |
| if ((estat & ESTATUS_1000_THALF) && |
| (lp->advertising & ADVERTISED_1000baseT_Half)) |
| ctrl1000 |= ADVERTISE_1000HALF; |
| if ((estat & ESTATUS_1000_TFULL) && |
| (lp->advertising & ADVERTISED_1000baseT_Full)) |
| ctrl1000 |= ADVERTISE_1000FULL; |
| err = mii_write(np, np->phy_addr, |
| MII_CTRL1000, ctrl1000); |
| if (err) |
| return err; |
| } |
| |
| bmcr |= (BMCR_ANENABLE | BMCR_ANRESTART); |
| } else { |
| /* !lp->autoneg */ |
| int fulldpx; |
| |
| if (lp->duplex == DUPLEX_FULL) { |
| bmcr |= BMCR_FULLDPLX; |
| fulldpx = 1; |
| } else if (lp->duplex == DUPLEX_HALF) |
| fulldpx = 0; |
| else |
| return -EINVAL; |
| |
| if (lp->speed == SPEED_1000) { |
| /* if X-full requested while not supported, or |
| X-half requested while not supported... */ |
| if ((fulldpx && !(estat & ESTATUS_1000_TFULL)) || |
| (!fulldpx && !(estat & ESTATUS_1000_THALF))) |
| return -EINVAL; |
| bmcr |= BMCR_SPEED1000; |
| } else if (lp->speed == SPEED_100) { |
| if ((fulldpx && !(bmsr & BMSR_100FULL)) || |
| (!fulldpx && !(bmsr & BMSR_100HALF))) |
| return -EINVAL; |
| bmcr |= BMCR_SPEED100; |
| } else if (lp->speed == SPEED_10) { |
| if ((fulldpx && !(bmsr & BMSR_10FULL)) || |
| (!fulldpx && !(bmsr & BMSR_10HALF))) |
| return -EINVAL; |
| } else |
| return -EINVAL; |
| } |
| |
| err = mii_write(np, np->phy_addr, MII_BMCR, bmcr); |
| if (err) |
| return err; |
| |
| #if 0 |
| err = mii_read(np, np->phy_addr, MII_BMCR); |
| if (err < 0) |
| return err; |
| bmcr = err; |
| |
| err = mii_read(np, np->phy_addr, MII_BMSR); |
| if (err < 0) |
| return err; |
| bmsr = err; |
| |
| pr_info("Port %u after MII init bmcr[%04x] bmsr[%04x]\n", |
| np->port, bmcr, bmsr); |
| #endif |
| |
| return 0; |
| } |
| |
| static int xcvr_init_1g(struct niu *np) |
| { |
| u64 val; |
| |
| /* XXX shared resource, lock parent XXX */ |
| val = nr64(MIF_CONFIG); |
| val &= ~MIF_CONFIG_INDIRECT_MODE; |
| nw64(MIF_CONFIG, val); |
| |
| return mii_init_common(np); |
| } |
| |
| static int niu_xcvr_init(struct niu *np) |
| { |
| const struct niu_phy_ops *ops = np->phy_ops; |
| int err; |
| |
| err = 0; |
| if (ops->xcvr_init) |
| err = ops->xcvr_init(np); |
| |
| return err; |
| } |
| |
| static int niu_serdes_init(struct niu *np) |
| { |
| const struct niu_phy_ops *ops = np->phy_ops; |
| int err; |
| |
| err = 0; |
| if (ops->serdes_init) |
| err = ops->serdes_init(np); |
| |
| return err; |
| } |
| |
| static void niu_init_xif(struct niu *); |
| static void niu_handle_led(struct niu *, int status); |
| |
| static int niu_link_status_common(struct niu *np, int link_up) |
| { |
| struct niu_link_config *lp = &np->link_config; |
| struct net_device *dev = np->dev; |
| unsigned long flags; |
| |
| if (!netif_carrier_ok(dev) && link_up) { |
| netif_info(np, link, dev, "Link is up at %s, %s duplex\n", |
| lp->active_speed == SPEED_10000 ? "10Gb/sec" : |
| lp->active_speed == SPEED_1000 ? "1Gb/sec" : |
| lp->active_speed == SPEED_100 ? "100Mbit/sec" : |
| "10Mbit/sec", |
| lp->active_duplex == DUPLEX_FULL ? "full" : "half"); |
| |
| spin_lock_irqsave(&np->lock, flags); |
| niu_init_xif(np); |
| niu_handle_led(np, 1); |
| spin_unlock_irqrestore(&np->lock, flags); |
| |
| netif_carrier_on(dev); |
| } else if (netif_carrier_ok(dev) && !link_up) { |
| netif_warn(np, link, dev, "Link is down\n"); |
| spin_lock_irqsave(&np->lock, flags); |
| niu_handle_led(np, 0); |
| spin_unlock_irqrestore(&np->lock, flags); |
| netif_carrier_off(dev); |
| } |
| |
| return 0; |
| } |
| |
| static int link_status_10g_mrvl(struct niu *np, int *link_up_p) |
| { |
| int err, link_up, pma_status, pcs_status; |
| |
| link_up = 0; |
| |
| err = mdio_read(np, np->phy_addr, MRVL88X2011_USER_DEV1_ADDR, |
| MRVL88X2011_10G_PMD_STATUS_2); |
| if (err < 0) |
| goto out; |
| |
| /* Check PMA/PMD Register: 1.0001.2 == 1 */ |
| err = mdio_read(np, np->phy_addr, MRVL88X2011_USER_DEV1_ADDR, |
| MRVL88X2011_PMA_PMD_STATUS_1); |
| if (err < 0) |
| goto out; |
| |
| pma_status = ((err & MRVL88X2011_LNK_STATUS_OK) ? 1 : 0); |
| |
| /* Check PMC Register : 3.0001.2 == 1: read twice */ |
| err = mdio_read(np, np->phy_addr, MRVL88X2011_USER_DEV3_ADDR, |
| MRVL88X2011_PMA_PMD_STATUS_1); |
| if (err < 0) |
| goto out; |
| |
| err = mdio_read(np, np->phy_addr, MRVL88X2011_USER_DEV3_ADDR, |
| MRVL88X2011_PMA_PMD_STATUS_1); |
| if (err < 0) |
| goto out; |
| |
| pcs_status = ((err & MRVL88X2011_LNK_STATUS_OK) ? 1 : 0); |
| |
| /* Check XGXS Register : 4.0018.[0-3,12] */ |
| err = mdio_read(np, np->phy_addr, MRVL88X2011_USER_DEV4_ADDR, |
| MRVL88X2011_10G_XGXS_LANE_STAT); |
| if (err < 0) |
| goto out; |
| |
| if (err == (PHYXS_XGXS_LANE_STAT_ALINGED | PHYXS_XGXS_LANE_STAT_LANE3 | |
| PHYXS_XGXS_LANE_STAT_LANE2 | PHYXS_XGXS_LANE_STAT_LANE1 | |
| PHYXS_XGXS_LANE_STAT_LANE0 | PHYXS_XGXS_LANE_STAT_MAGIC | |
| 0x800)) |
| link_up = (pma_status && pcs_status) ? 1 : 0; |
| |
| np->link_config.active_speed = SPEED_10000; |
| np->link_config.active_duplex = DUPLEX_FULL; |
| err = 0; |
| out: |
| mrvl88x2011_act_led(np, (link_up ? |
| MRVL88X2011_LED_CTL_PCS_ACT : |
| MRVL88X2011_LED_CTL_OFF)); |
| |
| *link_up_p = link_up; |
| return err; |
| } |
| |
| static int link_status_10g_bcm8706(struct niu *np, int *link_up_p) |
| { |
| int err, link_up; |
| link_up = 0; |
| |
| err = mdio_read(np, np->phy_addr, BCM8704_PMA_PMD_DEV_ADDR, |
| BCM8704_PMD_RCV_SIGDET); |
| if (err < 0 || err == 0xffff) |
| goto out; |
| if (!(err & PMD_RCV_SIGDET_GLOBAL)) { |
| err = 0; |
| goto out; |
| } |
| |
| err = mdio_read(np, np->phy_addr, BCM8704_PCS_DEV_ADDR, |
| BCM8704_PCS_10G_R_STATUS); |
| if (err < 0) |
| goto out; |
| |
| if (!(err & PCS_10G_R_STATUS_BLK_LOCK)) { |
| err = 0; |
| goto out; |
| } |
| |
| err = mdio_read(np, np->phy_addr, BCM8704_PHYXS_DEV_ADDR, |
| BCM8704_PHYXS_XGXS_LANE_STAT); |
| if (err < 0) |
| goto out; |
| if (err != (PHYXS_XGXS_LANE_STAT_ALINGED | |
| PHYXS_XGXS_LANE_STAT_MAGIC | |
| PHYXS_XGXS_LANE_STAT_PATTEST | |
| PHYXS_XGXS_LANE_STAT_LANE3 | |
| PHYXS_XGXS_LANE_STAT_LANE2 | |
| PHYXS_XGXS_LANE_STAT_LANE1 | |
| PHYXS_XGXS_LANE_STAT_LANE0)) { |
| err = 0; |
| np->link_config.active_speed = SPEED_INVALID; |
| np->link_config.active_duplex = DUPLEX_INVALID; |
| goto out; |
| } |
| |
| link_up = 1; |
| np->link_config.active_speed = SPEED_10000; |
| np->link_config.active_duplex = DUPLEX_FULL; |
| err = 0; |
| |
| out: |
| *link_up_p = link_up; |
| return err; |
| } |
| |
| static int link_status_10g_bcom(struct niu *np, int *link_up_p) |
| { |
| int err, link_up; |
| |
| link_up = 0; |
| |
| err = mdio_read(np, np->phy_addr, BCM8704_PMA_PMD_DEV_ADDR, |
| BCM8704_PMD_RCV_SIGDET); |
| if (err < 0) |
| goto out; |
| if (!(err & PMD_RCV_SIGDET_GLOBAL)) { |
| err = 0; |
| goto out; |
| } |
| |
| err = mdio_read(np, np->phy_addr, BCM8704_PCS_DEV_ADDR, |
| BCM8704_PCS_10G_R_STATUS); |
| if (err < 0) |
| goto out; |
| if (!(err & PCS_10G_R_STATUS_BLK_LOCK)) { |
| err = 0; |
| goto out; |
| } |
| |
| err = mdio_read(np, np->phy_addr, BCM8704_PHYXS_DEV_ADDR, |
| BCM8704_PHYXS_XGXS_LANE_STAT); |
| if (err < 0) |
| goto out; |
| |
| if (err != (PHYXS_XGXS_LANE_STAT_ALINGED | |
| PHYXS_XGXS_LANE_STAT_MAGIC | |
| PHYXS_XGXS_LANE_STAT_LANE3 | |
| PHYXS_XGXS_LANE_STAT_LANE2 | |
| PHYXS_XGXS_LANE_STAT_LANE1 | |
| PHYXS_XGXS_LANE_STAT_LANE0)) { |
| err = 0; |
| goto out; |
| } |
| |
| link_up = 1; |
| np->link_config.active_speed = SPEED_10000; |
| np->link_config.active_duplex = DUPLEX_FULL; |
| err = 0; |
| |
| out: |
| *link_up_p = link_up; |
| return err; |
| } |
| |
| static int link_status_10g(struct niu *np, int *link_up_p) |
| { |
| unsigned long flags; |
| int err = -EINVAL; |
| |
| spin_lock_irqsave(&np->lock, flags); |
| |
| if (np->link_config.loopback_mode == LOOPBACK_DISABLED) { |
| int phy_id; |
| |
| phy_id = phy_decode(np->parent->port_phy, np->port); |
| phy_id = np->parent->phy_probe_info.phy_id[phy_id][np->port]; |
| |
| /* handle different phy types */ |
| switch (phy_id & NIU_PHY_ID_MASK) { |
| case NIU_PHY_ID_MRVL88X2011: |
| err = link_status_10g_mrvl(np, link_up_p); |
| break; |
| |
| default: /* bcom 8704 */ |
| err = link_status_10g_bcom(np, link_up_p); |
| break; |
| } |
| } |
| |
| spin_unlock_irqrestore(&np->lock, flags); |
| |
| return err; |
| } |
| |
| static int niu_10g_phy_present(struct niu *np) |
| { |
| u64 sig, mask, val; |
| |
| sig = nr64(ESR_INT_SIGNALS); |
| switch (np->port) { |
| case 0: |
| mask = ESR_INT_SIGNALS_P0_BITS; |
| val = (ESR_INT_SRDY0_P0 | |
| ESR_INT_DET0_P0 | |
| ESR_INT_XSRDY_P0 | |
| ESR_INT_XDP_P0_CH3 | |
| ESR_INT_XDP_P0_CH2 | |
| ESR_INT_XDP_P0_CH1 | |
| ESR_INT_XDP_P0_CH0); |
| break; |
| |
| case 1: |
| mask = ESR_INT_SIGNALS_P1_BITS; |
| val = (ESR_INT_SRDY0_P1 | |
| ESR_INT_DET0_P1 | |
| ESR_INT_XSRDY_P1 | |
| ESR_INT_XDP_P1_CH3 | |
| ESR_INT_XDP_P1_CH2 | |
| ESR_INT_XDP_P1_CH1 | |
| ESR_INT_XDP_P1_CH0); |
| break; |
| |
| default: |
| return 0; |
| } |
| |
| if ((sig & mask) != val) |
| return 0; |
| return 1; |
| } |
| |
| static int link_status_10g_hotplug(struct niu *np, int *link_up_p) |
| { |
| unsigned long flags; |
| int err = 0; |
| int phy_present; |
| int phy_present_prev; |
| |
| spin_lock_irqsave(&np->lock, flags); |
| |
| if (np->link_config.loopback_mode == LOOPBACK_DISABLED) { |
| phy_present_prev = (np->flags & NIU_FLAGS_HOTPLUG_PHY_PRESENT) ? |
| 1 : 0; |
| phy_present = niu_10g_phy_present(np); |
| if (phy_present != phy_present_prev) { |
| /* state change */ |
| if (phy_present) { |
| /* A NEM was just plugged in */ |
| np->flags |= NIU_FLAGS_HOTPLUG_PHY_PRESENT; |
| if (np->phy_ops->xcvr_init) |
| err = np->phy_ops->xcvr_init(np); |
| if (err) { |
| err = mdio_read(np, np->phy_addr, |
| BCM8704_PHYXS_DEV_ADDR, MII_BMCR); |
| if (err == 0xffff) { |
| /* No mdio, back-to-back XAUI */ |
| goto out; |
| } |
| /* debounce */ |
| np->flags &= ~NIU_FLAGS_HOTPLUG_PHY_PRESENT; |
| } |
| } else { |
| np->flags &= ~NIU_FLAGS_HOTPLUG_PHY_PRESENT; |
| *link_up_p = 0; |
| netif_warn(np, link, np->dev, |
| "Hotplug PHY Removed\n"); |
| } |
| } |
| out: |
| if (np->flags & NIU_FLAGS_HOTPLUG_PHY_PRESENT) { |
| err = link_status_10g_bcm8706(np, link_up_p); |
| if (err == 0xffff) { |
| /* No mdio, back-to-back XAUI: it is C10NEM */ |
| *link_up_p = 1; |
| np->link_config.active_speed = SPEED_10000; |
| np->link_config.active_duplex = DUPLEX_FULL; |
| } |
| } |
| } |
| |
| spin_unlock_irqrestore(&np->lock, flags); |
| |
| return 0; |
| } |
| |
| static int niu_link_status(struct niu *np, int *link_up_p) |
| { |
| const struct niu_phy_ops *ops = np->phy_ops; |
| int err; |
| |
| err = 0; |
| if (ops->link_status) |
| err = ops->link_status(np, link_up_p); |
| |
| return err; |
| } |
| |
| static void niu_timer(unsigned long __opaque) |
| { |
| struct niu *np = (struct niu *) __opaque; |
| unsigned long off; |
| int err, link_up; |
| |
| err = niu_link_status(np, &link_up); |
| if (!err) |
| niu_link_status_common(np, link_up); |
| |
| if (netif_carrier_ok(np->dev)) |
| off = 5 * HZ; |
| else |
| off = 1 * HZ; |
| np->timer.expires = jiffies + off; |
| |
| add_timer(&np->timer); |
| } |
| |
| static const struct niu_phy_ops phy_ops_10g_serdes = { |
| .serdes_init = serdes_init_10g_serdes, |
| .link_status = link_status_10g_serdes, |
| }; |
| |
| static const struct niu_phy_ops phy_ops_10g_serdes_niu = { |
| .serdes_init = serdes_init_niu_10g_serdes, |
| .link_status = link_status_10g_serdes, |
| }; |
| |
| static const struct niu_phy_ops phy_ops_1g_serdes_niu = { |
| .serdes_init = serdes_init_niu_1g_serdes, |
| .link_status = link_status_1g_serdes, |
| }; |
| |
| static const struct niu_phy_ops phy_ops_1g_rgmii = { |
| .xcvr_init = xcvr_init_1g_rgmii, |
| .link_status = link_status_1g_rgmii, |
| }; |
| |
| static const struct niu_phy_ops phy_ops_10g_fiber_niu = { |
| .serdes_init = serdes_init_niu_10g_fiber, |
| .xcvr_init = xcvr_init_10g, |
| .link_status = link_status_10g, |
| }; |
| |
| static const struct niu_phy_ops phy_ops_10g_fiber = { |
| .serdes_init = serdes_init_10g, |
| .xcvr_init = xcvr_init_10g, |
| .link_status = link_status_10g, |
| }; |
| |
| static const struct niu_phy_ops phy_ops_10g_fiber_hotplug = { |
| .serdes_init = serdes_init_10g, |
| .xcvr_init = xcvr_init_10g_bcm8706, |
| .link_status = link_status_10g_hotplug, |
| }; |
| |
| static const struct niu_phy_ops phy_ops_niu_10g_hotplug = { |
| .serdes_init = serdes_init_niu_10g_fiber, |
| .xcvr_init = xcvr_init_10g_bcm8706, |
| .link_status = link_status_10g_hotplug, |
| }; |
| |
| static const struct niu_phy_ops phy_ops_10g_copper = { |
| .serdes_init = serdes_init_10g, |
| .link_status = link_status_10g, /* XXX */ |
| }; |
| |
| static const struct niu_phy_ops phy_ops_1g_fiber = { |
| .serdes_init = serdes_init_1g, |
| .xcvr_init = xcvr_init_1g, |
| .link_status = link_status_1g, |
| }; |
| |
| static const struct niu_phy_ops phy_ops_1g_copper = { |
| .xcvr_init = xcvr_init_1g, |
| .link_status = link_status_1g, |
| }; |
| |
| struct niu_phy_template { |
| const struct niu_phy_ops *ops; |
| u32 phy_addr_base; |
| }; |
| |
| static const struct niu_phy_template phy_template_niu_10g_fiber = { |
| .ops = &phy_ops_10g_fiber_niu, |
| .phy_addr_base = 16, |
| }; |
| |
| static const struct niu_phy_template phy_template_niu_10g_serdes = { |
| .ops = &phy_ops_10g_serdes_niu, |
| .phy_addr_base = 0, |
| }; |
| |
| static const struct niu_phy_template phy_template_niu_1g_serdes = { |
| .ops = &phy_ops_1g_serdes_niu, |
| .phy_addr_base = 0, |
| }; |
| |
| static const struct niu_phy_template phy_template_10g_fiber = { |
| .ops = &phy_ops_10g_fiber, |
| .phy_addr_base = 8, |
| }; |
| |
| static const struct niu_phy_template phy_template_10g_fiber_hotplug = { |
| .ops = &phy_ops_10g_fiber_hotplug, |
| .phy_addr_base = 8, |
| }; |
| |
| static const struct niu_phy_template phy_template_niu_10g_hotplug = { |
| .ops = &phy_ops_niu_10g_hotplug, |
| .phy_addr_base = 8, |
| }; |
| |
| static const struct niu_phy_template phy_template_10g_copper = { |
| .ops = &phy_ops_10g_copper, |
| .phy_addr_base = 10, |
| }; |
| |
| static const struct niu_phy_template phy_template_1g_fiber = { |
| .ops = &phy_ops_1g_fiber, |
| .phy_addr_base = 0, |
| }; |
| |
| static const struct niu_phy_template phy_template_1g_copper = { |
| .ops = &phy_ops_1g_copper, |
| .phy_addr_base = 0, |
| }; |
| |
| static const struct niu_phy_template phy_template_1g_rgmii = { |
| .ops = &phy_ops_1g_rgmii, |
| .phy_addr_base = 0, |
| }; |
| |
| static const struct niu_phy_template phy_template_10g_serdes = { |
| .ops = &phy_ops_10g_serdes, |
| .phy_addr_base = 0, |
| }; |
| |
| static int niu_atca_port_num[4] = { |
| 0, 0, 11, 10 |
| }; |
| |
| static int serdes_init_10g_serdes(struct niu *np) |
| { |
| struct niu_link_config *lp = &np->link_config; |
| unsigned long ctrl_reg, test_cfg_reg, pll_cfg, i; |
| u64 ctrl_val, test_cfg_val, sig, mask, val; |
| u64 reset_val; |
| |
| switch (np->port) { |
| case 0: |
| reset_val = ENET_SERDES_RESET_0; |
| ctrl_reg = ENET_SERDES_0_CTRL_CFG; |
| test_cfg_reg = ENET_SERDES_0_TEST_CFG; |
| pll_cfg = ENET_SERDES_0_PLL_CFG; |
| break; |
| case 1: |
| reset_val = ENET_SERDES_RESET_1; |
| ctrl_reg = ENET_SERDES_1_CTRL_CFG; |
| test_cfg_reg = ENET_SERDES_1_TEST_CFG; |
| pll_cfg = ENET_SERDES_1_PLL_CFG; |
| break; |
| |
| default: |
| return -EINVAL; |
| } |
| ctrl_val = (ENET_SERDES_CTRL_SDET_0 | |
| ENET_SERDES_CTRL_SDET_1 | |
| ENET_SERDES_CTRL_SDET_2 | |
| ENET_SERDES_CTRL_SDET_3 | |
| (0x5 << ENET_SERDES_CTRL_EMPH_0_SHIFT) | |
| (0x5 << ENET_SERDES_CTRL_EMPH_1_SHIFT) | |
| (0x5 << ENET_SERDES_CTRL_EMPH_2_SHIFT) | |
| (0x5 << ENET_SERDES_CTRL_EMPH_3_SHIFT) | |
| (0x1 << ENET_SERDES_CTRL_LADJ_0_SHIFT) | |
| (0x1 << ENET_SERDES_CTRL_LADJ_1_SHIFT) | |
| (0x1 << ENET_SERDES_CTRL_LADJ_2_SHIFT) | |
| (0x1 << ENET_SERDES_CTRL_LADJ_3_SHIFT)); |
| test_cfg_val = 0; |
| |
| if (lp->loopback_mode == LOOPBACK_PHY) { |
| test_cfg_val |= ((ENET_TEST_MD_PAD_LOOPBACK << |
| ENET_SERDES_TEST_MD_0_SHIFT) | |
| (ENET_TEST_MD_PAD_LOOPBACK << |
| ENET_SERDES_TEST_MD_1_SHIFT) | |
| (ENET_TEST_MD_PAD_LOOPBACK << |
| ENET_SERDES_TEST_MD_2_SHIFT) | |
| (ENET_TEST_MD_PAD_LOOPBACK << |
| ENET_SERDES_TEST_MD_3_SHIFT)); |
| } |
| |
| esr_reset(np); |
| nw64(pll_cfg, ENET_SERDES_PLL_FBDIV2); |
| nw64(ctrl_reg, ctrl_val); |
| nw64(test_cfg_reg, test_cfg_val); |
| |
| /* Initialize all 4 lanes of the SERDES. */ |
| for (i = 0; i < 4; i++) { |
| u32 rxtx_ctrl, glue0; |
| int err; |
| |
| err = esr_read_rxtx_ctrl(np, i, &rxtx_ctrl); |
| if (err) |
| return err; |
| err = esr_read_glue0(np, i, &glue0); |
| if (err) |
| return err; |
| |
| rxtx_ctrl &= ~(ESR_RXTX_CTRL_VMUXLO); |
| rxtx_ctrl |= (ESR_RXTX_CTRL_ENSTRETCH | |
| (2 << ESR_RXTX_CTRL_VMUXLO_SHIFT)); |
| |
| glue0 &= ~(ESR_GLUE_CTRL0_SRATE | |
| ESR_GLUE_CTRL0_THCNT | |
| ESR_GLUE_CTRL0_BLTIME); |
| glue0 |= (ESR_GLUE_CTRL0_RXLOSENAB | |
| (0xf << ESR_GLUE_CTRL0_SRATE_SHIFT) | |
| (0xff << ESR_GLUE_CTRL0_THCNT_SHIFT) | |
| (BLTIME_300_CYCLES << |
| ESR_GLUE_CTRL0_BLTIME_SHIFT)); |
| |
| err = esr_write_rxtx_ctrl(np, i, rxtx_ctrl); |
| if (err) |
| return err; |
| err = esr_write_glue0(np, i, glue0); |
| if (err) |
| return err; |
| } |
| |
| |
| sig = nr64(ESR_INT_SIGNALS); |
| switch (np->port) { |
| case 0: |
| mask = ESR_INT_SIGNALS_P0_BITS; |
| val = (ESR_INT_SRDY0_P0 | |
| ESR_INT_DET0_P0 | |
| ESR_INT_XSRDY_P0 | |
| ESR_INT_XDP_P0_CH3 | |
| ESR_INT_XDP_P0_CH2 | |
| ESR_INT_XDP_P0_CH1 | |
| ESR_INT_XDP_P0_CH0); |
| break; |
| |
| case 1: |
| mask = ESR_INT_SIGNALS_P1_BITS; |
| val = (ESR_INT_SRDY0_P1 | |
| ESR_INT_DET0_P1 | |
| ESR_INT_XSRDY_P1 | |
| ESR_INT_XDP_P1_CH3 | |
| ESR_INT_XDP_P1_CH2 | |
| ESR_INT_XDP_P1_CH1 | |
| ESR_INT_XDP_P1_CH0); |
| break; |
| |
| default: |
| return -EINVAL; |
| } |
| |
| if ((sig & mask) != val) { |
| int err; |
| err = serdes_init_1g_serdes(np); |
| if (!err) { |
| np->flags &= ~NIU_FLAGS_10G; |
| np->mac_xcvr = MAC_XCVR_PCS; |
| } else { |
| netdev_err(np->dev, "Port %u 10G/1G SERDES Link Failed\n", |
| np->port); |
| return -ENODEV; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int niu_determine_phy_disposition(struct niu *np) |
| { |
| struct niu_parent *parent = np->parent; |
| u8 plat_type = parent->plat_type; |
| const struct niu_phy_template *tp; |
| u32 phy_addr_off = 0; |
| |
| if (plat_type == PLAT_TYPE_NIU) { |
| switch (np->flags & |
| (NIU_FLAGS_10G | |
| NIU_FLAGS_FIBER | |
| NIU_FLAGS_XCVR_SERDES)) { |
| case NIU_FLAGS_10G | NIU_FLAGS_XCVR_SERDES: |
| /* 10G Serdes */ |
| tp = &phy_template_niu_10g_serdes; |
| break; |
| case NIU_FLAGS_XCVR_SERDES: |
| /* 1G Serdes */ |
| tp = &phy_template_niu_1g_serdes; |
| break; |
| case NIU_FLAGS_10G | NIU_FLAGS_FIBER: |
| /* 10G Fiber */ |
| default: |
| if (np->flags & NIU_FLAGS_HOTPLUG_PHY) { |
| tp = &phy_template_niu_10g_hotplug; |
| if (np->port == 0) |
| phy_addr_off = 8; |
| if (np->port == 1) |
| phy_addr_off = 12; |
| } else { |
| tp = &phy_template_niu_10g_fiber; |
| phy_addr_off += np->port; |
| } |
| break; |
| } |
| } else { |
| switch (np->flags & |
| (NIU_FLAGS_10G | |
| NIU_FLAGS_FIBER | |
| NIU_FLAGS_XCVR_SERDES)) { |
| case 0: |
| /* 1G copper */ |
| tp = &phy_template_1g_copper; |
| if (plat_type == PLAT_TYPE_VF_P0) |
| phy_addr_off = 10; |
| else if (plat_type == PLAT_TYPE_VF_P1) |
| phy_addr_off = 26; |
| |
| phy_addr_off += (np->port ^ 0x3); |
| break; |
| |
| case NIU_FLAGS_10G: |
| /* 10G copper */ |
| tp = &phy_template_10g_copper; |
| break; |
| |
| case NIU_FLAGS_FIBER: |
| /* 1G fiber */ |
| tp = &phy_template_1g_fiber; |
| break; |
| |
| case NIU_FLAGS_10G | NIU_FLAGS_FIBER: |
| /* 10G fiber */ |
| tp = &phy_template_10g_fiber; |
| if (plat_type == PLAT_TYPE_VF_P0 || |
| plat_type == PLAT_TYPE_VF_P1) |
| phy_addr_off = 8; |
| phy_addr_off += np->port; |
| if (np->flags & NIU_FLAGS_HOTPLUG_PHY) { |
| tp = &phy_template_10g_fiber_hotplug; |
| if (np->port == 0) |
| phy_addr_off = 8; |
| if (np->port == 1) |
| phy_addr_off = 12; |
| } |
| break; |
| |
| case NIU_FLAGS_10G | NIU_FLAGS_XCVR_SERDES: |
| case NIU_FLAGS_XCVR_SERDES | NIU_FLAGS_FIBER: |
| case NIU_FLAGS_XCVR_SERDES: |
| switch(np->port) { |
| case 0: |
| case 1: |
| tp = &phy_template_10g_serdes; |
| break; |
| case 2: |
| case 3: |
| tp = &phy_template_1g_rgmii; |
| break; |
| default: |
| return -EINVAL; |
| break; |
| } |
| phy_addr_off = niu_atca_port_num[np->port]; |
| break; |
| |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| np->phy_ops = tp->ops; |
| np->phy_addr = tp->phy_addr_base + phy_addr_off; |
| |
| return 0; |
| } |
| |
| static int niu_init_link(struct niu *np) |
| { |
| struct niu_parent *parent = np->parent; |
| int err, ignore; |
| |
| if (parent->plat_type == PLAT_TYPE_NIU) { |
| err = niu_xcvr_init(np); |
| if (err) |
| return err; |
| msleep(200); |
| } |
| err = niu_serdes_init(np); |
| if (err && !(np->flags & NIU_FLAGS_HOTPLUG_PHY)) |
| return err; |
| msleep(200); |
| err = niu_xcvr_init(np); |
| if (!err || (np->flags & NIU_FLAGS_HOTPLUG_PHY)) |
| niu_link_status(np, &ignore); |
| return 0; |
| } |
| |
| static void niu_set_primary_mac(struct niu *np, unsigned char *addr) |
| { |
| u16 reg0 = addr[4] << 8 | addr[5]; |
| u16 reg1 = addr[2] << 8 | addr[3]; |
| u16 reg2 = addr[0] << 8 | addr[1]; |
| |
| if (np->flags & NIU_FLAGS_XMAC) { |
| nw64_mac(XMAC_ADDR0, reg0); |
| nw64_mac(XMAC_ADDR1, reg1); |
| nw64_mac(XMAC_ADDR2, reg2); |
| } else { |
| nw64_mac(BMAC_ADDR0, reg0); |
| nw64_mac(BMAC_ADDR1, reg1); |
| nw64_mac(BMAC_ADDR2, reg2); |
| } |
| } |
| |
| static int niu_num_alt_addr(struct niu *np) |
| { |
| if (np->flags & NIU_FLAGS_XMAC) |
| return XMAC_NUM_ALT_ADDR; |
| else |
| return BMAC_NUM_ALT_ADDR; |
| } |
| |
| static int niu_set_alt_mac(struct niu *np, int index, unsigned char *addr) |
| { |
| u16 reg0 = addr[4] << 8 | addr[5]; |
| u16 reg1 = addr[2] << 8 | addr[3]; |
| u16 reg2 = addr[0] << 8 | addr[1]; |
| |
| if (index >= niu_num_alt_addr(np)) |
| return -EINVAL; |
| |
| if (np->flags & NIU_FLAGS_XMAC) { |
| nw64_mac(XMAC_ALT_ADDR0(index), reg0); |
| nw64_mac(XMAC_ALT_ADDR1(index), reg1); |
| nw64_mac(XMAC_ALT_ADDR2(index), reg2); |
| } else { |
| nw64_mac(BMAC_ALT_ADDR0(index), reg0); |
| nw64_mac(BMAC_ALT_ADDR1(index), reg1); |
| nw64_mac(BMAC_ALT_ADDR2(index), reg2); |
| } |
| |
| return 0; |
| } |
| |
| static int niu_enable_alt_mac(struct niu *np, int index, int on) |
| { |
| unsigned long reg; |
| u64 val, mask; |
| |
| if (index >= niu_num_alt_addr(np)) |
| return -EINVAL; |
| |
| if (np->flags & NIU_FLAGS_XMAC) { |
| reg = XMAC_ADDR_CMPEN; |
| mask = 1 << index; |
| } else { |
| reg = BMAC_ADDR_CMPEN; |
| mask = 1 << (index + 1); |
| } |
| |
| val = nr64_mac(reg); |
| if (on) |
| val |= mask; |
| else |
| val &= ~mask; |
| nw64_mac(reg, val); |
| |
| return 0; |
| } |
| |
| static void __set_rdc_table_num_hw(struct niu *np, unsigned long reg, |
| int num, int mac_pref) |
| { |
| u64 val = nr64_mac(reg); |
| val &= ~(HOST_INFO_MACRDCTBLN | HOST_INFO_MPR); |
| val |= num; |
| if (mac_pref) |
| val |= HOST_INFO_MPR; |
| nw64_mac(reg, val); |
| } |
| |
| static int __set_rdc_table_num(struct niu *np, |
| int xmac_index, int bmac_index, |
| int rdc_table_num, int mac_pref) |
| { |
| unsigned long reg; |
| |
| if (rdc_table_num & ~HOST_INFO_MACRDCTBLN) |
| return -EINVAL; |
| if (np->flags & NIU_FLAGS_XMAC) |
| reg = XMAC_HOST_INFO(xmac_index); |
| else |
| reg = BMAC_HOST_INFO(bmac_index); |
| __set_rdc_table_num_hw(np, reg, rdc_table_num, mac_pref); |
| return 0; |
| } |
| |
| static int niu_set_primary_mac_rdc_table(struct niu *np, int table_num, |
| int mac_pref) |
| { |
| return __set_rdc_table_num(np, 17, 0, table_num, mac_pref); |
| } |
| |
| static int niu_set_multicast_mac_rdc_table(struct niu *np, int table_num, |
| int mac_pref) |
| { |
| return __set_rdc_table_num(np, 16, 8, table_num, mac_pref); |
| } |
| |
| static int niu_set_alt_mac_rdc_table(struct niu *np, int idx, |
| int table_num, int mac_pref) |
| { |
| if (idx >= niu_num_alt_addr(np)) |
| return -EINVAL; |
| return __set_rdc_table_num(np, idx, idx + 1, table_num, mac_pref); |
| } |
| |
| static u64 vlan_entry_set_parity(u64 reg_val) |
| { |
| u64 port01_mask; |
| u64 port23_mask; |
| |
| port01_mask = 0x00ff; |
| port23_mask = 0xff00; |
| |
| if (hweight64(reg_val & port01_mask) & 1) |
| reg_val |= ENET_VLAN_TBL_PARITY0; |
| else |
| reg_val &= ~ENET_VLAN_TBL_PARITY0; |
| |
| if (hweight64(reg_val & port23_mask) & 1) |
| reg_val |= ENET_VLAN_TBL_PARITY1; |
| else |
| reg_val &= ~ENET_VLAN_TBL_PARITY1; |
| |
| return reg_val; |
| } |
| |
| static void vlan_tbl_write(struct niu *np, unsigned long index, |
| int port, int vpr, int rdc_table) |
| { |
| u64 reg_val = nr64(ENET_VLAN_TBL(index)); |
| |
| reg_val &= ~((ENET_VLAN_TBL_VPR | |
| ENET_VLAN_TBL_VLANRDCTBLN) << |
| ENET_VLAN_TBL_SHIFT(port)); |
| if (vpr) |
| reg_val |= (ENET_VLAN_TBL_VPR << |
| ENET_VLAN_TBL_SHIFT(port)); |
| reg_val |= (rdc_table << ENET_VLAN_TBL_SHIFT(port)); |
| |
| reg_val = vlan_entry_set_parity(reg_val); |
| |
| nw64(ENET_VLAN_TBL(index), reg_val); |
| } |
| |
| static void vlan_tbl_clear(struct niu *np) |
| { |
| int i; |
| |
| for (i = 0; i < ENET_VLAN_TBL_NUM_ENTRIES; i++) |
| nw64(ENET_VLAN_TBL(i), 0); |
| } |
| |
| static int tcam_wait_bit(struct niu *np, u64 bit) |
| { |
| int limit = 1000; |
| |
| while (--limit > 0) { |
| if (nr64(TCAM_CTL) & bit) |
| break; |
| udelay(1); |
| } |
| if (limit <= 0) |
| return -ENODEV; |
| |
| return 0; |
| } |
| |
| static int tcam_flush(struct niu *np, int index) |
| { |
| nw64(TCAM_KEY_0, 0x00); |
| nw64(TCAM_KEY_MASK_0, 0xff); |
| nw64(TCAM_CTL, (TCAM_CTL_RWC_TCAM_WRITE | index)); |
| |
| return tcam_wait_bit(np, TCAM_CTL_STAT); |
| } |
| |
| #if 0 |
| static int tcam_read(struct niu *np, int index, |
| u64 *key, u64 *mask) |
| { |
| int err; |
| |
| nw64(TCAM_CTL, (TCAM_CTL_RWC_TCAM_READ | index)); |
| err = tcam_wait_bit(np, TCAM_CTL_STAT); |
| if (!err) { |
| key[0] = nr64(TCAM_KEY_0); |
| key[1] = nr64(TCAM_KEY_1); |
| key[2] = nr64(TCAM_KEY_2); |
| key[3] = nr64(TCAM_KEY_3); |
| mask[0] = nr64(TCAM_KEY_MASK_0); |
| mask[1] = nr64(TCAM_KEY_MASK_1); |
| mask[2] = nr64(TCAM_KEY_MASK_2); |
| mask[3] = nr64(TCAM_KEY_MASK_3); |
| } |
| return err; |
| } |
| #endif |
| |
| static int tcam_write(struct niu *np, int index, |
| u64 *key, u64 *mask) |
| { |
| nw64(TCAM_KEY_0, key[0]); |
| nw64(TCAM_KEY_1, key[1]); |
| nw64(TCAM_KEY_2, key[2]); |
| nw64(TCAM_KEY_3, key[3]); |
| nw64(TCAM_KEY_MASK_0, mask[0]); |
| nw64(TCAM_KEY_MASK_1, mask[1]); |
| nw64(TCAM_KEY_MASK_2, mask[2]); |
| nw64(TCAM_KEY_MASK_3, mask[3]); |
| nw64(TCAM_CTL, (TCAM_CTL_RWC_TCAM_WRITE | index)); |
| |
| return tcam_wait_bit(np, TCAM_CTL_STAT); |
| } |
| |
| #if 0 |
| static int tcam_assoc_read(struct niu *np, int index, u64 *data) |
| { |
| int err; |
| |
| nw64(TCAM_CTL, (TCAM_CTL_RWC_RAM_READ | index)); |
| err = tcam_wait_bit(np, TCAM_CTL_STAT); |
| if (!err) |
| *data = nr64(TCAM_KEY_1); |
| |
| return err; |
| } |
| #endif |
| |
| static int tcam_assoc_write(struct niu *np, int index, u64 assoc_data) |
| { |
| nw64(TCAM_KEY_1, assoc_data); |
| nw64(TCAM_CTL, (TCAM_CTL_RWC_RAM_WRITE | index)); |
| |
| return tcam_wait_bit(np, TCAM_CTL_STAT); |
| } |
| |
| static void tcam_enable(struct niu *np, int on) |
| { |
| u64 val = nr64(FFLP_CFG_1); |
| |
| if (on) |
| val &= ~FFLP_CFG_1_TCAM_DIS; |
| else |
| val |= FFLP_CFG_1_TCAM_DIS; |
| nw64(FFLP_CFG_1, val); |
| } |
| |
| static void tcam_set_lat_and_ratio(struct niu *np, u64 latency, u64 ratio) |
| { |
| u64 val = nr64(FFLP_CFG_1); |
| |
| val &= ~(FFLP_CFG_1_FFLPINITDONE | |
| FFLP_CFG_1_CAMLAT | |
| FFLP_CFG_1_CAMRATIO); |
| val |= (latency << FFLP_CFG_1_CAMLAT_SHIFT); |
| val |= (ratio << FFLP_CFG_1_CAMRATIO_SHIFT); |
| nw64(FFLP_CFG_1, val); |
| |
| val = nr64(FFLP_CFG_1); |
| val |= FFLP_CFG_1_FFLPINITDONE; |
| nw64(FFLP_CFG_1, val); |
| } |
| |
| static int tcam_user_eth_class_enable(struct niu *np, unsigned long class, |
| int on) |
| { |
| unsigned long reg; |
| u64 val; |
| |
| if (class < CLASS_CODE_ETHERTYPE1 || |
| class > CLASS_CODE_ETHERTYPE2) |
| return -EINVAL; |
| |
| reg = L2_CLS(class - CLASS_CODE_ETHERTYPE1); |
| val = nr64(reg); |
| if (on) |
| val |= L2_CLS_VLD; |
| else |
| val &= ~L2_CLS_VLD; |
| nw64(reg, val); |
| |
| return 0; |
| } |
| |
| #if 0 |
| static int tcam_user_eth_class_set(struct niu *np, unsigned long class, |
| u64 ether_type) |
| { |
| unsigned long reg; |
| u64 val; |
| |
| if (class < CLASS_CODE_ETHERTYPE1 || |
| class > CLASS_CODE_ETHERTYPE2 || |
| (ether_type & ~(u64)0xffff) != 0) |
| return -EINVAL; |
| |
| reg = L2_CLS(class - CLASS_CODE_ETHERTYPE1); |
| val = nr64(reg); |
| val &= ~L2_CLS_ETYPE; |
| val |= (ether_type << L2_CLS_ETYPE_SHIFT); |
| nw64(reg, val); |
| |
| return 0; |
| } |
| #endif |
| |
| static int tcam_user_ip_class_enable(struct niu *np, unsigned long class, |
| int on) |
| { |
| unsigned long reg; |
| u64 val; |
| |
| if (class < CLASS_CODE_USER_PROG1 || |
| class > CLASS_CODE_USER_PROG4) |
| return -EINVAL; |
| |
| reg = L3_CLS(class - CLASS_CODE_USER_PROG1); |
| val = nr64(reg); |
| if (on) |
| val |= L3_CLS_VALID; |
| else |
| val &= ~L3_CLS_VALID; |
| nw64(reg, val); |
| |
| return 0; |
| } |
| |
| static int tcam_user_ip_class_set(struct niu *np, unsigned long class, |
| int ipv6, u64 protocol_id, |
| u64 tos_mask, u64 tos_val) |
| { |
| unsigned long reg; |
| u64 val; |
| |
| if (class < CLASS_CODE_USER_PROG1 || |
| class > CLASS_CODE_USER_PROG4 || |
| (protocol_id & ~(u64)0xff) != 0 || |
| (tos_mask & ~(u64)0xff) != 0 || |
| (tos_val & ~(u64)0xff) != 0) |
| return -EINVAL; |
| |
| reg = L3_CLS(class - CLASS_CODE_USER_PROG1); |
| val = nr64(reg); |
| val &= ~(L3_CLS_IPVER | L3_CLS_PID | |
| L3_CLS_TOSMASK | L3_CLS_TOS); |
| if (ipv6) |
| val |= L3_CLS_IPVER; |
| val |= (protocol_id << L3_CLS_PID_SHIFT); |
| val |= (tos_mask << L3_CLS_TOSMASK_SHIFT); |
| val |= (tos_val << L3_CLS_TOS_SHIFT); |
| nw64(reg, val); |
| |
| return 0; |
| } |
| |
| static int tcam_early_init(struct niu *np) |
| { |
| unsigned long i; |
| int err; |
| |
| tcam_enable(np, 0); |
| tcam_set_lat_and_ratio(np, |
| DEFAULT_TCAM_LATENCY, |
| DEFAULT_TCAM_ACCESS_RATIO); |
| for (i = CLASS_CODE_ETHERTYPE1; i <= CLASS_CODE_ETHERTYPE2; i++) { |
| err = tcam_user_eth_class_enable(np, i, 0); |
| if (err) |
| return err; |
| } |
| for (i = CLASS_CODE_USER_PROG1; i <= CLASS_CODE_USER_PROG4; i++) { |
| err = tcam_user_ip_class_enable(np, i, 0); |
| if (err) |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| static int tcam_flush_all(struct niu *np) |
| { |
| unsigned long i; |
| |
| for (i = 0; i < np->parent->tcam_num_entries; i++) { |
| int err = tcam_flush(np, i); |
| if (err) |
| return err; |
| } |
| return 0; |
| } |
| |
| static u64 hash_addr_regval(unsigned long index, unsigned long num_entries) |
| { |
| return (u64)index | (num_entries == 1 ? HASH_TBL_ADDR_AUTOINC : 0); |
| } |
| |
| #if 0 |
| static int hash_read(struct niu *np, unsigned long partition, |
| unsigned long index, unsigned long num_entries, |
| u64 *data) |
| { |
| u64 val = hash_addr_regval(index, num_entries); |
| unsigned long i; |
| |
| if (partition >= FCRAM_NUM_PARTITIONS || |
| index + num_entries > FCRAM_SIZE) |
| return -EINVAL; |
| |
| nw64(HASH_TBL_ADDR(partition), val); |
| for (i = 0; i < num_entries; i++) |
| data[i] = nr64(HASH_TBL_DATA(partition)); |
| |
| return 0; |
| } |
| #endif |
| |
| static int hash_write(struct niu *np, unsigned long partition, |
| unsigned long index, unsigned long num_entries, |
| u64 *data) |
| { |
|