linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* Re: [PATCH] MII bus API for PHY devices
       [not found] <069B6F33-341C-11D9-9652-000393DBC2E8@freescale.com>
@ 2004-11-18 17:52 ` Andy Fleming
  2004-11-18 19:34   ` Jason McMullan
  2004-11-18 23:26   ` Benjamin Herrenschmidt
  0 siblings, 2 replies; 19+ messages in thread
From: Andy Fleming @ 2004-11-18 17:52 UTC (permalink / raw)
  To: jason.mcmullan; +Cc: netdev, linux-kernel

Replying this to the netdev list for their perusal.

First, I'm flattered that you've based this on the gianfar phy code, 
which I stole shamelessly from Benh's code, but Benh changed his code, 
and so I stole once more (the sungem_phy code he mentioned).  The 
current 2.6.9 gianfar driver has a completely different PHY 
infrastructure.  One which is a better model, I think, for abstraction 
than my previous code.

Which brings me to item #2: I have taken the code from the previous 
patch, and combined it with my newer PHY code, which should ease the 
way for someone to add some of the WOL features, and such.  I have 
compiled, and tested this code, and it works with the gianfar driver.

However, I've got a few questions for the network device community, on 
how best to finish this off.

The primary issue is how to classify the MII bus.  Should it be a 
proper bus, fitting within the new driver model?  This seems like the 
proper method to me, since the MII bus is, in fact, a bus.  But it's a 
bit of a strange bus.  The bus is attached at two ends.  One end has 
some number of PHY devices, and the corresponding drivers for them.  
The other end has some number of ethernet controllers, and their 
drivers.

So we have 3 implementation decisions which are affected by this:

1) How should we pass initialization information from the system to the 
bus.  Information like which irq to use for each PHY, and what the 
address space for the bus's controls is.  I would like to enforce 
encapsulation so that the ethernet drivers don't need to know this 
information, or pass it to the bus.

2) How should we reflect the dependency of the ethernet driver on the 
mii bus driver?

3) How should we bind ethernet drivers to PHY drivers?

Oh, and a 4th side-issue:
Should each PHY have its own file?  Or should we dump all the PHY 
drivers in one file?  And if so, should THAT file be separate from the 
mii bus implementation file?

Andy Fleming
Open Source Team
Freescale Semiconductor, Inc


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH] MII bus API for PHY devices
  2004-11-18 17:52 ` [PATCH] MII bus API for PHY devices Andy Fleming
@ 2004-11-18 19:34   ` Jason McMullan
  2004-11-18 19:50     ` Andy Fleming
  2004-11-18 23:26   ` Benjamin Herrenschmidt
  1 sibling, 1 reply; 19+ messages in thread
From: Jason McMullan @ 2004-11-18 19:34 UTC (permalink / raw)
  To: Andy Fleming; +Cc: netdev, linux-kernel

On Thu, 2004-11-18 at 11:52 -0600, Andy Fleming wrote:
> 1) How should we pass initialization information from the system to the 
> bus.  Information like which irq to use for each PHY, and what the 
> address space for the bus's controls is.  I would like to enforce 
> encapsulation so that the ethernet drivers don't need to know this 
> information, or pass it to the bus.

(Just an off-the-cuff answer here)

In line with the OCP->platform work I've been doing, I would think
that creating 'phy' devices on the platform bus would be appropriate,
with 'platform_data' that describes (a) the platform device ethernet
it's bus is on and (b) it's PHY ID on that bus. The PHY's IRQ would
be in it's platform resources.

> 2) How should we reflect the dependency of the ethernet driver on the 
> mii bus driver?

Hmm. Don't really know from a sysfs perspective...


> 3) How should we bind ethernet drivers to PHY drivers?

A PHY 'platform_data' struct like:

struct phy_device_data {
	struct {
		const char *name;
		int id;
	} ethernet_platform_device_parent;
	int	phy_id;
}
	
> Oh, and a 4th side-issue:
> Should each PHY have its own file? 

Actually, each PHY should have it's own device directory, like every
other device. Eventually, PHYs should have /dev/phy* entries, where
user-space can read/write PHY registers.

-- 
Jason McMullan <jason.mcmullan@timesys.com>

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH] MII bus API for PHY devices
  2004-11-18 19:34   ` Jason McMullan
@ 2004-11-18 19:50     ` Andy Fleming
  2004-11-18 21:00       ` Jason McMullan
  0 siblings, 1 reply; 19+ messages in thread
From: Andy Fleming @ 2004-11-18 19:50 UTC (permalink / raw)
  To: Jason McMullan
  Cc: <netdev@oss.sgi.com>, <linux-kernel@vger.kernel.org>


On Nov 18, 2004, at 13:34, Jason McMullan wrote:
>> 3) How should we bind ethernet drivers to PHY drivers?
>
> A PHY 'platform_data' struct like:
>
> struct phy_device_data {
> 	struct {
> 		const char *name;
> 		int id;
> 	} ethernet_platform_device_parent;
> 	int	phy_id;
> }

So you would have each PHY know the controller to which it's attached?  
I would have thought the other way around... Hm.  I will definitely 
have to read up on my driver model stuff

> 	
>> Oh, and a 4th side-issue:
>> Should each PHY have its own file?
>
> Actually, each PHY should have it's own device directory, like every
> other device. Eventually, PHYs should have /dev/phy* entries, where
> user-space can read/write PHY registers.

I think you misunderstood.  Are you talking about sysfs?  I was talking 
about actual source files.  i.e. should there be dm9161.c, m88e1101.c, 
cis8201.c, etc.

Also, do we need user-space to read/write PHY registers.  ethtool has 
this capability, I believe, and the interfaces there are settled.

Andy Fleming


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH] MII bus API for PHY devices
  2004-11-18 19:50     ` Andy Fleming
@ 2004-11-18 21:00       ` Jason McMullan
  0 siblings, 0 replies; 19+ messages in thread
From: Jason McMullan @ 2004-11-18 21:00 UTC (permalink / raw)
  To: Andy Fleming
  Cc: <netdev@oss.sgi.com>, <linux-kernel@vger.kernel.org>

On Thu, 2004-11-18 at 13:50 -0600, Andy Fleming wrote:
> Jason McMullan said:
> >
> > Actually, each PHY should have it's own device directory, like every
> > other device. Eventually, PHYs should have /dev/phy* entries, where
> > user-space can read/write PHY registers.
> 
> I think you misunderstood.  Are you talking about sysfs?  I was talking 
> about actual source files.  i.e. should there be dm9161.c, m88e1101.c, 
> cis8201.c, etc.

	Yes, I am talking about sysfs. And yes, I think every PHY should have
it's own .c file. (although most people could get away with
using a non-IRQ 'drivers/net/phy/phy-generic.c'

> Also, do we need user-space to read/write PHY registers.  ethtool has 
> this capability, I believe, and the interfaces there are settled.

Doh! I forgot.

-- 
Jason McMullan <jason.mcmullan@timesys.com>

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH] MII bus API for PHY devices
  2004-11-18 17:52 ` [PATCH] MII bus API for PHY devices Andy Fleming
  2004-11-18 19:34   ` Jason McMullan
@ 2004-11-18 23:26   ` Benjamin Herrenschmidt
  2004-11-19 16:41     ` Jason McMullan
  2004-11-19 21:18     ` Andy Fleming
  1 sibling, 2 replies; 19+ messages in thread
From: Benjamin Herrenschmidt @ 2004-11-18 23:26 UTC (permalink / raw)
  To: Andy Fleming; +Cc: jason.mcmullan, netdev, Linux Kernel list

On Thu, 2004-11-18 at 11:52 -0600, Andy Fleming wrote:

> 1) How should we pass initialization information from the system to the 
> bus.  Information like which irq to use for each PHY, and what the 
> address space for the bus's controls is.  I would like to enforce 
> encapsulation so that the ethernet drivers don't need to know this 
> information, or pass it to the bus.

Unfortunately, this is all quite platform specific and the ethernet
driver may be the only one to know what to do here... add to that
various special cases of the way the PHY is wired or controlled, I think
we can't completely avoid special casing...

> 2) How should we reflect the dependency of the ethernet driver on the 
> mii bus driver?

The ethernet driver can instanciate the PHYs at it's childs, though the
case of several MACs sharing PHYs will be difficult to represent...

> 3) How should we bind ethernet drivers to PHY drivers?

I would have them instanciated by the ethernet driver. Besides, the PHY
driver will need to be able to identify it's "parent" driver in some
ways to deal with special cases. It would be nice to have a library of
utility code to independently deal with link tracking (basically what
drivers like sungem do independently), with a callback to the ethernet
driver to inform it of actual changes (notifier ?). MACs often have
autopoll features and PHY often have interrupts, but from experience,
that's not very useful and a good old timer based polling tend to do a
better job most of the time.

> Oh, and a 4th side-issue:
> Should each PHY have its own file?  Or should we dump all the PHY 
> drivers in one file?  And if so, should THAT file be separate from the 
> mii bus implementation file?

I'd put all bcm5xxx in the same file ... they can be put together by
families...

Also, 


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH] MII bus API for PHY devices
  2004-11-18 23:26   ` Benjamin Herrenschmidt
@ 2004-11-19 16:41     ` Jason McMullan
  2004-11-19 21:18     ` Andy Fleming
  1 sibling, 0 replies; 19+ messages in thread
From: Jason McMullan @ 2004-11-19 16:41 UTC (permalink / raw)
  To: Benjamin Herrenschmidt; +Cc: Andy Fleming, netdev, Linux Kernel list

On Fri, 2004-11-19 at 10:26 +1100, Benjamin Herrenschmidt wrote:
> The ethernet driver can instanciate the PHYs at it's childs, though the
> case of several MACs sharing PHYs will be difficult to represent...

I think *requiring* a binding to ethernet devices is a bad idea. 

For example, on many MPC8260 embedded systems, the MII bus is controlled
by GPIO pins, not an ethernet MDIO system.

Some applications may use the MII bus to control non-PHY devices, such
as Broadcom ethernet switches. I'm working for a general MII bus that
ethernet PHYs and other devices can all use.

-- 
Jason McMullan <jason.mcmullan@timesys.com>

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH] MII bus API for PHY devices
  2004-11-18 23:26   ` Benjamin Herrenschmidt
  2004-11-19 16:41     ` Jason McMullan
@ 2004-11-19 21:18     ` Andy Fleming
  2004-11-19 22:43       ` Benjamin Herrenschmidt
  1 sibling, 1 reply; 19+ messages in thread
From: Andy Fleming @ 2004-11-19 21:18 UTC (permalink / raw)
  To: Benjamin Herrenschmidt
  Cc: netdev, Linux Kernel list, jason.mcmullan, Andy Fleming


On Nov 18, 2004, at 17:26, Benjamin Herrenschmidt wrote:

> On Thu, 2004-11-18 at 11:52 -0600, Andy Fleming wrote:
>
>> 1) How should we pass initialization information from the system to 
>> the
>> bus.  Information like which irq to use for each PHY, and what the
>> address space for the bus's controls is.  I would like to enforce
>> encapsulation so that the ethernet drivers don't need to know this
>> information, or pass it to the bus.
>
> Unfortunately, this is all quite platform specific and the ethernet
> driver may be the only one to know what to do here... add to that
> various special cases of the way the PHY is wired or controlled, I 
> think
> we can't completely avoid special casing...

Well, under the system I'm currently envisioning, the driver would be 
able to provide the data needed by the mii bus, but the hope would be 
to enable board files (for when the PHY is soldered on the motherboard, 
and the enet is not -- like on an MPC85xx) to provide this information 
instead, and leave out the enet as middleman.

>
>> 2) How should we reflect the dependency of the ethernet driver on the
>> mii bus driver?
>
> The ethernet driver can instanciate the PHYs at it's childs, though the
> case of several MACs sharing PHYs will be difficult to represent...

I really don't want the driver to intantiate PHYs directly.  The PHY is 
its own device, and the less net drivers have to understand their inner 
workings, the better.  However, I hadn't considered the possibility of 
multiple MACs sharing the same PHY.  It does, as you say, support my 
argument, though.  With some careful design, the mii bus should be able 
to handle this type of setup easily.

One of my goals, personally, is to allow multiple net drivers to share 
the same mii bus, as in the case of the FCC enet controllers' PHYs on 
an 8560 ADS, which can be accessed through TSEC1's MII Management bus.


>
>> 3) How should we bind ethernet drivers to PHY drivers?
>
> I would have them instanciated by the ethernet driver. Besides, the PHY
> driver will need to be able to identify it's "parent" driver in some
> ways to deal with special cases. It would be nice to have a library of
> utility code to independently deal with link tracking (basically what
> drivers like sungem do independently), with a callback to the ethernet
> driver to inform it of actual changes (notifier ?). MACs often have
> autopoll features and PHY often have interrupts, but from experience,
> that's not very useful and a good old timer based polling tend to do a
> better job most of the time.

So when you say instantiated, would you consider calling an "attach" 
function with the phy_id and bus_id of the desired PHY instantiation?  
I'm fine with that.  The PHY would need to be able to send 
notifications to the enet controller (currently done through a 
callback).  I'm interested in ideas on how the notifier could be used 
(I have a distaste for callbacks).

Autopoll features sound pretty neat.  I think the system should support 
that.  PHY interrupts are supported (they work quite well on my 85xx 
system), as is timer-based polling.  Do you really think that there are 
special cases which can't be handled using a library similar to the 
sungem_phy one?
>
>> Oh, and a 4th side-issue:
>> Should each PHY have its own file?  Or should we dump all the PHY
>> drivers in one file?  And if so, should THAT file be separate from the
>> mii bus implementation file?
>
> I'd put all bcm5xxx in the same file ... they can be put together by
> families...

