📄 gmac.c
字号:
/* * Network device driver for the GMAC ethernet controller on * Apple G4 Powermacs. * * Copyright (C) 2000 Paul Mackerras & Ben. Herrenschmidt * * portions based on sunhme.c by David S. Miller * * Changes: * Arnaldo Carvalho de Melo <acme@conectiva.com.br> - 08/06/2000 * - check init_etherdev return in gmac_probe1 * BenH <benh@kernel.crashing.org> - 03/09/2000 * - Add support for new PHYs * - Add some PowerBook sleep code * BenH <benh@kernel.crashing.org> - ??/??/???? * - PHY updates * BenH <benh@kernel.crashing.org> - 08/08/2001 * - Add more PHYs, fixes to sleep code */#include <linux/module.h>#include <linux/config.h>#include <linux/kernel.h>#include <linux/sched.h>#include <linux/types.h>#include <linux/fcntl.h>#include <linux/interrupt.h>#include <linux/netdevice.h>#include <linux/etherdevice.h>#include <linux/delay.h>#include <linux/string.h>#include <linux/timer.h>#include <linux/init.h>#include <linux/pci.h>#include <asm/prom.h>#include <asm/io.h>#include <asm/pgtable.h>#include <asm/machdep.h>#include <asm/pmac_feature.h>#include <asm/keylargo.h>#include <asm/pci-bridge.h>#ifdef CONFIG_PMAC_PBOOK#include <linux/adb.h>#include <linux/pmu.h>#include <asm/irq.h>#endif#include "gmac.h"#define DEBUG_PHY/* Driver version 1.5, kernel 2.4.x */#define GMAC_VERSION "v1.5k4"#define DUMMY_BUF_LEN RX_BUF_ALLOC_SIZE + RX_OFFSET + GMAC_BUFFER_ALIGNstatic unsigned char *dummy_buf;static struct net_device *gmacs;/* Prototypes */static int mii_read(struct gmac *gm, int phy, int r);static int mii_write(struct gmac *gm, int phy, int r, int v);static void mii_poll_start(struct gmac *gm);static void mii_poll_stop(struct gmac *gm);static void mii_interrupt(struct gmac *gm);static int mii_lookup_and_reset(struct gmac *gm);static void mii_setup_phy(struct gmac *gm);static int mii_do_reset_phy(struct gmac *gm, int phy_addr);static void mii_init_BCM5400(struct gmac *gm);static void mii_init_BCM5401(struct gmac *gm);static void gmac_set_power(struct gmac *gm, int power_up);static int gmac_powerup_and_reset(struct net_device *dev);static void gmac_set_gigabit_mode(struct gmac *gm, int gigabit);static void gmac_set_duplex_mode(struct gmac *gm, int full_duplex);static void gmac_mac_init(struct gmac *gm, unsigned char *mac_addr);static void gmac_init_rings(struct gmac *gm, int from_irq);static void gmac_start_dma(struct gmac *gm);static void gmac_stop_dma(struct gmac *gm);static void gmac_set_multicast(struct net_device *dev);static int gmac_open(struct net_device *dev);static int gmac_close(struct net_device *dev);static void gmac_tx_timeout(struct net_device *dev);static int gmac_xmit_start(struct sk_buff *skb, struct net_device *dev);static void gmac_tx_cleanup(struct net_device *dev, int force_cleanup);static void gmac_receive(struct net_device *dev);static void gmac_interrupt(int irq, void *dev_id, struct pt_regs *regs);static struct net_device_stats *gmac_stats(struct net_device *dev);static int gmac_probe(void);static void gmac_probe1(struct device_node *gmac);#ifdef CONFIG_PMAC_PBOOKint gmac_sleep_notify(struct pmu_sleep_notifier *self, int when);static struct pmu_sleep_notifier gmac_sleep_notifier = { gmac_sleep_notify, SLEEP_LEVEL_NET,};#endif/* * Read via the mii interface from a PHY register */static intmii_read(struct gmac *gm, int phy, int r){ int timeout; GM_OUT(GM_MIF_FRAME_CTL_DATA, (0x01 << GM_MIF_FRAME_START_SHIFT) | (0x02 << GM_MIF_FRAME_OPCODE_SHIFT) | GM_MIF_FRAME_TURNAROUND_HI | (phy << GM_MIF_FRAME_PHY_ADDR_SHIFT) | (r << GM_MIF_FRAME_REG_ADDR_SHIFT)); for (timeout = 1000; timeout > 0; --timeout) { udelay(20); if (GM_IN(GM_MIF_FRAME_CTL_DATA) & GM_MIF_FRAME_TURNAROUND_LO) return GM_IN(GM_MIF_FRAME_CTL_DATA) & GM_MIF_FRAME_DATA_MASK; } return -1;}/* * Write on the mii interface to a PHY register */static intmii_write(struct gmac *gm, int phy, int r, int v){ int timeout; GM_OUT(GM_MIF_FRAME_CTL_DATA, (0x01 << GM_MIF_FRAME_START_SHIFT) | (0x01 << GM_MIF_FRAME_OPCODE_SHIFT) | GM_MIF_FRAME_TURNAROUND_HI | (phy << GM_MIF_FRAME_PHY_ADDR_SHIFT) | (r << GM_MIF_FRAME_REG_ADDR_SHIFT) | (v & GM_MIF_FRAME_DATA_MASK)); for (timeout = 1000; timeout > 0; --timeout) { udelay(20); if (GM_IN(GM_MIF_FRAME_CTL_DATA) & GM_MIF_FRAME_TURNAROUND_LO) return 0; } return -1;}/* * Start MIF autopolling of the PHY status register */static void mii_poll_start(struct gmac *gm){ unsigned int tmp; /* Start the MIF polling on the external transceiver. */ tmp = GM_IN(GM_MIF_CFG); tmp &= ~(GM_MIF_CFGPR_MASK | GM_MIF_CFGPD_MASK); tmp |= ((gm->phy_addr & 0x1f) << GM_MIF_CFGPD_SHIFT); tmp |= (MII_SR << GM_MIF_CFGPR_SHIFT); tmp |= GM_MIF_CFGPE; GM_OUT(GM_MIF_CFG, tmp); /* Let the bits set. */ udelay(GM_MIF_POLL_DELAY); GM_OUT(GM_MIF_IRQ_MASK, 0xffc0);}/* * Stop MIF autopolling of the PHY status register */static void mii_poll_stop(struct gmac *gm){ GM_OUT(GM_MIF_IRQ_MASK, 0xffff); GM_BIC(GM_MIF_CFG, GM_MIF_CFGPE); udelay(GM_MIF_POLL_DELAY);}/* * Called when the MIF detect a change of the PHY status * * handles monitoring the link and updating GMAC with the correct * duplex mode. * * Note: Are we missing status changes ? In this case, we'll have to * a timer and control the autoneg. process more closely. Also, we may * want to stop rx and tx side when the link is down. *//* Link modes of the BCM5400 PHY */static int phy_BCM5400_link_table[8][3] = { { 0, 0, 0 }, /* No link */ { 0, 0, 0 }, /* 10BT Half Duplex */ { 1, 0, 0 }, /* 10BT Full Duplex */ { 0, 1, 0 }, /* 100BT Half Duplex */ { 0, 1, 0 }, /* 100BT Half Duplex */ { 1, 1, 0 }, /* 100BT Full Duplex*/ { 1, 0, 1 }, /* 1000BT */ { 1, 0, 1 }, /* 1000BT */};static voidmii_interrupt(struct gmac *gm){ int phy_status; int lpar_ability; mii_poll_stop(gm); /* May the status change before polling is re-enabled ? */ mii_poll_start(gm); /* We read the Auxilliary Status Summary register */ phy_status = mii_read(gm, gm->phy_addr, MII_SR); if ((phy_status ^ gm->phy_status) & (MII_SR_ASSC | MII_SR_LKS)) { int full_duplex = 0; int link_100 = 0; int gigabit = 0;#ifdef DEBUG_PHY printk(KERN_INFO "%s: Link state change, phy_status: 0x%04x\n", gm->dev->name, phy_status);#endif gm->phy_status = phy_status; /* Should we enable that in generic mode ? */ lpar_ability = mii_read(gm, gm->phy_addr, MII_ANLPA); if (lpar_ability & MII_ANLPA_PAUS) GM_BIS(GM_MAC_CTRL_CONFIG, GM_MAC_CTRL_CONF_SND_PAUSE_EN); else GM_BIC(GM_MAC_CTRL_CONFIG, GM_MAC_CTRL_CONF_SND_PAUSE_EN); /* Link ? Check for speed and duplex */ if ((phy_status & MII_SR_LKS) && (phy_status & MII_SR_ASSC)) { int restart = 0; int aux_stat, link; switch (gm->phy_type) { case PHY_B5201: case PHY_B5221: aux_stat = mii_read(gm, gm->phy_addr, MII_BCM5201_AUXCTLSTATUS);#ifdef DEBUG_PHY printk(KERN_INFO "%s: Link up ! BCM5201/5221 aux_stat: 0x%04x\n", gm->dev->name, aux_stat);#endif full_duplex = ((aux_stat & MII_BCM5201_AUXCTLSTATUS_DUPLEX) != 0); link_100 = ((aux_stat & MII_BCM5201_AUXCTLSTATUS_SPEED) != 0); break; case PHY_B5400: case PHY_B5401: case PHY_B5411: aux_stat = mii_read(gm, gm->phy_addr, MII_BCM5400_AUXSTATUS); link = (aux_stat & MII_BCM5400_AUXSTATUS_LINKMODE_MASK) >> MII_BCM5400_AUXSTATUS_LINKMODE_SHIFT;#ifdef DEBUG_PHY printk(KERN_INFO "%s: Link up ! BCM54xx aux_stat: 0x%04x (link mode: %d)\n", gm->dev->name, aux_stat, link);#endif full_duplex = phy_BCM5400_link_table[link][0]; link_100 = phy_BCM5400_link_table[link][1]; gigabit = phy_BCM5400_link_table[link][2]; break; case PHY_LXT971: aux_stat = mii_read(gm, gm->phy_addr, MII_LXT971_STATUS2);#ifdef DEBUG_PHY printk(KERN_INFO "%s: Link up ! LXT971 stat2: 0x%04x\n", gm->dev->name, aux_stat);#endif full_duplex = ((aux_stat & MII_LXT971_STATUS2_FULLDUPLEX) != 0); link_100 = ((aux_stat & MII_LXT971_STATUS2_SPEED) != 0); break; default: full_duplex = (lpar_ability & MII_ANLPA_FDAM) != 0; link_100 = (lpar_ability & MII_ANLPA_100M) != 0; break; }#ifdef DEBUG_PHY printk(KERN_INFO "%s: Full Duplex: %d, Speed: %s\n", gm->dev->name, full_duplex, gigabit ? "1000" : (link_100 ? "100" : "10"));#endif if (gigabit != gm->gigabit) { gm->gigabit = gigabit; gmac_set_gigabit_mode(gm, gm->gigabit); restart = 1; } if (full_duplex != gm->full_duplex) { gm->full_duplex = full_duplex; gmac_set_duplex_mode(gm, gm->full_duplex); restart = 1; } if (restart) gmac_start_dma(gm); } else if (!(phy_status & MII_SR_LKS)) {#ifdef DEBUG_PHY printk(KERN_INFO "%s: Link down !\n", gm->dev->name);#endif } }}#ifdef CONFIG_PMAC_PBOOK/* Power management: stop PHY chip for suspend mode * * TODO: This will have to be modified is WOL is to be supported. */static voidgmac_suspend(struct gmac* gm){ int data, timeout; unsigned long flags; gm->sleeping = 1; netif_device_detach(gm->dev); spin_lock_irqsave(&gm->lock, flags); if (gm->opened) { disable_irq(gm->dev->irq); /* Stop polling PHY */ mii_poll_stop(gm); } /* Mask out all chips interrupts */ GM_OUT(GM_IRQ_MASK, 0xffffffff); spin_unlock_irqrestore(&gm->lock, flags); if (gm->opened) { int i; /* Empty Tx ring of any remaining gremlins */ gmac_tx_cleanup(gm->dev, 1); /* Empty Rx ring of any remaining gremlins */ for (i = 0; i < NRX; ++i) { if (gm->rx_buff[i] != 0) { dev_kfree_skb_irq(gm->rx_buff[i]); gm->rx_buff[i] = 0; } } } /* Clear interrupts on 5201 */ if (gm->phy_type == PHY_B5201 || gm->phy_type == PHY_B5221) mii_write(gm, gm->phy_addr, MII_BCM5201_INTERRUPT, 0); /* Drive MDIO high */ GM_OUT(GM_MIF_CFG, 0); /* Unchanged, don't ask me why */ data = mii_read(gm, gm->phy_addr, MII_ANLPA); mii_write(gm, gm->phy_addr, MII_ANLPA, data); /* Stop everything */ GM_OUT(GM_MAC_RX_CONFIG, 0); GM_OUT(GM_MAC_TX_CONFIG, 0); GM_OUT(GM_MAC_XIF_CONFIG, 0); GM_OUT(GM_TX_CONF, 0); GM_OUT(GM_RX_CONF, 0); /* Set MAC in reset state */ GM_OUT(GM_RESET, GM_RESET_TX | GM_RESET_RX); for (timeout = 100; timeout > 0; --timeout) { mdelay(10); if ((GM_IN(GM_RESET) & (GM_RESET_TX | GM_RESET_RX)) == 0) break; } GM_OUT(GM_MAC_TX_RESET, GM_MAC_TX_RESET_NOW); GM_OUT(GM_MAC_RX_RESET, GM_MAC_RX_RESET_NOW); /* Superisolate PHY */ if (gm->phy_type == PHY_B5201 || gm->phy_type == PHY_B5221) mii_write(gm, gm->phy_addr, MII_BCM5201_MULTIPHY, MII_BCM5201_MULTIPHY_SUPERISOLATE); /* Put MDIO in sane electric state. According to an obscure * Apple comment, not doing so may let them drive some current * during sleep and possibly damage BCM PHYs. */ GM_OUT(GM_MIF_CFG, GM_MIF_CFGBB); GM_OUT(GM_MIF_BB_CLOCK, 0); GM_OUT(GM_MIF_BB_DATA, 0); GM_OUT(GM_MIF_BB_OUT_ENABLE, 0); GM_OUT(GM_MAC_XIF_CONFIG, GM_MAC_XIF_CONF_GMII_MODE|GM_MAC_XIF_CONF_MII_INT_LOOP); (void)GM_IN(GM_MAC_XIF_CONFIG); /* Unclock the GMAC chip */ gmac_set_power(gm, 0);}static voidgmac_resume(struct gmac *gm){ int data; if (gmac_powerup_and_reset(gm->dev)) { printk(KERN_ERR "%s: Couldn't revive gmac ethernet !\n", gm->dev->name); return; } gm->sleeping = 0; if (gm->opened) { /* Create fresh rings */ gmac_init_rings(gm, 1); /* re-initialize the MAC */ gmac_mac_init(gm, gm->dev->dev_addr); /* re-initialize the multicast tables & promisc mode if any */ gmac_set_multicast(gm->dev); } /* Early enable Tx and Rx so that we are clocked */ GM_BIS(GM_TX_CONF, GM_TX_CONF_DMA_EN); mdelay(20); GM_BIS(GM_RX_CONF, GM_RX_CONF_DMA_EN); mdelay(20); GM_BIS(GM_MAC_TX_CONFIG, GM_MAC_TX_CONF_ENABLE); mdelay(20); GM_BIS(GM_MAC_RX_CONFIG, GM_MAC_RX_CONF_ENABLE); mdelay(20); if (gm->phy_type == PHY_B5201 || gm->phy_type == PHY_B5221) { data = mii_read(gm, gm->phy_addr, MII_BCM5201_MULTIPHY); mii_write(gm, gm->phy_addr, MII_BCM5201_MULTIPHY, data & ~MII_BCM5201_MULTIPHY_SUPERISOLATE); } mdelay(1); if (gm->opened) { /* restart polling PHY */ mii_interrupt(gm); /* restart DMA operations */ gmac_start_dma(gm); netif_device_attach(gm->dev); enable_irq(gm->dev->irq); } else { /* Driver not opened, just leave things off. Note that * we could be smart and superisolate the PHY when the * driver is closed, but I won't do that unless I have * a better understanding of some electrical issues with * this PHY chip --BenH */ GM_OUT(GM_MAC_RX_CONFIG, 0); GM_OUT(GM_MAC_TX_CONFIG, 0); GM_OUT(GM_MAC_XIF_CONFIG, 0); GM_OUT(GM_TX_CONF, 0); GM_OUT(GM_RX_CONF, 0); }}#endifstatic intmii_do_reset_phy(struct gmac *gm, int phy_addr){ int mii_control, timeout; mii_control = mii_read(gm, phy_addr, MII_CR); mii_write(gm, phy_addr, MII_CR, mii_control | MII_CR_RST); mdelay(10); for (timeout = 100; timeout > 0; --timeout) { mii_control = mii_read(gm, phy_addr, MII_CR); if (mii_control == -1) { printk(KERN_ERR "%s PHY died after reset !\n", gm->dev->name); return 1; } if ((mii_control & MII_CR_RST) == 0) break; mdelay(10); } if (mii_control & MII_CR_RST) { printk(KERN_ERR "%s PHY reset timeout !\n", gm->dev->name); return 1; } mii_write(gm, phy_addr, MII_CR, mii_control & ~MII_CR_ISOL); return 0;}/* Here's a bunch of configuration routines for * Broadcom PHYs used on various Mac models. Unfortunately, * except for the 5201, Broadcom never sent me any documentation, * so this is from my understanding of Apple's Open Firmware * drivers and Darwin's implementation */ static voidmii_init_BCM5400(struct gmac *gm){ int data; /* Configure for gigabit full duplex */ data = mii_read(gm, gm->phy_addr, MII_BCM5400_AUXCONTROL); data |= MII_BCM5400_AUXCONTROL_PWR10BASET; mii_write(gm, gm->phy_addr, MII_BCM5400_AUXCONTROL, data); data = mii_read(gm, gm->phy_addr, MII_BCM5400_GB_CONTROL); data |= MII_BCM5400_GB_CONTROL_FULLDUPLEXCAP; mii_write(gm, gm->phy_addr, MII_BCM5400_GB_CONTROL, data); mdelay(10); /* Reset and configure cascaded 10/100 PHY */ mii_do_reset_phy(gm, 0x1f); data = mii_read(gm, 0x1f, MII_BCM5201_MULTIPHY); data |= MII_BCM5201_MULTIPHY_SERIALMODE; mii_write(gm, 0x1f, MII_BCM5201_MULTIPHY, data); data = mii_read(gm, gm->phy_addr, MII_BCM5400_AUXCONTROL); data &= ~MII_BCM5400_AUXCONTROL_PWR10BASET; mii_write(gm, gm->phy_addr, MII_BCM5400_AUXCONTROL, data);}static voidmii_init_BCM5401(struct gmac *gm){ int data; int rev; rev = mii_read(gm, gm->phy_addr, MII_ID1) & 0x000f; if (rev == 0 || rev == 3) { /* Some revisions of 5401 appear to need this * initialisation sequence to disable, according * to OF, "tap power management" * * WARNING ! OF and Darwin don't agree on the * register addresses. OF seem to interpret the * register numbers below as decimal */ mii_write(gm, gm->phy_addr, 0x18, 0x0c20); mii_write(gm, gm->phy_addr, 0x17, 0x0012); mii_write(gm, gm->phy_addr, 0x15, 0x1804); mii_write(gm, gm->phy_addr, 0x17, 0x0013); mii_write(gm, gm->phy_addr, 0x15, 0x1204); mii_write(gm, gm->phy_addr, 0x17, 0x8006); mii_write(gm, gm->phy_addr, 0x15, 0x0132); mii_write(gm, gm->phy_addr, 0x17, 0x8006); mii_write(gm, gm->phy_addr, 0x15, 0x0232); mii_write(gm, gm->phy_addr, 0x17, 0x201f); mii_write(gm, gm->phy_addr, 0x15, 0x0a20); } /* Configure for gigabit full duplex */ data = mii_read(gm, gm->phy_addr, MII_BCM5400_GB_CONTROL); data |= MII_BCM5400_GB_CONTROL_FULLDUPLEXCAP; mii_write(gm, gm->phy_addr, MII_BCM5400_GB_CONTROL, data); mdelay(10); /* Reset and configure cascaded 10/100 PHY */ mii_do_reset_phy(gm, 0x1f); data = mii_read(gm, 0x1f, MII_BCM5201_MULTIPHY); data |= MII_BCM5201_MULTIPHY_SERIALMODE; mii_write(gm, 0x1f, MII_BCM5201_MULTIPHY, data);}static voidmii_init_BCM5411(struct gmac *gm){ int data; /* Here's some more Apple black magic to setup * some voltage stuffs. */ mii_write(gm, gm->phy_addr, 0x1c, 0x8c23); mii_write(gm, gm->phy_addr, 0x1c, 0x8ca3); mii_write(gm, gm->phy_addr, 0x1c, 0x8c23); /* Here, Apple seems to want to reset it, do * it as well */ mii_write(gm, gm->phy_addr, MII_CR, MII_CR_RST); /* Start autoneg */ mii_write(gm, gm->phy_addr, MII_CR, MII_CR_ASSE|MII_CR_FDM| /* Autospeed, full duplex */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -