* [PATCH] natsemi update 3/4 External PHY operation
@ 2004-06-04 19:55 Gary N Spiess
2004-06-06 15:50 ` Manfred Spraul
0 siblings, 1 reply; 4+ messages in thread
From: Gary N Spiess @ 2004-06-04 19:55 UTC (permalink / raw)
To: netdev; +Cc: manfred
[-- Attachment #1.1: Type: text/plain, Size: 989 bytes --]
Jeff,
This is the third of a series of patches needed to support our product using the DP83815.
This patch permits operation of an external PHY. Updates the mdio_read and mdio_write functions to permit actual MII operation. Modify miscellaneous code to use the MII registers instead of the internal registers where it makes a difference. Relocate the internal phy to phy_address=1, and add find_mii() to locate the address of the external mii phy. Prevent the 'nasty random phy-reset' code from operating when an external phy is being used.
The probe code determines the previously used internal or external phy to determine the initial mode it will use. Again, our implementation does not have an EEPROM, so we rely on the mode that our boot rom used to setup the Ethernet.
Gary
oooooooooooooooooooooooooooooooooooooooooooooooooo
Gary Spiess (Gary.Spiess@Intermec.com)
MobileLan Wireless Products Group, Intermec Technology Corp
voice: 319 369-3580 fax: 319 369-3804
[-- Attachment #1.2: Type: text/html, Size: 1379 bytes --]
[-- Attachment #2: natsemi-2.6.6-3.patch --]
[-- Type: application/octet-stream, Size: 7352 bytes --]
--- linux-2.6.6/drivers/net/natsemi.c 2004-06-03 15:44:34.000000000 -0500
+++ linux/drivers/net/natsemi.c 2004-06-03 15:44:47.000000000 -0500
@@ -468,6 +468,9 @@
EE_DataIn = 0x01,
EE_ChipSelect = 0x08,
EE_DataOut = 0x02,
+ MII_Data = 0x10,
+ MII_Write = 0x20,
+ MII_ShiftClk = 0x40,
};
enum PCIBusCfg_bits {
@@ -602,6 +605,9 @@
PhyAddrMask = 0x1f,
};
+/* Internal Phy address is fixed */
+#define PHY_ADDR_INTERNAL 0x01
+
/* values we might find in the silicon revision register */
#define SRR_DP83815_C 0x0302
#define SRR_DP83815_D 0x0403
@@ -685,6 +691,7 @@
static int eeprom_read(long ioaddr, int location);
static int mdio_read(struct net_device *dev, int phy_id, int reg);
static void mdio_write(struct net_device *dev, int phy_id, int reg, u16 data);
+static int find_mii(struct net_device *dev);
static void natsemi_reset(struct net_device *dev);
static void natsemi_reload_eeprom(struct net_device *dev);
static void natsemi_stop_rxtx(struct net_device *dev);
@@ -812,10 +819,17 @@
np->msg_enable = (debug >= 0) ? (1<<debug)-1 : NATSEMI_DEF_MSG;
np->hands_off = 0;
+ /* default to external phy, if previously selected */
+ if (readl(dev->base_addr + ChipConfig) & CfgExtPhy)
+ dev->if_port = PORT_MII;
+ else
+ dev->if_port = PORT_TP;
+
/* Reset the chip to erase previous misconfiguration. */
natsemi_reload_eeprom(dev);
natsemi_reset(dev);
+ np->phy_addr_external = find_mii(dev);
option = find_cnt < MAX_UNITS ? options[find_cnt] : 0;
if (dev->mem_start)
option = dev->mem_start;
@@ -860,16 +874,22 @@
}
np->advertising = mdio_read(dev, 1, MII_ADVERTISE);
- if ((readl(ioaddr + ChipConfig) & 0xe000) != 0xe000
+ if ((np->advertising & (ADVERTISE_100FULL|ADVERTISE_10FULL|
+ ADVERTISE_100HALF|ADVERTISE_10HALF)) !=
+ (ADVERTISE_100FULL|ADVERTISE_10FULL|
+ ADVERTISE_100HALF|ADVERTISE_10HALF)
&& netif_msg_probe(np)) {
- u32 chip_config = readl(ioaddr + ChipConfig);
printk(KERN_INFO "%s: Transceiver default autonegotiation %s "
"10%s %s duplex.\n",
dev->name,
- chip_config & CfgAnegEnable ?
+ (mdio_read(dev, 1, MII_BMCR) & BMCR_ANENABLE)?
"enabled, advertise" : "disabled, force",
- chip_config & CfgAneg100 ? "0" : "",
- chip_config & CfgAnegFull ? "full" : "half");
+ (np->advertising &
+ (ADVERTISE_100FULL|ADVERTISE_100HALF))?
+ "0" : "",
+ (np->advertising &
+ (ADVERTISE_100FULL|ADVERTISE_10FULL))?
+ "full" : "half");
}
if (netif_msg_probe(np))
printk(KERN_INFO
@@ -954,25 +974,107 @@
/* MII transceiver control section.
* The 83815 series has an internal transceiver, and we present the
- * management registers as if they were MII connected. */
+ * internal management registers as if they were MII connected.
+ * External Phy registers are referenced through the MII interface.
+ */
+
+static int mii_getbit (struct net_device *dev)
+{
+ int data;
+
+ writeb(MII_ShiftClk, dev->base_addr + EECtrl);
+ data = readb(dev->base_addr + EECtrl);
+ udelay(1);
+ writeb(0, dev->base_addr + EECtrl);
+ udelay(1);
+ return (data & MII_Data)? 1 : 0;
+}
+
+static void mii_send_bits (struct net_device *dev, u32 data, int len)
+{
+ u32 i;
+
+ for (i = (1 << (len-1)); i; i >>= 1)
+ {
+ u32 mdio_val = MII_Write | ((data & i)? MII_Data : 0);
+ writeb(mdio_val, dev->base_addr + EECtrl);
+ udelay(1);
+ writeb(mdio_val | MII_ShiftClk, dev->base_addr + EECtrl);
+ udelay(1);
+ }
+ writeb(0, dev->base_addr + EECtrl);
+ udelay(1);
+}
static int mdio_read(struct net_device *dev, int phy_id, int reg)
{
- if (phy_id == 1 && reg < 32)
+ struct netdev_private *np = dev->priv;
+ u32 cmd;
+ int i;
+ u32 retval = 0;
+
+ /* The 83815 series has an internal transceiver; handle separately */
+ if (dev->if_port == PORT_TP)
return readl(dev->base_addr+BasicControl+(reg<<2))&0xffff;
- else
- return 0xffff;
+ if (phy_id == PHY_ADDR_INTERNAL)
+ phy_id = np->phy_addr_external;
+
+ /* Ensure sync */
+ mii_send_bits (dev, 0xffffffff, 32);
+ /* ST(2), OP(2), ADDR(5), REG#(5), TA(2), Data(16) total 32 bits */
+ /* ST,OP = 0110'b for read operation */
+ cmd = (0x06 << 10) | (phy_id << 5) | reg;
+ mii_send_bits (dev, cmd, 14);
+ /* Turnaround */
+ if (mii_getbit (dev))
+ return 0;
+ /* Read data */
+ for (i = 0; i < 16; i++) {
+ retval <<= 1;
+ retval |= mii_getbit (dev);
+ }
+ /* End cycle */
+ mii_getbit (dev);
+ return retval;
}
static void mdio_write(struct net_device *dev, int phy_id, int reg, u16 data)
{
struct netdev_private *np = dev->priv;
- if (phy_id == 1 && reg < 32) {
+ u32 cmd;
+
+ switch (reg) {
+ case MII_ADVERTISE: np->advertising = data; break;
+ }
+
+ /* The 83815 series has an internal transceiver; handle separately */
+ if (dev->if_port == PORT_TP) {
writew(data, dev->base_addr+BasicControl+(reg<<2));
- switch (reg) {
- case MII_ADVERTISE: np->advertising = data; break;
- }
+ return;
}
+ if (phy_id == PHY_ADDR_INTERNAL)
+ phy_id = np->phy_addr_external;
+
+ /* Ensure sync */
+ mii_send_bits (dev, 0xffffffff, 32);
+ /* ST(2), OP(2), ADDR(5), REG#(5), TA(2), Data(16) total 32 bits */
+ /* ST,OP,AAAAA,RRRRR,TA = 0101xxxxxxxxxx10'b = 0x5002 for write */
+ cmd = (0x5002 << 16) | (phy_id << 23) | (reg << 18) | data;
+ mii_send_bits (dev, cmd, 32);
+ /* End cycle */
+ mii_getbit (dev);
+}
+
+static int find_mii(struct net_device *dev)
+{
+ int tmp;
+ int i;
+ for (i = 0x1f; i; i--) {
+ tmp = mdio_read(dev, i, MII_BMSR);
+ if (tmp != 0xffff && tmp != 0x0000)
+ return i;
+ }
+ return PHY_ADDR_INTERNAL;
}
/* CFG bits [13:16] [18:23] */
@@ -1032,6 +1134,11 @@
dev->name, i*5);
}
+ /* reset internal phy to fixed mii address */
+ writew(PHY_ADDR_INTERNAL, dev->base_addr + PhyCtrl);
+ /* turn on external phy if it was selected */
+ if (dev->if_port != PORT_TP)
+ cfg |= (CfgExtPhy | CfgPhyDis);
/* restore CFG */
cfg |= readl(dev->base_addr + ChipConfig) & ~CFG_RESET_SAVE;
writel(cfg, dev->base_addr + ChipConfig);
@@ -1204,9 +1311,9 @@
struct netdev_private *np = dev->priv;
long ioaddr = dev->base_addr;
int duplex;
- int chipcfg = readl(ioaddr + ChipConfig);
+ int bmsr = mdio_read(dev, 1, MII_BMSR);
- if (!(chipcfg & CfgLink)) {
+ if (!(bmsr & BMSR_LSTATUS)) {
if (netif_carrier_ok(dev)) {
if (netif_msg_link(np))
printk(KERN_NOTICE "%s: link down.\n",
@@ -1223,7 +1330,16 @@
do_cable_magic(dev);
}
- duplex = np->full_duplex || (chipcfg & CfgFullDuplex ? 1 : 0);
+ duplex = np->full_duplex;
+ if (!duplex) {
+ if (bmsr & BMSR_ANEGCOMPLETE) {
+ int tmp = mii_nway_result(
+ np->advertising & mdio_read(dev, 1, MII_LPA));
+ if (tmp == LPA_100FULL || tmp == LPA_10FULL)
+ duplex = 1;
+ } else if (mdio_read(dev, 1, MII_BMCR) & BMCR_FULLDPLX)
+ duplex = 1;
+ }
/* if duplex is set then bit 28 must be set, too */
if (duplex ^ !!(np->rx_config & RxAcceptTx)) {
@@ -1278,6 +1394,13 @@
writew(0, ioaddr + PGSEL);
np->dspcfg = DSPCFG_VAL;
+ /* if external phy, then DSPCFG register isn't functional.
+ Fix the value here so the "nasty random phy-reset" code doesn't
+ think it needs to reinitialize the chip.
+ */
+ if (dev->if_port != PORT_TP)
+ np->dspcfg = 0;
+
/* Enable PHY Specific event based interrupts. Link state change
and Auto-Negotiation Completion are among the affected.
Read the intr status to clear it (needed for wake events).
^ permalink raw reply [flat|nested] 4+ messages in thread* Re: [PATCH] natsemi update 3/4 External PHY operation
2004-06-04 19:55 [PATCH] natsemi update 3/4 External PHY operation Gary N Spiess
@ 2004-06-06 15:50 ` Manfred Spraul
2004-06-07 21:08 ` Gary N Spiess
0 siblings, 1 reply; 4+ messages in thread
From: Manfred Spraul @ 2004-06-06 15:50 UTC (permalink / raw)
To: Gary N Spiess; +Cc: netdev, jgarzik
Gary N Spiess wrote:
>Relocate the internal phy to phy_address=1, and add find_mii() to locate the address of the external mii phy.
>
What if phy_address 1 is already in use?
> }
> + if (phy_id == PHY_ADDR_INTERNAL)
> + phy_id = np->phy_addr_external;
> +
Hmm. If the phy_id is internal then it's external.
What do you actually try to do? If I understand the hardware correctly,
it supports
- an internal PHY. Accessed through mapped registers. Used if
dev->if_port == PORT_TP.
- an external MII bus. Accessed by bit banging. Used if dev->if_port ==
PORT_MII.
- most users of mdio_{read,write} want to access the currently selected
PHY, but they call mdio_read(,1,). The "if (phy_id ==INTERNAL)
phy_id=external" line is a hack to handle that.
What about defining a PHY_ADDR_CUR (32, whatever). Everyone except the
probe code uses that value and mdio_read selects the correct port/phy
value from the dev structure. Or create a mdio_read_cur() function.
> + /* if external phy, then DSPCFG register isn't functional.
> + Fix the value here so the "nasty random phy-reset" code doesn't
> + think it needs to reinitialize the chip.
> + */
> + if (dev->if_port != PORT_TP)
> + np->dspcfg = 0;
> +
What about making the phy reset itself dependant on if->if_port? This
approach just asks for bugs - switch with ethtool from PORT_TP to
PORT_MII and suddenly short cables stop working.
--
Manfred
^ permalink raw reply [flat|nested] 4+ messages in thread* Re: [PATCH] natsemi update 3/4 External PHY operation
2004-06-06 15:50 ` Manfred Spraul
@ 2004-06-07 21:08 ` Gary N Spiess
2004-06-08 5:30 ` Manfred Spraul
0 siblings, 1 reply; 4+ messages in thread
From: Gary N Spiess @ 2004-06-07 21:08 UTC (permalink / raw)
To: manfred; +Cc: netdev, jgarzik
Manfred,
> + if (phy_id == PHY_ADDR_INTERNAL)
> + phy_id = np->phy_addr_external;
- Your point is well taken. That *is* confusing, but what I meant to do.
The existing driver code was already written to reference the "current PHY" with address 1. To select a different address to represent the current PHY would have required me to touch more lines of source.
- My external and internal PHY hardware both default to address 31. When I want to reference the external PHY, I must change the internal address to something else. Because I already clobbered the meaning of address '1' to mean the 'current PHY' (as described above), I also used the number for the actual address of the internal PHY. I don't think there is a benefit to determining that '1' was not being used by an external PHY.
- The short cable fix (to the best of my knowledge) is internal PHY related. The unmodified code didn't cause me problems when operating with an external PHY.
- The partial reset fix (again, to the best of my knowledge) is only needed when the internal PHY being used has reset, but the MAC has not. The dspcfg != np->dspcfg test identifies an internal PHY that has been reset, and triggers the driver to do a a complete MAC/PHY reset. When using an external PHY, the test continually causes unneeded resets. The change to set np->dspcfg to 0 prevents it. This was the minimal change to get the desired results.
- I have not been able to actually use the bit-banging MII interface to reference the internal PHY, regardless of which address it occupies. That's why I've chosen the internal PHY to be if_port = PORT_TP and external to be if_port = PORT_MII. You won't be able to use the internal PHY without also enabling the short cable and partial reset fixes.
Gary
*********** REPLY SEPARATOR ***********
On 6/6/2004 at 5:50 PM Manfred Spraul wrote:
>Gary N Spiess wrote:
>
>>Relocate the internal phy to phy_address=1, and add find_mii() to locate
>the address of the external mii phy.
>>
>What if phy_address 1 is already in use?
>
>> }
>> + if (phy_id == PHY_ADDR_INTERNAL)
>> + phy_id = np->phy_addr_external;
>> +
>
>Hmm. If the phy_id is internal then it's external.
>What do you actually try to do? If I understand the hardware correctly,
>it supports
>- an internal PHY. Accessed through mapped registers. Used if
>dev->if_port == PORT_TP.
>- an external MII bus. Accessed by bit banging. Used if dev->if_port ==
>PORT_MII.
>- most users of mdio_{read,write} want to access the currently selected
>PHY, but they call mdio_read(,1,). The "if (phy_id ==INTERNAL)
>phy_id=external" line is a hack to handle that.
>
>What about defining a PHY_ADDR_CUR (32, whatever). Everyone except the
>probe code uses that value and mdio_read selects the correct port/phy
>value from the dev structure. Or create a mdio_read_cur() function.
>
>> + /* if external phy, then DSPCFG register isn't functional.
>> + Fix the value here so the "nasty random phy-reset" code
>doesn't
>> + think it needs to reinitialize the chip.
>> + */
>> + if (dev->if_port != PORT_TP)
>> + np->dspcfg = 0;
>> +
>
>What about making the phy reset itself dependant on if->if_port? This
>approach just asks for bugs - switch with ethtool from PORT_TP to
>PORT_MII and suddenly short cables stop working.
>
>--
> Manfred
oooooooooooooooooooooooooooooooooooooooooooooooooo
Gary Spiess (Gary.Spiess@Intermec.com)
MobileLan Wireless Products Group, Intermec Technology Corp
voice: 319 369-3580 fax: 319 369-3804
^ permalink raw reply [flat|nested] 4+ messages in thread* Re: [PATCH] natsemi update 3/4 External PHY operation
2004-06-07 21:08 ` Gary N Spiess
@ 2004-06-08 5:30 ` Manfred Spraul
0 siblings, 0 replies; 4+ messages in thread
From: Manfred Spraul @ 2004-06-08 5:30 UTC (permalink / raw)
To: Gary N Spiess; +Cc: netdev, jgarzik
Gary N Spiess wrote:
>The existing driver code was already written to reference the "current PHY" with address 1. To select a different address to represent the current PHY would have required me to touch more lines of source.
>
>
>
Then touch more lines of source - the patch as it is is unreadable.
>- The partial reset fix (again, to the best of my knowledge) is only needed when the internal PHY being used has reset, but the MAC has not. The dspcfg != np->dspcfg test identifies an internal PHY that has been reset, and triggers the driver to do a a complete MAC/PHY reset. When using an external PHY, the test continually causes unneeded resets. The change to set np->dspcfg to 0 prevents it. This was the minimal change to get the desired results.
>
Again, miminal change doesn't matter. Add a
+ if (dev->if_port != PORT_TP) return;
into {do,undo}_cable_magic() and netdev_timer(). Perhaps with a comment
that the following block is specific to the internal PHY.
--
Manfred
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2004-06-08 5:30 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2004-06-04 19:55 [PATCH] natsemi update 3/4 External PHY operation Gary N Spiess
2004-06-06 15:50 ` Manfred Spraul
2004-06-07 21:08 ` Gary N Spiess
2004-06-08 5:30 ` Manfred Spraul
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).