Yeah, that sounds good.


Andy Fleming
Open Source Team
Freescale Semiconductor, Inc


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH] MII bus API for PHY devices
  2004-11-19 21:18     ` Andy Fleming
@ 2004-11-19 22:43       ` Benjamin Herrenschmidt
  2004-11-20  0:04         ` Andy Fleming
  0 siblings, 1 reply; 19+ messages in thread
From: Benjamin Herrenschmidt @ 2004-11-19 22:43 UTC (permalink / raw)
  To: Andy Fleming; +Cc: netdev, Linux Kernel list, jason.mcmullan, Andy Fleming

On Fri, 2004-11-19 at 15:18 -0600, Andy Fleming wrote:

> So when you say instantiated, would you consider calling an "attach" 
> function with the phy_id and bus_id of the desired PHY instantiation?  
> I'm fine with that.  The PHY would need to be able to send 
> notifications to the enet controller (currently done through a 
> callback).  I'm interested in ideas on how the notifier could be used 
> (I have a distaste for callbacks).

Look at the notifier lists in include/linux/notifier.h

> Autopoll features sound pretty neat.  I think the system should support 
> that.

But that becomes MAC-dependant again... That means you'd need 1) a way
for the MAC driver to ask the PHY driver what register it wants
autopolled, and a function in the PHY driver for the MAC to call when it
detects a change. Also, autopoll is broken in some MACs...

>   PHY interrupts are supported (they work quite well on my 85xx 
> system), as is timer-based polling.  Do you really think that there are 
> special cases which can't be handled using a library similar to the 
> sungem_phy one?

Nope. I think timer based polling with a sungem-like fallback mecanism
to forced speeds would be nice.

Ben.



^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH] MII bus API for PHY devices
  2004-11-19 22:43       ` Benjamin Herrenschmidt
@ 2004-11-20  0:04         ` Andy Fleming
  0 siblings, 0 replies; 19+ messages in thread
From: Andy Fleming @ 2004-11-20  0:04 UTC (permalink / raw)
  To: Benjamin Herrenschmidt; +Cc: Jason McMullan, Linux Kernel list, Netdev


On Nov 19, 2004, at 16:43, Benjamin Herrenschmidt wrote:

> On Fri, 2004-11-19 at 15:18 -0600, Andy Fleming wrote:
>
>> So when you say instantiated, would you consider calling an "attach"
>> function with the phy_id and bus_id of the desired PHY instantiation?
>> I'm fine with that.  The PHY would need to be able to send
>> notifications to the enet controller (currently done through a
>> callback).  I'm interested in ideas on how the notifier could be used
>> (I have a distaste for callbacks).
>
> Look at the notifier lists in include/linux/notifier.h

Ok, will do.

>
>> Autopoll features sound pretty neat.  I think the system should 
>> support
>> that.
>
> But that becomes MAC-dependant again... That means you'd need 1) a way
> for the MAC driver to ask the PHY driver what register it wants
> autopolled, and a function in the PHY driver for the MAC to call when 
> it
> detects a change. Also, autopoll is broken in some MACs...

What I'm envisioning here is that the driver would be able to tell the 
PHY infrastructure that it's going to do its own thing, and then make 
use of the reading/configuring part of the infrastructure, similar to 
how sungem and gianfar are currently set up.  But they would have the 
option of letting the infrastructure also handle the status updates.  
And, of course, the driver would not go through the effort to use 
autopoll if it were broken.

>
>>   PHY interrupts are supported (they work quite well on my 85xx
>> system), as is timer-based polling.  Do you really think that there 
>> are
>> special cases which can't be handled using a library similar to the
>> sungem_phy one?
>
> Nope. I think timer based polling with a sungem-like fallback mecanism
> to forced speeds would be nice.

Yes, I agree.  The system I currently have does fallback to forced, 
though it doesn't yet support the "magic aneg" feature you mentioned.  
But that should be easy to add, and so it shall be.


Andy Fleming


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH] MII bus API for PHY devices
  2004-11-19 20:18 Manfred Spraul
@ 2004-11-19 21:01 ` Andy Fleming
  0 siblings, 0 replies; 19+ messages in thread
From: Andy Fleming @ 2004-11-19 21:01 UTC (permalink / raw)
  To: Manfred Spraul; +Cc: Jason McMullan, Linux Kernel Mailing List, Netdev


On Nov 19, 2004, at 14:18, Manfred Spraul wrote:

> Hi,
>
> I don't like the polling/interrupt setup part:
> - for a nic driver, there is no irq line that could be requested by 
> mii_phy_irq_enable().
> - if the mii bus driver uses it's own timers, then locking within the 
> nics will be more difficult.

I'm not sure I accept the argument that locking will be more difficult. 
  Jason's patch requires that a callback be registered for the interrupt 
or the polling (My update has a similar scheme).  The function you 
register is essentially like an extra interrupt, except it is never 
invoked at interrupt time.  All the function has to do is react 
properly to link state.  If, previously, you checked link state in your 
interrupt handler, you could still do it there, I suspect.

>
> Could you make that part optional? For a nic driver, I would prefer if 
> I could just call the ->startup part without the request_irq. If the 
> nic irq handler notices that the nic got an event, then it would call 
> an appropriate mii_bus function.

I think it would be doable to arrange the interface such that drivers 
could adopt only the PHY configuration infrastructure, and not any of 
the polling/interrupt infrastructure.  Of course, as it is, it is at 
least WHOLLY optional, so no driver has to use it at all.

>
> This also applies for something like /dev/phy/xy: With natsemi, it 
> would be very tricky to add proper locking. The nic as an internal phy 
> and an external mii bus. The internal phy is partially visible on the 
> external bus and any accesses to the phy id of the internal phy on the 
> external bus cause lockups. No big deal, I just move the internal phy 
> around [the phy id doesn't matter], but I would prefer if I have to do 
> that just for ethtool, not for multiple interfaces.

I agree with this point -- Accessing the PHY through /dev registers is 
a recipe for some mess.  Though I could be convinced that it is 
manageable.  I do think, however, that the ethtool interface is 
sufficient to the task.


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH] MII bus API for PHY devices
@ 2004-11-19 20:18 Manfred Spraul
  2004-11-19 21:01 ` Andy Fleming
  0 siblings, 1 reply; 19+ messages in thread
From: Manfred Spraul @ 2004-11-19 20:18 UTC (permalink / raw)
  To: Andy Fleming, Jason McMullan; +Cc: Linux Kernel Mailing List, Netdev

Hi,

I don't like the polling/interrupt setup part:
- for a nic driver, there is no irq line that could be requested by 
mii_phy_irq_enable().
- if the mii bus driver uses it's own timers, then locking within the 
nics will be more difficult.

Could you make that part optional? For a nic driver, I would prefer if I 
could just call the ->startup part without the request_irq. If the nic 
irq handler notices that the nic got an event, then it would call an 
appropriate mii_bus function.

This also applies for something like /dev/phy/xy: With natsemi, it would 
be very tricky to add proper locking. The nic as an internal phy and an 
external mii bus. The internal phy is partially visible on the external 
bus and any accesses to the phy id of the internal phy on the external 
bus cause lockups. No big deal, I just move the internal phy around [the 
phy id doesn't matter], but I would prefer if I have to do that just for 
ethtool, not for multiple interfaces.

--
    Manfred

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH] MII bus API for PHY devices
  2004-11-12 16:47   ` Jason McMullan
@ 2004-11-13  1:36     ` Benjamin Herrenschmidt
  0 siblings, 0 replies; 19+ messages in thread
From: Benjamin Herrenschmidt @ 2004-11-13  1:36 UTC (permalink / raw)
  To: Jason McMullan; +Cc: Linux Kernel list, Jeff Garzik, romieu

On Fri, 2004-11-12 at 11:47 -0500, Jason McMullan wrote:

> 	For the 'wake on lan' stuff, could you give me a list of the
> types of features you'd need? I haven't really looked at WOL just yet.

Me neither, though Colin Leroy has, I'll check out his stuff next week.

> 	We can add WOL, suspend, etc. as needed. I just want to
> get the base infrastructure in first, and gradually start migrating
> phys to the mii_bus on embedded systems.
> 
-- 
Benjamin Herrenschmidt <benh@kernel.crashing.org>


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH] MII bus API for PHY devices
  2004-11-12  6:15 ` Benjamin Herrenschmidt
@ 2004-11-12 16:47   ` Jason McMullan
  2004-11-13  1:36     ` Benjamin Herrenschmidt
  0 siblings, 1 reply; 19+ messages in thread
From: Jason McMullan @ 2004-11-12 16:47 UTC (permalink / raw)
  To: Benjamin Herrenschmidt; +Cc: Linux Kernel list, Jeff Garzik, romieu

On Fri, 2004-11-12 at 17:15 +1100, Benjamin Herrenschmidt wrote:
> Have you looked at what sungem/sungem_phy does ?
> 
> Among others, sungem has an algorithm for automatically testing fallback
> forced speeds when aneg fails, which has proven useful with a variety of
> PHY/hub combos, plus a "magic_aneg" bit in the PHY definition for PHYs
> taht can do that themselves automatically.

	I'll look into it.

> Also, besides shutdown(), you probably want a suspend() callback used by
> the MAC driver when the machine is entering a suspend() state (I
> definitely need that with various PHYs on powermacs) along with the
> various WOL parameters.

	For the 'wake on lan' stuff, could you give me a list of the
types of features you'd need? I haven't really looked at WOL just yet.

	We can add WOL, suspend, etc. as needed. I just want to
get the base infrastructure in first, and gradually start migrating
phys to the mii_bus on embedded systems.

-- 
Jason McMullan <jason.mcmullan@timesys.com>

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH] MII bus API for PHY devices
  2004-11-11 22:48 Jason McMullan
  2004-11-11 23:54 ` Francois Romieu
@ 2004-11-12  6:15 ` Benjamin Herrenschmidt
  2004-11-12 16:47   ` Jason McMullan
  1 sibling, 1 reply; 19+ messages in thread
From: Benjamin Herrenschmidt @ 2004-11-12  6:15 UTC (permalink / raw)
  To: Jason McMullan; +Cc: Linux Kernel list, Jeff Garzik, romieu

On Thu, 2004-11-11 at 17:48 -0500, Jason McMullan wrote:
> Second attempt, more polish.
> 
> (Though I'm leaving my macro abuse in for now!)
> 
> Description: MII Bus interface
> Date:        Thu, 11 Nov 2004 17:44:57 -0500
> Signed-off-by:  Jason McMullan <jmcmullan@timesys.com>
> Depends:
> 	linux-2.6.9

Have you looked at what sungem/sungem_phy does ?

Among others, sungem has an algorithm for automatically testing fallback
forced speeds when aneg fails, which has proven useful with a variety of
PHY/hub combos, plus a "magic_aneg" bit in the PHY definition for PHYs
taht can do that themselves automatically.

Also, besides shutdown(), you probably want a suspend() callback used by
the MAC driver when the machine is entering a suspend() state (I
definitely need that with various PHYs on powermacs) along with the
various WOL parameters.

Ben.



^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH] MII bus API for PHY devices
  2004-11-11 23:54 ` Francois Romieu
@ 2004-11-12  0:07   ` Francois Romieu
  0 siblings, 0 replies; 19+ messages in thread
From: Francois Romieu @ 2004-11-12  0:07 UTC (permalink / raw)
  To: Jason McMullan; +Cc: linux-kernel, jgarzik

Francois Romieu <romieu@fr.zoreil.com> :
[...]

Okayyy, I should have issued a diff to check that you had not sent
the exact same patch twice.

--
Ueimor

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH] MII bus API for PHY devices
  2004-11-11 22:48 Jason McMullan
@ 2004-11-11 23:54 ` Francois Romieu
  2004-11-12  0:07   ` Francois Romieu
  2004-11-12  6:15 ` Benjamin Herrenschmidt
  1 sibling, 1 reply; 19+ messages in thread
From: Francois Romieu @ 2004-11-11 23:54 UTC (permalink / raw)
  To: Jason McMullan; +Cc: linux-kernel, jgarzik

