/*
* JMicron JMC2x0 series PCIe Ethernet Linux Device Driver
*
* Copyright 2008 JMicron Technology Corporation
* http://www.jmicron.com/
* Copyright (c) 2009 - 2010 Guo-Fu Tseng <cooldavid@cooldavid.org>
*
* Author: Guo-Fu Tseng <cooldavid@cooldavid.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/pci-aspm.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/ethtool.h>
#include <linux/mii.h>
#include <linux/crc32.h>
#include <linux/delay.h>
#include <linux/spinlock.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/ipv6.h>
#include <linux/tcp.h>
#include <linux/udp.h>
#include <linux/if_vlan.h>
#include <linux/slab.h>
#include <net/ip6_checksum.h>
#include "jme.h"
static int force_pseudohp = -1;
static int no_pseudohp = -1;
static int no_extplug = -1;
module_param(force_pseudohp, int, 0);
MODULE_PARM_DESC(force_pseudohp,
"Enable pseudo hot-plug feature manually by driver instead of BIOS.");
module_param(no_pseudohp, int, 0);
MODULE_PARM_DESC(no_pseudohp, "Disable pseudo hot-plug feature.");
module_param(no_extplug, int, 0);
MODULE_PARM_DESC(no_extplug,
"Do not use external plug signal for pseudo hot-plug.");
static int
jme_mdio_read(struct net_device *netdev, int phy, int reg)
{
struct jme_adapter *jme = netdev_priv(netdev);
int i, val, again = (reg == MII_BMSR) ? 1 : 0;
read_again:
jwrite32(jme, JME_SMI, SMI_OP_REQ |
smi_phy_addr(phy) |
smi_reg_addr(reg));
wmb();
for (i = JME_PHY_TIMEOUT * 50 ; i > 0 ; --i) {
udelay(20);
val = jread32(jme, JME_SMI);
if ((val & SMI_OP_REQ) == 0)
break;
}
if (i == 0) {
pr_err("phy(%d) read timeout : %d\n", phy, reg);
return 0;
}
if (again--)
goto read_again;
return (val & SMI_DATA_MASK) >> SMI_DATA_SHIFT;
}
static void
jme_mdio_write(struct net_device *netdev,
int phy, int reg, int val)
{
struct jme_adapter *jme = netdev_priv(netdev);
int i;
jwrite32(jme, JME_SMI, SMI_OP_WRITE | SMI_OP_REQ |
((val << SMI_DATA_SHIFT) & SMI_DATA_MASK) |
smi_phy_addr(phy) | smi_reg_addr(reg));
wmb();
for (i = JME_PHY_TIMEOUT * 50 ; i > 0 ; --i) {
udelay(20);
if ((jread32(jme, JME_SMI) & SMI_OP_REQ) == 0)
break;
}
if (i == 0)
pr_err("phy(%d) write timeout : %d\n", phy, reg);
}
static inline void
jme_reset_phy_processor(struct jme_adapter *jme)
{
u32 val;
jme_mdio_write(jme->dev,
jme->mii_if.phy_id,
MII_ADVERTISE, ADVERTISE_ALL |
ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM);
if (jme->pdev->device == PCI_DEVICE_ID_JMICRON_JMC250)
jme_mdio_write(jme->dev,
jme->mii_if.phy_id,
MII_CTRL1000,
ADVERTISE_1000FULL | ADVERTISE_1000HALF);
val = jme_mdio_read(jme->dev,
jme->mii_if.phy_id,
MII_BMCR);
jme_mdio_write(jme->dev,
jme->mii_if.phy_id,
MII_BMCR, val | BMCR_RESET);
}
static void
jme_setup_wakeup_frame(struct jme_adapter *jme,
const u32 *mask, u32 crc, int fnr)
{
int i;
/*
* Setup CRC pattern
*/
jwrite32(jme, JME_WFOI, WFOI_CRC_SEL | (fnr & WFOI_FRAME_SEL));
wmb();
jwrite32(jme, JME_WFODP, crc);
wmb();
/*
* Setup Mask
*/
for (i = 0 ; i < WAKEUP_FRAME_MASK_DWNR ; ++i) {
jwrite32(jme, JME_WFOI,
((i << WFOI_MASK_SHIFT) & WFOI_MASK_SEL) |
(fnr & WFOI_FRAME_SEL));
wmb();
jwrite32(jme, JME_WFODP, mask[i]);
wmb();
}
}
static inline void
jme_mac_rxclk_off(struct jme_adapter *jme)
{
jme->reg_gpreg1 |= GPREG1_RXCLKOFF;
jwrite32f(jme, JME_GPREG1, jme->reg_gpreg1);
}
static inline void
jme_mac_rxclk_on(struct jme_adapter *jme)
{
jme->reg_gpreg1 &= ~GPREG1_RXCLKOFF;
jwrite32f(jme, JME_GPREG1, jme->reg_gpreg1);
}
static inline void
jme_mac_txclk_off(struct jme_adapter *jme)
{
jme->reg_ghc &= ~(GHC_TO_CLK_SRC | GHC_TXMAC_CLK_SRC);
jwrite32f(jme, JME_GHC, jme->reg_ghc);
}
static inline void
jme_mac_txclk_on(struct jme_adapter *jme)
{
u32 speed = jme->reg_ghc & GHC_SPEED;
if (speed == GHC_SPEED_1000M)
jme->reg_ghc |= GHC_TO_CLK_GPHY | GHC_TXMAC_CLK_GPHY;
else
jme->reg_ghc |= GHC_TO_CLK_PCIE | GHC_TXMAC_CLK_PCIE;
jwrite32f(jme, JME_GHC, jme->reg_ghc);
}
static inline void
jme_reset_ghc_speed(struct jme_adapter *jme)
{
jme->reg_ghc &= ~(GHC_SPEED | GHC_DPX);
jwrite32f(jme, JME_GHC, jme->reg_ghc);
}
static inline void
jme_reset_250A2_workaround(struct jme_adapter *jme)
{
jme->reg_gpreg1 &= ~(GPREG1_HALFMODEPATCH |
GPREG1_RSSPATCH);
jwrite32(jme, JME_GPREG1, jme->reg_gpreg1);
}
static inline void
jme_assert_ghc_reset(struct jme_adapter *jme)
{
jme->reg_ghc |= GHC_SWRST;
jwrite32f(jme, JME_GHC, jme->reg_ghc);
}
static inline void
jme_clear_ghc_reset(struct jme_adapter *jme)
{
jme->reg_ghc &= ~GHC_SWRST;
jwrite32f(jme, JME_GHC, jme->reg_ghc);
}
static inline void
jme_reset_mac_processor(struct jme_adapter *jme)
{
static const u32 mask[WAKEUP_FRAME_MASK_DWNR] = {0, 0, 0, 0};
u32 crc = 0xCDCDCDCD;
u32 gpreg0;
int i;
jme_reset_ghc_speed(jme);
jme_reset_250A2_workaround(jme);
jme_mac_rxclk_on(jme);
jme_mac_txclk_on(jme);
udelay(1);
jme_assert_ghc_reset(jme);
udelay(1);
jme_mac_rxclk_off(jme);
jme_mac_txclk_off(jme);
udelay(1);
jme_clear_ghc_reset(jme);
udelay(1);
jme_mac_rxclk_on(jme);
jme_mac_txclk_on(jme);
udelay(1);
jme_mac_rxclk_off(jme);
jme_mac_txclk_off(jme);
jwrite32(jme, JME_RXDBA_LO, 0x00000000);
jwrite32(jme, JME_RXDBA_HI, 0x00000000);
jwrite32(jme, JME_RXQDC, 0x00000000);
jwrite32(jme, JME_RXNDA, 0x00000000);
jwrite32(jme, JME_TXDBA_LO, 0x00000000);
jwrite32(jme, JME_TXDBA_HI, 0x00000000);
jwrite32(jme, JME_TXQDC, 0x00000000);
jwrite32(jme, JME_TXNDA, 0x00000000);
jwrite32(jme, JME_RXMCHT_LO, 0x00000000);
jwrite32(jme, JME_RXMCHT_HI, 0x00000000);
for (i = 0 ; i < WAKEUP_FRAME_NR ; ++i)
jme_setup_wakeup_frame(jme, mask, crc, i);
if (jme->fpgaver)
gpreg0 = GPREG0_DEFAULT | GPREG0_LNKINTPOLL;
else
gpreg0 = GPREG0_DEFAULT;
jwrite32(jme, JME_GPREG0, gpreg0);
}
static inline void
jme_clear_pm(struct jme_adapter *jme)
{
jwrite32(jme, JME_PMCS, PMCS_STMASK | jme->reg_pmcs);
}
static int
jme_reload_eeprom(struct jme_adapter *jme)
{
u32 val;
int i;
val = jread32(jme, JME_SMBCSR);
if (val & SMBCSR_EEPROMD) {
val |= SMBCSR_CNACK;
jwrite32(jme, JME_SMBCSR, val);
val |= SMBCSR_RELOAD;
jwrite32(jme, JME_SMBCSR, val);
mdelay(12);
for (i = JME_EEPROM_RELOAD_TIMEOUT; i > 0; --i) {
mdelay(1);
if ((jread32(jme, JME_SMBCSR) & SMBCSR_RELOAD) == 0)
break;
}
if (i == 0) {
pr_err("eeprom reload timeout\n");
return -EIO;
}
}
return 0;
}
static void
jme_load_macaddr(struct net_device *netdev)
{
struct jme_adapter *jme = netdev_priv(netdev);
unsigned char macaddr[ETH_ALEN];
u32 val;
spin_lock_bh(&jme->macaddr_lock);
val = jread32(jme, JME_RXUMA_LO);
macaddr[0] = (val >> 0) & 0xFF;
macaddr[1] = (val >> 8) & 0xFF;
macaddr[2] = (val >> 16) & 0xFF;
macaddr[3] = (val >> 24) & 0xFF;
val = jread32(jme, JME_RXUMA_HI);
macaddr[4] = (val >> 0) & 0xFF;
macaddr[5] = (val >> 8) & 0xFF;
memcpy(netdev->dev_addr, macaddr, ETH_ALEN);
spin_unlock_bh(&jme->macaddr_lock);
}
static inline void
jme_set_rx_pcc(struct jme_adapter *jme, int p)
{
switch (p) {
case PCC_OFF:
jwrite32(jme, JME_PCCRX0,
((PCC_OFF_TO << PCCRXTO_SHIFT) & PCCRXTO_MASK) |
((PCC_OFF_CNT << PCCRX_SHIFT) & PCCRX_MASK));
break;
case