#define net_tx_err(p, arg...) if (netif_msg_tx_err(p)) \
printk(arg)
+#define PHY_MAX_ADDR 32
+#define PHY_ID_ANY 0x1f
+#define MII_REG_ANY 0x1f
+
#ifdef CONFIG_SIS190_NAPI
#define NAPI_SUFFIX "-NAPI"
#else
#define MAC_ADDR_LEN 6
-#define NUM_TX_DESC 64
-#define NUM_RX_DESC 64
+#define NUM_TX_DESC 64 /* [8..1024] */
+#define NUM_RX_DESC 64 /* [8..8192] */
#define TX_RING_BYTES (NUM_TX_DESC * sizeof(struct TxDesc))
#define RX_RING_BYTES (NUM_RX_DESC * sizeof(struct RxDesc))
#define RX_BUF_SIZE 1536
+#define RX_BUF_MASK 0xfff8
#define SIS190_REGS_SIZE 0x80
#define SIS190_TX_TIMEOUT (6*HZ)
RxHalt = 0x00000002,
TxHalt = 0x00000001,
- /* RxStatusDesc */
- RxRES = 0x00200000, // unused
- RxCRC = 0x00080000,
- RxRUNT = 0x00100000, // unused
- RxRWT = 0x00400000, // unused
-
/* {Rx/Tx}CmdBits */
CmdReset = 0x10,
CmdRxEnb = 0x08, // unused
};
struct TxDesc {
- u32 PSize;
- u32 status;
- u32 addr;
- u32 size;
+ __le32 PSize;
+ __le32 status;
+ __le32 addr;
+ __le32 size;
};
struct RxDesc {
- u32 PSize;
- u32 status;
- u32 addr;
- u32 size;
+ __le32 PSize;
+ __le32 status;
+ __le32 addr;
+ __le32 size;
};
enum _DescStatusBit {
/* _Desc.status */
- OWNbit = 0x80000000,
- INTbit = 0x40000000,
- DEFbit = 0x00200000,
- CRCbit = 0x00020000,
- PADbit = 0x00010000,
+ OWNbit = 0x80000000, // RXOWN/TXOWN
+ INTbit = 0x40000000, // RXINT/TXINT
+ CRCbit = 0x00020000, // CRCOFF/CRCEN
+ PADbit = 0x00010000, // PREADD/PADEN
/* _Desc.size */
- RingEnd = (1 << 31),
- /* _Desc.PSize */
+ RingEnd = 0x80000000,
+ /* TxDesc.status */
+ LSEN = 0x08000000, // TSO ? -- FR
+ IPCS = 0x04000000,
+ TCPCS = 0x02000000,
+ UDPCS = 0x01000000,
+ BSTEN = 0x00800000,
+ EXTEN = 0x00400000,
+ DEFEN = 0x00200000,
+ BKFEN = 0x00100000,
+ CRSEN = 0x00080000,
+ COLEN = 0x00040000,
+ THOL3 = 0x30000000,
+ THOL2 = 0x20000000,
+ THOL1 = 0x10000000,
+ THOL0 = 0x00000000,
+ /* RxDesc.status */
+ IPON = 0x20000000,
+ TCPON = 0x10000000,
+ UDPON = 0x08000000,
+ Wakup = 0x00400000,
+ Magic = 0x00200000,
+ Pause = 0x00100000,
+ DEFbit = 0x00200000,
+ BCAST = 0x000c0000,
+ MCAST = 0x00080000,
+ UCAST = 0x00040000,
+ /* RxDesc.PSize */
+ TAGON = 0x80000000,
+ RxDescCountMask = 0x7f000000, // multi-desc pkt when > 1 ? -- FR
+ ABORT = 0x00800000,
+ SHORT = 0x00400000,
+ LIMIT = 0x00200000,
+ MIIER = 0x00100000,
+ OVRUN = 0x00080000,
+ NIBON = 0x00040000,
+ COLON = 0x00020000,
+ CRCOK = 0x00010000,
RxSizeMask = 0x0000ffff
+ /*
+ * The asic could apparently do vlan, TSO, jumbo (sis191 only) and
+ * provide two (unused with Linux) Tx queues. No publically
+ * available documentation alas.
+ */
};
enum sis190_eeprom_access_register_bits {
struct timer_list timer;
u32 msg_enable;
struct mii_if_info mii_if;
+ struct list_head first_phy;
+};
+
+struct sis190_phy {
+ struct list_head list;
+ int phy_id;
+ u16 id[2];
+ u16 status;
+ u8 type;
+};
+
+enum sis190_phy_type {
+ UNKNOWN = 0x00,
+ HOME = 0x01,
+ LAN = 0x02,
+ MIX = 0x03
+};
+
+static struct mii_chip_info {
+ const char *name;
+ u16 id[2];
+ unsigned int type;
+} mii_chip_table[] = {
+ { "Broadcom PHY BCM5461", { 0x0020, 0x60c0 }, LAN },
+ { "Agere PHY ET1101B", { 0x0282, 0xf010 }, LAN },
+ { "Marvell PHY 88E1111", { 0x0141, 0x0cc0 }, LAN },
+ { "Realtek PHY RTL8201", { 0x0000, 0x8200 }, LAN },
+ { NULL, }
};
const static struct {
printk(KERN_ERR PFX "PHY command failed !\n");
}
-static void mdio_write(void __iomem *ioaddr, int reg, int val)
+static void mdio_write(void __iomem *ioaddr, int phy_id, int reg, int val)
{
- u32 pmd = 1;
-
__mdio_cmd(ioaddr, EhnMIIreq | EhnMIIwrite |
- (((u32) reg) << EhnMIIregShift) | (pmd << EhnMIIpmdShift) |
+ (((u32) reg) << EhnMIIregShift) | (phy_id << EhnMIIpmdShift) |
(((u32) val) << EhnMIIdataShift));
}
-static int mdio_read(void __iomem *ioaddr, int reg)
+static int mdio_read(void __iomem *ioaddr, int phy_id, int reg)
{
- u32 pmd = 1;
-
__mdio_cmd(ioaddr, EhnMIIreq | EhnMIIread |
- (((u32) reg) << EhnMIIregShift) | (pmd << EhnMIIpmdShift));
+ (((u32) reg) << EhnMIIregShift) | (phy_id << EhnMIIpmdShift));
return (u16) (SIS_R32(GMIIControl) >> EhnMIIdataShift);
}
{
struct sis190_private *tp = netdev_priv(dev);
- mdio_write(tp->mmio_addr, reg, val);
+ mdio_write(tp->mmio_addr, phy_id, reg, val);
}
static int __mdio_read(struct net_device *dev, int phy_id, int reg)
{
struct sis190_private *tp = netdev_priv(dev);
- return mdio_read(tp->mmio_addr, reg);
+ return mdio_read(tp->mmio_addr, phy_id, reg);
+}
+
+static u16 mdio_read_latched(void __iomem *ioaddr, int phy_id, int reg)
+{
+ mdio_read(ioaddr, phy_id, reg);
+ return mdio_read(ioaddr, phy_id, reg);
}
static u16 __devinit sis190_read_eeprom(void __iomem *ioaddr, u32 reg)
u32 eor = le32_to_cpu(desc->size) & RingEnd;
desc->PSize = 0x0;
- desc->size = cpu_to_le32(rx_buf_sz | eor);
+ desc->size = cpu_to_le32((rx_buf_sz & RX_BUF_MASK) | eor);
wmb();
desc->status = cpu_to_le32(OWNbit | INTbit);
}
return ret;
}
+static inline int sis190_rx_pkt_err(u32 status, struct net_device_stats *stats)
+{
+#define ErrMask (OVRUN | SHORT | LIMIT | MIIER | NIBON | COLON | ABORT)
+
+ if ((status & CRCOK) && !(status & ErrMask))
+ return 0;
+
+ if (!(status & CRCOK))
+ stats->rx_crc_errors++;
+ else if (status & OVRUN)
+ stats->rx_over_errors++;
+ else if (status & (SHORT | LIMIT))
+ stats->rx_length_errors++;
+ else if (status & (MIIER | NIBON | COLON))
+ stats->rx_frame_errors++;
+
+ stats->rx_errors++;
+ return -1;
+}
+
static int sis190_rx_interrupt(struct net_device *dev,
struct sis190_private *tp, void __iomem *ioaddr)
{
// net_intr(tp, KERN_INFO "%s: Rx PSize = %08x.\n", dev->name,
// status);
- if (status & RxCRC) {
- net_intr(tp, KERN_INFO "%s: bad crc. status = %08x.\n",
- dev->name, status);
- stats->rx_errors++;
- stats->rx_crc_errors++;
- sis190_give_to_asic(desc, tp->rx_buf_sz);
- } else if (!(status & PADbit)) {
- net_intr(tp, KERN_INFO "%s: bad pad. status = %08x.\n",
- dev->name, status);
- stats->rx_errors++;
- stats->rx_length_errors++;
+ if (sis190_rx_pkt_err(status, stats) < 0)
sis190_give_to_asic(desc, tp->rx_buf_sz);
- } else {
+ else {
struct sk_buff *skb = tp->Rx_skbuff[entry];
int pkt_size = (status & RxSizeMask) - 4;
void (*pci_action)(struct pci_dev *, dma_addr_t,
sis190_rx_skb(skb);
dev->last_rx = jiffies;
- stats->rx_bytes += pkt_size;
stats->rx_packets++;
+ stats->rx_bytes += pkt_size;
+ if ((status & BCAST) == MCAST)
+ stats->multicast++;
}
}
count = cur_rx - tp->cur_rx;
struct net_device *dev = data;
struct sis190_private *tp = netdev_priv(dev);
void __iomem *ioaddr = tp->mmio_addr;
+ int phy_id = tp->mii_if.phy_id;
u16 val;
rtnl_lock();
- val = mdio_read(ioaddr, MII_BMCR);
+ val = mdio_read(ioaddr, phy_id, MII_BMCR);
if (val & BMCR_RESET) {
// FIXME: needlessly high ? -- FR 02/07/2005
mod_timer(&tp->timer, jiffies + HZ/10);
- } else if (!(mdio_read(ioaddr, MII_BMSR) & BMSR_ANEGCOMPLETE)) {
+ } else if (!(mdio_read_latched(ioaddr, phy_id, MII_BMSR) &
+ BMSR_ANEGCOMPLETE)) {
net_link(tp, KERN_WARNING "%s: PHY reset until link up.\n",
dev->name);
- mdio_write(ioaddr, MII_BMCR, val | BMCR_RESET);
+ mdio_write(ioaddr, phy_id, MII_BMCR, val | BMCR_RESET);
mod_timer(&tp->timer, jiffies + SIS190_PHY_TIMEOUT);
} else {
/* Rejoice ! */
0x01 | _10bpsH },
{ 0, "unknown", 0x0000 }
}, *p;
+ u16 adv;
- val = mdio_read(ioaddr, 0x1f);
+ val = mdio_read(ioaddr, phy_id, 0x1f);
net_link(tp, KERN_INFO "%s: mii ext = %04x.\n", dev->name, val);
- val = mdio_read(ioaddr, MII_LPA);
- net_link(tp, KERN_INFO "%s: mii lpa = %04x.\n", dev->name, val);
+ val = mdio_read(ioaddr, phy_id, MII_LPA);
+ adv = mdio_read(ioaddr, phy_id, MII_ADVERTISE);
+ net_link(tp, KERN_INFO "%s: mii lpa = %04x adv = %04x.\n",
+ dev->name, val, adv);
+
+ val &= adv;
for (p = reg31; p->ctl; p++) {
if ((val & p->val) == p->val)
unsigned int mtu = dev->mtu;
tp->rx_buf_sz = (mtu > RX_BUF_SIZE) ? mtu + ETH_HLEN + 8 : RX_BUF_SIZE;
+ /* RxDesc->size has a licence to kill the lower bits */
+ if (tp->rx_buf_sz & 0x07) {
+ tp->rx_buf_sz += 8;
+ tp->rx_buf_sz &= RX_BUF_MASK;
+ }
}
static int sis190_open(struct net_device *dev)
return &tp->stats;
}
+static void sis190_free_phy(struct list_head *first_phy)
+{
+ struct sis190_phy *cur, *next;
+
+ list_for_each_entry_safe(cur, next, first_phy, list) {
+ kfree(cur);
+ }
+}
+
+/**
+ * sis190_default_phy - Select default PHY for sis190 mac.
+ * @dev: the net device to probe for
+ *
+ * Select first detected PHY with link as default.
+ * If no one is link on, select PHY whose types is HOME as default.
+ * If HOME doesn't exist, select LAN.
+ */
+static u16 sis190_default_phy(struct net_device *dev)
+{
+ struct sis190_phy *phy, *phy_home, *phy_default, *phy_lan;
+ struct sis190_private *tp = netdev_priv(dev);
+ struct mii_if_info *mii_if = &tp->mii_if;
+ void __iomem *ioaddr = tp->mmio_addr;
+ u16 status;
+
+ phy_home = phy_default = phy_lan = NULL;
+
+ list_for_each_entry(phy, &tp->first_phy, list) {
+ status = mdio_read_latched(ioaddr, phy->phy_id, MII_BMSR);
+
+ // Link ON & Not select default PHY & not ghost PHY.
+ if ((status & BMSR_LSTATUS) &&
+ !phy_default &&
+ (phy->type != UNKNOWN)) {
+ phy_default = phy;
+ } else {
+ status = mdio_read(ioaddr, phy->phy_id, MII_BMCR);
+ mdio_write(ioaddr, phy->phy_id, MII_BMCR,
+ status | BMCR_ANENABLE | BMCR_ISOLATE);
+ if (phy->type == HOME)
+ phy_home = phy;
+ else if (phy->type == LAN)
+ phy_lan = phy;
+ }
+ }
+
+ if (!phy_default) {
+ if (phy_home)
+ phy_default = phy_home;
+ else if (phy_lan)
+ phy_default = phy_lan;
+ else
+ phy_default = list_entry(&tp->first_phy,
+ struct sis190_phy, list);
+ }
+
+ if (mii_if->phy_id != phy_default->phy_id) {
+ mii_if->phy_id = phy_default->phy_id;
+ net_probe(tp, KERN_INFO
+ "%s: Using transceiver at address %d as default.\n",
+ pci_name(tp->pci_dev), mii_if->phy_id);
+ }
+
+ status = mdio_read(ioaddr, mii_if->phy_id, MII_BMCR);
+ status &= (~BMCR_ISOLATE);
+
+ mdio_write(ioaddr, mii_if->phy_id, MII_BMCR, status);
+ status = mdio_read_latched(ioaddr, mii_if->phy_id, MII_BMSR);
+
+ return status;
+}
+
+static void sis190_init_phy(struct net_device *dev, struct sis190_private *tp,
+ struct sis190_phy *phy, unsigned int phy_id,
+ u16 mii_status)
+{
+ void __iomem *ioaddr = tp->mmio_addr;
+ struct mii_chip_info *p;
+
+ INIT_LIST_HEAD(&phy->list);
+ phy->status = mii_status;
+ phy->phy_id = phy_id;
+
+ phy->id[0] = mdio_read(ioaddr, phy_id, MII_PHYSID1);
+ phy->id[1] = mdio_read(ioaddr, phy_id, MII_PHYSID2);
+
+ for (p = mii_chip_table; p->type; p++) {
+ if ((p->id[0] == phy->id[0]) &&
+ (p->id[1] == (phy->id[1] & 0xfff0))) {
+ break;
+ }
+ }
+
+ if (p->id[1]) {
+ phy->type = (p->type == MIX) ?
+ ((mii_status & (BMSR_100FULL | BMSR_100HALF)) ?
+ LAN : HOME) : p->type;
+ } else
+ phy->type = UNKNOWN;
+
+ net_probe(tp, KERN_INFO "%s: %s transceiver at address %d.\n",
+ pci_name(tp->pci_dev),
+ (phy->type == UNKNOWN) ? "Unknown PHY" : p->name, phy_id);
+}
+
+/**
+ * sis190_mii_probe - Probe MII PHY for sis190
+ * @dev: the net device to probe for
+ *
+ * Search for total of 32 possible mii phy addresses.
+ * Identify and set current phy if found one,
+ * return error if it failed to found.
+ */
+static int __devinit sis190_mii_probe(struct net_device *dev)
+{
+ struct sis190_private *tp = netdev_priv(dev);
+ struct mii_if_info *mii_if = &tp->mii_if;
+ void __iomem *ioaddr = tp->mmio_addr;
+ int phy_id;
+ int rc = 0;
+
+ INIT_LIST_HEAD(&tp->first_phy);
+
+ for (phy_id = 0; phy_id < PHY_MAX_ADDR; phy_id++) {
+ struct sis190_phy *phy;
+ u16 status;
+
+ status = mdio_read_latched(ioaddr, phy_id, MII_BMSR);
+
+ // Try next mii if the current one is not accessible.
+ if (status == 0xffff || status == 0x0000)
+ continue;
+
+ phy = kmalloc(sizeof(*phy), GFP_KERNEL);
+ if (!phy) {
+ sis190_free_phy(&tp->first_phy);
+ rc = -ENOMEM;
+ goto out;
+ }
+
+ sis190_init_phy(dev, tp, phy, phy_id, status);
+
+ list_add(&tp->first_phy, &phy->list);
+ }
+
+ if (list_empty(&tp->first_phy)) {
+ net_probe(tp, KERN_INFO "%s: No MII transceivers found!\n",
+ pci_name(tp->pci_dev));
+ rc = -EIO;
+ goto out;
+ }
+
+ /* Select default PHY for mac */
+ sis190_default_phy(dev);
+
+ mii_if->dev = dev;
+ mii_if->mdio_read = __mdio_read;
+ mii_if->mdio_write = __mdio_write;
+ mii_if->phy_id_mask = PHY_ID_ANY;
+ mii_if->reg_num_mask = MII_REG_ANY;
+out:
+ return rc;
+}
+
+static void __devexit sis190_mii_remove(struct net_device *dev)
+{
+ struct sis190_private *tp = netdev_priv(dev);
+
+ sis190_free_phy(&tp->first_phy);
+}
+
static void sis190_release_board(struct pci_dev *pdev)
{
struct net_device *dev = pci_get_drvdata(pdev);
tp->pci_dev = pdev;
tp->mmio_addr = ioaddr;
- tp->mii_if.dev = dev;
- tp->mii_if.mdio_read = __mdio_read;
- tp->mii_if.mdio_write = __mdio_write;
- // tp->mii_if.phy_id = XXX;
- tp->mii_if.phy_id_mask = 0x1f;
- tp->mii_if.reg_num_mask = 0x1f;
-
sis190_irq_mask_and_ack(ioaddr);
sis190_soft_reset(ioaddr);
/* Get MAC address from EEPROM */
for (i = 0; i < MAC_ADDR_LEN / 2; i++) {
- u16 w = sis190_read_eeprom(ioaddr, EEPROMMACAddr + i);
+ __le16 w = sis190_read_eeprom(ioaddr, EEPROMMACAddr + i);
((u16 *)dev->dev_addr)[0] = le16_to_cpu(w);
}
{
struct sis190_private *tp = netdev_priv(dev);
void __iomem *ioaddr = tp->mmio_addr;
+ int phy_id = tp->mii_if.phy_id;
int val;
net_link(tp, KERN_INFO "%s: Enabling Auto-negotiation.\n", dev->name);
- val = mdio_read(ioaddr, MII_ADVERTISE);
+ val = mdio_read(ioaddr, phy_id, MII_ADVERTISE);
// Enable 10/100 Full/Half Mode, leave MII_ADVERTISE bit4:0
// unchanged.
- mdio_write(ioaddr, MII_ADVERTISE, (val & ADVERTISE_SLCT) |
+ mdio_write(ioaddr, phy_id, MII_ADVERTISE, (val & ADVERTISE_SLCT) |
ADVERTISE_100FULL | ADVERTISE_10FULL |
ADVERTISE_100HALF | ADVERTISE_10HALF);
// Enable 1000 Full Mode.
- mdio_write(ioaddr, MII_CTRL1000, ADVERTISE_1000FULL);
+ mdio_write(ioaddr, phy_id, MII_CTRL1000, ADVERTISE_1000FULL);
// Enable auto-negotiation and restart auto-negotiation.
- mdio_write(ioaddr, MII_BMCR,
+ mdio_write(ioaddr, phy_id, MII_BMCR,
BMCR_ANENABLE | BMCR_ANRESTART | BMCR_RESET);
}
dev->base_addr = (unsigned long) 0xdead;
spin_lock_init(&tp->lock);
- rc = register_netdev(dev);
+
+ rc = sis190_mii_probe(dev);
if (rc < 0)
goto err_release_board;
+ rc = register_netdev(dev);
+ if (rc < 0)
+ goto err_remove_mii;
+
pci_set_drvdata(pdev, dev);
net_probe(tp, KERN_INFO "%s: %s at %p (IRQ: %d), "
out:
return rc;
+err_remove_mii:
+ sis190_mii_remove(dev);
err_release_board:
sis190_release_board(pdev);
goto out;
{
struct net_device *dev = pci_get_drvdata(pdev);
+ sis190_mii_remove(dev);
unregister_netdev(dev);
sis190_release_board(pdev);
pci_set_drvdata(pdev, NULL);