📄 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 <bh40@calva.net> - 03/09/2000 * - Add support for new PHYs * - Add some PowerBook 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/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.3, kernel 2.4.x */#define GMAC_VERSION "v1.3k4"static unsigned char dummy_buf[RX_BUF_ALLOC_SIZE + RX_OFFSET + GMAC_BUFFER_ALIGN];static struct net_device *gmacs = NULL;/* 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 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("Link state change, phy_status: 0x%04x\n", phy_status);#endif gm->phy_status = phy_status; 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; if (gm->phy_type == PHY_B5201) { int aux_stat = mii_read(gm, gm->phy_addr, MII_BCM5201_AUXCTLSTATUS);#ifdef DEBUG_PHY printk(" Link up ! BCM5201 aux_stat: 0x%04x\n", aux_stat);#endif full_duplex = ((aux_stat & MII_BCM5201_AUXCTLSTATUS_DUPLEX) != 0); link_100 = ((aux_stat & MII_BCM5201_AUXCTLSTATUS_SPEED) != 0); } else if (gm->phy_type == PHY_B5400) { int aux_stat = mii_read(gm, gm->phy_addr, MII_BCM5400_AUXSTATUS); int link = (aux_stat & MII_BCM5400_AUXSTATUS_LINKMODE_MASK) >> MII_BCM5400_AUXSTATUS_LINKMODE_SHIFT;#ifdef DEBUG_PHY printk(" Link up ! BCM5400 aux_stat: 0x%04x (link mode: %d)\n", 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]; } else if (gm->phy_type == PHY_LXT971) { int stat2 = mii_read(gm, gm->phy_addr, MII_LXT971_STATUS2);#ifdef DEBUG_PHY printk(" Link up ! LXT971 stat2: 0x%04x\n", stat2);#endif full_duplex = ((stat2 & MII_LXT971_STATUS2_FULLDUPLEX) != 0); link_100 = ((stat2 & MII_LXT971_STATUS2_SPEED) != 0); }#ifdef DEBUG_PHY printk(" full_duplex: %d, speed: %s\n", 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(" Link down !\n");#endif } }}static 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;}static voidmii_init_BCM5400(struct gmac *gm){ int 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); 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); 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 intmii_lookup_and_reset(struct gmac *gm){ int i, mii_status, mii_control; gm->phy_addr = -1; gm->phy_type = PHY_UNKNOWN; /* Hard reset the PHY */ feature_set_gmac_phy_reset(gm->of_node, KL_GPIO_ETH_PHY_RESET_ASSERT); mdelay(10); feature_set_gmac_phy_reset(gm->of_node, KL_GPIO_ETH_PHY_RESET_RELEASE); mdelay(10); /* Find the PHY */ for(i=0; i<=31; i++) { mii_control = mii_read(gm, i, MII_CR); mii_status = mii_read(gm, i, MII_SR); if (mii_control != -1 && mii_status != -1 && (mii_control != 0xffff || mii_status != 0xffff)) break; } gm->phy_addr = i; if (gm->phy_addr > 31) return 0; /* Reset it */ if (mii_do_reset_phy(gm, gm->phy_addr)) goto fail; /* Read the PHY ID */ gm->phy_id = (mii_read(gm, gm->phy_addr, MII_ID0) << 16) | mii_read(gm, gm->phy_addr, MII_ID1);#ifdef DEBUG_PHY printk("%s PHY ID: 0x%08x\n", gm->dev->name, gm->phy_id);#endif if ((gm->phy_id & MII_BCM5400_MASK) == MII_BCM5400_ID) { gm->phy_type = PHY_B5400; printk(KERN_ERR "%s Found Broadcom BCM5400 PHY (Gigabit)\n", gm->dev->name); mii_init_BCM5400(gm); } else if ((gm->phy_id & MII_BCM5201_MASK) == MII_BCM5201_ID) { gm->phy_type = PHY_B5201; printk(KERN_INFO "%s Found Broadcom BCM5201 PHY\n", gm->dev->name); } else if ((gm->phy_id & MII_LXT971_MASK) == MII_LXT971_ID) { gm->phy_type = PHY_LXT971; printk(KERN_INFO "%s Found LevelOne LX971 PHY\n", gm->dev->name); } else { printk(KERN_ERR "%s: Warning ! Unknown PHY ID 0x%08x !\n", gm->dev->name, gm->phy_id); } return 1; fail: gm->phy_addr = -1; return 0;}/* * Setup the PHY autonegociation parameters * * Code to force the PHY duplex mode and speed should be * added here */static voidmii_setup_phy(struct gmac *gm){ int data; /* Stop auto-negociation */ data = mii_read(gm, gm->phy_addr, MII_CR); mii_write(gm, gm->phy_addr, MII_CR, data & ~MII_CR_ASSE); /* Set advertisement to 10/100 and Half/Full duplex * (full capabilities) */ data = mii_read(gm, gm->phy_addr, MII_ANA); data |= MII_ANA_TXAM | MII_ANA_FDAM | MII_ANA_10M; mii_write(gm, gm->phy_addr, MII_ANA, data); /* Restart auto-negociation */ data = mii_read(gm, gm->phy_addr, MII_CR); data |= MII_CR_ASSE; mii_write(gm, gm->phy_addr, MII_CR, data); data |= MII_CR_RAN; mii_write(gm, gm->phy_addr, MII_CR, data);}/* * Turn On/Off the gmac cell inside Uni-N * * ToDo: Add code to support powering down of the PHY. */static voidgmac_set_power(struct gmac *gm, int power_up){ if (power_up) { feature_set_gmac_power(gm->of_node, 1); if (gm->pci_devfn != 0xff) { u16 cmd; /* * Make sure PCI is correctly configured * * We use old pci_bios versions of the function since, by * default, gmac is not powered up, and so will be absent * from the kernel initial PCI lookup. * * Should be replaced by 2.4 new PCI mecanisms and really * regiser the device. */ pcibios_read_config_word(gm->pci_bus, gm->pci_devfn, PCI_COMMAND, &cmd); cmd |= PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER | PCI_COMMAND_INVALIDATE; pcibios_write_config_word(gm->pci_bus, gm->pci_devfn, PCI_COMMAND, cmd); pcibios_write_config_byte(gm->pci_bus, gm->pci_devfn, PCI_LATENCY_TIMER, 16); pcibios_write_config_byte(gm->pci_bus, gm->pci_devfn, PCI_CACHE_LINE_SIZE, 8); } } else { /* FIXME: Add PHY power down */ gm->phy_type = 0; feature_set_gmac_power(gm->of_node, 0); }}/* * Makes sure the GMAC cell is powered up, and reset it */static intgmac_powerup_and_reset(struct net_device *dev){ struct gmac *gm = (struct gmac *) dev->priv; int timeout; /* turn on GB clock */ gmac_set_power(gm, 1); /* Perform a software reset */ 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) { /* Mask out all chips interrupts */ GM_OUT(GM_IRQ_MASK, 0xffffffff); return 0; } } printk(KERN_ERR "%s reset failed!\n", dev->name); gmac_set_power(gm, 0);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -