/*
* New driver for Marvell Yukon chipset and SysKonnect Gigabit
* Ethernet adapters. Based on earlier sk98lin, e100 and
* FreeBSD if_sk drivers.
*
* This driver intentionally does not support all the features
* of the original driver such as link fail-over and link management because
* those should be done at higher levels.
*
* Copyright (C) 2004, 2005 Stephen Hemminger <shemminger@osdl.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.
*/
#include <linux/in.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/ethtool.h>
#include <linux/pci.h>
#include <linux/if_vlan.h>
#include <linux/ip.h>
#include <linux/delay.h>
#include <linux/crc32.h>
#include <linux/dma-mapping.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/mii.h>
#include <asm/irq.h>
#include "skge.h"
#define DRV_NAME "skge"
#define DRV_VERSION "1.13"
#define PFX DRV_NAME " "
#define DEFAULT_TX_RING_SIZE 128
#define DEFAULT_RX_RING_SIZE 512
#define MAX_TX_RING_SIZE 1024
#define TX_LOW_WATER (MAX_SKB_FRAGS + 1)
#define MAX_RX_RING_SIZE 4096
#define RX_COPY_THRESHOLD 128
#define RX_BUF_SIZE 1536
#define PHY_RETRIES 1000
#define ETH_JUMBO_MTU 9000
#define TX_WATCHDOG (5 * HZ)
#define NAPI_WEIGHT 64
#define BLINK_MS 250
#define LINK_HZ HZ
#define SKGE_EEPROM_MAGIC 0x9933aabb
MODULE_DESCRIPTION("SysKonnect Gigabit Ethernet driver");
MODULE_AUTHOR("Stephen Hemminger <shemminger@linux-foundation.org>");
MODULE_LICENSE("GPL");
MODULE_VERSION(DRV_VERSION);
static const u32 default_msg
= NETIF_MSG_DRV| NETIF_MSG_PROBE| NETIF_MSG_LINK
| NETIF_MSG_IFUP| NETIF_MSG_IFDOWN;
static int debug = -1; /* defaults above */
module_param(debug, int, 0);
MODULE_PARM_DESC(debug, "Debug level (0=none,...,16=all)");
static const struct pci_device_id skge_id_table[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_3COM, PCI_DEVICE_ID_3COM_3C940) },
{ PCI_DEVICE(PCI_VENDOR_ID_3COM, PCI_DEVICE_ID_3COM_3C940B) },
{ PCI_DEVICE(PCI_VENDOR_ID_SYSKONNECT, PCI_DEVICE_ID_SYSKONNECT_GE) },
{ PCI_DEVICE(PCI_VENDOR_ID_SYSKONNECT, PCI_DEVICE_ID_SYSKONNECT_YU) },
{ PCI_DEVICE(PCI_VENDOR_ID_DLINK, PCI_DEVICE_ID_DLINK_DGE510T) },
{ PCI_DEVICE(PCI_VENDOR_ID_DLINK, 0x4b01) }, /* DGE-530T */
{ PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4320) },
{ PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x5005) }, /* Belkin */
{ PCI_DEVICE(PCI_VENDOR_ID_CNET, PCI_DEVICE_ID_CNET_GIGACARD) },
{ PCI_DEVICE(PCI_VENDOR_ID_LINKSYS, PCI_DEVICE_ID_LINKSYS_EG1064) },
{ PCI_VENDOR_ID_LINKSYS, 0x1032, PCI_ANY_ID, 0x0015 },
{ 0 }
};
MODULE_DEVICE_TABLE(pci, skge_id_table);
static int skge_up(struct net_device *dev);
static int skge_down(struct net_device *dev);
static void skge_phy_reset(struct skge_port *skge);
static void skge_tx_clean(struct net_device *dev);
static int xm_phy_write(struct skge_hw *hw, int port, u16 reg, u16 val);
static int gm_phy_write(struct skge_hw *hw, int port, u16 reg, u16 val);
static void genesis_get_stats(struct skge_port *skge, u64 *data);
static void yukon_get_stats(struct skge_port *skge, u64 *data);
static void yukon_init(struct skge_hw *hw, int port);
static void genesis_mac_init(struct skge_hw *hw, int port);
static void genesis_link_up(struct skge_port *skge);
static void skge_set_multicast(struct net_device *dev);
/* Avoid conditionals by using array */
static const int txqaddr[] = { Q_XA1, Q_XA2 };
static const int rxqaddr[] = { Q_R1, Q_R2 };
static const u32 rxirqmask[] = { IS_R1_F, IS_R2_F };
static const u32 txirqmask[] = { IS_XA1_F, IS_XA2_F };
static const u32 napimask[] = { IS_R1_F|IS_XA1_F, IS_R2_F|IS_XA2_F };
static const u32 portmask[] = { IS_PORT_1, IS_PORT_2 };
static int skge_get_regs_len(struct net_device *dev)
{
return 0x4000;
}
/*
* Returns copy of whole control register region
* Note: skip RAM address register because accessing it will
* cause bus hangs!
*/
static void skge_get_regs(struct net_device *dev, struct ethtool_regs *regs,
void *p)
{
const struct skge_port *skge = netdev_priv(dev);
const void __iomem *io = skge->hw->regs;
regs->version = 1;
memset(p, 0, regs->len);
memcpy_fromio(p, io, B3_RAM_ADDR);
memcpy_fromio(p + B3_RI_WTO_R1, io + B3_RI_WTO_R1,
regs->len - B3_RI_WTO_R1);
}
/* Wake on Lan only supported on Yukon chips with rev 1 or above */
static u32 wol_supported(const struct skge_hw *hw)
{
if (hw->chip_id == CHIP_ID_GENESIS)
return 0;
if (hw->chip_id == CHIP_ID_YUKON && hw->chip_rev == 0)
return 0;
return WAKE_MAGIC | WAKE_PHY;
}
static void skge_wol_init(struct skge_port *skge)
{
struct skge_hw *hw = skge->hw;
int port = skge->port;
u16 ctrl;
skge_write16(hw, B0_CTST, CS_RST_CLR);
skge_write16(hw, SK_REG(port, GMAC_LINK_CTRL), GMLC_RST_CLR);
/* Turn on Vaux */
skge_write8(hw, B0_POWER_CTRL,
PC_VAUX_ENA | PC_VCC_ENA | PC_VAUX_ON | PC_VCC_OFF);
/* WA code for COMA mode -- clear PHY reset */
if (hw->chip_id == CHIP_ID_YUKON_LITE &&
hw->chip_rev >= CHIP_REV_YU_LITE_A3) {
u32 reg = skge_read32(hw, B2_GP_IO);
reg |= GP_DIR_9;
reg &= ~GP_IO_9;
skge_write32(hw, B2_GP_IO, reg);
}
skge_write32(hw, SK_REG(port, GPHY_CTRL),
GPC_DIS_SLEEP |
GPC_HWCFG_M_3 | GPC_HWCFG_M_2 | GPC_HWCFG_M_1 | GPC_HWCFG_M_0 |
GPC_ANEG_1 | GPC_RST_SET);
skge_write32(hw, SK_REG(port, GPHY_CTRL),
GPC_DIS_SLEEP |
GPC_HWCFG_M_3 | GPC_HWCFG_M_2 | GPC_HWCFG_M_1 | GPC_HWCFG_M_0 |
GPC_ANEG_1 | GPC_RST_CLR);
skge_write32(hw, SK_REG(port, GMAC_CTRL), GMC_RST_CLR);
/* Force to 10/100 skge_reset will re-enable on resume */
gm_phy_write(hw, port, PHY_MARV_AUNE_ADV,
PHY_AN_100FULL | PHY_AN_100HALF |
PHY_AN_10FULL | PHY_AN_10HALF| PHY_AN_CSMA);
/* no 1000 HD/FD */
gm_phy_write(hw, port, PHY_MARV_1000T_CTRL, 0);
gm_phy_write(hw, port, PHY_MARV_CTRL,
PHY_CT_RESET | PHY_CT_SPS_LSB | PHY_CT_ANE |
PHY_CT_RE_CFG | PHY_CT_DUP_MD);
/* Set GMAC to no flow control and auto update for speed/duplex */
gma_write16(hw, port, GM_GP_CTRL,
GM_GPCR_FC_TX_DIS|GM_GPCR_TX_ENA|GM_GPCR_RX_ENA|
GM_GPCR_DUP_FULL|GM_GPCR_FC_RX_DIS|GM_GPCR_AU_FCT_DIS);
/* Set WOL address */
memcpy_toio(hw->regs + WOL_REGS(port, WOL_MAC_ADDR),
skge->netdev->dev_addr, ETH_ALEN);
/* Turn on appropriate WOL control bits */
skge_write16(hw, WOL_REGS(port, WOL_CTRL_STAT), WOL_CTL_CLEAR_RESULT);
ctrl = 0;
if (skge->wol & WAKE_PHY)
ctrl |= WOL_CTL_ENA_PME_ON_LINK_CHG|WOL_CTL_ENA_LINK_CHG_UNIT;
else
ctrl |= WOL_CTL_DIS_PME_ON_LINK_CHG|WOL_CTL_DIS_LINK_CHG_UNIT;
if (skge->wol & WAKE_MAGIC)
ctrl |= WOL_CTL_ENA_PME_ON_MAGIC_PKT|WOL_CTL_ENA_MAGIC_PKT_UNIT;
else
ctrl |= WOL_CTL_DIS_PME_ON_MAGIC_PKT|WOL_CTL_DIS_MAGIC_PKT_UNIT;;
ctrl |= WOL_CTL_DIS_PME_ON_PATTERN|WOL_CTL_DIS_PATTERN_UNIT;
skge_write16(hw, WOL_REGS(port, WOL_CTRL_STAT), ctrl);
/* block receiver */
skge_write8(hw, SK_REG(port, RX_GMF_CTRL_T), GMF_RST_SET);
}
static void skge_get_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
{
struct skge_port *skge = netdev_priv(dev);
wol->supported = wol_supported(skge->hw);
wol->wolopts = skge->wol;
}
static int skge_set_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
{
struct skge_port *skge = netdev_priv(dev);
struct skge_hw *hw = skge->hw;
if ((wol->wolopts & ~wol_supported(hw))
|| !device_can_wakeup(&hw->pdev->dev))
return -EOPNOTSUPP;
skge->wol = wol->wolopts;
device_set_wakeup_enable(&hw->pdev->dev, skge->wol);
return 0;
}
/* Determine supported/advertised modes based on hardware.
* Note: ethtool ADVERTISED_xxx == SUPPORTED_xxx
*/
static u32 skge_supported_modes(const struct skge_hw *hw)
{
u32 supported;
if (hw->copper) {
supported = SUPPORTED_10baseT_Half
| SUPPORTED_10baseT_Full
| SUPPORTED_100baseT_Half
| SUPPORTED_100baseT_Full
| SUPPORTED_1000baseT_Half
| SUPPORTED_1000baseT_Full
| SUPPORTED_Autoneg| SUPPORTED_TP;
if (hw->chip_id == CHIP_ID_GENESIS)
supported &= ~(SUPPORTED_10baseT_Half
| SUPPORTED_10baseT_Full
| SUPPORTED_100baseT_Half
| SUPPORTED_100baseT_Full);
else if (hw->chip_id == CHIP_ID_YUKON)
supported &= ~SUPPORTED_1000baseT_Half;
} else
supported = SUPPORTED_1000baseT_Full | SUPPORTED_1000baseT_Half
| SUPPORTED_FIBRE | SUPPORTED_Autoneg;
return supported;
}
static int skge_get_settings(struct net_device *dev,
struct ethtool_cmd *ecmd)
{
struct skge_port *skge = netdev_priv(dev);
struct skge_hw *hw = skge->hw;
ecmd->transceiver = XCVR_INTERNAL;
ecmd->supported = skge_supported_modes(hw);
if (hw->copper) {
ecmd->port = PORT_TP;
ecmd->phy_address = hw->phy_addr;
} else
ecmd->port = PORT_FIBRE;
ecmd->advertising = skge->advertising;
ecmd->autoneg = skge->autoneg;
ecmd->speed = skge->speed;
ecmd->duplex = skge->duplex;
return 0;
}
static int skge_set_settings(struct net_device *dev, struct ethtool_cmd *ecmd)
{
struct skge_port *skge = netdev_priv(dev);
const struct skge_hw *hw = skge->hw;
u32 supported = skge_supported_modes(hw);
int err = 0;
if (ecmd->autoneg == AUTONEG_ENABLE) {
ecmd->advertising = supported;
skge->duplex = -1;
skge->speed = -1;
} else {
u32 setting;
switch (ecmd->speed) {
case SPEED_1000:
if (ecmd->duplex == DUPLEX_FULL)
setting = SUPPORTED_1000baseT_Full;
else if (ecmd->duplex == DUPLEX_HALF)
setting = SUPPORTED_1000baseT_Half;
else
return -EINVAL;
break;
case SPEED_100:
if (ecmd->duplex == DUPLEX_FULL)
setting = SUPPORTED_100baseT_Full;
else if (ecmd->duplex == DUPLEX_HALF)
setting = SUPPORTED_100baseT_Half;
else
return -EINVAL;
break;
case SPEED_10:
if (ecmd->duplex == DUPLEX_FULL)
setting = SUPPORTED_10baseT_Full;
else if (ecmd->duplex == DUPLEX_HALF)
setting = SUPPORTED_10baseT_Half;
else
return -EINVAL;
break;
default:
return -EINVAL;
}
if ((setting & supported) == 0)
return -EINVAL;
skge->speed = ecmd->speed;
skge->duplex = ecmd->duplex;
}
skge->autoneg = ecmd->autoneg;
skge->advertising = ecmd->advertising;
if (netif_running(dev)) {
skge_down(dev);
err = skge_up(dev);
if (err) {
dev_close(dev);
return err;
}
}
return (0);
}
static void skge_get_drvinfo(struct net_device *dev,
struct ethtool_drvinfo *info)
{
struct skge_port *skge = netdev_priv(dev);
strcpy(info->driver, DRV_NAME);
strcpy(info->version, DRV_VERSION);
strcpy(info->fw_version, "N/A");
strcpy(info->bus_info, pci_name(skge->hw->pdev));
}
static const struct skge_stat {
char name[ETH_GSTRING_LEN];
u16 xmac_offset;
u16 gma_offset;
} skge_stats[] = {
{ "tx_bytes", XM_TXO_OK_HI, GM_TXO_OK_HI },
{ "rx_bytes", XM_RXO_OK_HI, GM_RXO_OK_HI },
{ "tx_broadcast", XM_TXF_BC_OK, GM_TXF_BC_OK },
{ "rx_broadcast", XM_RXF_BC_OK, GM_RXF_BC_OK },
{ "tx_multicast", XM_TXF_MC_OK, GM_TXF_MC_OK },
{ "rx_multicast", XM_RXF_MC_OK, GM_RXF_MC_OK },
{ "tx_unicast", XM_TXF_UC_OK, GM_TXF_UC_OK },
{ "rx_unicast", XM_RXF_UC_OK, GM_RXF_UC_OK },
{ "tx_mac_pause", XM_TXF_MPAUSE, GM_TXF_MPAUSE },
{ "rx_mac_pause", XM_RXF_MPAUSE, GM_RXF_MPAUSE },
{ "collisions", XM_TXF_SNG_COL, GM_TXF_SNG_COL },
{ "multi_collisions", XM_TXF_MUL_COL, GM_TXF_MUL_COL },
{ "aborted", XM_TXF_ABO_COL, GM_TXF_ABO_COL },
{ "late_collision", XM_TXF_LAT_COL, GM_TXF_LAT_COL },
{ "fifo_underrun", XM_TXE_FIFO_UR, GM_TXE_FIFO_UR },
{ "fifo_overflow", XM_RXE_FIFO_OV, GM_RXE_FIFO_OV },
{ "rx_toolong", XM_RXF_LNG_ERR, GM_RXF_LNG_ERR },
{ "rx_jabber", XM_RXF_JAB_PKT, GM_RXF_JAB_PKT },
{ "rx_runt", XM_RXE_RUNT, GM_RXE_FRAG },
{ "rx_too_long", XM_RXF_LNG_ERR, GM_RXF_LNG_ERR },
{ "rx_fcs_error", XM_RXF_FCS_ERR, GM_RXF_FCS_ERR },
};
static int skge_get_sset_count(struct net_device *dev, int sset)
{
switch (sset) {
case ETH_SS_STATS:
return ARRAY_SIZE(skge_stats);
default:
return -EOPNOTSUPP;
}
}
static void skge_get_ethtool_stats(struct net_device *dev,
struct ethtool_stats *stats, u64 *data)
{
struct skge_port *skge = netdev_priv(dev);
if (skge->hw->chip_id == CHIP_ID_GENESIS)
genesis_get_stats(skge, data);
else
yukon_get_stats(skge, data);
}
/* Use hardware MIB variables for critical path statistics and
* transmit feedback not reported at interrupt.
* Other errors are accounted for in interrupt handler.
*/
static struct net_device_stats *skge_get_stats(struct net_device *dev)
{
struct skge_port *skge = netdev_priv(dev);
u64 data[ARRAY_SIZE(skge_stats)];
if (skge->hw->chip_id == CHIP_ID_GENESIS)
genesis_get_stats(skge, data);
else
yukon_get_stats(skge, data);
dev->stats.tx_bytes = data[0];
dev->stats.rx_bytes = data[1];
dev->stats.tx_packets = data[2] + data[4] + data[6];
dev->stats.rx_packets = data[3] + data[5] + data[7];
dev->stats.multicast = data[3] + data[5];
dev->stats.collisions = data[10];
dev->stats.tx_aborted_errors = data[12];
return &dev->stats;
}
static void skge_get_strings(struct net_device *dev, u32 stringset, u8 *data)
{
int i;
switch (stringset) {
case ETH_SS_STATS:
for (i = 0; i < ARRAY_SIZE(skge_stats); i++)
memcpy(data + i * ETH_GSTRING_LEN,
skge_stats[i].name, ETH_GSTRING_LEN);
break;
}
}
static void skge_get_ring_param(struct net_device *dev,
struct ethtool_ringparam *p)
{
struct skge_port *skge = netdev_priv(dev);
p->rx_max_pending = MAX_RX_RING_SIZE;
p->tx_max_pending = MAX_TX_RING_SIZE;
p->rx_mini_max_pending = 0;
p->rx_jumbo_max_pending = 0;
p->rx_pending = skge->rx_ring.count;
p->tx_pending = skge->tx_ring.count;
p->rx_mini_pending = 0;
p->rx_jumbo_pending = 0;
}
static int skge_set_ring_param(struct net_device *dev,
struct ethtool_ringparam *p)
{
struct skge_port *skge = netdev_priv(dev);
int err = 0;
if (p->rx_pending == 0 || p->rx_pending > MAX_RX_RING_SIZE ||
p->tx_pending < TX_LOW_WATER || p->tx_pending > MAX_TX_RING_SIZE)
return -EINVAL;
skge->rx_ring.count = p->rx_pending;
skge->tx_ring.count = p->tx_pending;
if (netif_running(dev)) {
skge_down(dev);
err = skge_up(dev);
if (err)
dev_close(dev);
}
return err;
}
static u32 skge_get_msglevel(struct net_device *netdev)
{
struct skge_port *skge = netdev_priv(netdev);
return skge->msg_enable;
}
static void skge_set_msglevel(struct net_device *netdev, u32 value)
{
struct skge_port *skge = netdev_priv(netdev);
skge->msg_enable = value;
}
static int skge_nway_reset(struct net_device *dev)
{
struct skge_port *skge = netdev_priv(dev);
if (skge->autoneg != AUTONEG_ENABLE || !netif_running(dev))
return -EINVAL;
skge_phy_reset(skge);
return 0;
}
static int skge_set_sg(struct net_device *dev, u32 data)
{
struct skge_port *skge = netdev_priv(dev);
struct skge_hw *hw = skge->hw;
if (hw->chip_id == CHIP_ID_GENESIS && data)
return -EOPNOTSUPP;
return ethtool_op_set_sg(dev, data);
}
static int skge_set_tx_csum(struct net_device *dev, u32 data)
{
struct skge_port *skge = netdev_priv(dev);
struct skge_hw *hw = skge->hw;
if (hw->chip_id == CHIP_ID_GENESIS && data)
return -EOPNOTSUPP;
return ethtool_op_set_tx_csum(dev, data);
}
static u32 skge_get_rx_csum(struct net_device *dev)
{
struct skge_port *skge = netdev_priv(dev);
return skge->rx_csum;
}
/* Only Yukon supports checksum offload. */
static int skge_set_rx_csum(struct net_device *dev, u32 data)
{
struct skge_port *skge = netdev_priv(dev);
if (skge->hw->chip_id == CHIP_ID_GENESIS && data)
return -EOPNOTSUPP;
skge->rx_csum = data;
return 0;
}
static void skge_get_pauseparam(struct net_device *dev,
struct ethtool_pauseparam *ecmd)
{
struct skge_port *skge = netdev_priv(dev);
ecmd->rx_pause = (skge->flow_control == FLOW_MODE_SYMMETRIC)
|| (skge->flow_control == FLOW_MODE_SYM_OR_REM);
ecmd->tx_pause = ecmd->rx_pause || (skge->flow_control == FLOW_MODE_LOC_SEND);
ecmd->autoneg = ecmd->rx_pause || ecmd->tx_pause;
}
static int skge_set_pauseparam(struct net_device *dev,
struct ethtool_pauseparam *ecmd)
{
struct skge_port *skge = netdev_priv(dev);
struct ethtool_pauseparam old;
int err = 0;
skge_get_pauseparam(dev, &old);
if (ecmd->autoneg != old.autoneg)
skge->flow_control = ecmd->autoneg ? FLOW_MODE_NONE : FLOW_MODE_SYMMETRIC;
else {
if (ecmd->rx_pause && ecmd->tx_pause)
skge->flow_control = FLOW_MODE_SYMMETRIC;
else if (ecmd->rx_pause && !ecmd->tx_pause)
skge->flow_control = FLOW_MODE_SYM_OR_REM;
else if (!ecmd->rx_pause && ecmd->tx_pause)
skge->flow_control = FLOW_MODE_LOC_SEND;
else
skge->flow_control = FLOW_MODE_NONE;
}
if (netif_running(dev)) {
skge_down(dev);
err = skge_up(dev);
if (err) {
dev_close(dev);
return err;
}
}
return 0;
}
/* Chip internal frequency for clock calculations */
static inline u32 hwkhz(const struct skge_hw *hw)
{
return (hw->chip_id == CHIP_ID_GENESIS) ? 53125 : 78125;
}
/* Chip HZ to microseconds */
static inline u32 skge_clk2usec(const struct skge_hw *hw, u32 ticks)
{
return (ticks * 1000) / hwkhz(hw);
}
/* Microseconds to chip HZ */
static inline u32 skge_usecs2clk(const struct skge_hw *hw, u32 usec)
{
return hwkhz(hw) * usec / 1000;
}
static int skge_get_coalesce(struct net_device *dev,
struct ethtool_coalesce *ecmd)
{
struct skge_port *skge = netdev_priv(dev);
struct skge_hw *hw = skge->hw;
int port = skge->port;
ecmd->rx_coalesce_usecs = 0;
ecmd->tx_coalesce_usecs = 0;
if (skge_read32(hw, B2_IRQM_CTRL) & TIM_START) {
u32 delay = skge_clk2usec(hw, skge_read32(hw, B2_IRQM_INI));
u32 msk = skge_read32(hw, B2_IRQM_MSK);
if (msk & rxirqmask[port])
ecmd->rx_coalesce_usecs = delay;
if (msk & txirqmask[port])
ecmd->tx_coalesce_usecs = delay;
}
return 0;
}
/* Note: interrupt timer is per board, but can turn on/off per port */
static int skge_set_coalesce(struct net_device *dev,
struct ethtool_coalesce *ecmd)
{
struct skge_port *skge = netdev_priv(dev);
struct skge_hw *hw = skge->hw;
int port = skge->port;
u32 msk = skge_read32(hw, B2_IRQM_MSK);
u32 delay = 25;
if (ecmd->rx_coalesce_usecs == 0)
msk &= ~rxirqmask[port];
else if (ecmd->rx_coalesce_usecs < 25 ||
ecmd->rx_coalesce_usecs > 33333)
return -EINVAL;
else {
msk |= rxirqmask[port];
delay = ecmd->rx_coalesce_usecs;
}
if (ecmd->tx_coalesce_usecs == 0)
msk &= ~txirqmask[port];
else if (ecmd->tx_coalesce_usecs < 25 ||
ecmd->tx_coalesce_usecs > 33333)
return -EINVAL;
else {
msk |= txirqmask[port];
delay = min(delay, ecmd->rx_coalesce_usecs);
}
skge_write32(hw, B2_IRQM_MSK, msk);
if (msk == 0)
skge_write32(hw, B2_IRQM_CTRL, TIM_STOP);
else {
skge_write32(hw, B2_IRQM_INI, skge_usecs2clk(hw, delay));
skge_write32(hw, B2_IRQM_CTRL, TIM_START);
}
return 0;
}
enum led_mode { LED_MODE_OFF, LED_MODE_ON, LED_MODE_TST };
static void skge_led(struct skge_port *skge, enum led_mode mode)
{
struct skge_hw *hw = skge->hw;
int port = skge->port;
spin_lock_bh(&hw->phy_lock);
if (hw->chip_id == CHIP_ID_GENESIS) {
switch (mode) {
case LED_MODE_OFF:
if (hw->phy_type == SK_PHY_BCOM)
xm_phy_write(hw, port, PHY_BCOM_P_EXT_CTRL, PHY_B_PEC_LED_OFF);
else {
skge_write32(hw, SK_REG(port, TX_LED_VAL), 0);
skge_write8(hw, SK_REG(port, TX_LED_CTRL), LED_T_OFF);
}
skge_write8(hw, SK_REG(port, LNK_LED_REG), LINKLED_OFF);
skge_write32(hw, SK_REG(port, RX_LED_VAL), 0);
skge_write8(hw, SK_REG(port, RX_LED_CTRL), LED_T_OFF);
break;
case LED_MODE_ON:
skge_write8(hw, SK_REG(port, LNK_LED_REG), LINKLED_ON);
skge_write8(hw, SK_REG(port, LNK_LED_REG), LINKLED_LINKSYNC_ON);
skge_write8(hw, SK_REG(port, RX_LED_CTRL), LED_START);
skge_write8(hw, SK_REG(port, TX_LED_CTRL), LED_START);
break;
case LED_MODE_TST:
skge_write8(hw, SK_REG(port, RX_LED_TST), LED_T_ON);
skge_write32(hw, SK_REG(port, RX_LED_VAL), 100);
skge_write8(hw, SK_REG(port, RX_LED_CTRL), LED_START);
if (hw->phy_type == SK_PHY_BCOM)
xm_phy_write(hw, port, PHY_BCOM_P_EXT_CTRL, PHY_B_PEC_LED_ON);
else {
skge_write8(hw, SK_REG(port, TX_LED_TST), LED_T_ON);
skge_write32(hw, SK_REG(port, TX_LED_VAL), 100);
skge_write8(hw, SK_REG(port, TX_LED_CTRL), LED_START);
}
}
} else {
switch (mode) {
case LED_MODE_OFF:
gm_phy_write(hw, port, PHY_MARV_LED_CTRL, 0);
gm_phy_write(hw, port, PHY_MARV_LED_OVER,
PHY_M_LED_MO_DUP(MO_LED_OFF) |
PHY_M_LED_MO_10(MO_LED_OFF) |
PHY_M_LED_MO_100(MO_LED_OFF) |
PHY_M_LED_MO_1000(MO_LED_OFF) |
PHY_M_LED_MO_RX(MO_LED_OFF));
break;
case LED_MODE_ON:
gm_phy_write(hw, port, PHY_MARV_LED_CTRL,
PHY_M_LED_PULS_DUR(PULS_170MS) |
PHY_M_LED_BLINK_RT(BLINK_84MS) |
PHY_M_LEDC_TX_CTRL |
PHY_M_LEDC_DP_CTRL);
gm_phy_write(hw, port, PHY_MARV_LED_OVER,
PHY_M_LED_MO_RX(MO_LED_OFF) |
(skge->speed == SPEED_100 ?
PHY_M_LED_MO_100(MO_LED_ON) : 0));
break;
case LED_MODE_TST:
gm_phy_write(hw, port, PHY_MARV_LED_CTRL, 0);
gm_phy_write(hw, port, PHY_MARV_LED_OVER,
PHY_M_LED_MO_DUP(MO_LED_ON) |
PHY_M_LED_MO_10(MO_LED_ON) |
PHY_M_LED_MO_100(MO_LED_ON) |
PHY_M_LED_MO_1000(MO_LED_ON) |
PHY_M_LED_MO_RX(MO_LED_ON));
}
}
spin_unlock_bh(&hw->phy_lock);
}
/* blink LED's for finding board */
static int skge_phys_id(struct net_device *dev, u32 data)
{
struct skge_port *skge = netdev_priv(dev);
unsigned long ms;
enum led_mode mode = LED_MODE_TST;
if (!data || data > (u32)(MAX_SCHEDULE_TIMEOUT / HZ))
ms = jiffies_to_msecs(MAX_SCHEDULE_TIMEOUT / HZ) * 1000;
else
ms = data * 1000;
while (ms > 0) {
skge_led(skge, mode);
mode ^= LED_MODE_TST;
if (msleep_interruptible(BLINK_MS))
break;
ms -= BLINK_MS;
}
/* back to regular LED state */
skge_led(skge, netif_running(dev) ? LED_MODE_ON : LED_MODE_OFF);
return 0;
}
static int skge_get_eeprom_len(struct net_device *dev)
{
struct skge_port *skge = netdev_priv(dev);
u32 reg2;
pci_read_config_dword(skge->hw->pdev, PCI_DEV_REG2, ®2);
return 1 << ( ((reg2 & PCI_VPD_ROM_SZ) >> 14) + 8);
}
static u32 skge_vpd_read(struct pci_dev *pdev, int cap, u16 offset)
{
u32 val;
pci_write_config_word(pdev, cap + PCI_VPD_ADDR, offset);
do {
pci_read_config_word(pdev, cap + PCI_VPD_ADDR, &offset);
} while (!(offset & PCI_VPD_ADDR_F));
pci_read_config_dword(pdev, cap + PCI_VPD_DATA, &val);
return val;
}
static void skge_vpd_write(struct pci_dev *pdev, int cap, u16 offset, u32 val)
{
pci_write_config_dword(pdev, cap + PCI_VPD_DATA, val);
pci_write_config_word(pdev, cap + PCI_VPD_ADDR,
offset | PCI_VPD_ADDR_F);
do {
pci_read_config_word(pdev, cap + PCI_VPD_ADDR, &offset);
} while (offset & PCI_VPD_ADDR_F);
}
static int skge_get_eeprom(struct net_device *dev, struct ethtool_eeprom *eeprom,
u8 *data)
{
struct skge_port *skge = netdev_priv(dev);
struct pci_dev *pdev = skge->hw->pdev;
int cap = pci_find_capability(pdev, PCI_CAP_ID_VPD);
int length = eeprom->len;
u16 offset = eeprom->offset;
if (!cap)
return -EINVAL;
eeprom->magic = SKGE_EEPROM_MAGIC;
while (length > 0) {
u32 val = skge_vpd_read(pdev, cap, offset);
int n = min_t(int, length, sizeof(val));
memcpy(data, &val, n);
length -= n;
data += n;
offset += n;
}
return 0;
}
static int skge_set_eeprom(struct net_device *dev, struct ethtool_eeprom *eeprom,
u8 *data)
{
struct skge_port *skge = netdev_priv(dev);
struct pci_dev *pdev = skge->hw->pdev;
int cap = pci_find_capability(pdev, PCI_CAP_ID_VPD);
int length = eeprom->len;
u16 offset = eeprom->offset;
if (!cap)
return -EINVAL;
if (eeprom->magic != SKGE_EEPROM_MAGIC)
return -EINVAL;
while (length > 0) {
u32 val;
int n = min_t(int, length, sizeof(val));
if (n < sizeof(val))
val = skge_vpd_read(pdev, cap, offset);
memcpy(&val, data, n);
skge_vpd_write(pdev, cap, offset, val);
length -= n;
data += n;
offset += n;
}
return 0;
}
static const struct ethtool_ops skge_ethtool_ops = {
.get_settings = skge_get_settings,
.set_settings = skge_set_settings,
|