sungem_phy.c

来自「linux 内核源代码」· C语言 代码 · 共 1,200 行 · 第 1/2 页

C
1,200
字号
/* * PHY drivers for the sungem ethernet driver. * * This file could be shared with other drivers. * * (c) 2002-2007, Benjamin Herrenscmidt (benh@kernel.crashing.org) * * TODO: *  - Add support for PHYs that provide an IRQ line *  - Eventually moved the entire polling state machine in *    there (out of the eth driver), so that it can easily be *    skipped on PHYs that implement it in hardware. *  - On LXT971 & BCM5201, Apple uses some chip specific regs *    to read the link status. Figure out why and if it makes *    sense to do the same (magic aneg ?) *  - Apple has some additional power management code for some *    Broadcom PHYs that they "hide" from the OpenSource version *    of darwin, still need to reverse engineer that */#include <linux/module.h>#include <linux/kernel.h>#include <linux/types.h>#include <linux/netdevice.h>#include <linux/etherdevice.h>#include <linux/mii.h>#include <linux/ethtool.h>#include <linux/delay.h>#ifdef CONFIG_PPC_PMAC#include <asm/prom.h>#endif#include "sungem_phy.h"/* Link modes of the BCM5400 PHY */static const 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 inline int __phy_read(struct mii_phy* phy, int id, int reg){	return phy->mdio_read(phy->dev, id, reg);}static inline void __phy_write(struct mii_phy* phy, int id, int reg, int val){	phy->mdio_write(phy->dev, id, reg, val);}static inline int phy_read(struct mii_phy* phy, int reg){	return phy->mdio_read(phy->dev, phy->mii_id, reg);}static inline void phy_write(struct mii_phy* phy, int reg, int val){	phy->mdio_write(phy->dev, phy->mii_id, reg, val);}static int reset_one_mii_phy(struct mii_phy* phy, int phy_id){	u16 val;	int limit = 10000;	val = __phy_read(phy, phy_id, MII_BMCR);	val &= ~(BMCR_ISOLATE | BMCR_PDOWN);	val |= BMCR_RESET;	__phy_write(phy, phy_id, MII_BMCR, val);	udelay(100);	while (limit--) {		val = __phy_read(phy, phy_id, MII_BMCR);		if ((val & BMCR_RESET) == 0)			break;		udelay(10);	}	if ((val & BMCR_ISOLATE) && limit > 0)		__phy_write(phy, phy_id, MII_BMCR, val & ~BMCR_ISOLATE);	return (limit <= 0);}static int bcm5201_init(struct mii_phy* phy){	u16 data;	data = phy_read(phy, MII_BCM5201_MULTIPHY);	data &= ~MII_BCM5201_MULTIPHY_SUPERISOLATE;	phy_write(phy, MII_BCM5201_MULTIPHY, data);	phy_write(phy, MII_BCM5201_INTERRUPT, 0);	return 0;}static int bcm5201_suspend(struct mii_phy* phy){	phy_write(phy, MII_BCM5201_INTERRUPT, 0);	phy_write(phy, MII_BCM5201_MULTIPHY, MII_BCM5201_MULTIPHY_SUPERISOLATE);	return 0;}static int bcm5221_init(struct mii_phy* phy){	u16 data;	data = phy_read(phy, MII_BCM5221_TEST);	phy_write(phy, MII_BCM5221_TEST,		data | MII_BCM5221_TEST_ENABLE_SHADOWS);	data = phy_read(phy, MII_BCM5221_SHDOW_AUX_STAT2);	phy_write(phy, MII_BCM5221_SHDOW_AUX_STAT2,		data | MII_BCM5221_SHDOW_AUX_STAT2_APD);	data = phy_read(phy, MII_BCM5221_SHDOW_AUX_MODE4);	phy_write(phy, MII_BCM5221_SHDOW_AUX_MODE4,		data | MII_BCM5221_SHDOW_AUX_MODE4_CLKLOPWR);	data = phy_read(phy, MII_BCM5221_TEST);	phy_write(phy, MII_BCM5221_TEST,		data & ~MII_BCM5221_TEST_ENABLE_SHADOWS);	return 0;}static int bcm5221_suspend(struct mii_phy* phy){	u16 data;	data = phy_read(phy, MII_BCM5221_TEST);	phy_write(phy, MII_BCM5221_TEST,		data | MII_BCM5221_TEST_ENABLE_SHADOWS);	data = phy_read(phy, MII_BCM5221_SHDOW_AUX_MODE4);	phy_write(phy, MII_BCM5221_SHDOW_AUX_MODE4,		  data | MII_BCM5221_SHDOW_AUX_MODE4_IDDQMODE);	return 0;}static int bcm5241_init(struct mii_phy* phy){	u16 data;	data = phy_read(phy, MII_BCM5221_TEST);	phy_write(phy, MII_BCM5221_TEST,		data | MII_BCM5221_TEST_ENABLE_SHADOWS);	data = phy_read(phy, MII_BCM5221_SHDOW_AUX_STAT2);	phy_write(phy, MII_BCM5221_SHDOW_AUX_STAT2,		data | MII_BCM5221_SHDOW_AUX_STAT2_APD);	data = phy_read(phy, MII_BCM5221_SHDOW_AUX_MODE4);	phy_write(phy, MII_BCM5221_SHDOW_AUX_MODE4,		data & ~MII_BCM5241_SHDOW_AUX_MODE4_STANDBYPWR);	data = phy_read(phy, MII_BCM5221_TEST);	phy_write(phy, MII_BCM5221_TEST,		data & ~MII_BCM5221_TEST_ENABLE_SHADOWS);	return 0;}static int bcm5241_suspend(struct mii_phy* phy){	u16 data;	data = phy_read(phy, MII_BCM5221_TEST);	phy_write(phy, MII_BCM5221_TEST,		data | MII_BCM5221_TEST_ENABLE_SHADOWS);	data = phy_read(phy, MII_BCM5221_SHDOW_AUX_MODE4);	phy_write(phy, MII_BCM5221_SHDOW_AUX_MODE4,		  data | MII_BCM5241_SHDOW_AUX_MODE4_STANDBYPWR);	return 0;}static int bcm5400_init(struct mii_phy* phy){	u16 data;	/* Configure for gigabit full duplex */	data = phy_read(phy, MII_BCM5400_AUXCONTROL);	data |= MII_BCM5400_AUXCONTROL_PWR10BASET;	phy_write(phy, MII_BCM5400_AUXCONTROL, data);	data = phy_read(phy, MII_BCM5400_GB_CONTROL);	data |= MII_BCM5400_GB_CONTROL_FULLDUPLEXCAP;	phy_write(phy, MII_BCM5400_GB_CONTROL, data);	udelay(100);	/* Reset and configure cascaded 10/100 PHY */	(void)reset_one_mii_phy(phy, 0x1f);	data = __phy_read(phy, 0x1f, MII_BCM5201_MULTIPHY);	data |= MII_BCM5201_MULTIPHY_SERIALMODE;	__phy_write(phy, 0x1f, MII_BCM5201_MULTIPHY, data);	data = phy_read(phy, MII_BCM5400_AUXCONTROL);	data &= ~MII_BCM5400_AUXCONTROL_PWR10BASET;	phy_write(phy, MII_BCM5400_AUXCONTROL, data);	return 0;}static int bcm5400_suspend(struct mii_phy* phy){#if 0 /* Commented out in Darwin... someone has those dawn docs ? */	phy_write(phy, MII_BMCR, BMCR_PDOWN);#endif	return 0;}static int bcm5401_init(struct mii_phy* phy){	u16 data;	int rev;	rev = phy_read(phy, MII_PHYSID2) & 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		 *		 * Note: This should (and does) match tg3_init_5401phy_dsp		 *       in the tg3.c driver. -DaveM		 */		phy_write(phy, 0x18, 0x0c20);		phy_write(phy, 0x17, 0x0012);		phy_write(phy, 0x15, 0x1804);		phy_write(phy, 0x17, 0x0013);		phy_write(phy, 0x15, 0x1204);		phy_write(phy, 0x17, 0x8006);		phy_write(phy, 0x15, 0x0132);		phy_write(phy, 0x17, 0x8006);		phy_write(phy, 0x15, 0x0232);		phy_write(phy, 0x17, 0x201f);		phy_write(phy, 0x15, 0x0a20);	}	/* Configure for gigabit full duplex */	data = phy_read(phy, MII_BCM5400_GB_CONTROL);	data |= MII_BCM5400_GB_CONTROL_FULLDUPLEXCAP;	phy_write(phy, MII_BCM5400_GB_CONTROL, data);	udelay(10);	/* Reset and configure cascaded 10/100 PHY */	(void)reset_one_mii_phy(phy, 0x1f);	data = __phy_read(phy, 0x1f, MII_BCM5201_MULTIPHY);	data |= MII_BCM5201_MULTIPHY_SERIALMODE;	__phy_write(phy, 0x1f, MII_BCM5201_MULTIPHY, data);	return 0;}static int bcm5401_suspend(struct mii_phy* phy){#if 0 /* Commented out in Darwin... someone has those dawn docs ? */	phy_write(phy, MII_BMCR, BMCR_PDOWN);#endif	return 0;}static int bcm5411_init(struct mii_phy* phy){	u16 data;	/* Here's some more Apple black magic to setup	 * some voltage stuffs.	 */	phy_write(phy, 0x1c, 0x8c23);	phy_write(phy, 0x1c, 0x8ca3);	phy_write(phy, 0x1c, 0x8c23);	/* Here, Apple seems to want to reset it, do	 * it as well	 */	phy_write(phy, MII_BMCR, BMCR_RESET);	phy_write(phy, MII_BMCR, 0x1340);	data = phy_read(phy, MII_BCM5400_GB_CONTROL);	data |= MII_BCM5400_GB_CONTROL_FULLDUPLEXCAP;	phy_write(phy, MII_BCM5400_GB_CONTROL, data);	udelay(10);	/* Reset and configure cascaded 10/100 PHY */	(void)reset_one_mii_phy(phy, 0x1f);	return 0;}static int genmii_setup_aneg(struct mii_phy *phy, u32 advertise){	u16 ctl, adv;	phy->autoneg = 1;	phy->speed = SPEED_10;	phy->duplex = DUPLEX_HALF;	phy->pause = 0;	phy->advertising = advertise;	/* Setup standard advertise */	adv = phy_read(phy, MII_ADVERTISE);	adv &= ~(ADVERTISE_ALL | ADVERTISE_100BASE4);	if (advertise & ADVERTISED_10baseT_Half)		adv |= ADVERTISE_10HALF;	if (advertise & ADVERTISED_10baseT_Full)		adv |= ADVERTISE_10FULL;	if (advertise & ADVERTISED_100baseT_Half)		adv |= ADVERTISE_100HALF;	if (advertise & ADVERTISED_100baseT_Full)		adv |= ADVERTISE_100FULL;	phy_write(phy, MII_ADVERTISE, adv);	/* Start/Restart aneg */	ctl = phy_read(phy, MII_BMCR);	ctl |= (BMCR_ANENABLE | BMCR_ANRESTART);	phy_write(phy, MII_BMCR, ctl);	return 0;}static int genmii_setup_forced(struct mii_phy *phy, int speed, int fd){	u16 ctl;	phy->autoneg = 0;	phy->speed = speed;	phy->duplex = fd;	phy->pause = 0;	ctl = phy_read(phy, MII_BMCR);	ctl &= ~(BMCR_FULLDPLX|BMCR_SPEED100|BMCR_ANENABLE);	/* First reset the PHY */	phy_write(phy, MII_BMCR, ctl | BMCR_RESET);	/* Select speed & duplex */	switch(speed) {	case SPEED_10:		break;	case SPEED_100:		ctl |= BMCR_SPEED100;		break;	case SPEED_1000:	default:		return -EINVAL;	}	if (fd == DUPLEX_FULL)		ctl |= BMCR_FULLDPLX;	phy_write(phy, MII_BMCR, ctl);	return 0;}static int genmii_poll_link(struct mii_phy *phy){	u16 status;	(void)phy_read(phy, MII_BMSR);	status = phy_read(phy, MII_BMSR);	if ((status & BMSR_LSTATUS) == 0)		return 0;	if (phy->autoneg && !(status & BMSR_ANEGCOMPLETE))		return 0;	return 1;}static int genmii_read_link(struct mii_phy *phy){	u16 lpa;	if (phy->autoneg) {		lpa = phy_read(phy, MII_LPA);		if (lpa & (LPA_10FULL | LPA_100FULL))			phy->duplex = DUPLEX_FULL;		else			phy->duplex = DUPLEX_HALF;		if (lpa & (LPA_100FULL | LPA_100HALF))			phy->speed = SPEED_100;		else			phy->speed = SPEED_10;		phy->pause = 0;	}	/* On non-aneg, we assume what we put in BMCR is the speed,	 * though magic-aneg shouldn't prevent this case from occurring	 */	 return 0;}static int generic_suspend(struct mii_phy* phy){	phy_write(phy, MII_BMCR, BMCR_PDOWN);	return 0;}static int bcm5421_init(struct mii_phy* phy){	u16 data;	unsigned int id;	id = (phy_read(phy, MII_PHYSID1) << 16 | phy_read(phy, MII_PHYSID2));	/* Revision 0 of 5421 needs some fixups */	if (id == 0x002060e0) {		/* This is borrowed from MacOS		 */		phy_write(phy, 0x18, 0x1007);		data = phy_read(phy, 0x18);		phy_write(phy, 0x18, data | 0x0400);		phy_write(phy, 0x18, 0x0007);		data = phy_read(phy, 0x18);		phy_write(phy, 0x18, data | 0x0800);		phy_write(phy, 0x17, 0x000a);		data = phy_read(phy, 0x15);		phy_write(phy, 0x15, data | 0x0200);	}	/* Pick up some init code from OF for K2 version */	if ((id & 0xfffffff0) == 0x002062e0) {		phy_write(phy, 4, 0x01e1);		phy_write(phy, 9, 0x0300);	}	/* Check if we can enable automatic low power */#ifdef CONFIG_PPC_PMAC	if (phy->platform_data) {		struct device_node *np = of_get_parent(phy->platform_data);		int can_low_power = 1;		if (np == NULL || of_get_property(np, "no-autolowpower", NULL))			can_low_power = 0;		if (can_low_power) {			/* Enable automatic low-power */			phy_write(phy, 0x1c, 0x9002);			phy_write(phy, 0x1c, 0xa821);			phy_write(phy, 0x1c, 0x941d);		}	}#endif /* CONFIG_PPC_PMAC */	return 0;}static int bcm54xx_setup_aneg(struct mii_phy *phy, u32 advertise){	u16 ctl, adv;	phy->autoneg = 1;	phy->speed = SPEED_10;	phy->duplex = DUPLEX_HALF;	phy->pause = 0;	phy->advertising = advertise;	/* Setup standard advertise */	adv = phy_read(phy, MII_ADVERTISE);	adv &= ~(ADVERTISE_ALL | ADVERTISE_100BASE4);	if (advertise & ADVERTISED_10baseT_Half)		adv |= ADVERTISE_10HALF;	if (advertise & ADVERTISED_10baseT_Full)		adv |= ADVERTISE_10FULL;	if (advertise & ADVERTISED_100baseT_Half)		adv |= ADVERTISE_100HALF;	if (advertise & ADVERTISED_100baseT_Full)		adv |= ADVERTISE_100FULL;	if (advertise & ADVERTISED_Pause)		adv |= ADVERTISE_PAUSE_CAP;	if (advertise & ADVERTISED_Asym_Pause)		adv |= ADVERTISE_PAUSE_ASYM;	phy_write(phy, MII_ADVERTISE, adv);	/* Setup 1000BT advertise */	adv = phy_read(phy, MII_1000BASETCONTROL);	adv &= ~(MII_1000BASETCONTROL_FULLDUPLEXCAP|MII_1000BASETCONTROL_HALFDUPLEXCAP);	if (advertise & SUPPORTED_1000baseT_Half)		adv |= MII_1000BASETCONTROL_HALFDUPLEXCAP;	if (advertise & SUPPORTED_1000baseT_Full)		adv |= MII_1000BASETCONTROL_FULLDUPLEXCAP;	phy_write(phy, MII_1000BASETCONTROL, adv);	/* Start/Restart aneg */	ctl = phy_read(phy, MII_BMCR);	ctl |= (BMCR_ANENABLE | BMCR_ANRESTART);	phy_write(phy, MII_BMCR, ctl);	return 0;}static int bcm54xx_setup_forced(struct mii_phy *phy, int speed, int fd){	u16 ctl;	phy->autoneg = 0;	phy->speed = speed;	phy->duplex = fd;	phy->pause = 0;	ctl = phy_read(phy, MII_BMCR);	ctl &= ~(BMCR_FULLDPLX|BMCR_SPEED100|BMCR_SPD2|BMCR_ANENABLE);	/* First reset the PHY */	phy_write(phy, MII_BMCR, ctl | BMCR_RESET);	/* Select speed & duplex */	switch(speed) {	case SPEED_10:		break;	case SPEED_100:		ctl |= BMCR_SPEED100;		break;	case SPEED_1000:		ctl |= BMCR_SPD2;	}	if (fd == DUPLEX_FULL)		ctl |= BMCR_FULLDPLX;	// XXX Should we set the sungem to GII now on 1000BT ?	phy_write(phy, MII_BMCR, ctl);	return 0;}static int bcm54xx_read_link(struct mii_phy *phy){	int link_mode;	u16 val;	if (phy->autoneg) {	    	val = phy_read(phy, MII_BCM5400_AUXSTATUS);		link_mode = ((val & MII_BCM5400_AUXSTATUS_LINKMODE_MASK) >>			     MII_BCM5400_AUXSTATUS_LINKMODE_SHIFT);		phy->duplex = phy_BCM5400_link_table[link_mode][0] ?			DUPLEX_FULL : DUPLEX_HALF;		phy->speed = phy_BCM5400_link_table[link_mode][2] ?				SPEED_1000 :				(phy_BCM5400_link_table[link_mode][1] ?				 SPEED_100 : SPEED_10);		val = phy_read(phy, MII_LPA);		phy->pause = (phy->duplex == DUPLEX_FULL) &&			((val & LPA_PAUSE) != 0);	}	/* On non-aneg, we assume what we put in BMCR is the speed,	 * though magic-aneg shouldn't prevent this case from occurring	 */	return 0;}static int marvell88e1111_init(struct mii_phy* phy){	u16 rev;	/* magic init sequence for rev 0 */	rev = phy_read(phy, MII_PHYSID2) & 0x000f;	if (rev == 0) {		phy_write(phy, 0x1d, 0x000a);		phy_write(phy, 0x1e, 0x0821);		phy_write(phy, 0x1d, 0x0006);		phy_write(phy, 0x1e, 0x8600);		phy_write(phy, 0x1d, 0x000b);		phy_write(phy, 0x1e, 0x0100);		phy_write(phy, 0x1d, 0x0004);		phy_write(phy, 0x1e, 0x4850);	}	return 0;}#define BCM5421_MODE_MASK	(1 << 5)static int bcm5421_poll_link(struct mii_phy* phy){	u32 phy_reg;	int mode;

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?