Jason McMullan <jason.mcmullan@timesys.com> :
[...]
> --- /dev/null
> +++ linux/drivers/net/mii_bus.c
[...]
> +struct phy_cmd {
> +    u32 mii_reg;
> +    u32 mii_data;
> +    u16 (*funct) (u16 mii_reg, int bus, int id);
   ^^^^

-> spaces

[...]
> +    /* Local state information */
> +    struct {
> +	int irq;
> +	unsigned long msecs;
> +	void (*func)(void *data);
> +	void *data;
> +    	struct work_struct tq;
> +	struct timer_list timer;
   ^^^^^

-> tab...

> +    } delta;
   ^^^^

-> ...spaces

[...]
> +/* Write value to the PHY for this device to the register at regnum, */
> +/* waiting until the write is done before it returns.  All PHY */
> +/* configuration has to be done through the TSEC1 MIIM regs */
> +EXPORT_SYMBOL(mii_bus_write);
> +int mii_bus_write(int bus, int id, int regnum, uint16_t value)
                                                  ^^^^^^^^
-> the code of this file has already used some u16/u32 before this point.

> +{
> +	if (!VALID_BUS(bus))
> +		return -EINVAL;
> +
> +       	mii_bus[bus]->write(mii_bus[bus]->priv,id,regnum,value);
   ^^^^^^^^^^^^^

-> spaces instead of tab
-> please add a space after a colon

-> AFAIKS, the code guarantees that VALID_BUS will not fail.

> +	return 0;
> +}
> +
> +/* Reads from register regnum in the PHY for device dev, */
> +/* returning the value.  Clears miimcom first.  All PHY */
> +/* configuration has to be done through the TSEC1 MIIM regs */
> +EXPORT_SYMBOL(mii_bus_read);
> +int mii_bus_read(int bus, int id, int regnum)
> +{
> +	if (!VALID_BUS(bus))
> +		return -EINVAL;
> +
> +       	return mii_bus[bus]->read(mii_bus[bus]->priv,id,regnum);

-> see above.

[...]
> +#define BRIEF_MII_ERRORS
> +/* Wait for auto-negotiation to complete */
> +u16 mii_parse_sr(u16 mii_reg, int bus, int id)
> +{
> +	unsigned int timeout = MII_TIMEOUT;
> +	struct phy_state *pstate;
> +
> +	if (!VALID(bus, id)) return 0xffff;

-> the return on a separate line only costs an extra character
   and makes the style more consistent (see the code above).

[...]
> +u16 mii_parse_cis8201(u16 mii_reg, int bus, int id)
> +{
> +	unsigned int speed;
> +	struct phy_state *pstate;
> +
> +	if (!VALID(bus, id)) return 0xffff;
> +	pstate = &mii_bus[bus]->phy[id]->state;
> +
> +	if (pstate->link) {
> +		if (mii_reg & MIIM_CIS8201_AUXCONSTAT_DUPLEX)
> +			pstate->duplex = 1;
> +		else
> +			pstate->duplex = 0;

-> If you are really concerned about the size of the source code:
		pstate->duplex =
			(mii_reg & MIIM_CIS8201_AUXCONSTAT_DUPLEX) ? 1 : 0;
> +	NULL
> +};
> +
> +/* Use the PHY ID registers to determine what type of PHY is attached
> + * to device dev.  return a struct phy_info structure describing that PHY
> + */
> +struct phy_info *mii_phy_get_info(int bus, int id)
> +{
> +	u16 phy_reg;
> +	u32 phy_ID;
> +	int i;
> +	struct phy_info *theInfo = NULL;

-> s/theInfo/info/ ?


> +	if (mii_bus[bus] == NULL)
> +		return NULL;

-> This function is not exported and the caller has already
   issued a VALID_BUS.

[...]
> +	/* loop through all the known PHY types, and find one that */
> +	/* matches the ID we read from the PHY. */
> +	for (i = 0; phy_info[i]; i++)
> +		if (phy_info[i]->id == (phy_ID >> phy_info[i]->shift))
> +			theInfo = phy_info[i];

-> add braces around the for block + break ?

> +
> +	if (theInfo == NULL) {
> +		printk("phy %d.%d: PHY id 0x%x is not supported!\n", bus, id, phy_ID);
> +		return NULL;

-> no need to return here.

> +	} else {
> +		printk("phy %d.%d: PHY is %s (%x)\n", bus, id, theInfo->name,
> +		       phy_ID);
> +	}
> +
> +	return theInfo;
> +}
[...]
> +int mii_phy_attach(struct mii_if_info *mii, struct net_device *dev, int bus, int id)
> +{
> +	struct phy_info *phy,*info;
> +
> +	if (mii_bus[bus] == NULL) {
> +		printk(KERN_ERR "mii_phy_attach: Can't attach %s, no MII bus %d present\n",dev->name,bus);
> +		return -ENODEV;
> +	}
> +
> +	if (VALID(bus,id)) {
> +		printk(KERN_ERR "mii_phy_attach: PHY %d.%d is already attached to %s\n",bus,id,dev->name);
> +		return -EBUSY;
> +	}
> +
> +	info = mii_phy_get_info(bus, id);
> +	if (info == NULL)
> +		return -ENODEV;
> +
> +	phy = kmalloc(sizeof(*phy), GFP_KERNEL);
> +	memcpy(phy,info,sizeof(*phy));

-> kmalloc can fail.

[...]
> +int mii_bus_register(struct mii_bus *bus)
> +{
> +	int bus_id;
> +
> +	if (bus == NULL || bus->name == NULL || bus->read == NULL ||
> +	    bus->write == NULL)
> +		return -EINVAL;
[...]
> +	mii_bus[bus_id] = bus;
> +
> +	if (mii_bus[bus_id] == NULL) {

-> bus is not NULL, neither is mii_bus[bus_id] (see above).

> +		up(&mii_bus_lock);
> +		return -EINVAL;
> +	}
> +
> +	if (mii_bus[bus_id]->reset)
> +		mii_bus[bus_id]->reset(mii_bus[bus_id]->priv);

	if (bus->reset)
		bus->reset(bus->priv);

Please, pretty please, more polish (I did not say that it could
be done instantly :o) ).

--
Ueimor

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH] MII bus API for PHY devices
@ 2004-11-11 22:48 Jason McMullan
  2004-11-11 23:54 ` Francois Romieu
  2004-11-12  6:15 ` Benjamin Herrenschmidt
  0 siblings, 2 replies; 19+ messages in thread
From: Jason McMullan @ 2004-11-11 22:48 UTC (permalink / raw)
  To: linux-kernel; +Cc: jgarzik, romieu

Second attempt, more polish.

(Though I'm leaving my macro abuse in for now!)

Description: MII Bus interface
Date:        Thu, 11 Nov 2004 17:44:57 -0500
Signed-off-by:  Jason McMullan <jmcmullan@timesys.com>
Depends:
	linux-2.6.9

###############################

Index of changes:

 drivers/net/Makefile          |    2 
 linux/drivers/net/mii_bus.c   |  857 ++++++++++++++++++++++++++++++++++++++++++
 linux/include/linux/mii_bus.h |  132 ++++++
 3 files changed, 990 insertions(+), 1 deletion(-)


--- linux-orig/drivers/net/Makefile
+++ linux/drivers/net/Makefile
@@ -62,7 +62,7 @@
 # end link order section
 #
 
-obj-$(CONFIG_MII) += mii.o
+obj-$(CONFIG_MII) += mii.o mii_bus.o
 
 obj-$(CONFIG_SUNDANCE) += sundance.o
 obj-$(CONFIG_HAMACHI) += hamachi.o
--- /dev/null
+++ linux/drivers/net/mii_bus.c
@@ -0,0 +1,857 @@
+/* 
+ * drivers/net/mii_bus.c
+ *
+ * Adapeted from drivers/net/gianfar_mii.c, by Andy Fleming
+ *
+ * Author: Jason McMullan (jason.mcmullan@timesys.com) to 
+ * 	   be a generic mii interface
+ *
+ * Copyright (c) 2004 Timesys Inc
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ *
+ */
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/spinlock.h>
+#include <linux/mm.h>
+#include <linux/mii_bus.h>
+
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/uaccess.h>
+#include <linux/module.h>
+
+/*
+ * struct phy_cmd:  A command for reading or writing a PHY register
+ *
+ * mii_reg:  The register to read or write
+ *
+ * mii_data:  For writes, the value to put in the register.
+ * 	A value of -1 indicates this is a read.
+ *
+ * funct: A function pointer which is invoked for each command.
+ * 	For reads, this function will be passed the value read
+ *	from the PHY, and process it.
+ *	For writes, the result of this function will be written
+ *	to the PHY register
+ */
+struct phy_cmd {
+    u32 mii_reg;
+    u32 mii_data;
+    u16 (*funct) (u16 mii_reg, int bus, int id);
+};
+
+/* struct phy_info: a structure which defines attributes for a PHY
+ *
+ * id will contain a number which represents the PHY.  During
+ * startup, the driver will poll the PHY to find out what its
+ * UID--as defined by registers 2 and 3--is.  The 32-bit result
+ * gotten from the PHY will be shifted right by "shift" bits to
+ * discard any bits which may change based on revision numbers
+ * unimportant to functionality
+ *
+ * The struct phy_cmd entries represent pointers to an arrays of
+ * commands which tell the driver what to do to the PHY.
+ */
+struct phy_info {
+    u32 id;
+    const char *name;
+    unsigned int shift;
+    /* Called to configure the PHY, and modify the controller
+     * based on the results */
+    const struct phy_cmd *config;
+
+    /* Called when starting up the controller.  Usually sets
+     * up the interrupt for state changes */
+    const struct phy_cmd *startup;
+
+    /* Called inside the interrupt handler to acknowledge
+     * the interrupt */
+    const struct phy_cmd *ack_int;
+
+    /* Called in the bottom half to get the state */
+    const struct phy_cmd *poll;
+
+    /* Called to re-enable interrupts */
+    const struct phy_cmd *end_int;
+
+    /* Called when bringing down the controller.  Usually stops
+     * the interrupts from being generated */
+    const struct phy_cmd *shutdown;
+
+    /* Local state information */
+    struct {
+	int irq;
+	unsigned long msecs;
+	void (*func)(void *data);
+	void *data;
+    	struct work_struct tq;
+	struct timer_list timer;
+    } delta;
+
+    struct phy_state state;
+};
+
+static struct mii_bus *mii_bus[4];
+
+#define MII_BUS_MAX	(sizeof(mii_bus)/sizeof(struct mii_bus *))
+
+#define VALID_BUS(bus) (mii_bus[bus]!=NULL)
+#define VALID(bus,x) (mii_bus[bus]!=NULL && (mii_bus[bus]->phy[id] != NULL))
+#define PHY_ID(phy_id) (phy_id & 0x1f)
+#define PHY_BUS(phy_id) ((phy_id & 0x20) ? 1 : 0)
+#define MII_ID(mii) PHY_ID(mii->phy_id)
+#define MII_BUS(mii) PHY_BUS(mii->phy_id)
+#define BUS_ID(phy_id) int bus = PHY_BUS(phy_id); int id = PHY_ID(phy_id);
+
+static inline struct phy_info *mii_phy_of(struct mii_if_info *mii)
+{
+	if (mii != NULL) {
+		BUS_ID(mii->phy_id);
+		return mii_bus[bus]->phy[id];
+	}
+
+	return NULL;
+}
+
+/* Write value to the PHY for this device to the register at regnum, */
+/* waiting until the write is done before it returns.  All PHY */
+/* configuration has to be done through the TSEC1 MIIM regs */
+EXPORT_SYMBOL(mii_bus_write);
+int mii_bus_write(int bus, int id, int regnum, uint16_t value)
+{
+	if (!VALID_BUS(bus))
+		return -EINVAL;
+
+       	mii_bus[bus]->write(mii_bus[bus]->priv,id,regnum,value);
+	return 0;
+}
+
+/* Reads from register regnum in the PHY for device dev, */
+/* returning the value.  Clears miimcom first.  All PHY */
+/* configuration has to be done through the TSEC1 MIIM regs */
+EXPORT_SYMBOL(mii_bus_read);
+int mii_bus_read(int bus, int id, int regnum)
+{
+	if (!VALID_BUS(bus))
+		return -EINVAL;
+
+       	return mii_bus[bus]->read(mii_bus[bus]->priv,id,regnum);
+}
+
+/* returns which value to write to the control register. */
+/* For 10/100 the value is slightly different. */
+static u16 mii_cr_init(u16 mii_reg, int bus, int id)
+{
+		return BMCR_ANRESTART;
+}
+
+#define BRIEF_MII_ERRORS
+/* Wait for auto-negotiation to complete */
+u16 mii_parse_sr(u16 mii_reg, int bus, int id)
+{
+	unsigned int timeout = MII_TIMEOUT;
+	struct phy_state *pstate;
+
+	if (!VALID(bus, id)) return 0xffff;
+	pstate = &mii_bus[bus]->phy[id]->state;
+
+	if (mii_reg & BMSR_LSTATUS) {
+		pstate->link = 1;
+	} else {
+		pstate->link = 0;
+		pstate->auto_neg = 1;
+	}
+
+	/* Only auto-negotiate if the link has just gone up */
+	if (pstate->link && pstate->auto_neg) {
+		while ((!(mii_reg & BMSR_ANEGCOMPLETE)) && timeout--)
+			mii_reg = mii_bus_read(bus, id, MII_BMSR);
+
+#if defined(BRIEF_MII_ERRORS)
+		if (mii_reg & BMSR_ANEGCOMPLETE)
+			printk(KERN_INFO "phy %d.%d: Auto-negotiation done\n",
+			       bus,id);
+		else
+			printk(KERN_INFO "phy %d.%d: Auto-negotiation timed out\n",
+			       bus,id);
+#endif
+		pstate->auto_neg = 0;
+
+		if (mii_reg & BMSR_ANEGCOMPLETE) {
+			mii_reg = mii_bus_read(bus, id, MII_LPA);
+			mii_reg &= mii_bus_read(bus, id, MII_ADVERTISE);
+			/* Get the speed */
+			if (mii_reg & (LPA_100FULL | LPA_100HALF))
+				pstate->speed = 100;
+			else if (mii_reg & (LPA_10FULL | LPA_10HALF))
+				pstate->speed = 10;
+
+			/* Get the duplex */
+			if (mii_reg & (LPA_100FULL | LPA_10FULL))
+				pstate->duplex = 1;
+			else if (mii_reg & (LPA_100HALF | LPA_10HALF))
+				pstate->duplex = 0;
+		}
+	}
+
+	return 0;
+}
+
+/* Determine the speed and duplex which was negotiated */
+u16 mii_parse_88E1011_psr(u16 mii_reg, int bus, int id)
+{
+	unsigned int speed;
+	struct phy_state *pstate;
+
+	if (!VALID(bus, id)) return 0xffff;
+	pstate = &mii_bus[bus]->phy[id]->state;
+
+	if (pstate->link) {
+		if (mii_reg & MIIM_88E1011_PHYSTAT_DUPLEX)
+			pstate->duplex = 1;
+		else
+			pstate->duplex = 0;
+
+		speed = (mii_reg & MIIM_88E1011_PHYSTAT_SPEED);
+
+		switch (speed) {
+		case MIIM_88E1011_PHYSTAT_GBIT:
+			pstate->speed = 1000;
+			break;
+		case MIIM_88E1011_PHYSTAT_100:
+			pstate->speed = 100;
+			break;
+		default:
+			pstate->speed = 10;
+			break;
+		}
+	} else {
+		pstate->speed = 0;
+		pstate->duplex = 0;
+	}
+
+	return 0;
+}
+
+u16 mii_parse_cis8201(u16 mii_reg, int bus, int id)
+{
+	unsigned int speed;
+	struct phy_state *pstate;
+
+	if (!VALID(bus, id)) return 0xffff;
+	pstate = &mii_bus[bus]->phy[id]->state;
+
+	if (pstate->link) {
+		if (mii_reg & MIIM_CIS8201_AUXCONSTAT_DUPLEX)
+			pstate->duplex = 1;
+		else
+			pstate->duplex = 0;
+
+		speed = mii_reg & MIIM_CIS8201_AUXCONSTAT_SPEED;
+
+		switch (speed) {
+		case MIIM_CIS8201_AUXCONSTAT_GBIT:
+			pstate->speed = 1000;
+			break;
+		case MIIM_CIS8201_AUXCONSTAT_100:
+			pstate->speed = 100;
+			break;
+		default:
+			pstate->speed = 10;
+			break;
+		}
+	} else {
+		pstate->speed = 0;
+		pstate->duplex = 0;
+	}
+
+	return 0;
+}
+
+u16 mii_parse_dm9161_scsr(u16 mii_reg, int bus, int id)
+{
+	struct phy_state *pstate;
+
+	if (!VALID(bus, id)) return 0xffff;
+	pstate = &mii_bus[bus]->phy[id]->state;
+
+	if (mii_reg & (MIIM_DM9161_SCSR_100F | MIIM_DM9161_SCSR_100H))
+		pstate->speed = 100;
+	else
+		pstate->speed = 10;
+
+	if (mii_reg & (MIIM_DM9161_SCSR_100F | MIIM_DM9161_SCSR_10F))
+		pstate->duplex = 1;
+	else
+		pstate->duplex = 0;
+
+	return 0;
+}
+
+u16 dm9161_wait(u16 mii_reg, int bus, int id)
+{
+	int timeout = 3*HZ;
+
+	if (!VALID(bus,id)) return 0xffff;
+
+	/* Davicom takes a bit to come up after a reset,
+	 * so wait here for a bit */
+	set_current_state(TASK_UNINTERRUPTIBLE);
+	schedule_timeout(timeout);
+
+	return 0;
+}
+
+static struct phy_info phy_info_M88E1011S = {
+	.id = 0x01410c6,
+	.name = "Marvell 88E1011S",
+	.shift = 4,
+	.config = (const struct phy_cmd[]) {	/* config */
+		/* Reset and configure the PHY */
+		{MII_BMCR, BMCR_ANRESTART, mii_cr_init},
+		{miim_end,}
+	},
+	.startup = (const struct phy_cmd[]) {	/* startup */
+		/* Clear the IEVENT register */
+		{MIIM_88E1011_IEVENT, miim_read, NULL},
+		/* Set up the mask */
+		{MIIM_88E1011_IMASK, MIIM_88E1011_IMASK_INIT, NULL},
+		{miim_end,}
+	},
+	.ack_int = (const struct phy_cmd[]) {	/* ack_int */
+		/* Clear the interrupt */
+		{MIIM_88E1011_IEVENT, miim_read, NULL},
+		/* Disable interrupts */
+		{MIIM_88E1011_IMASK, MIIM_88E1011_IMASK_CLEAR, NULL},
+		{miim_end,}
+	},
+	.poll = (const struct phy_cmd[]) {	/* poll */
+		/* Read the Status (2x to make sure link is right) */
+		{MII_BMSR, miim_read, NULL},
+		/* Check the status */
+		{MII_BMSR, miim_read, mii_parse_sr},
+		{MIIM_88E1011_PHY_STATUS, miim_read, mii_parse_88E1011_psr},
+		{miim_end,}
+	},
+	.end_int = (const struct phy_cmd[]) {	/* end_int */
+			/* Enable Interrupts */
+		{MIIM_88E1011_IMASK, MIIM_88E1011_IMASK_INIT, NULL},
+		{miim_end,}
+	},
+	.shutdown = (const struct phy_cmd[]) {	/* shutdown */
+		{MIIM_88E1011_IEVENT, miim_read, NULL},
+		{MIIM_88E1011_IMASK, MIIM_88E1011_IMASK_CLEAR, NULL},
+		{miim_end,}
+	},
+};
+
+/* Cicada 8201 */
+static struct phy_info phy_info_cis8201 = {
+	.id = 0xfc41,
+	.name = "CIS8201",
+	.shift = 4,
+	.config = (const struct phy_cmd[]) {	/* config */
+		/* Override PHY config settings */
+		{MIIM_CIS8201_AUX_CONSTAT, MIIM_CIS8201_AUXCONSTAT_INIT, NULL},
+		/* Set up the interface mode */
+		{MIIM_CIS8201_EXT_CON1, MIIM_CIS8201_EXTCON1_INIT, NULL},
+		/* Configure some basic stuff */
+		{MII_BMCR, BMCR_ANRESTART, mii_cr_init},
+		{miim_end,}
+	},
+	.poll = (const struct phy_cmd[]) {	/* poll */
+		/* Read the Status (2x to make sure link is right) */
+		{MII_BMSR, miim_read, NULL},
+		/* Auto-negotiate */
+		{MII_BMSR, miim_read, mii_parse_sr},
+		/* Read the status */
+		{MIIM_CIS8201_AUX_CONSTAT, miim_read, mii_parse_cis8201},
+		{miim_end,}
+	},
+};
+
+static struct phy_info phy_info_dm9161 = {
+	.id = 0x0181b88,
+	.name = "Davicom DM9161E",
+	.shift = 4,
+	.config = (const struct phy_cmd[]) {	/* config */
+		{MII_BMCR, MIIM_DM9161_CR_STOP, NULL},
+		/* Do not bypass the scrambler/descrambler */
+		{MIIM_DM9161_SCR, MIIM_DM9161_SCR_INIT, NULL},
+		/* Clear 10BTCSR to default */
+		{MIIM_DM9161_10BTCSR, MIIM_DM9161_10BTCSR_INIT, NULL},
+		/* Configure some basic stuff */
+		{MII_BMCR, BMCR_ANRESTART, NULL},
+		/* Restart Auto Negotiation */
+		{MII_BMCR, MIIM_DM9161_CR_RSTAN, NULL},
+		{miim_end,}
+	},
+	.startup = (const struct phy_cmd[]) {	/* startup */
+		/* Status is read once to clear old link state */
+		{MII_BMSR, miim_read, dm9161_wait},
+		/* Clear any pending interrupts */
+		{MIIM_DM9161_INTR, miim_read, NULL},
+		{miim_end,}
+	},
+	.ack_int = (const struct phy_cmd[]) {	/* ack_int */
+		{MIIM_DM9161_INTR, miim_read, NULL},
+		{miim_end,}
+	},
+	.poll = (const struct phy_cmd[]) {	/* poll */
+		{MII_BMSR, miim_read, NULL},
+		{MII_BMSR, miim_read, mii_parse_sr},
+		{MIIM_DM9161_SCSR, miim_read, mii_parse_dm9161_scsr},
+		{miim_end,}
+	},
+	.shutdown = (const struct phy_cmd[]) {	/* shutdown */
+		{MIIM_DM9161_INTR, miim_read, NULL},
+		{miim_end,}
+	},
+};
+
+static struct phy_info phy_info_bcm5222 = {
+	.id = 0x0040632,
+	.name = "Broadcom BCM5222",
+	.shift = 4,
+	.config = (const struct phy_cmd[]) {	/* config */
+		/* Configure some basic stuff */
+		{MII_BMCR, BMCR_ANRESTART, NULL},
+		/* Status is read once to clear old link state */
+		{MII_BMSR, miim_read, NULL},
+		{miim_end,}
+	},
+	.poll = (const struct phy_cmd[]) {	/* poll */
+		{MII_BMSR, miim_read, NULL},
+		{MII_BMSR, miim_read, mii_parse_sr},
+		{miim_end,}
+	},
+};
+
+static struct phy_info phy_info_generic = {
+	.id = 0x0,
+	.name = "Generic PHY",
+	.shift = 32,
+	.config = (const struct phy_cmd[]) {	/* config */
+		/* Configure some basic stuff */
+		{MII_BMCR, BMCR_ANRESTART, NULL},
+		/* Ignore old link state */
+		{MII_BMSR, miim_read, NULL},
+		{miim_end,}
+	},
+	.poll = (const struct phy_cmd[]) {	/* handle_int */
+		{MII_BMSR, miim_read, NULL},
+		{MII_BMSR, miim_read, mii_parse_sr},
+		{miim_end,}
+	},
+};
+
+static struct phy_info *phy_info[] = {
+	&phy_info_generic,
+	&phy_info_cis8201,
+	&phy_info_M88E1011S,
+	&phy_info_dm9161,
+	&phy_info_bcm5222,
+	NULL
+};
+
+/* Use the PHY ID registers to determine what type of PHY is attached
+ * to device dev.  return a struct phy_info structure describing that PHY
+ */
+struct phy_info *mii_phy_get_info(int bus, int id)
+{
+	u16 phy_reg;
+	u32 phy_ID;
+	int i;
+	struct phy_info *theInfo = NULL;
+
+	if (mii_bus[bus] == NULL)
+		return NULL;
+
+	/* Grab the bits from PHYIR1, and put them in the upper half */
+	phy_reg = mii_bus_read(bus, id, MII_PHYSID1);
+	phy_ID = (phy_reg & 0xffff) << 16;
+
+	/* Grab the bits from PHYIR2, and put them in the lower half */
+	phy_reg = mii_bus_read(bus, id, MII_PHYSID2);
+	phy_ID |= (phy_reg & 0xffff);
+
+	/* loop through all the known PHY types, and find one that */
+	/* matches the ID we read from the PHY. */
+	for (i = 0; phy_info[i]; i++)
+		if (phy_info[i]->id == (phy_ID >> phy_info[i]->shift))
+			theInfo = phy_info[i];
+
+	if (theInfo == NULL) {
+		printk("phy %d.%d: PHY id 0x%x is not supported!\n", bus, id, phy_ID);
+		return NULL;
+	} else {
+		printk("phy %d.%d: PHY is %s (%x)\n", bus, id, theInfo->name,
+		       phy_ID);
+	}
+
+	return theInfo;
+}
+
+/* Take a list of struct phy_cmd, and, depending on the values, either */
+/* read or write, using a helper function if provided */
+/* It is assumed that all lists of struct phy_cmd will be terminated by */
+/* mii_end. */
+static void phy_run_commands(int bus, int id, const struct phy_cmd *cmd)
+{
+	int i;
+	u16 result;
+
+	if (!VALID(bus, id)) return;
+
+	if (cmd == NULL)
+		return;
+
+	for (i = 0; cmd->mii_reg != miim_end; i++) {
+		/* The command is a read if mii_data is miim_read */
+		if (cmd->mii_data == miim_read) {
+			/* Read the value of the PHY reg */
+			result = mii_bus_read(bus, id, cmd->mii_reg);
+
+			/* If a function was supplied, we need to let it process */
+			/* the result. */
+			if (cmd->funct != NULL)
+				(*(cmd->funct)) (result, bus, id);
+		} else {	/* Otherwise, it's a write */
+			/* If a function was supplied, it will provide 
+			 * the value to write */
+			/* Otherwise, the value was supplied in cmd->mii_data */
+			if (cmd->funct != NULL)
+				result = (*(cmd->funct)) (0, bus, id);
+			else
+				result = cmd->mii_data;
+
+			/* Write the appropriate value to the PHY reg */
+			mii_bus_write(bus, id, cmd->mii_reg, result);
+		}
+		cmd++;
+	}
+}
+
+static int mdio_read(struct net_device *dev, int phy, int reg)
+{
+	BUS_ID(phy);
+
+	return mii_bus_read(bus, id, reg);
+}
+
+static void mdio_write(struct net_device *dev, int phy, int reg, int val)
+{
+	BUS_ID(phy);
+
+	mii_bus_write(bus, id, reg, val & 0xffff);
+}
+
+static inline void mii_phy_irq_ack(struct mii_if_info *mii)
+{
+	BUS_ID(mii->phy_id);
+
+	if (!VALID(bus, id))
+		return;
+
+	phy_run_commands(bus, id, mii_bus[bus]->phy[id]->ack_int);
+}
+
+static inline void mii_phy_irq_handle(struct mii_if_info *mii)
+{
+	BUS_ID(mii->phy_id);
+
+       	if (!VALID(bus, id))
+		return;
+
+	phy_run_commands(bus, id, mii_bus[bus]->phy[id]->poll);
+	phy_run_commands(bus, id, mii_bus[bus]->phy[id]->end_int);
+}
+
+static irqreturn_t mii_phy_irq(int irq, void *data, struct pt_regs *regs)
+{
+	struct mii_if_info *mii = (void *)data;
+	struct phy_info *phy = mii_phy_of(mii);
+
+	mii_phy_irq_ack(mii);
+
+	/* Schedule the bottom half */
+	schedule_work(&phy->delta.tq);
+
+	return IRQ_HANDLED;
+}
+
+EXPORT_SYMBOL(mii_phy_irq_enable);
+int mii_phy_irq_enable(struct mii_if_info *mii,int irq,void (*func)(void *),void *data)
+{
+	struct phy_info *phy = mii_phy_of(mii);
+	int err;
+	BUS_ID(mii->phy_id);
+
+	if (!VALID(bus, id))
+		return -EINVAL;
+
+	if (phy->delta.data != NULL)
+		return -EBUSY;
+
+	phy->delta.irq = irq;
+	phy->delta.func = func;
+	phy->delta.data = data;
+
+	err = request_irq(irq, mii_phy_irq, SA_SHIRQ, phy->name, mii);
+	if (err < 0) {
+		phy->delta.irq = -1;
+		phy->delta.func = NULL;
+		phy->delta.data = NULL;
+		return err;
+	}
+
+	phy_run_commands(bus, id, mii_bus[bus]->phy[id]->startup);
+	return 0;
+}
+
+EXPORT_SYMBOL(mii_phy_irq_disable);
+void mii_phy_irq_disable(struct mii_if_info *mii,void *data)
+{
+	struct phy_info *phy = mii_phy_of(mii);
+	BUS_ID(mii->phy_id);
+
+	if (!VALID(bus, id))
+		return;
+
+	if (phy->delta.data != data)
+		return;
+
+	phy_run_commands(bus, id, mii_bus[bus]->phy[id]->shutdown);
+
+	free_irq(phy->delta.irq, mii);
+	phy->delta.irq = -1;
+}
+
+/* Scheduled by the task queue */
+static void mii_phy_delta(void *data)
+{
+	struct mii_if_info *mii = (void *)data;
+	struct phy_info *phy = mii_phy_of(mii);
+	int timeout = HZ / 1000 + 1;
+
+	set_current_state(TASK_UNINTERRUPTIBLE);
+	schedule_timeout(timeout);
+
+	if (phy->delta.irq >= 0)
+		mii_phy_irq_handle(mii);
+	else {
+		BUS_ID(mii->phy_id);
+		phy_run_commands(bus, id, mii_bus[bus]->phy[id]->poll);
+	}
+
+	if (phy->delta.func)
+		phy->delta.func(phy->delta.data);
+}
+
+static void mii_phy_poll(unsigned long data)
+{
+	struct mii_if_info *mii = (void *)data;
+	struct phy_info *phy = mii_phy_of(mii);
+
+	BUG_ON(phy == NULL);
+	schedule_work(&phy->delta.tq);
+
+	mod_timer(&phy->delta.timer, jiffies + HZ*phy->delta.msecs/1000);
+}
+
+
+EXPORT_SYMBOL(mii_phy_poll_enable);
+int mii_phy_poll_enable(struct mii_if_info *mii,unsigned long msecs,void (*func)(void *),void *data)
+{
+	struct phy_info *phy = mii_phy_of(mii);
+
+	if (phy == NULL)
+		return -EINVAL;
+
+	if (HZ*msecs/1000 <= 0 || func == NULL)
+		return -EINVAL;
+
+	if (phy->delta.data != NULL)
+		return -EINVAL;
+
+	init_timer(&phy->delta.timer);
+	phy->delta.timer.function = mii_phy_poll;
+	phy->delta.timer.data = (unsigned long)mii;
+	phy->delta.data = data;
+	phy->delta.func = func;
+	phy->delta.msecs = msecs;
+	mod_timer(&phy->delta.timer, jiffies + HZ*msecs/1000);
+	schedule_work(&phy->delta.tq);
+
+	return 0;
+}
+
+EXPORT_SYMBOL(mii_phy_poll_disable);
+void mii_phy_poll_disable(struct mii_if_info *mii,void *data)
+{
+	struct phy_info *phy = mii_phy_of(mii);
+
+	if (phy == NULL || phy->delta.data == NULL)
+		return;
+
+	del_timer_sync(&phy->delta.timer);
+	phy->delta.func = NULL;
+	phy->delta.data = NULL;
+}
+
+EXPORT_SYMBOL(mii_phy_attach);
+int mii_phy_attach(struct mii_if_info *mii, struct net_device *dev, int bus, int id)
+{
+	struct phy_info *phy,*info;
+
+	if (mii_bus[bus] == NULL) {
+		printk(KERN_ERR "mii_phy_attach: Can't attach %s, no MII bus %d present\n",dev->name,bus);
+		return -ENODEV;
+	}
+
+	if (VALID(bus,id)) {
+		printk(KERN_ERR "mii_phy_attach: PHY %d.%d is already attached to %s\n",bus,id,dev->name);
+		return -EBUSY;
+	}
+
+	info = mii_phy_get_info(bus, id);
+	if (info == NULL)
+		return -ENODEV;
+
+	phy = kmalloc(sizeof(*phy), GFP_KERNEL);
+	memcpy(phy,info,sizeof(*phy));
+
+	INIT_WORK(&phy->delta.tq, mii_phy_delta, mii);
+	phy->delta.data = NULL;
+	phy->delta.irq = -1;
+	phy->state.link=0;
+	phy->state.duplex=0;
+	phy->state.auto_neg=1;
+	phy->state.speed=0;
+
+	memset(mii,0,sizeof(*mii));
+	mii->phy_id = (bus << 5) | id;
+	mii->phy_id_mask = 0xff;
+	mii->reg_num_mask = 0x1f;
+	mii->dev = dev;
+	mii->mdio_read = mdio_read;
+	mii->mdio_write = mdio_write;
+
+	mii_bus[bus]->phy[id] = phy;
+
+	phy_run_commands(bus, id, phy->startup);
+
+	return 0;
+}
+
+EXPORT_SYMBOL(mii_phy_detach);
+void mii_phy_detach(struct mii_if_info *mii)
+{
+	struct phy_info *phy;
+	struct mii_bus *pbus;
+	BUS_ID(mii->phy_id);
+
+	if (!VALID(bus, id))
+		return;
+
+	pbus = mii_bus[bus];
+	phy = pbus->phy[id];
+
+	if (phy->delta.data != NULL) {
+		if (phy->delta.irq < 0)
+			mii_phy_poll_disable(mii, phy->delta.data);
+		else
+			mii_phy_irq_disable(mii, phy->delta.data);
+	}
+
+	phy_run_commands(bus, id, phy->shutdown);
+	pbus->phy[id]=NULL;
+	kfree(phy);
+}
+
+EXPORT_SYMBOL(mii_phy_state);
+int mii_phy_state(struct mii_if_info *mii, struct phy_state *state)
+{
+	struct phy_info *phy = mii_phy_of(mii);
+	BUS_ID(mii->phy_id);
+
+	if (!VALID(bus, id))
+		return -ENODEV;
+
+	if (phy->delta.data == NULL)
+		phy_run_commands(bus, id, phy->poll);
+
+	memcpy(state,&phy->state,sizeof(*state));
+
+	return 0;
+}
+
+
+static DECLARE_MUTEX(mii_bus_lock);
+
+EXPORT_SYMBOL(mii_bus_register);
+int mii_bus_register(struct mii_bus *bus)
+{
+	int bus_id;
+
+	if (bus == NULL || bus->name == NULL || bus->read == NULL ||
+	    bus->write == NULL)
+		return -EINVAL;
+
+	down(&mii_bus_lock);
+
+	for (bus_id = 0; bus_id < MII_BUS_MAX; bus_id++) {
+		if (mii_bus[bus_id] == NULL)
+			break;
+	}
+
+	if (bus_id >= MII_BUS_MAX) {
+		up(&mii_bus_lock);
+		return -ENOMEM;
+	}
+
+	mii_bus[bus_id] = bus;
+
+	if (mii_bus[bus_id] == NULL) {
+		up(&mii_bus_lock);
+		return -EINVAL;
+	}
+
+	if (mii_bus[bus_id]->reset)
+		mii_bus[bus_id]->reset(mii_bus[bus_id]->priv);
+
+	up(&mii_bus_lock);
+
+	printk(KERN_INFO "%s: registered as PHY bus %d\n",bus->name,bus_id);
+	
+	return bus_id;
+}
+
+EXPORT_SYMBOL(mii_bus_unregister);
+void mii_bus_unregister(struct mii_bus *bus)
+{
+	int i;
+
+	down(&mii_bus_lock);
+
+	for (i=0; i < MII_BUS_MAX; i++)
+		if (mii_bus[i] == bus)
+			mii_bus[i] = NULL;
+
+	up(&mii_bus_lock);
+}	

--- /dev/null
+++ linux/include/linux/mii_bus.h
@@ -0,0 +1,132 @@
+/* 
+ * include/linux/mii_bus.h
+ *
+ * Author: Jason McMullan
+ *
+ * Copyright (c) 2004 Timesys Corp.
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ *
+ */
+#ifndef __MII_BUS_H
+#define __MII_BUS_H
+
+#include <linux/mii.h>
+
+#define MII_TIMEOUT 	(1*HZ)
+
+#define miim_end ((u32)-2)
+#define miim_read ((u32)-1)
+
+/* Cicada Auxiliary Control/Status Register */
+#define MIIM_CIS8201_AUX_CONSTAT        0x1c
+#define MIIM_CIS8201_AUXCONSTAT_INIT    0x0004
+#define MIIM_CIS8201_AUXCONSTAT_DUPLEX  0x0020
+#define MIIM_CIS8201_AUXCONSTAT_SPEED   0x0018
+#define MIIM_CIS8201_AUXCONSTAT_GBIT    0x0010
+#define MIIM_CIS8201_AUXCONSTAT_100     0x0008
+                                                                                
+/* Cicada Extended Control Register 1 */
+#define MIIM_CIS8201_EXT_CON1           0x17
+#define MIIM_CIS8201_EXTCON1_INIT       0x0000
+
+/* 88E1011 PHY Status Register */
+#define MIIM_88E1011_PHY_STATUS         0x11
+#define MIIM_88E1011_PHYSTAT_SPEED      0xc000
+#define MIIM_88E1011_PHYSTAT_GBIT       0x8000
+#define MIIM_88E1011_PHYSTAT_100        0x4000
+#define MIIM_88E1011_PHYSTAT_DUPLEX     0x2000
+#define MIIM_88E1011_PHYSTAT_LINK	0x0400
+
+#define MIIM_88E1011_IEVENT		0x13
+#define MIIM_88E1011_IEVENT_CLEAR	0x0000
+
+#define MIIM_88E1011_IMASK		0x12
+#define MIIM_88E1011_IMASK_INIT		0x6400
+#define MIIM_88E1011_IMASK_CLEAR	0x0000
+
+/* DM9161 Control register values */
+#define MIIM_DM9161_CR_STOP	0x0400
+#define MIIM_DM9161_CR_RSTAN	0x1200
+
+#define MIIM_DM9161_SCR		0x10
+#define MIIM_DM9161_SCR_INIT	0x0610
+
+/* DM9161 Specified Configuration and Status Register */
+#define MIIM_DM9161_SCSR	0x11
+#define MIIM_DM9161_SCSR_100F	0x8000
+#define MIIM_DM9161_SCSR_100H	0x4000
+#define MIIM_DM9161_SCSR_10F	0x2000
+#define MIIM_DM9161_SCSR_10H	0x1000
+
+/* DM9161 Interrupt Register */
+#define MIIM_DM9161_INTR	0x15
+#define MIIM_DM9161_INTR_PEND		0x8000
+#define MIIM_DM9161_INTR_DPLX_MASK	0x0800
+#define MIIM_DM9161_INTR_SPD_MASK	0x0400
+#define MIIM_DM9161_INTR_LINK_MASK	0x0200
+#define MIIM_DM9161_INTR_MASK		0x0100
+#define MIIM_DM9161_INTR_DPLX_CHANGE	0x0010
+#define MIIM_DM9161_INTR_SPD_CHANGE	0x0008
+#define MIIM_DM9161_INTR_LINK_CHANGE	0x0004
+#define MIIM_DM9161_INTR_INIT 		0x0000
+#define MIIM_DM9161_INTR_STOP	\
+(MIIM_DM9161_INTR_DPLX_MASK | MIIM_DM9161_INTR_SPD_MASK \
+ | MIIM_DM9161_INTR_LINK_MASK | MIIM_DM9161_INTR_MASK)
+
+/* DM9161 10BT Configuration/Status */
+#define MIIM_DM9161_10BTCSR	0x12
+#define MIIM_DM9161_10BTCSR_INIT	0x7800
+
+struct phy_state {
+	unsigned int link:1;
+	unsigned int duplex:1;
+	unsigned int auto_neg:1;
+	unsigned int speed:29;
+};
+
+struct mii_bus {
+	const char *name;
+	void *priv;
+	int (*read)(void *priv, int phy_id, int location);
+	int (*write)(void *priv, int phy_id, int location, uint16_t val);
+	void (*reset)(void *priv);
+
+	/* Auto-filled in values */
+	struct phy_info *phy[32];
+};
+
+/* MII bus registration
+ */
+extern int mii_bus_register(struct mii_bus *bus);
+extern void mii_bus_unregister(struct mii_bus *bus);
+
+/* Raw read/write routines
+ * Returns a 16-bit register value, or < 0 error code
+ */
+extern int mii_bus_read(int bus_id, int phy_id, int reg);
+extern int mii_bus_write(int bus_id, int phy_id, int reg, uint16_t val);
+
+/* Routines used by network devices that use the MII bus
+ */
+extern int mii_phy_attach(struct mii_if_info *mii, struct net_device *dev, int phy_bus, int phy_id);
+extern void mii_phy_detach(struct mii_if_info *mii);
+
+/* Read current phy state
+ */
+extern int mii_phy_state(struct mii_if_info *mii, struct phy_state *state);
+
+/* Use an IRQ to determine when the PHY changes
+ */
+extern int mii_phy_irq_enable(struct mii_if_info *mii,int irq,void (*func)(void *),void *data);
+extern void mii_phy_irq_disable(struct mii_if_info *mii,void *data);
+
+/* Poll the PHY
+ */
+extern int mii_phy_poll_enable(struct mii_if_info *mii, unsigned long msecs, void (*func)(void *),void *data);
+extern void mii_phy_poll_disable(struct mii_if_info *mii,void *data);
+
+#endif /* __MII_BUS_H */


-- 
Jason McMullan <jason.mcmullan@timesys.com>
TimeSys Corporation

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH] MII bus API for PHY devices
  2004-11-11 19:45 Jason McMullan
@ 2004-11-11 21:31 ` Francois Romieu
  0 siblings, 0 replies; 19+ messages in thread
From: Francois Romieu @ 2004-11-11 21:31 UTC (permalink / raw)
  To: Jason McMullan; +Cc: linux-kernel, jgarzik

Jason McMullan <jason.mcmullan@timesys.com> :
> First patch for consolidation of PHY handling into one location.

A little pass of polish could make it more cool imho:
- macro abuse;
- unchecked malloc;
- use plain old style multi-lines C comments (/* ...\n * ... \n * ... \n */) ?
- whitespace/tabulation damage (search for series of 2 or more spaces);
- hidden return: please put them on a separate line;
- no need to BUG_ON when there is an immediate dereference;
- mixed case variables/names (pLEAse DO not dO tHAT);
- unneeded checks ? How could VALID_BUS() in mii_bus_write() fail ?
- use goto when locking primitives are used (btw the last check in
  mii_bus_register is not needed and you can s/mii_bus[bus_id]/bus/ 
  a few times in this function) ?
- add a break in mii_bus_unregister() ?
- I'd probably favor ternary operators here and there (your choice, really)
- u32 and uint16_t in the same file: choose one style ?

--
Ueimor

^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH] MII bus API for PHY devices
@ 2004-11-11 19:45 Jason McMullan
  2004-11-11 21:31 ` Francois Romieu
  0 siblings, 1 reply; 19+ messages in thread
From: Jason McMullan @ 2004-11-11 19:45 UTC (permalink / raw)
  To: linux-kernel; +Cc: jgarzik

First patch for consolidation of PHY handling into one location.

Signed-off-by: Jason McMullan <jason.mcmullan@timesys.com>
Date:        Thu, 11 Nov 2004 14:12:46 -0500
Depends:     linux-2.6.9
Description: This add a new API, in include/linux/mii_bus.h,
	that allows common PHY handing code, and wrappers
	for IRQ and polling based PHY status. It's based
	off of the sungem.c interface.

###############################

Index of changes:

 drivers/net/Makefile          |    2 
 linux/drivers/net/mii_bus.c   |  857 ++++++++++++++++++++++++++++++++++++++++++
 linux/include/linux/mii_bus.h |  132 ++++++
 3 files changed, 990 insertions(+), 1 deletion(-)


--- linux-orig/drivers/net/Makefile
+++ linux/drivers/net/Makefile
@@ -62,7 +62,7 @@
 # end link order section
 #
 
-obj-$(CONFIG_MII) += mii.o
+obj-$(CONFIG_MII) += mii.o mii_bus.o
 
 obj-$(CONFIG_SUNDANCE) += sundance.o
 obj-$(CONFIG_HAMACHI) += hamachi.o
--- /dev/null
+++ linux/drivers/net/mii_bus.c
@@ -0,0 +1,857 @@
+/* 
+ * drivers/net/mii_bus.c
+ *
+ * Adapeted from drivers/net/gianfar_mii.c, by Andy Fleming
+ *
+ * Author: Jason McMullan (jason.mcmullan@timesys.com) to 
+ * 	   be a generic mii interface
+ *
+ * Copyright (c) 2004 Timesys Inc
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ *
+ */
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/spinlock.h>
+#include <linux/mm.h>
+#include <linux/mii_bus.h>
+
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/uaccess.h>
+#include <linux/module.h>
+
+/*
+ * struct phy_cmd:  A command for reading or writing a PHY register
+ *
+ * mii_reg:  The register to read or write
+ *
+ * mii_data:  For writes, the value to put in the register.
+ * 	A value of -1 indicates this is a read.
+ *
+ * funct: A function pointer which is invoked for each command.
+ * 	For reads, this function will be passed the value read
+ *	from the PHY, and process it.
+ *	For writes, the result of this function will be written
+ *	to the PHY register
+ */
+struct phy_cmd {
+    u32 mii_reg;
+    u32 mii_data;
+    u16 (*funct) (u16 mii_reg, int bus, int id);
+};
+
+/* struct phy_info: a structure which defines attributes for a PHY
+ *
+ * id will contain a number which represents the PHY.  During
+ * startup, the driver will poll the PHY to find out what its
+ * UID--as defined by registers 2 and 3--is.  The 32-bit result
+ * gotten from the PHY will be shifted right by "shift" bits to
+ * discard any bits which may change based on revision numbers
+ * unimportant to functionality
+ *
+ * The struct phy_cmd entries represent pointers to an arrays of
+ * commands which tell the driver what to do to the PHY.
+ */
+struct phy_info {
+    u32 id;
+    const char *name;
+    unsigned int shift;
+    /* Called to configure the PHY, and modify the controller
+     * based on the results */
+    const struct phy_cmd *config;
+
+    /* Called when starting up the controller.  Usually sets
+     * up the interrupt for state changes */
+    const struct phy_cmd *startup;
+
+    /* Called inside the interrupt handler to acknowledge
+     * the interrupt */
+    const struct phy_cmd *ack_int;
+
+    /* Called in the bottom half to get the state */
+    const struct phy_cmd *poll;
+
+    /* Called to re-enable interrupts */
+    const struct phy_cmd *end_int;
+
+    /* Called when bringing down the controller.  Usually stops
+     * the interrupts from being generated */
+    const struct phy_cmd *shutdown;
+
+    /* Local state information */
+    struct {
+	int irq;
+	unsigned long msecs;
+	void (*func)(void *data);
+	void *data;
+    	struct work_struct tq;
+	struct timer_list timer;
+    } delta;
+
+    struct phy_state state;
+};
+
+static struct mii_bus *mii_bus[4];
+
+#define MII_BUS_MAX	(sizeof(mii_bus)/sizeof(struct mii_bus *))
+
+#define VALID_BUS(bus) (mii_bus[bus]!=NULL)
+#define VALID(bus,x) (mii_bus[bus]!=NULL && (mii_bus[bus]->phy[id] != NULL))
+#define PHY_ID(phy_id) (phy_id & 0x1f)
+#define PHY_BUS(phy_id) ((phy_id & 0x20) ? 1 : 0)
+#define MII_ID(mii) PHY_ID(mii->phy_id)
+#define MII_BUS(mii) PHY_BUS(mii->phy_id)
+#define BUS_ID(phy_id) int bus = PHY_BUS(phy_id); int id = PHY_ID(phy_id);
+
+static inline struct phy_info *mii_phy_of(struct mii_if_info *mii)
+{
+	if (mii != NULL) {
+		BUS_ID(mii->phy_id);
+		return mii_bus[bus]->phy[id];
+	}
+
+	return NULL;
+}
+
+/* Write value to the PHY for this device to the register at regnum, */
+/* waiting until the write is done before it returns.  All PHY */
+/* configuration has to be done through the TSEC1 MIIM regs */
+EXPORT_SYMBOL(mii_bus_write);
+int mii_bus_write(int bus, int id, int regnum, uint16_t value)
+{
+	if (!VALID_BUS(bus))
+		return -EINVAL;
+
+       	mii_bus[bus]->write(mii_bus[bus]->priv,id,regnum,value);
+	return 0;
+}
+
+/* Reads from register regnum in the PHY for device dev, */
+/* returning the value.  Clears miimcom first.  All PHY */
+/* configuration has to be done through the TSEC1 MIIM regs */
+EXPORT_SYMBOL(mii_bus_read);
+int mii_bus_read(int bus, int id, int regnum)
+{
+	if (!VALID_BUS(bus))
+		return -EINVAL;
+
+       	return mii_bus[bus]->read(mii_bus[bus]->priv,id,regnum);
+}
+
+/* returns which value to write to the control register. */
+/* For 10/100 the value is slightly different. */
+static u16 mii_cr_init(u16 mii_reg, int bus, int id)
+{
+		return BMCR_ANRESTART;
+}
+
+#define BRIEF_MII_ERRORS
+/* Wait for auto-negotiation to complete */
+u16 mii_parse_sr(u16 mii_reg, int bus, int id)
+{
+	unsigned int timeout = MII_TIMEOUT;
+	struct phy_state *pstate;
+
+	if (!VALID(bus, id)) return 0xffff;
+	pstate = &mii_bus[bus]->phy[id]->state;
+
+	if (mii_reg & BMSR_LSTATUS) {
+		pstate->link = 1;
+	} else {
+		pstate->link = 0;
+		pstate->auto_neg = 1;
+	}
+
+	/* Only auto-negotiate if the link has just gone up */
+	if (pstate->link && pstate->auto_neg) {
+		while ((!(mii_reg & BMSR_ANEGCOMPLETE)) && timeout--)
+			mii_reg = mii_bus_read(bus, id, MII_BMSR);
+
+#if defined(BRIEF_MII_ERRORS)
+		if (mii_reg & BMSR_ANEGCOMPLETE)
+			printk(KERN_INFO "phy %d.%d: Auto-negotiation done\n",
+			       bus,id);
+		else
+			printk(KERN_INFO "phy %d.%d: Auto-negotiation timed out\n",
+			       bus,id);
+#endif
+		pstate->auto_neg = 0;
+
+		if (mii_reg & BMSR_ANEGCOMPLETE) {
+			mii_reg = mii_bus_read(bus, id, MII_LPA);
+			mii_reg &= mii_bus_read(bus, id, MII_ADVERTISE);
+			/* Get the speed */
+			if (mii_reg & (LPA_100FULL | LPA_100HALF))
+				pstate->speed = 100;
+			else if (mii_reg & (LPA_10FULL | LPA_10HALF))
+				pstate->speed = 10;
+
+			/* Get the duplex */
+			if (mii_reg & (LPA_100FULL | LPA_10FULL))
+				pstate->duplex = 1;
+			else if (mii_reg & (LPA_100HALF | LPA_10HALF))
+				pstate->duplex = 0;
+		}
+	}
+
+	return 0;
+}
+
+/* Determine the speed and duplex which was negotiated */
+u16 mii_parse_88E1011_psr(u16 mii_reg, int bus, int id)
+{
+	unsigned int speed;
+	struct phy_state *pstate;
+
+	if (!VALID(bus, id)) return 0xffff;
+	pstate = &mii_bus[bus]->phy[id]->state;
+
+	if (pstate->link) {
+		if (mii_reg & MIIM_88E1011_PHYSTAT_DUPLEX)
+			pstate->duplex = 1;
+		else
+			pstate->duplex = 0;
+
+		speed = (mii_reg & MIIM_88E1011_PHYSTAT_SPEED);
+
+		switch (speed) {
+		case MIIM_88E1011_PHYSTAT_GBIT:
+			pstate->speed = 1000;
+			break;
+		case MIIM_88E1011_PHYSTAT_100:
+			pstate->speed = 100;
+			break;
+		default:
+			pstate->speed = 10;
+			break;
+		}
+	} else {
+		pstate->speed = 0;
+		pstate->duplex = 0;
+	}
+
+	return 0;
+}
+
+u16 mii_parse_cis8201(u16 mii_reg, int bus, int id)
+{
+	unsigned int speed;
+	struct phy_state *pstate;
+
+	if (!VALID(bus, id)) return 0xffff;
+	pstate = &mii_bus[bus]->phy[id]->state;
+
+	if (pstate->link) {
+		if (mii_reg & MIIM_CIS8201_AUXCONSTAT_DUPLEX)
+			pstate->duplex = 1;
+		else
+			pstate->duplex = 0;
+
+		speed = mii_reg & MIIM_CIS8201_AUXCONSTAT_SPEED;
+
+		switch (speed) {
+		case MIIM_CIS8201_AUXCONSTAT_GBIT:
+			pstate->speed = 1000;
+			break;
+		case MIIM_CIS8201_AUXCONSTAT_100:
+			pstate->speed = 100;
+			break;
+		default:
+			pstate->speed = 10;
+			break;
+		}
+	} else {
+		pstate->speed = 0;
+		pstate->duplex = 0;
+	}
+
+	return 0;
+}
+
+u16 mii_parse_dm9161_scsr(u16 mii_reg, int bus, int id)
+{
+	struct phy_state *pstate;
+
+	if (!VALID(bus, id)) return 0xffff;
+	pstate = &mii_bus[bus]->phy[id]->state;
+
+	if (mii_reg & (MIIM_DM9161_SCSR_100F | MIIM_DM9161_SCSR_100H))
+		pstate->speed = 100;
+	else
+		pstate->speed = 10;
+
+	if (mii_reg & (MIIM_DM9161_SCSR_100F | MIIM_DM9161_SCSR_10F))
+		pstate->duplex = 1;
+	else
+		pstate->duplex = 0;
+
+	return 0;
+}
+
+u16 dm9161_wait(u16 mii_reg, int bus, int id)
+{
+	int timeout = 3*HZ;
+
+	if (!VALID(bus,id)) return 0xffff;
+
+	/* Davicom takes a bit to come up after a reset,
+	 * so wait here for a bit */
+	set_current_state(TASK_UNINTERRUPTIBLE);
+	schedule_timeout(timeout);
+
+	return 0;
+}
+
+static struct phy_info phy_info_M88E1011S = {
+	.id = 0x01410c6,
+	.name = "Marvell 88E1011S",
+	.shift = 4,
+	.config = (const struct phy_cmd[]) {	/* config */
+		/* Reset and configure the PHY */
+		{MII_BMCR, BMCR_ANRESTART, mii_cr_init},
+		{miim_end,}
+	},
+	.startup = (const struct phy_cmd[]) {	/* startup */
+		/* Clear the IEVENT register */
+		{MIIM_88E1011_IEVENT, miim_read, NULL},
+		/* Set up the mask */
+		{MIIM_88E1011_IMASK, MIIM_88E1011_IMASK_INIT, NULL},
+		{miim_end,}
+	},
+	.ack_int = (const struct phy_cmd[]) {	/* ack_int */
+		/* Clear the interrupt */
+		{MIIM_88E1011_IEVENT, miim_read, NULL},
+		/* Disable interrupts */
+		{MIIM_88E1011_IMASK, MIIM_88E1011_IMASK_CLEAR, NULL},
+		{miim_end,}
+	},
+	.poll = (const struct phy_cmd[]) {	/* poll */
+		/* Read the Status (2x to make sure link is right) */
+		{MII_BMSR, miim_read, NULL},
+		/* Check the status */
+		{MII_BMSR, miim_read, mii_parse_sr},
+		{MIIM_88E1011_PHY_STATUS, miim_read, mii_parse_88E1011_psr},
+		{miim_end,}
+	},
+	.end_int = (const struct phy_cmd[]) {	/* end_int */
+			/* Enable Interrupts */
+		{MIIM_88E1011_IMASK, MIIM_88E1011_IMASK_INIT, NULL},
+		{miim_end,}
+	},
+	.shutdown = (const struct phy_cmd[]) {	/* shutdown */
+		{MIIM_88E1011_IEVENT, miim_read, NULL},
+		{MIIM_88E1011_IMASK, MIIM_88E1011_IMASK_CLEAR, NULL},
+		{miim_end,}
+	},
+};
+
+/* Cicada 8201 */
+static struct phy_info phy_info_cis8201 = {
+	.id = 0xfc41,
+	.name = "CIS8201",
+	.shift = 4,
+	.config = (const struct phy_cmd[]) {	/* config */
+		/* Override PHY config settings */
+		{MIIM_CIS8201_AUX_CONSTAT, MIIM_CIS8201_AUXCONSTAT_INIT, NULL},
+		/* Set up the interface mode */
+		{MIIM_CIS8201_EXT_CON1, MIIM_CIS8201_EXTCON1_INIT, NULL},
+		/* Configure some basic stuff */
+		{MII_BMCR, BMCR_ANRESTART, mii_cr_init},
+		{miim_end,}
+	},
+	.poll = (const struct phy_cmd[]) {	/* poll */
+		/* Read the Status (2x to make sure link is right) */
+		{MII_BMSR, miim_read, NULL},
+		/* Auto-negotiate */
+		{MII_BMSR, miim_read, mii_parse_sr},
+		/* Read the status */
+		{MIIM_CIS8201_AUX_CONSTAT, miim_read, mii_parse_cis8201},
+		{miim_end,}
+	},
+};
+
+static struct phy_info phy_info_dm9161 = {
+	.id = 0x0181b88,
+	.name = "Davicom DM9161E",
+	.shift = 4,
+	.config = (const struct phy_cmd[]) {	/* config */
+		{MII_BMCR, MIIM_DM9161_CR_STOP, NULL},
+		/* Do not bypass the scrambler/descrambler */
+		{MIIM_DM9161_SCR, MIIM_DM9161_SCR_INIT, NULL},
+		/* Clear 10BTCSR to default */
+		{MIIM_DM9161_10BTCSR, MIIM_DM9161_10BTCSR_INIT, NULL},
+		/* Configure some basic stuff */
+		{MII_BMCR, BMCR_ANRESTART, NULL},
+		/* Restart Auto Negotiation */
+		{MII_BMCR, MIIM_DM9161_CR_RSTAN, NULL},
+		{miim_end,}
+	},
+	.startup = (const struct phy_cmd[]) {	/* startup */
+		/* Status is read once to clear old link state */
+		{MII_BMSR, miim_read, dm9161_wait},
+		/* Clear any pending interrupts */
+		{MIIM_DM9161_INTR, miim_read, NULL},
+		{miim_end,}
+	},
+	.ack_int = (const struct phy_cmd[]) {	/* ack_int */
+		{MIIM_DM9161_INTR, miim_read, NULL},
+		{miim_end,}
+	},
+	.poll = (const struct phy_cmd[]) {	/* poll */
+		{MII_BMSR, miim_read, NULL},
+		{MII_BMSR, miim_read, mii_parse_sr},
+		{MIIM_DM9161_SCSR, miim_read, mii_parse_dm9161_scsr},
+		{miim_end,}
+	},
+	.shutdown = (const struct phy_cmd[]) {	/* shutdown */
+		{MIIM_DM9161_INTR, miim_read, NULL},
+		{miim_end,}
+	},
+};
+
+static struct phy_info phy_info_bcm5222 = {
+	.id = 0x0040632,
+	.name = "Broadcom BCM5222",
+	.shift = 4,
+	.config = (const struct phy_cmd[]) {	/* config */
+		/* Configure some basic stuff */
+		{MII_BMCR, BMCR_ANRESTART, NULL},
+		/* Status is read once to clear old link state */
+		{MII_BMSR, miim_read, NULL},
+		{miim_end,}
+	},
+	.poll = (const struct phy_cmd[]) {	/* poll */
+		{MII_BMSR, miim_read, NULL},
+		{MII_BMSR, miim_read, mii_parse_sr},
+		{miim_end,}
+	},
+};
+
+static struct phy_info phy_info_generic = {
+	.id = 0x0,
+	.name = "Generic PHY",
+	.shift = 32,
+	.config = (const struct phy_cmd[]) {	/* config */
+		/* Configure some basic stuff */
+		{MII_BMCR, BMCR_ANRESTART, NULL},
+		/* Ignore old link state */
+		{MII_BMSR, miim_read, NULL},
+		{miim_end,}
+	},
+	.poll = (const struct phy_cmd[]) {	/* handle_int */
+		{MII_BMSR, miim_read, NULL},
+		{MII_BMSR, miim_read, mii_parse_sr},
+		{miim_end,}
+	},
+};
+
+static struct phy_info *phy_info[] = {
+	&phy_info_generic,
+	&phy_info_cis8201,
+	&phy_info_M88E1011S,
+	&phy_info_dm9161,
+	&phy_info_bcm5222,
+	NULL
+};
+
+/* Use the PHY ID registers to determine what type of PHY is attached
+ * to device dev.  return a struct phy_info structure describing that PHY
+ */
+struct phy_info *mii_phy_get_info(int bus, int id)
+{
+	u16 phy_reg;
+	u32 phy_ID;
+	int i;
+	struct phy_info *theInfo = NULL;
+
+	if (mii_bus[bus] == NULL)
+		return NULL;
+
+	/* Grab the bits from PHYIR1, and put them in the upper half */
+	phy_reg = mii_bus_read(bus, id, MII_PHYSID1);
+	phy_ID = (phy_reg & 0xffff) << 16;
+
+	/* Grab the bits from PHYIR2, and put them in the lower half */
+	phy_reg = mii_bus_read(bus, id, MII_PHYSID2);
+	phy_ID |= (phy_reg & 0xffff);
+
+	/* loop through all the known PHY types, and find one that */
+	/* matches the ID we read from the PHY. */
+	for (i = 0; phy_info[i]; i++)
+		if (phy_info[i]->id == (phy_ID >> phy_info[i]->shift))
+			theInfo = phy_info[i];
+
+	if (theInfo == NULL) {
+		printk("phy %d.%d: PHY id 0x%x is not supported!\n", bus, id, phy_ID);
+		return NULL;
+	} else {
+		printk("phy %d.%d: PHY is %s (%x)\n", bus, id, theInfo->name,
+		       phy_ID);
+	}
+
+	return theInfo;
+}
+
+/* Take a list of struct phy_cmd, and, depending on the values, either */
+/* read or write, using a helper function if provided */
+/* It is assumed that all lists of struct phy_cmd will be terminated by */
+/* mii_end. */
+static void phy_run_commands(int bus, int id, const struct phy_cmd *cmd)
+{
+	int i;
+	u16 result;
+
+	if (!VALID(bus, id)) return;
+
+	if (cmd == NULL)
+		return;
+
+	for (i = 0; cmd->mii_reg != miim_end; i++) {
+		/* The command is a read if mii_data is miim_read */
+		if (cmd->mii_data == miim_read) {
+			/* Read the value of the PHY reg */
+			result = mii_bus_read(bus, id, cmd->mii_reg);
+
+			/* If a function was supplied, we need to let it process */
+			/* the result. */
+			if (cmd->funct != NULL)
+				(*(cmd->funct)) (result, bus, id);
+		} else {	/* Otherwise, it's a write */
+			/* If a function was supplied, it will provide 
+			 * the value to write */
+			/* Otherwise, the value was supplied in cmd->mii_data */
+			if (cmd->funct != NULL)
+				result = (*(cmd->funct)) (0, bus, id);
+			else
+				result = cmd->mii_data;
+
+			/* Write the appropriate value to the PHY reg */
+			mii_bus_write(bus, id, cmd->mii_reg, result);
+		}
+		cmd++;
+	}
+}
+
+static int mdio_read(struct net_device *dev, int phy, int reg)
+{
+	BUS_ID(phy);
+
+	return mii_bus_read(bus, id, reg);
+}
+
+static void mdio_write(struct net_device *dev, int phy, int reg, int val)
+{
+	BUS_ID(phy);
+
+	mii_bus_write(bus, id, reg, val & 0xffff);
+}
+
+static inline void mii_phy_irq_ack(struct mii_if_info *mii)
+{
+	BUS_ID(mii->phy_id);
+
+	if (!VALID(bus, id))
+		return;
+
+	phy_run_commands(bus, id, mii_bus[bus]->phy[id]->ack_int);
+}
+
+static inline void mii_phy_irq_handle(struct mii_if_info *mii)
+{
+	BUS_ID(mii->phy_id);
+
+       	if (!VALID(bus, id))
+		return;
+
+	phy_run_commands(bus, id, mii_bus[bus]->phy[id]->poll);
+	phy_run_commands(bus, id, mii_bus[bus]->phy[id]->end_int);
+}
+
+static irqreturn_t mii_phy_irq(int irq, void *data, struct pt_regs *regs)
+{
+	struct mii_if_info *mii = (void *)data;
+	struct phy_info *phy = mii_phy_of(mii);
+
+	mii_phy_irq_ack(mii);
+
+	/* Schedule the bottom half */
+	schedule_work(&phy->delta.tq);
+
+	return IRQ_HANDLED;
+}
+
+EXPORT_SYMBOL(mii_phy_irq_enable);
+int mii_phy_irq_enable(struct mii_if_info *mii,int irq,void (*func)(void *),void *data)
+{
+	struct phy_info *phy = mii_phy_of(mii);
+	int err;
+	BUS_ID(mii->phy_id);
+
+	if (!VALID(bus, id))
+		return -EINVAL;
+
+	if (phy->delta.data != NULL)
+		return -EBUSY;
+
+	phy->delta.irq = irq;
+	phy->delta.func = func;
+	phy->delta.data = data;
+
+	err = request_irq(irq, mii_phy_irq, SA_SHIRQ, phy->name, mii);
+	if (err < 0) {
+		phy->delta.irq = -1;
+		phy->delta.func = NULL;
+		phy->delta.data = NULL;
+		return err;
+	}
+
+	phy_run_commands(bus, id, mii_bus[bus]->phy[id]->startup);
+	return 0;
+}
+
+EXPORT_SYMBOL(mii_phy_irq_disable);
+void mii_phy_irq_disable(struct mii_if_info *mii,void *data)
+{
+	struct phy_info *phy = mii_phy_of(mii);
+	BUS_ID(mii->phy_id);
+
+	if (!VALID(bus, id))
+		return;
+
+	if (phy->delta.data != data)
+		return;
+
+	phy_run_commands(bus, id, mii_bus[bus]->phy[id]->shutdown);
+
+	free_irq(phy->delta.irq, mii);
+	phy->delta.irq = -1;
+}
+
+/* Scheduled by the task queue */
+static void mii_phy_delta(void *data)
+{
+	struct mii_if_info *mii = (void *)data;
+	struct phy_info *phy = mii_phy_of(mii);
+	int timeout = HZ / 1000 + 1;
+
+	set_current_state(TASK_UNINTERRUPTIBLE);
+	schedule_timeout(timeout);
+
+	if (phy->delta.irq >= 0)
+		mii_phy_irq_handle(mii);
+	else {
+		BUS_ID(mii->phy_id);
+		phy_run_commands(bus, id, mii_bus[bus]->phy[id]->poll);
+	}
+
+	if (phy->delta.func)
+		phy->delta.func(phy->delta.data);
+}
+
+static void mii_phy_poll(unsigned long data)
+{
+	struct mii_if_info *mii = (void *)data;
+	struct phy_info *phy = mii_phy_of(mii);
+
+	BUG_ON(phy == NULL);
+	schedule_work(&phy->delta.tq);
+
+	mod_timer(&phy->delta.timer, jiffies + HZ*phy->delta.msecs/1000);
+}
+
+
+EXPORT_SYMBOL(mii_phy_poll_enable);
+int mii_phy_poll_enable(struct mii_if_info *mii,unsigned long msecs,void (*func)(void *),void *data)
+{
+	struct phy_info *phy = mii_phy_of(mii);
+
+	if (phy == NULL)
+		return -EINVAL;
+
+	if (HZ*msecs/1000 <= 0 || func == NULL)
+		return -EINVAL;
+
+	if (phy->delta.data != NULL)
+		return -EINVAL;
+
+	init_timer(&phy->delta.timer);
+	phy->delta.timer.function = mii_phy_poll;
+	phy->delta.timer.data = (unsigned long)mii;
+	phy->delta.data = data;
+	phy->delta.func = func;
+	phy->delta.msecs = msecs;
+	mod_timer(&phy->delta.timer, jiffies + HZ*msecs/1000);
+	schedule_work(&phy->delta.tq);
+
+	return 0;
+}
+
+EXPORT_SYMBOL(mii_phy_poll_disable);
+void mii_phy_poll_disable(struct mii_if_info *mii,void *data)
+{
+	struct phy_info *phy = mii_phy_of(mii);
+
+	if (phy == NULL || phy->delta.data == NULL)
+		return;
+
+	del_timer_sync(&phy->delta.timer);
+	phy->delta.func = NULL;
+	phy->delta.data = NULL;
+}
+
+EXPORT_SYMBOL(mii_phy_attach);
+int mii_phy_attach(struct mii_if_info *mii, struct net_device *dev, int bus, int id)
+{
+	struct phy_info *phy,*info;
+
+	if (mii_bus[bus] == NULL) {
+		printk(KERN_ERR "mii_phy_attach: Can't attach %s, no MII bus %d present\n",dev->name,bus);
+		return -ENODEV;
+	}
+
+	if (VALID(bus,id)) {
+		printk(KERN_ERR "mii_phy_attach: PHY %d.%d is already attached to %s\n",bus,id,dev->name);
+		return -EBUSY;
+	}
+
+	info = mii_phy_get_info(bus, id);
+	if (info == NULL)
+		return -ENODEV;
+
+	phy = kmalloc(sizeof(*phy), GFP_KERNEL);
+	memcpy(phy,info,sizeof(*phy));
+
+	INIT_WORK(&phy->delta.tq, mii_phy_delta, mii);
+	phy->delta.data = NULL;
+	phy->delta.irq = -1;
+	phy->state.link=0;
+	phy->state.duplex=0;
+	phy->state.auto_neg=1;
+	phy->state.speed=0;
+
+	memset(mii,0,sizeof(*mii));
+	mii->phy_id = (bus << 5) | id;
+	mii->phy_id_mask = 0xff;
+	mii->reg_num_mask = 0x1f;
+	mii->dev = dev;
+	mii->mdio_read = mdio_read;
+	mii->mdio_write = mdio_write;
+
+	mii_bus[bus]->phy[id] = phy;
+
+	phy_run_commands(bus, id, phy->startup);
+
+	return 0;
+}
+
+EXPORT_SYMBOL(mii_phy_detach);
+void mii_phy_detach(struct mii_if_info *mii)
+{
+	struct phy_info *phy;
+	struct mii_bus *pbus;
+	BUS_ID(mii->phy_id);
+
+	if (!VALID(bus, id))
+		return;
+
+	pbus = mii_bus[bus];
+	phy = pbus->phy[id];
+
+	if (phy->delta.data != NULL) {
+		if (phy->delta.irq < 0)
+			mii_phy_poll_disable(mii, phy->delta.data);
+		else
+			mii_phy_irq_disable(mii, phy->delta.data);
+	}
+
+	phy_run_commands(bus, id, phy->shutdown);
+	pbus->phy[id]=NULL;
+	kfree(phy);
+}
+
+EXPORT_SYMBOL(mii_phy_state);
+int mii_phy_state(struct mii_if_info *mii, struct phy_state *state)
+{
+	struct phy_info *phy = mii_phy_of(mii);
+	BUS_ID(mii->phy_id);
+
+	if (!VALID(bus, id))
+		return -ENODEV;
+
+	if (phy->delta.data == NULL)
+		phy_run_commands(bus, id, phy->poll);
+
+	memcpy(state,&phy->state,sizeof(*state));
+
+	return 0;
+}
+
+
+static DECLARE_MUTEX(mii_bus_lock);
+
+EXPORT_SYMBOL(mii_bus_register);
+int mii_bus_register(struct mii_bus *bus)
+{
+	int bus_id;
+
+	if (bus == NULL || bus->name == NULL || bus->read == NULL ||
+	    bus->write == NULL)
+		return -EINVAL;
+
+	down(&mii_bus_lock);
+
+	for (bus_id = 0; bus_id < MII_BUS_MAX; bus_id++) {
+		if (mii_bus[bus_id] == NULL)
+			break;
+	}
+
+	if (bus_id >= MII_BUS_MAX) {
+		up(&mii_bus_lock);
+		return -ENOMEM;
+	}
+
+	mii_bus[bus_id] = bus;
+
+	if (mii_bus[bus_id] == NULL) {
+		up(&mii_bus_lock);
+		return -EINVAL;
+	}
+
+	if (mii_bus[bus_id]->reset)
+		mii_bus[bus_id]->reset(mii_bus[bus_id]->priv);
+
+	up(&mii_bus_lock);
+
+	printk(KERN_INFO "%s: registered as PHY bus %d\n",bus->name,bus_id);
+	
+	return bus_id;
+}
+
+EXPORT_SYMBOL(mii_bus_unregister);
+void mii_bus_unregister(struct mii_bus *bus)
+{
+	int i;
+
+	down(&mii_bus_lock);
+
+	for (i=0; i < MII_BUS_MAX; i++)
+		if (mii_bus[i] == bus)
+			mii_bus[i] = NULL;
+
+	up(&mii_bus_lock);
+}	

--- /dev/null
+++ linux/include/linux/mii_bus.h
@@ -0,0 +1,132 @@
+/* 
+ * include/linux/mii_bus.h
+ *
+ * Author: Jason McMullan
+ *
+ * Copyright (c) 2004 Timesys Corp.
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ *
+ */
+#ifndef __MII_BUS_H
+#define __MII_BUS_H
+
+#include <linux/mii.h>
+
+#define MII_TIMEOUT 	(1*HZ)
+
+#define miim_end ((u32)-2)
+#define miim_read ((u32)-1)
+
+/* Cicada Auxiliary Control/Status Register */
+#define MIIM_CIS8201_AUX_CONSTAT        0x1c
+#define MIIM_CIS8201_AUXCONSTAT_INIT    0x0004
+#define MIIM_CIS8201_AUXCONSTAT_DUPLEX  0x0020
+#define MIIM_CIS8201_AUXCONSTAT_SPEED   0x0018
+#define MIIM_CIS8201_AUXCONSTAT_GBIT    0x0010
+#define MIIM_CIS8201_AUXCONSTAT_100     0x0008
+                                                                                
+/* Cicada Extended Control Register 1 */
+#define MIIM_CIS8201_EXT_CON1           0x17
+#define MIIM_CIS8201_EXTCON1_INIT       0x0000
+
+/* 88E1011 PHY Status Register */
+#define MIIM_88E1011_PHY_STATUS         0x11
+#define MIIM_88E1011_PHYSTAT_SPEED      0xc000
+#define MIIM_88E1011_PHYSTAT_GBIT       0x8000
+#define MIIM_88E1011_PHYSTAT_100        0x4000
+#define MIIM_88E1011_PHYSTAT_DUPLEX     0x2000
+#define MIIM_88E1011_PHYSTAT_LINK	0x0400
+
+#define MIIM_88E1011_IEVENT		0x13
+#define MIIM_88E1011_IEVENT_CLEAR	0x0000
+
+#define MIIM_88E1011_IMASK		0x12
+#define MIIM_88E1011_IMASK_INIT		0x6400
+#define MIIM_88E1011_IMASK_CLEAR	0x0000
+
+/* DM9161 Control register values */
+#define MIIM_DM9161_CR_STOP	0x0400
+#define MIIM_DM9161_CR_RSTAN	0x1200
+
+#define MIIM_DM9161_SCR		0x10
+#define MIIM_DM9161_SCR_INIT	0x0610
+
+/* DM9161 Specified Configuration and Status Register */
+#define MIIM_DM9161_SCSR	0x11
+#define MIIM_DM9161_SCSR_100F	0x8000
+#define MIIM_DM9161_SCSR_100H	0x4000
+#define MIIM_DM9161_SCSR_10F	0x2000
+#define MIIM_DM9161_SCSR_10H	0x1000
+
+/* DM9161 Interrupt Register */
+#define MIIM_DM9161_INTR	0x15
+#define MIIM_DM9161_INTR_PEND		0x8000
+#define MIIM_DM9161_INTR_DPLX_MASK	0x0800
+#define MIIM_DM9161_INTR_SPD_MASK	0x0400
+#define MIIM_DM9161_INTR_LINK_MASK	0x0200
+#define MIIM_DM9161_INTR_MASK		0x0100
+#define MIIM_DM9161_INTR_DPLX_CHANGE	0x0010
+#define MIIM_DM9161_INTR_SPD_CHANGE	0x0008
+#define MIIM_DM9161_INTR_LINK_CHANGE	0x0004
+#define MIIM_DM9161_INTR_INIT 		0x0000
+#define MIIM_DM9161_INTR_STOP	\
+(MIIM_DM9161_INTR_DPLX_MASK | MIIM_DM9161_INTR_SPD_MASK \
+ | MIIM_DM9161_INTR_LINK_MASK | MIIM_DM9161_INTR_MASK)
+
+/* DM9161 10BT Configuration/Status */
+#define MIIM_DM9161_10BTCSR	0x12
+#define MIIM_DM9161_10BTCSR_INIT	0x7800
+
+struct phy_state {
+	unsigned int link:1;
+	unsigned int duplex:1;
+	unsigned int auto_neg:1;
+	unsigned int speed:29;
+};
+
+struct mii_bus {
+	const char *name;
+	void *priv;
+	int (*read)(void *priv, int phy_id, int location);
+	int (*write)(void *priv, int phy_id, int location, uint16_t val);
+	void (*reset)(void *priv);
+
+	/* Auto-filled in values */
+	struct phy_info *phy[32];
+};
+
+/* MII bus registration
+ */
+extern int mii_bus_register(struct mii_bus *bus);
+extern void mii_bus_unregister(struct mii_bus *bus);
+
+/* Raw read/write routines
+ * Returns a 16-bit register value, or < 0 error code
+ */
+extern int mii_bus_read(int bus_id, int phy_id, int reg);
+extern int mii_bus_write(int bus_id, int phy_id, int reg, uint16_t val);
+
+/* Routines used by network devices that use the MII bus
+ */
+extern int mii_phy_attach(struct mii_if_info *mii, struct net_device *dev, int phy_bus, int phy_id);
+extern void mii_phy_detach(struct mii_if_info *mii);
+
+/* Read current phy state
+ */
+extern int mii_phy_state(struct mii_if_info *mii, struct phy_state *state);
+
+/* Use an IRQ to determine when the PHY changes
+ */
+extern int mii_phy_irq_enable(struct mii_if_info *mii,int irq,void (*func)(void *),void *data);
+extern void mii_phy_irq_disable(struct mii_if_info *mii,void *data);
+
+/* Poll the PHY
+ */
+extern int mii_phy_poll_enable(struct mii_if_info *mii, unsigned long msecs, void (*func)(void *),void *data);
+extern void mii_phy_poll_disable(struct mii_if_info *mii,void *data);
+
+#endif /* __MII_BUS_H */

-- 
Jason McMullan <jason.mcmullan@timesys.com>
TimeSys Corporation

^ permalink raw reply	[flat|nested] 19+ messages in thread

end of thread, other threads:[~2004-11-20  5:30 UTC | newest]

Thread overview: 19+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <069B6F33-341C-11D9-9652-000393DBC2E8@freescale.com>
2004-11-18 17:52 ` [PATCH] MII bus API for PHY devices Andy Fleming
2004-11-18 19:34   ` Jason McMullan
2004-11-18 19:50     ` Andy Fleming
2004-11-18 21:00       ` Jason McMullan
2004-11-18 23:26   ` Benjamin Herrenschmidt
2004-11-19 16:41     ` Jason McMullan
2004-11-19 21:18     ` Andy Fleming
2004-11-19 22:43       ` Benjamin Herrenschmidt
2004-11-20  0:04         ` Andy Fleming
2004-11-19 20:18 Manfred Spraul
2004-11-19 21:01 ` Andy Fleming
  -- strict thread matches above, loose matches on Subject: below --
2004-11-11 22:48 Jason McMullan
2004-11-11 23:54 ` Francois Romieu
2004-11-12  0:07   ` Francois Romieu
2004-11-12  6:15 ` Benjamin Herrenschmidt
2004-11-12 16:47   ` Jason McMullan
2004-11-13  1:36     ` Benjamin Herrenschmidt
2004-11-11 19:45 Jason McMullan
2004-11-11 21:31 ` Francois Romieu

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).