* Re: RFC: PHY Abstraction Layer II
From: Pantelis Antoniou @ 2005-05-12 6:08 UTC (permalink / raw)
To: Andy Fleming; +Cc: netdev, linuxppc-embedded, David S. Miller
In-Reply-To: <30d87aabd768216ef8bee800f3e09b9e@freescale.com>
Andy Fleming wrote:
>
> On Apr 17, 2005, at 08:00, James Chapman wrote:
>
>> Andy Fleming wrote:
>>
>>> Ok, here's the new patch with changes suggested by James Chapman:
>>
>>
>> I guess I still have questions about the way interrupts are used.
>>
>> Using an interrupt to schedule a work queue which then sets a variable
>> that is used by a timer seems odd. Why not do all the work in the work
>> queue and schedule it from the interrupt handler or timer?
>
>
> Ok, I've set up a new system for handling interrupts. There are now two
> "special" interrupt values, PHY_POLL, and PHY_IGNORE_INTERRUPT. The
> first one is used to indicate that the PHY layer will poll the PHY for
> state changes, and won't enable interrupts. The second indicates that
> the PHY layer will neither poll, nor enable interrupts, and thus will
> allow the driver to handle interrupts. The PHY layer will still operate
> its state machine, though.
>
> The driver must insure a couple things:
>
> 1) It must set phydev->state to PHY_CHANGELINK
> 2) It must do that in a work queue (or other non-interrupt time)
>
> The first one tells the PHY layer that the link state changed (it has to
> grab a lock to do this). The second one is required in order to
> properly take the lock.
>
>>
>> Also, did you mean to leave the #if 0 code in davicom.c?
>
>
> For now. It worked around a problem some people were reporting, so I'd
> like to see if they report it again now that the code's out. If so,
> they have a fairly easy fix, and I can reinsert it (or at least
> reevaluate it) in the future.
>
>>
>> /james
>
>
> Andy
>
>
> ------------------------------------------------------------------------
>
> _______________________________________________
> Linuxppc-embedded mailing list
> Linuxppc-embedded@ozlabs.org
> https://ozlabs.org/mailman/listinfo/linuxppc-embedded
Hi Andy.
Mind taking a look at the combined FEC/FCC driver I've posted and
comment on how hard it'll be to use your PAL? I'd be especially
interested at any API mismatches, and problems you might find.
Regards
Pantelis
^ permalink raw reply
* udhcp problem
From: Atit_Shah @ 2005-05-12 6:24 UTC (permalink / raw)
To: linuxppc-embedded
My configuration is as follows:
_______________________
| ROUTER |
| ___ ___ |
|_____| |_______| |___|
LAN WAN
/\ /\
| |
Stand Alone Company =20
Windows System Network
(eth0) (eth1)
I have a udhcp server running on eth0. When I give the command on the
windows machine "ipconfig /renew", the router assigns an IP address to
the System in the range 192.168.1.x while the router itself has the IP
192.168.1.1 (default). I can ping the router from the system and
vice-versa.
I manually assigned a static IP to eth1 as 172.19.56.x insync with the
company IP address scheme. I can ping from the router to any other
system on the network.
Now when I try to ping a system or eth1 from the windows machine I get a
time out. But if I have to manually assign an IP to the windows
machine(ie not through the udhcp server) I am able to ping eth1 and any
other system on the network.
Any ideas as to how and why? If you need more inform please let me
know...
Atit
=09
DISCLAIMER:
This email (including any attachments) is intended for the sole use of =
the intended recipient/s and may contain material that is CONFIDENTIAL =
AND PRIVATE COMPANY INFORMATION. Any review or reliance by others or =
copying or distribution or forwarding of any or all of the contents in =
this message is STRICTLY PROHIBITED. If you are not the intended =
recipient, please contact the sender by email and delete all copies; =
your cooperation in this regard is appreciated.
^ permalink raw reply
* RE: Network Setup - HOW TO
From: Atit_Shah @ 2005-05-12 6:24 UTC (permalink / raw)
To: linuxppc-embedded
Hi all,
I have a custom router and ported the udhcp server/client on it.
My router has 2 ports eth0 - LAN and eth1 - WAN. My setup is as follows:
1.=20
To eth0: connect a stand alone windows machine
when I run the server with the command "uphcpd", it by default runs on
eth0. then on the windows machine when I say "ipconfig /renew", the
router assigns an IP address to the system - perfect.
2.
To eth0: connect to my company network.
when I run the server with the command "uphcpd", it by default runs on
eth0. then when I run the client "udhcpc" the router assigns an IP
address to eth0 obtained from the company server - perfect.
All this happens on eth0 ONLY.
I wish to configure the router as follows:
To eth0: stand alone system
To eth1: company network
The router should get an IP from my company server and assign it to eth1
and then it should assign a IP to my standalone system connected to
eth0. how do I do this?
Regards
Atit Shah
DISCLAIMER:
This email (including any attachments) is intended for the sole use of =
the intended recipient/s and may contain material that is CONFIDENTIAL =
AND PRIVATE COMPANY INFORMATION. Any review or reliance by others or =
copying or distribution or forwarding of any or all of the contents in =
this message is STRICTLY PROHIBITED. If you are not the intended =
recipient, please contact the sender by email and delete all copies; =
your cooperation in this regard is appreciated.
^ permalink raw reply
* Re: m8xx_pcmcia driver ported to v2.6
From: Pantelis Antoniou @ 2005-05-12 6:06 UTC (permalink / raw)
To: Mark Chambers; +Cc: linux-ppc-embedded
In-Reply-To: <001701c555af$c8ca0e10$0301a8c0@chuck2>
Mark Chambers wrote:
>>There is one pending problem: the M8xx does not accept
>>access to non present ioports (an exception is triggered),
>>which is quite common while inserting/removing cards.
>>
>>We've hacked around this in our tree, Panto once mentioned
>>that another PPC's do handle those exceptions fine (?).
>>
>
>
> Ah yes, I remember this problem from the 68360 days.
>
> This is a matter of how you set up the option register ORx
> for a particular chip select. You can choose external or
> internal acknowledge. You can have both at the same time:
> if you set up the ORx for the maximum number of wait states
> and also select external acknowledge, the cycle will end
> with the external acknowledge, or end with the maximum
> wait states if no external ack is generated. Also note that
> the internal/external ack bit can be changed on the fly - so,
> for instance, you could turn external ack on once a card
> was inserted.
>
> As I recall, some PCMCIA cards can have VERY long
> cycles in the configuration space, so even the maximum
> number of wait states in ORx may not be sufficient.
>
> Mark Chambers
> _______________________________________________
> Linuxppc-embedded mailing list
> Linuxppc-embedded@ozlabs.org
> https://ozlabs.org/mailman/listinfo/linuxppc-embedded
>
>
Unfortunately this does not work on 8xx since the PCMCIA
port is not on a chip select. It's a different peripheral
and always expectes a DTACK to terminate the cycle.
To get over this problem we need to do something more drastic.
Take a look at the __do_in_asm macro in include/asm-ppc/io.h.
Regards
Pantelis
^ permalink raw reply
* Re: [ANNOUNCE] mini_fo-0.6.0 overlay file system
From: Pantelis Antoniou @ 2005-05-12 5:57 UTC (permalink / raw)
To: Markus Klotzbuecher; +Cc: linuxppc-embedded
In-Reply-To: <20050510172930.GA14125@mary>
Markus Klotzbuecher wrote:
> Hi Pantelis
>
> On Tue, May 10, 2005 at 01:57:00PM +0300, Pantelis Antoniou wrote:
>
>
>>This sounds interesting.
>>
>>Could you post the patches in the list so that we can discuss
>>them properly?
>
>
> The module is currently built outside of the kernel sources, how would
> you like me to post it?
>
> Markus
>
>
Yes, so we can discuss them...
It's a very useful functionality that's I'd like to be present
in the vanilla kernel if possible.
Regards
Pantelis
^ permalink raw reply
* memec v2p7
From: mcnernbm @ 2005-05-12 2:33 UTC (permalink / raw)
To: linuxppc-embedded
[-- Attachment #1: Type: text/plain, Size: 362 bytes --]
I am trying to build a kernel to run on a memec virtex II pro board. I
got a crosscompiler to build succesfully but when I run make menuconfig
and then run make dep &&make zImage && make modules I get an error
stating:
can't stat source zvmlinux
Any ideas on how to get rid of this?
Also has anyone gotten the 2.4 kernel running on this board?
Thanks
Brett
[-- Attachment #2: Type: text/html, Size: 641 bytes --]
^ permalink raw reply
* libssl in ELDK3.0
From: David Ho @ 2005-05-11 15:22 UTC (permalink / raw)
To: Wolfgang Denk; +Cc: linux-ppc-embedded
Hi Wolfgang,
I noticed that the libssl.so and libcrypto.so in the ELDK3.0 development
rootfs are built with the linux-elf option on the ppc when it should have
been built with linux-ppc. When I was trying to build ssh with these libs,
it would not work with an i386 Linux box. I had to build libssl from
scratch.
David
^ permalink raw reply
* [PATCH] ppc32: Fix uImage make target to report success correctly
From: Kumar Gala @ 2005-05-11 15:10 UTC (permalink / raw)
To: Andrew Morton; +Cc: Sam Ravnborg, linux-kernel, linuxppc-embedded
The existing make rule when building a uImage would check to see
if the image file existed to report 'is ready' or 'not made'.
However make appeared to compute the file list before the rule
was executed.
Signed-off-by: Chris Clark <cpclark@xmission.com>
Signed-off-by: Kumar Gala <kumar.gala@freescale.com>
Signed-off-by: Sam Ravnborg <sam@ravnborg.org>
---
commit f772a90e948f019c3111a94394b3a649874417c7
tree c9d59c269792db4933039da49b3b3836ac5b01f5
parent c140244727aa88fcaefe34af4abc56e85b471da2
author Kumar K. Gala <kumar.gala@freescale.com> Tue, 10 May 2005 10:27:46 -0500
committer Kumar K. Gala <kumar.gala@freescale.com> Tue, 10 May 2005 10:27:46 -0500
ppc/boot/images/Makefile | 3 ++-
1 files changed, 2 insertions(+), 1 deletion(-)
Index: arch/ppc/boot/images/Makefile
===================================================================
--- dd5d97e6f88b8fbfcc09878d837c8e90590484b8/arch/ppc/boot/images/Makefile (mode:100644)
+++ c9d59c269792db4933039da49b3b3836ac5b01f5/arch/ppc/boot/images/Makefile (mode:100644)
@@ -22,7 +22,8 @@
$(obj)/uImage: $(obj)/vmlinux.gz
$(Q)rm -f $@
$(call if_changed,uimage)
- @echo ' Image: $@' $(if $(wildcard $@),'is ready','not made')
+ @echo -n ' Image: $@ '
+ @if [ -f $@ ]; then echo 'is ready' ; else echo 'not made'; fi
# Files generated that shall be removed upon make clean
clean-files := sImage vmapus vmlinux* miboot* zImage* uImage
^ permalink raw reply
* Re: Status of isp116x driver... (on mpc8xx)?
From: Wolfgang Denk @ 2005-05-11 13:28 UTC (permalink / raw)
To: David Jander; +Cc: linux-ppc-embedded
In-Reply-To: <200505111240.35425.david.jander@protonic.nl>
Dear David,
in message <200505111240.35425.david.jander@protonic.nl> you wrote:
>
> I connected a Philips ISP1160 usb host controller to a MPC852T-based custom
> board. I am using CVS kernel from DENX.
> I added some stuff to hcd-1161.h and hcd-1161.c to support our hardware, and
> it seems to work at first.
...
> Is the isp1160 chip really supported by the driver? tested?
We only use (and tested) this driver with a ISP1361 ...
> Is someone using a isp116x on a mpc8xx and can tell his experience on getting
> it working?
Sorry, we don't have any experience with a 1160. The driver in our
tree works fine with the 1362, both as host driver and as device (as
a gadget driver).
Best regards,
Wolfgang Denk
--
Software Engineering: Embedded and Realtime Systems, Embedded Linux
Phone: (+49)-8142-66989-10 Fax: (+49)-8142-66989-80 Email: wd@denx.de
No man knows what true happiness is until he gets married. By then,
of course, its too late.
^ permalink raw reply
* 2.6.12-rc2 : i2c-mpc - failed to attach interrupt
From: Sam Song @ 2005-05-11 13:05 UTC (permalink / raw)
To: linux-ppc-embedded
Hi all,
Sth unexpected happened when I enabled MPC107 I2C bus
support on a MPC8241 board. The slave device DS1337
couldn't work at all. Any hints?
......
i2c /dev entries driver
i2c-mpc - failed to attach interrupt
iic: probe of 00 failed with error -38
---ds1337 driver registering---
......
bash-2.05b# cat /proc/driver/rtc
rtc_time : 00:00:-19
rtc_date : 1970-01-01
rtc_epoch : 1900
alarm : 00:00:00
DST_enable : no
BCD : yes
24hr : yes
square_wave : no
alarm_IRQ : no
update_IRQ : no
periodic_IRQ : no
periodic_freq : 0
batt_status : okay
bash-2.05b# date
Wed Dec 31 19:05:23 EST 1969
bash-2.05b#
Thanks in advance,
Sam
_________________________________________________________
Do You Yahoo!?
150万曲MP3疯狂搜,带您闯入音乐殿堂
http://music.yisou.com/
美女明星应有尽有,搜遍美图、艳图和酷图
http://image.yisou.com
1G就是1000兆,雅虎电邮自助扩容!
http://cn.rd.yahoo.com/mail_cn/tag/1g/*http://cn.mail.yahoo.com/event/mail_1g/
^ permalink raw reply
* Status of isp116x driver... (on mpc8xx)?
From: David Jander @ 2005-05-11 10:40 UTC (permalink / raw)
To: linux-ppc-embedded
Hi,
I connected a Philips ISP1160 usb host controller to a MPC852T-based custom
board. I am using CVS kernel from DENX.
I added some stuff to hcd-1161.h and hcd-1161.c to support our hardware, and
it seems to work at first.
The controller is recognized, initialized and everything seems fine, but when
I connect a device to one of the two ports, every time the function
fnvIsp1161AtlRead() is called, it reads in a 72 byte-long block of zeroes.
Then I get "not enough configurations..." (error=-22).
If I printk-out the contents of the buffer status register (REG_BUFF_STS) at
that point, it is zero (=no data read).
Nevertheless, putting a scope on the physical USB wires, I can see quite some
amount of non-zero data flying by.
Is the isp1160 chip really supported by the driver? tested?
Are access timings very critical? I see some ugly iodelay_hcd() calls here and
there, are they necessary? why?
What about the other (older??) hc_isp116x.c driver floating around somewhere
on the net, from Roman Weissgaerber? I found version 0.9.6 (apparently for
kernel 2.4.22) somewhere, made some adaptations to get it loaded in our
kernel but it either oopses after initializing the hc, or simply freezes.
Whats the relationship between this and the driver hacked by Wolfgang Denk?
Is someone using a isp116x on a mpc8xx and can tell his experience on getting
it working?
Greetings,
--
David Jander
Protonic Holland.
^ permalink raw reply
* Re: PPC uImage build not reporting correctly
From: Sam Ravnborg @ 2005-05-11 5:01 UTC (permalink / raw)
To: cpclark; +Cc: Tom Rini, Linux Kernel list, linuxppc-embedded list
In-Reply-To: <Pine.LNX.4.63.0505100420380.8140@xmission.xmission.com>
>
> > Looks ok - but I do not see why use of $(shell ...) did not work out.
>
> As I understand it, the $(shell ...) construct doesn't "work" in the case
> cited above because make evaluates/expands the $(shell ...) stuff while it
> is parsing the makefile and building the command list--i.e. before it has
> issued any commands to build anything. What seems to be desired in this
> case is a file-existence test which runs "inline" with respect to the
> preceding commands. The use of $(shell ...) inside a command
> subverts/preempts that natural sequence. I think. :-)
That explains it - thanks!
Sam
^ permalink raw reply
* Re: Laptop sleep & current "git" tree
From: John Steele Scott @ 2005-05-11 0:03 UTC (permalink / raw)
To: linuxppc-dev; +Cc: debian-powerpc
In-Reply-To: <20050509083553.25d58928@colin.toulouse>
[-- Attachment #1: Type: text/plain, Size: 578 bytes --]
Colin Leroy <colin@colino.net> writes:
> On Mon, 09 May 2005 08:05:18 +0930
> John Steele Scott <toojays@toojays.net> wrote:
>
>> I forwarded the bug report to linux-usb-devel as per Colin's
>> suggestion, and David Brownell has fixed it. His post, which includes
>> the patch, can be found at
>> <http://marc.theaimsgroup.com/?l=linux-usb-devel&m=111558764113428&w=2>
>
> Great! Do you know if he plans pushing it to 2.6.12 quickly?
I don't know. After fixing that hub issue, I still get an oops if my USB
trackball is connected during sleep.
cheers,
John
[-- Attachment #2: Type: application/pgp-signature, Size: 282 bytes --]
^ permalink raw reply
* Re: m8xx_pcmcia driver ported to v2.6
From: Mark Chambers @ 2005-05-10 22:29 UTC (permalink / raw)
To: Marcelo Tosatti, linux-ppc-embedded
In-Reply-To: <20050510154145.GB8444@logos.cnet>
>
> There is one pending problem: the M8xx does not accept
> access to non present ioports (an exception is triggered),
> which is quite common while inserting/removing cards.
>
> We've hacked around this in our tree, Panto once mentioned
> that another PPC's do handle those exceptions fine (?).
>
Ah yes, I remember this problem from the 68360 days.
This is a matter of how you set up the option register ORx
for a particular chip select. You can choose external or
internal acknowledge. You can have both at the same time:
if you set up the ORx for the maximum number of wait states
and also select external acknowledge, the cycle will end
with the external acknowledge, or end with the maximum
wait states if no external ack is generated. Also note that
the internal/external ack bit can be changed on the fly - so,
for instance, you could turn external ack on once a card
was inserted.
As I recall, some PCMCIA cards can have VERY long
cycles in the configuration space, so even the maximum
number of wait states in ORx may not be sufficient.
Mark Chambers
^ permalink raw reply
* m8xx_pcmcia driver ported to v2.6
From: Marcelo Tosatti @ 2005-05-10 15:41 UTC (permalink / raw)
To: McMullan, Jason, linux-ppc-embedded
[-- Attachment #1: Type: text/plain, Size: 789 bytes --]
Per Jason's request, here goes the m8xx PCMCIA driver ported to v2.6.
It needs some cleaning up (some #ifdef'd out code pieces need to
be removed), but should be fine other than that.
There is one pending problem: the M8xx does not accept
access to non present ioports (an exception is triggered),
which is quite common while inserting/removing cards.
We've hacked around this in our tree, Panto once mentioned
that another PPC's do handle those exceptions fine (?).
Makefile:
obj-$(CONFIG_M8XX) += m8xx_pcmcia.o
Kconfig:
config M8XX
tristate "MPC8xx PCMCIA support"
depends on PCMCIA && PPC
select PCCARD_NONSTATIC
help
This provides support for Motorola 8xx embedded PowerPC
series PCMCIA controller.
[-- Attachment #2: m8xx_pcmcia.c --]
[-- Type: text/plain, Size: 34915 bytes --]
/* -*- linux-c -*- */
/*
* m8xx_pcmcia.c - Linux PCMCIA socket driver for the mpc8xx series.
*
* (C) 1999-2000 Magnus Damm <damm@bitsmart.com>
* (C) 2001-2002 Montavista Software, Inc.
* <mlocke@mvista.com>
* Support for two slots by Cyclades Corporation
* <oliver.kurth@cyclades.de>
*
* v2.6 kernel port, further fixes by Marcelo Tosatti
* <marcelo.tosatti@cyclades.com>
*
* "The ExCA standard specifies that socket controllers should provide
* two IO and five memory windows per socket, which can be independently
* configured and positioned in the host address space and mapped to
* arbitrary segments of card address space. " - David A Hinds. 1999
*
* This controller does _not_ meet the ExCA standard.
*
* m8xx pcmcia controller brief info:
* + 8 windows (attrib, mem, i/o)
* + up to two slots (SLOT_A and SLOT_B)
* + inputpins, outputpins, event and mask registers.
* - no offset register. sigh.
*
* Because of the lacking offset register we must map the whole card.
* We assign each memory window PCMCIA_MEM_WIN_SIZE address space.
* Make sure there is (PCMCIA_MEM_WIN_SIZE * PCMCIA_MEM_WIN_NO
* * PCMCIA_SOCKETS_NO) bytes at PCMCIA_MEM_WIN_BASE.
* The i/o windows are dynamically allocated at PCMCIA_IO_WIN_BASE.
* They are maximum 64KByte each...
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/string.h>
#include <asm/io.h>
#include <asm/bitops.h>
#include <asm/segment.h>
#include <asm/system.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/timer.h>
#include <linux/ioport.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <asm/mpc8xx.h>
#include <asm/8xx_immap.h>
#include <asm/irq.h>
#include <pcmcia/version.h>
#include <pcmcia/cs_types.h>
#include <pcmcia/cs.h>
#include <pcmcia/ss.h>
#ifdef CONFIG_TSxK
#include <asm/cpld.h>
#endif
//#define PCMCIA_DEBUG 5
#ifdef PCMCIA_DEBUG
static int pc_debug = PCMCIA_DEBUG;
MODULE_PARM(pc_debug, "i");
#define DEBUG(n, args...) printk(KERN_DEBUG "m8xx_pcmcia: " args);
#else
#define DEBUG(n, args...)
#endif
#define PCMCIA_INFO(args...) printk(KERN_INFO "m8xx_pcmcia: "args)
#define PCMCIA_ERROR(args...) printk(KERN_ERR "m8xx_pcmcia: "args)
static const char *version = "Version 0.05-cyclades1, May 2003";
MODULE_LICENSE("Dual MPL/GPL");
#if !defined(CONFIG_PCMCIA_SLOT_A) && !defined(CONFIG_PCMCIA_SLOT_B)
/* The RPX series use SLOT_B */
#if defined(CONFIG_RPXCLASSIC) || defined(CONFIG_RPXLITE)
#define CONFIG_PCMCIA_SLOT_B
#define CONFIG_BD_IS_MHZ
#endif
/* The ADS board use SLOT_A */
#ifdef CONFIG_ADS
#define CONFIG_PCMCIA_SLOT_A
#define CONFIG_BD_IS_MHZ
#endif
/* The FADS series are a mess */
#ifdef CONFIG_FADS
#if defined(CONFIG_MPC860T) || defined(CONFIG_MPC860) || defined(CONFIG_MPC821)
#define CONFIG_PCMCIA_SLOT_A
#else
#define CONFIG_PCMCIA_SLOT_B
#endif
#endif
/* Cyclades ACS uses both slots */
#ifdef CONFIG_PRxK
#define CONFIG_PCMCIA_SLOT_A
#define CONFIG_PCMCIA_SLOT_B
#endif
#endif /* !defined(CONFIG_PCMCIA_SLOT_A) && !defined(CONFIG_PCMCIA_SLOT_B) */
#if defined(CONFIG_PCMCIA_SLOT_A) && defined(CONFIG_PCMCIA_SLOT_B)
#define PCMCIA_SOCKETS_NO 2
/* We have only 8 windows, dualsocket support will be limited. */
#define PCMCIA_MEM_WIN_NO 2
#define PCMCIA_IO_WIN_NO 2
#define PCMCIA_SLOT_MSG "SLOT_A and SLOT_B"
#elif defined(CONFIG_PCMCIA_SLOT_A) || defined(CONFIG_PCMCIA_SLOT_B)
#define PCMCIA_SOCKETS_NO 1
/* full support for one slot */
#define PCMCIA_MEM_WIN_NO 5
#define PCMCIA_IO_WIN_NO 2
/* define _slot_ to be able to optimize macros */
#ifdef CONFIG_PCMCIA_SLOT_A
#define _slot_ 0
#define PCMCIA_SLOT_MSG "SLOT_A"
#else
#define _slot_ 1
#define PCMCIA_SLOT_MSG "SLOT_B"
#endif
#else
#error m8xx_pcmcia: Bad configuration!
#endif
/* ------------------------------------------------------------------------- */
#define PCMCIA_MEM_WIN_BASE 0xe0000000 /* base address for memory window 0 */
#define PCMCIA_MEM_WIN_SIZE 0x04000000 /* each memory window is 64 MByte */
#define PCMCIA_IO_WIN_BASE _IO_BASE /* base address for io window 0 */
#define PCMCIA_SCHLVL PCMCIA_INTERRUPT /* Status Change Interrupt Level */
/* ------------------------------------------------------------------------- */
/* 2.4.x and newer has this always in HZ */
#define M8XX_BUSFREQ ((((bd_t *)&(__res))->bi_busfreq))
static int pcmcia_schlvl = PCMCIA_SCHLVL;
spinlock_t events_lock = SPIN_LOCK_UNLOCKED;
/* ------------------------------------------------------------------------- */
#define PCMCIA_SOCKET_KEY_5V 1
#define PCMCIA_SOCKET_KEY_LV 2
/* look up table for pgcrx registers */
static u_int *m8xx_pgcrx[2] = {
&((immap_t *)IMAP_ADDR)->im_pcmcia.pcmc_pgcra,
&((immap_t *)IMAP_ADDR)->im_pcmcia.pcmc_pgcrb
};
/*
* This structure is used to address each window in the PCMCIA controller.
*
* Keep in mind that we assume that pcmcia_win_t[n+1] is mapped directly
* after pcmcia_win_t[n]...
*/
typedef struct {
uint br;
uint or;
} pcmcia_win_t;
/*
* For some reason the hardware guys decided to make both slots share
* some registers.
*
* Could someone invent object oriented hardware ?
*
* The macros are used to get the right bit from the registers.
* SLOT_A : slot = 0
* SLOT_B : slot = 1
*/
#define M8XX_PCMCIA_VS1(slot) (0x80000000 >> (slot << 4))
#define M8XX_PCMCIA_VS2(slot) (0x40000000 >> (slot << 4))
#define M8XX_PCMCIA_VS_MASK(slot) (0xc0000000 >> (slot << 4))
#define M8XX_PCMCIA_VS_SHIFT(slot) (30 - (slot << 4))
#define M8XX_PCMCIA_WP(slot) (0x20000000 >> (slot << 4))
#define M8XX_PCMCIA_CD2(slot) (0x10000000 >> (slot << 4))
#define M8XX_PCMCIA_CD1(slot) (0x08000000 >> (slot << 4))
#define M8XX_PCMCIA_BVD2(slot) (0x04000000 >> (slot << 4))
#define M8XX_PCMCIA_BVD1(slot) (0x02000000 >> (slot << 4))
#define M8XX_PCMCIA_RDY(slot) (0x01000000 >> (slot << 4))
#define M8XX_PCMCIA_RDY_L(slot) (0x00800000 >> (slot << 4))
#define M8XX_PCMCIA_RDY_H(slot) (0x00400000 >> (slot << 4))
#define M8XX_PCMCIA_RDY_R(slot) (0x00200000 >> (slot << 4))
#define M8XX_PCMCIA_RDY_F(slot) (0x00100000 >> (slot << 4))
#define M8XX_PCMCIA_MASK(slot) (0xFFFF0000 >> (slot << 4))
#define M8XX_PCMCIA_POR_VALID 0x00000001
#define M8XX_PCMCIA_POR_WRPROT 0x00000002
#define M8XX_PCMCIA_POR_ATTRMEM 0x00000010
#define M8XX_PCMCIA_POR_IO 0x00000018
#define M8XX_PCMCIA_POR_16BIT 0x00000040
#define M8XX_PGCRX(slot) (*m8xx_pgcrx[slot])
#define M8XX_PGCRX_CXOE 0x00000080
#define M8XX_PGCRX_CXRESET 0x00000040
/* we keep one lookup table per socket to check flags */
#define PCMCIA_EVENTS_MAX 5 /* 4 max at a time + termination */
typedef struct {
u_int regbit;
u_int eventbit;
} event_table_t;
typedef struct socket_info_t {
void (*handler)(void *info, u_int events);
void *info;
u_int slot;
socket_state_t state;
struct pccard_mem_map mem_win[PCMCIA_MEM_WIN_NO];
struct pccard_io_map io_win[PCMCIA_IO_WIN_NO];
event_table_t events[PCMCIA_EVENTS_MAX];
struct pcmcia_socket socket;
} socket_info_t;
static socket_info_t socket[PCMCIA_SOCKETS_NO];
/*
* Search this table to see if the windowsize is
* supported...
*/
#define M8XX_SIZES_NO 32
static const u_int m8xx_size_to_gray[M8XX_SIZES_NO] =
{
0x00000001, 0x00000002, 0x00000008, 0x00000004,
0x00000080, 0x00000040, 0x00000010, 0x00000020,
0x00008000, 0x00004000, 0x00001000, 0x00002000,
0x00000100, 0x00000200, 0x00000800, 0x00000400,
0x0fffffff, 0xffffffff, 0xffffffff, 0xffffffff,
0x01000000, 0x02000000, 0xffffffff, 0x04000000,
0x00010000, 0x00020000, 0x00080000, 0x00040000,
0x00800000, 0x00400000, 0x00100000, 0x00200000
};
/* ------------------------------------------------------------------------- */
static irqreturn_t m8xx_interrupt(int irq, void *dev, struct pt_regs *regs);
#define PCMCIA_BMT_LIMIT (15*4) /* Bus Monitor Timeout value */
/* ------------------------------------------------------------------------- */
/* board specific stuff: */
/* voltage_set(), hardware_enable() and hardware_disable() */
/* ------------------------------------------------------------------------- */
/* RPX Boards from Embedded Planet */
#if defined(CONFIG_RPXCLASSIC) || defined(CONFIG_RPXLITE)
/* The RPX boards seems to have it's bus monitor timeout set to 6*8 clocks.
* SYPCR is write once only, therefore must the slowest memory be faster
* than the bus monitor or we will get a machine check due to the bus timeout.
*/
#define PCMCIA_BOARD_MSG "RPX CLASSIC or RPX LITE"
#undef PCMCIA_BMT_LIMIT
#define PCMCIA_BMT_LIMIT (6*8)
static int voltage_set(int slot, int vcc, int vpp)
{
u_int reg = 0;
switch(vcc) {
case 0: break;
case 33: reg |= BCSR1_PCVCTL4; break;
case 50: reg |= BCSR1_PCVCTL5; break;
default: return 1;
}
switch(vpp) {
case 0: break;
case 33:
case 50:
if(vcc == vpp)
reg |= BCSR1_PCVCTL6;
else
return 1;
break;
case 120:
reg |= BCSR1_PCVCTL7;
default: return 1;
}
if(!((vcc == 50) || (vcc == 0)))
return 1;
/* first, turn off all power */
*((uint *)RPX_CSR_ADDR) &= ~(BCSR1_PCVCTL4 | BCSR1_PCVCTL5
| BCSR1_PCVCTL6 | BCSR1_PCVCTL7);
/* enable new powersettings */
*((uint *)RPX_CSR_ADDR) |= reg;
return 0;
}
#define socket_get(_slot_) PCMCIA_SOCKET_KEY_5V
#define hardware_enable(_slot_) /* No hardware to enable */
#define hardware_disable(_slot_) /* No hardware to disable */
#endif /* CONFIG_RPXCLASSIC */
/* ------------------------------------------------------------------------- */
/* FADS Boards from Motorola */
#if defined(CONFIG_FADS)
#define PCMCIA_BOARD_MSG "FADS"
static int voltage_set(int slot, int vcc, int vpp)
{
uint reg = 0;
switch(vcc) {
case 0: break;
case 33: reg |= BCSR1_PCCVCC0; break;
case 50: reg |= BCSR1_PCCVCC1; break;
default: return 1;
}
switch(vpp) {
case 0: break;
case 33:
case 50:
if(vcc == vpp)
reg |= BCSR1_PCCVPP1;
else
return 1;
break;
case 120:
if ((vcc == 33) || (vcc == 50))
reg |= BCSR1_PCCVPP0;
else
return 1;
default: return 1;
}
/* first, turn off all power */
*((uint *)BCSR1) &= ~(BCSR1_PCCVCC_MASK | BCSR1_PCCVPP_MASK);
/* enable new powersettings */
*((uint *)BCSR1) |= reg;
return 0;
}
#define socket_get(_slot_) PCMCIA_SOCKET_KEY_5V
static void hardware_enable(int slot)
{
*((uint *)BCSR1) &= ~BCSR1_PCCEN;
}
static void hardware_disable(int slot)
{
*((uint *)BCSR1) |= BCSR1_PCCEN;
}
#endif
/* ------------------------------------------------------------------------- */
/* Motorola MBX860 */
#if defined(CONFIG_MBX)
#define PCMCIA_BOARD_MSG "MBX"
static int voltage_set(int slot, int vcc, int vpp)
{
unsigned char reg = 0;
switch(vcc) {
case 0: break;
case 33: reg |= CSR2_VCC_33; break;
case 50: reg |= CSR2_VCC_50; break;
default: return 1;
}
switch(vpp) {
case 0: break;
case 33:
case 50:
if(vcc == vpp)
reg |= CSR2_VPP_VCC;
else
return 1;
break;
case 120:
if ((vcc == 33) || (vcc == 50))
reg |= CSR2_VPP_12;
else
return 1;
default: return 1;
}
/* first, turn off all power */
*((unsigned char *)MBX_CSR2_ADDR) &= ~(CSR2_VCC_MASK | CSR2_VPP_MASK);
/* enable new powersettings */
*((unsigned char *)MBX_CSR2_ADDR) |= reg;
return 0;
}
#define socket_get(_slot_) PCMCIA_SOCKET_KEY_5V
#define hardware_enable(_slot_) /* No hardware to enable */
#define hardware_disable(_slot_) /* No hardware to disable */
#endif /* CONFIG_MBX */
/* ------------------------------------------------------------------------- */
#if defined(CONFIG_PRxK)
extern volatile fpga_pc_regs *fpga_pc;
#define PCMCIA_BOARD_MSG "MPC855T"
static int voltage_set(int slot, int vcc, int vpp)
{
unsigned char reg = 0;
unsigned char regread;
cpld_regs *ccpld = get_cpld();
switch(vcc) {
case 0: break;
case 33: reg |= PCMCIA_VCC_33; break;
case 50: reg |= PCMCIA_VCC_50; break;
default: return 1;
}
switch(vpp) {
case 0: break;
case 33:
case 50:
if(vcc == vpp)
reg |= PCMCIA_VPP_VCC;
else
return 1;
break;
case 120:
if ((vcc == 33) || (vcc == 50))
reg |= PCMCIA_VPP_12;
else
return 1;
default: return 1;
}
reg = reg >> (slot << 2);
regread = ccpld->fpga_pc_ctl;
if (reg != (regread & ((PCMCIA_VCC_MASK | PCMCIA_VPP_MASK) >> (slot << 2)))) {
/* enable new powersettings */
regread = regread & ~((PCMCIA_VCC_MASK | PCMCIA_VPP_MASK) >> (slot << 2));
ccpld->fpga_pc_ctl = reg | regread;
mdelay(100);
}
return 0;
}
#define socket_get(_slot_) PCMCIA_SOCKET_KEY_LV
#define hardware_enable(_slot_) /* No hardware to enable */
#define hardware_disable(_slot_) /* No hardware to disable */
#endif /* CONFIG_PRxK */
/* ------------------------------------------------------------------------- */
static void m8xx_shutdown(void)
{
u_int m, i;
pcmcia_win_t *w;
for(i = 0; i < PCMCIA_SOCKETS_NO; i++){
w = (void *) &((immap_t *)IMAP_ADDR)->im_pcmcia.pcmc_pbr0;
((immap_t *)IMAP_ADDR)->im_pcmcia.pcmc_pscr =
M8XX_PCMCIA_MASK(i);
((immap_t *)IMAP_ADDR)->im_pcmcia.pcmc_per
&= ~M8XX_PCMCIA_MASK(i);
/* turn off interrupt and disable CxOE */
M8XX_PGCRX(i) = M8XX_PGCRX_CXOE;
/* turn off memory windows */
for(m = 0; m < PCMCIA_MEM_WIN_NO; m++) {
w->or = 0; /* set to not valid */
w++;
}
/* turn off voltage */
voltage_set(i, 0, 0);
/* disable external hardware */
hardware_disable(i);
}
free_irq(pcmcia_schlvl, NULL);
}
/* ------------------------------------------------------------------------- */
/* ------------------------------------------------------------------------- */
/* copied from tcic.c */
static int m8xx_drv_suspend(struct device *dev, u32 state, u32 level)
{
int ret = 0;
if (level == SUSPEND_SAVE_STATE)
ret = pcmcia_socket_dev_suspend(dev, state);
return ret;
}
static int m8xx_drv_resume(struct device *dev, u32 level)
{
int ret = 0;
if (level == RESUME_RESTORE_STATE)
ret = pcmcia_socket_dev_resume(dev);
return ret;
}
static struct device_driver m8xx_driver = {
.name = "m8xx-pcmcia",
.bus = &platform_bus_type,
.suspend = m8xx_drv_suspend,
.resume = m8xx_drv_resume,
};
static struct platform_device m8xx_device = {
.name = "m8xx-pcmcia",
.id = 0,
};
static u_int pending_events[PCMCIA_SOCKETS_NO];
static spinlock_t pending_event_lock = SPIN_LOCK_UNLOCKED;
static irqreturn_t m8xx_interrupt(int irq, void *dev, struct pt_regs *regs)
{
socket_info_t *s;
event_table_t *e;
u_int i, events, pscr, pipr, per;
// DEBUG(3,"Interrupt!\n");
/* get interrupt sources */
pscr = ((immap_t *)IMAP_ADDR)->im_pcmcia.pcmc_pscr;
pipr = ((immap_t *)IMAP_ADDR)->im_pcmcia.pcmc_pipr;
per = ((immap_t *)IMAP_ADDR)->im_pcmcia.pcmc_per;
for(i = 0; i < PCMCIA_SOCKETS_NO; i++) {
s = &socket[i];
e = &s->events[0];
events = 0;
while(e->regbit) {
if(pscr & e->regbit)
events |= e->eventbit;
e++;
}
/*
* report only if both card detect signals are the same
* not too nice done,
* we depend on that CD2 is the bit to the left of CD1...
*/
if(events & SS_DETECT)
if(((pipr & M8XX_PCMCIA_CD2(i)) >> 1) ^
(pipr & M8XX_PCMCIA_CD1(i)))
{
events &= ~SS_DETECT;
}
#define PCMCIA_GLITCHY_CD
/*
* PCMCIA_GLITCHY_CD is enabled on the ACS because it avoids
* bad interrupts on "IDE" PCMCIA cards, and possibly others.
* These interrupts can be generated by the PCMCIA controller
* until the card is correctly settled down eletrically.
*/
#ifdef PCMCIA_GLITCHY_CD
/*
* I've experienced CD problems with my ADS board.
* We make an extra check to see if there was a
* real change of Card detection.
*/
if((events & SS_DETECT) &&
((pipr &
(M8XX_PCMCIA_CD2(i) | M8XX_PCMCIA_CD1(i))) == 0) &&
(s->state.Vcc | s->state.Vpp)) {
events &= ~SS_DETECT;
/*printk( "CD glitch workaround - CD = 0x%08x!\n",
(pipr & (M8XX_PCMCIA_CD2(i)
| M8XX_PCMCIA_CD1(i))));*/
}
#endif
/* call the handler */
DEBUG(3,
"slot %u: events = 0x%02x, pscr = 0x%08x, "
"pipr = 0x%08x\n",
i, events, pscr, pipr);
if(events) {
spin_lock(&pending_event_lock);
pending_events[i] |= events;
spin_unlock(&pending_event_lock);
/*
* Turn off RDY_L bits in the PER mask on
* CD interrupt receival.
*
* They can generate bad interrupts on the
* ACS4,8,16,32.
*/
per &= ~M8XX_PCMCIA_RDY_L(0);
per &= ~M8XX_PCMCIA_RDY_L(1);
((immap_t *)IMAP_ADDR)->im_pcmcia.pcmc_per = per;
if (events)
pcmcia_parse_events(&socket[i].socket, events);
}
}
/* clear the interrupt sources */
((immap_t *)IMAP_ADDR)->im_pcmcia.pcmc_pscr = pscr;
// DEBUG(3,"Interrupt done.\n");
return IRQ_HANDLED;
}
/* ------------------------------------------------------------------------- */
static u_int m8xx_get_graycode(u_int size)
{
u_int k;
for(k = 0; k < M8XX_SIZES_NO; k++)
if(m8xx_size_to_gray[k] == size)
break;
if((k == M8XX_SIZES_NO) || (m8xx_size_to_gray[k] == -1))
k = -1;
return k;
}
/* ------------------------------------------------------------------------- */
static u_int m8xx_get_speed(u_int ns, u_int is_io)
{
u_int reg, clocks, psst, psl, psht;
if(!ns) {
/*
* We get called with IO maps setup to 0ns
* if not specified by the user.
* They should be 255ns.
*/
if(is_io)
ns = 255;
else
ns = 100; /* fast memory if 0 */
}
/*
* In PSST, PSL, PSHT fields we tell the controller
* timing parameters in CLKOUT clock cycles.
* CLKOUT is the same as GCLK2_50.
*/
/* how we want to adjust the timing - in percent */
#define ADJ 180 /* 80 % longer accesstime - to be sure */
clocks = ((M8XX_BUSFREQ / 1000) * ns) / 1000;
clocks = (clocks * ADJ) / (100*1000);
if(clocks >= PCMCIA_BMT_LIMIT) {
printk( "Max access time limit reached\n");
clocks = PCMCIA_BMT_LIMIT-1;
}
psst = clocks / 7; /* setup time */
psht = clocks / 7; /* hold time */
psl = (clocks * 5) / 7; /* strobe length */
psst += clocks - (psst + psht + psl);
reg = psst << 12;
reg |= psl << 7;
reg |= psht << 16;
return reg;
}
/* ------------------------------------------------------------------------- */
/* ------------------------------------------------------------------------- */
static int m8xx_get_status(struct pcmcia_socket *sock, u_int *value)
{
int lsock = container_of(sock, struct socket_info_t, socket)->slot;
socket_info_t *s = &socket[lsock];
u_int pipr, reg;
pipr = ((immap_t *)IMAP_ADDR)->im_pcmcia.pcmc_pipr;
*value = ((pipr & (M8XX_PCMCIA_CD1(lsock)
| M8XX_PCMCIA_CD2(lsock))) == 0) ? SS_DETECT : 0;
*value |= (pipr & M8XX_PCMCIA_WP(lsock)) ? SS_WRPROT : 0;
if (s->state.flags & SS_IOCARD)
*value |= (pipr & M8XX_PCMCIA_BVD1(lsock)) ? SS_STSCHG : 0;
else {
*value |= (pipr & M8XX_PCMCIA_RDY(lsock)) ? SS_READY : 0;
*value |= (pipr & M8XX_PCMCIA_BVD1(lsock)) ? SS_BATDEAD : 0;
*value |= (pipr & M8XX_PCMCIA_BVD2(lsock)) ? SS_BATWARN : 0;
}
if (s->state.Vcc | s->state.Vpp)
*value |= SS_POWERON;
/*
* Voltage detection:
* This driver only supports 16-Bit pc-cards.
* Cardbus is not handled here.
*
* To determine what voltage to use we must read the VS1 and VS2 pin.
* Depending on what socket type is present,
* different combinations mean different things.
*
* Card Key Socket Key VS1 VS2 Card Vcc for CIS parse
*
* 5V 5V, LV* NC NC 5V only 5V (if available)
*
* 5V 5V, LV* GND NC 5 or 3.3V as low as possible
*
* 5V 5V, LV* GND GND 5, 3.3, x.xV as low as possible
*
* LV* 5V - - shall not fit into socket
*
* LV* LV* GND NC 3.3V only 3.3V
*
* LV* LV* NC GND x.xV x.xV (if avail.)
*
* LV* LV* GND GND 3.3 or x.xV as low as possible
*
* *LV means Low Voltage
*
*
* That gives us the following table:
*
* Socket VS1 VS2 Voltage
*
* 5V NC NC 5V
* 5V NC GND none (should not be possible)
* 5V GND NC >= 3.3V
* 5V GND GND >= x.xV
*
* LV NC NC 5V (if available)
* LV NC GND x.xV (if available)
* LV GND NC 3.3V
* LV GND GND >= x.xV
*
* So, how do I determine if I have a 5V or a LV
* socket on my board? Look at the socket!
*
*
* Socket with 5V key:
* ++--------------------------------------------+
* || |
* || ||
* || ||
* | |
* +---------------------------------------------+
*
* Socket with LV key:
* ++--------------------------------------------+
* || |
* | ||
* | ||
* | |
* +---------------------------------------------+
*
*
* With other words - LV only cards does not fit
* into the 5V socket!
*/
/* read out VS1 and VS2 */
reg = (pipr & M8XX_PCMCIA_VS_MASK(lsock))
>> M8XX_PCMCIA_VS_SHIFT(lsock);
if(socket_get(lsock) == PCMCIA_SOCKET_KEY_LV) {
switch(reg) {
case 1: *value |= SS_3VCARD; break; /* GND, NC - 3.3V only */
case 2: *value |= SS_XVCARD; break; /* NC. GND - x.xV only */
};
}
DEBUG(3,"GetStatus(%d) = %#2.2x\n", lsock, *value);
return 0;
}
static int m8xx_get_socket(struct pcmcia_socket *sock, socket_state_t *state)
{
int lsock = container_of(sock, struct socket_info_t, socket)->slot;
*state = socket[lsock].state; /* copy the whole structure */
DEBUG(3,"GetSocket(%d) = flags %#3.3x, Vcc %d, Vpp %d, "
"io_irq %d, csc_mask %#2.2x\n", lsock, state->flags,
state->Vcc, state->Vpp, state->io_irq, state->csc_mask);
return 0;
}
/* ------------------------------------------------------------------------- */
static int m8xx_set_socket(struct pcmcia_socket *sock, socket_state_t *state)
{
int lsock = container_of(sock, struct socket_info_t, socket)->slot;
socket_info_t *s = &socket[lsock];
event_table_t *e;
u_int reg;
u_long flags;
DEBUG(3, "SetSocket(%d, flags %#3.3x, Vcc %d, Vpp %d, "
"io_irq %d, csc_mask %#2.2x)\n", lsock, state->flags,
state->Vcc, state->Vpp, state->io_irq, state->csc_mask);
/* code by Fanny, solves trouble with a modem card,
which causes too many interrupts (oku) */
/* SS_DEBOUNCED handling copied from yenta driver */
#if 0
if (state->flags & SS_DEBOUNCED) {
/* The insertion debounce period has ended.
Clear any pending insertion events */
spin_lock(&pending_event_lock);
pending_events[lsock] &= ~SS_DETECT;
spin_unlock(&pending_event_lock);
state->flags &= ~SS_DEBOUNCED; /* SS_DEBOUNCED is oneshot */
}
#endif
/* First, set voltage - bail out if invalid */
if(voltage_set(lsock, state->Vcc, state->Vpp))
return -EINVAL;
/* Take care of reset... */
if(state->flags & SS_RESET)
M8XX_PGCRX(lsock) |= M8XX_PGCRX_CXRESET; /* active high */
else
M8XX_PGCRX(lsock) &= ~M8XX_PGCRX_CXRESET;
/* ... and output enable. */
/* The CxOE signal is connected to a 74541 on the ADS.
I guess most other boards used the ADS as a reference.
I tried to control the CxOE signal with SS_OUTPUT_ENA,
but the reset signal seems connected via the 541.
If the CxOE is left high are some signals tristated and
no pullups are present -> the cards act wierd.
So right now the buffers are enabled if the power is on. */
if(state->Vcc || state->Vpp)
M8XX_PGCRX(lsock) &= ~M8XX_PGCRX_CXOE; /* active low */
else
M8XX_PGCRX(lsock) |= M8XX_PGCRX_CXOE;
/*
* We'd better turn off interrupts before
* we mess with the events-table..
*/
spin_lock_irqsave(&events_lock, flags);
/*
* Play around with the interrupt mask to be able to
* give the events the generic pcmcia driver wants us to.
*/
e = &s->events[0];
reg = 0;
if(state->csc_mask & SS_DETECT) {
e->eventbit = SS_DETECT;
reg |= e->regbit = (M8XX_PCMCIA_CD2(lsock)
| M8XX_PCMCIA_CD1(lsock));
e++;
}
if(state->flags & SS_IOCARD) {
/*
* I/O card
*/
if(state->csc_mask & SS_STSCHG) {
e->eventbit = SS_STSCHG;
reg |= e->regbit = M8XX_PCMCIA_BVD1(lsock);
e++;
}
/*
* If io_irq is non-zero we should enable irq.
*/
if(state->io_irq) {
M8XX_PGCRX(lsock) |=
mk_int_int_mask(state->io_irq) << 24;
/*
* Strange thing here:
* The manual does not tell us which interrupt
* the sources generate.
* Anyhow, I found out that RDY_L generates IREQLVL.
*
* We use level triggerd interrupts, and they don't
* have to be cleared in PSCR in the interrupt handler.
*/
reg |= M8XX_PCMCIA_RDY_L(lsock);
}
else
M8XX_PGCRX(lsock) &= 0x00ffffff;
}
else {
/*
* Memory card
*/
if(state->csc_mask & SS_BATDEAD) {
e->eventbit = SS_BATDEAD;
reg |= e->regbit = M8XX_PCMCIA_BVD1(lsock);
e++;
}
if(state->csc_mask & SS_BATWARN) {
e->eventbit = SS_BATWARN;
reg |= e->regbit = M8XX_PCMCIA_BVD2(lsock);
e++;
}
/* What should I trigger on - low/high,raise,fall? */
if(state->csc_mask & SS_READY) {
e->eventbit = SS_READY;
reg |= e->regbit = 0; //??
e++;
}
}
e->regbit = 0; /* terminate list */
/*
* Clear the status changed .
* Port A and Port B share the same port.
* Writing ones will clear the bits.
*/
((immap_t *)IMAP_ADDR)->im_pcmcia.pcmc_pscr = reg;
/*
* Write the mask.
* Port A and Port B share the same port.
* Need for read-modify-write.
* Ones will enable the interrupt.
*/
/*
reg |= ((immap_t *)IMAP_ADDR)->im_pcmcia.pcmc_per
& M8XX_PCMCIA_MASK(lsock);
*/
reg |= ((immap_t *)IMAP_ADDR)->im_pcmcia.pcmc_per
& ( M8XX_PCMCIA_MASK(0) | M8XX_PCMCIA_MASK(1) );
((immap_t *)IMAP_ADDR)->im_pcmcia.pcmc_per = reg;
spin_unlock_irqrestore(&events_lock, flags);
/* copy the struct and modify the copy */
s->state = *state;
return 0;
}
#if 0
static int m8xx_get_io_map(struct pcmcia_socket *sock, struct pccard_io_map *io)
{
int lsock = container_of(sock, struct socket_info_t, socket)->slot;
if(io->map >= PCMCIA_IO_WIN_NO)
return -EINVAL;
*io = socket[lsock].io_win[io->map]; /* copy the struct */
DEBUG(3,"GetIOMap(%d, %d) = %#2.2x, %d ns, "
"%#4.4x-%#4.4x\n", lsock, io->map, io->flags,
io->speed, io->start, io->stop);
return 0;
}
#endif
/* ------------------------------------------------------------------------- */
static int m8xx_set_io_map(struct pcmcia_socket *sock, struct pccard_io_map *io)
{
int lsock = container_of(sock, struct socket_info_t, socket)->slot;
socket_info_t *s = &socket[lsock];
pcmcia_win_t *w;
u_int reg, winnr;
#define M8XX_SIZE (io->stop - io->start + 1)
#define M8XX_BASE (PCMCIA_IO_WIN_BASE + io->start)
DEBUG(3, "SetIOMap(%d, %d, %#2.2x, %d ns, "
"%#4.4x-%#4.4x)\n", lsock, io->map, io->flags,
io->speed, io->start, io->stop);
if ((io->map >= PCMCIA_IO_WIN_NO) || (io->start > 0xffff)
|| (io->stop > 0xffff) || (io->stop < io->start))
return -EINVAL;
if((reg = m8xx_get_graycode(M8XX_SIZE)) == -1)
return -EINVAL;
if(io->flags & MAP_ACTIVE) {
DEBUG(3, "io->flags & MAP_ACTIVE\n");
winnr = (PCMCIA_MEM_WIN_NO * PCMCIA_SOCKETS_NO)
+ (lsock * PCMCIA_IO_WIN_NO) + io->map;
/* setup registers */
w = (void *) &((immap_t *)IMAP_ADDR)->im_pcmcia.pcmc_pbr0;
w += winnr;
w->or = 0; /* turn off window first */
w->br = M8XX_BASE;
reg <<= 27;
reg |= M8XX_PCMCIA_POR_IO |(lsock << 2);
reg |= m8xx_get_speed(io->speed, 1);
if(io->flags & MAP_WRPROT)
reg |= M8XX_PCMCIA_POR_WRPROT;
// if(io->flags & (MAP_16BIT | MAP_AUTOSZ))
if(io->flags & MAP_16BIT)
reg |= M8XX_PCMCIA_POR_16BIT;
if(io->flags & MAP_ACTIVE)
reg |= M8XX_PCMCIA_POR_VALID;
w->or = reg;
DEBUG(3,"Socket %u: Mapped io window %u at %#8.8x, "
"OR = %#8.8x.\n", lsock, io->map, w->br, w->or);
}
/* copy the struct and modify the copy */
s->io_win[io->map] = *io;
s->io_win[io->map].flags &= (MAP_WRPROT
| MAP_16BIT
| MAP_ACTIVE);
DEBUG(3, "SetIOMap exit\n");
return 0;
}
#if 0
static int m8xx_get_mem_map(struct pcmcia_socket *sock, struct pccard_mem_map *mem)
{
int lsock = container_of(sock, struct socket_info_t, socket)->slot;
if(mem->map >= PCMCIA_MEM_WIN_NO)
return -EINVAL;
*mem = socket[lsock].mem_win[mem->map]; /* copy the struct */
DEBUG(3, "GetMemMap(%d, %d) = %#2.2x, %d ns, "
"%#5.5lx, %#5.5x\n", lsock, mem->map, mem->flags,
mem->speed, mem->static_start, mem->card_start);
return 0;
}
#endif
/* ------------------------------------------------------------------------- */
static int m8xx_set_mem_map(struct pcmcia_socket *sock, struct pccard_mem_map *mem)
{
int lsock = container_of(sock, struct socket_info_t, socket)->slot;
socket_info_t *s = &socket[lsock];
pcmcia_win_t *w;
struct pccard_mem_map *old;
u_int reg, winnr;
DEBUG(3, "SetMemMap(%d, %d, %#2.2x, %d ns, "
"%#5.5lx, %#5.5x)\n", lsock, mem->map, mem->flags,
mem->speed, mem->static_start, mem->card_start);
if ((mem->map >= PCMCIA_MEM_WIN_NO)
// || ((mem->s) >= PCMCIA_MEM_WIN_SIZE)
|| (mem->card_start >= 0x04000000)
|| (mem->static_start & 0xfff) /* 4KByte resolution */
|| (mem->card_start & 0xfff))
return -EINVAL;
if((reg = m8xx_get_graycode(PCMCIA_MEM_WIN_SIZE)) == -1) {
printk( "Cannot set size to 0x%08x.\n", PCMCIA_MEM_WIN_SIZE);
return -EINVAL;
}
reg <<= 27;
winnr = (lsock * PCMCIA_MEM_WIN_NO) + mem->map;
/* Setup the window in the pcmcia controller */
w = (void *) &((immap_t *)IMAP_ADDR)->im_pcmcia.pcmc_pbr0;
w += winnr;
reg |= lsock << 2;
reg |= m8xx_get_speed(mem->speed, 0);
if(mem->flags & MAP_ATTRIB)
reg |= M8XX_PCMCIA_POR_ATTRMEM;
if(mem->flags & MAP_WRPROT)
reg |= M8XX_PCMCIA_POR_WRPROT;
if(mem->flags & MAP_16BIT)
reg |= M8XX_PCMCIA_POR_16BIT;
if(mem->flags & MAP_ACTIVE)
reg |= M8XX_PCMCIA_POR_VALID;
w->or = reg;
DEBUG(3, "Socket %u: Mapped memory window %u at %#8.8x, "
"OR = %#8.8x.\n", lsock, mem->map, w->br, w->or);
if(mem->flags & MAP_ACTIVE) {
/* get the new base address */
mem->static_start = PCMCIA_MEM_WIN_BASE +
(PCMCIA_MEM_WIN_SIZE * winnr)
+ mem->card_start;
}
DEBUG(3, "SetMemMap(%d, %d, %#2.2x, %d ns, "
"%#5.5lx, %#5.5x)\n", lsock, mem->map, mem->flags,
mem->speed, mem->static_start, mem->card_start);
/* copy the struct and modify the copy */
old = &s->mem_win[mem->map];
*old = *mem;
old->flags &= (MAP_ATTRIB
| MAP_WRPROT
| MAP_16BIT
| MAP_ACTIVE);
return 0;
}
static int m8xx_sock_init(struct pcmcia_socket *sock)
{
int i;
pccard_io_map io = { 0, 0, 0, 0, 1 };
pccard_mem_map mem = { 0, 0, 0, 0, 0, 0 };
DEBUG(3, "sock_init(%d)\n", s);
m8xx_set_socket(sock, &dead_socket);
for (i = 0; i < PCMCIA_IO_WIN_NO; i++) {
io.map = i;
m8xx_set_io_map(sock, &io);
}
for (i = 0; i < PCMCIA_MEM_WIN_NO; i++) {
mem.map = i;
m8xx_set_mem_map(sock, &mem);
}
return 0;
}
static int m8xx_suspend(struct pcmcia_socket *sock)
{
return(m8xx_set_socket(sock, &dead_socket));
}
/* ------------------------------------------------------------------------- */
static struct pccard_operations m8xx_services = {
.init = m8xx_sock_init,
.suspend = m8xx_suspend,
.get_status = m8xx_get_status,
.get_socket = m8xx_get_socket,
.set_socket = m8xx_set_socket,
.set_io_map = m8xx_set_io_map,
.set_mem_map = m8xx_set_mem_map,
};
static int __init m8xx_init(void)
{
pcmcia_win_t *w;
u_int i,m;
PCMCIA_INFO("%s\n", version);
/* CardServices(GetCardServicesInfo, &serv);
if (serv.Revision != CS_RELEASE_CODE) {
PCMCIA_ERROR("Card Services release does not match!\n");
return -1;
}*/
if (driver_register(&m8xx_driver))
return -1;
PCMCIA_INFO(PCMCIA_BOARD_MSG " using " PCMCIA_SLOT_MSG
" with IRQ %u.\n", pcmcia_schlvl);
/* Configure Status change interrupt */
if(request_irq(pcmcia_schlvl, m8xx_interrupt, 0,
"m8xx_pcmcia", NULL)) {
PCMCIA_ERROR("Cannot allocate IRQ %u for SCHLVL!\n",
pcmcia_schlvl);
return -1;
}
w = (void *) &((immap_t *)IMAP_ADDR)->im_pcmcia.pcmc_pbr0;
((immap_t *)IMAP_ADDR)->im_pcmcia.pcmc_pscr =
M8XX_PCMCIA_MASK(0)| M8XX_PCMCIA_MASK(1);
((immap_t *)IMAP_ADDR)->im_pcmcia.pcmc_per
&= ~(M8XX_PCMCIA_MASK(0)| M8XX_PCMCIA_MASK(1));
/* connect interrupt and disable CxOE */
M8XX_PGCRX(0) = M8XX_PGCRX_CXOE |
(mk_int_int_mask(pcmcia_schlvl) << 16);
M8XX_PGCRX(1) = M8XX_PGCRX_CXOE |
(mk_int_int_mask(pcmcia_schlvl) << 16);
/* intialize the fixed memory windows */
for(i = 0; i < PCMCIA_SOCKETS_NO; i++){
for(m = 0; m < PCMCIA_MEM_WIN_NO; m++) {
w->br = PCMCIA_MEM_WIN_BASE +
(PCMCIA_MEM_WIN_SIZE
* (m + i * PCMCIA_MEM_WIN_NO));
w->or = 0; /* set to not valid */
w++;
}
}
/* turn off voltage */
voltage_set(0, 0, 0);
voltage_set(1, 0, 0);
/* Enable external hardware */
hardware_enable(0);
hardware_enable(1);
platform_device_register(&m8xx_device);
for (i = 0 ; i < PCMCIA_SOCKETS_NO; i++) {
socket[i].slot = i;
socket[i].socket.owner = THIS_MODULE;
socket[i].socket.features = SS_CAP_PCCARD | SS_CAP_MEM_ALIGN
| SS_CAP_STATIC_MAP;
socket[i].socket.irq_mask = 0x000;
socket[i].socket.map_size = 0x1000;
socket[i].socket.io_offset = 0;
socket[i].socket.pci_irq = i ? 7 : 9;
socket[i].socket.ops = &m8xx_services;
socket[i].socket.resource_ops = &pccard_nonstatic_ops;
socket[i].socket.cb_dev = NULL;
socket[i].socket.dev.dev = &m8xx_device.dev;
}
for (i = 0; i < PCMCIA_SOCKETS_NO; i++) {
pcmcia_register_socket(&socket[i].socket);
}
return 0;
}
static void __exit m8xx_exit(void)
{
int i;
for (i = 0; i < PCMCIA_SOCKETS_NO; i++)
pcmcia_unregister_socket(&socket[i].socket);
m8xx_shutdown();
platform_device_unregister(&m8xx_device);
driver_unregister(&m8xx_driver);
}
module_init(m8xx_init);
module_exit(m8xx_exit);
^ permalink raw reply
* Re: [PATCH 03/04] Freescale Ethernet combined driver
From: Dan Malek @ 2005-05-10 18:24 UTC (permalink / raw)
To: Rune Torgersen; +Cc: linuxppc-embedded
In-Reply-To: <DCEAAC0833DD314AB0B58112AD99B93B85938C@ismail.innsys.innovsys.com>
On May 10, 2005, at 10:53 AM, Rune Torgersen wrote:
> For 8xx/82xx (and other PQ cpus) there is a lot of drivers liberally
> using
> __pa and __va to get physical and virtual adresses for structures and
> buffer descriptor tables for the CPM.
I changed nearly all of these in 2.4 when the DMA assist functions
came into being. Unfortunately, many of the patches submitted to
these drivers over time were from out of date sources that seemed
to revert back to old behavior instead of making the updates adapt
the new. From looking at the CPM support functions in 2.6 recently,
I can't believe it even works. In some cases there are conflicting
implementations sharing information that is just by luck works.
> What would the preferred way of handling both of these be?
Read DMA-API.txt
Thanks.
-- Dan
^ permalink raw reply
* Re: [PATCH 03/04] Freescale Ethernet combined driver
From: Dan Malek @ 2005-05-10 18:14 UTC (permalink / raw)
To: Pantelis Antoniou; +Cc: Tom Rini, linuxppc-embedded
In-Reply-To: <4280B3CB.8030007@intracom.gr>
On May 10, 2005, at 9:14 AM, Pantelis Antoniou wrote:
> What's the recommended function to call to go from a
> virtual -> physical address, but without doing a cache
> flush/invalidate?
It doesn't have anything to do with cache flush/invalidate,
it has to do with the way the memory is mapped to a different
VM space. You do not (and in some cases can't) perform
invalidate/flush on uncached addresses.
What we need to do is fix up the CPM allocation functions,
so when it does the dma_alloc_consistent (or whatever the
name is today) it keeps both the physical and virtual addresses.
When we call cpm_hostalloc(), it has to return both the physical
and virtual addresses, and the driver must take note of them.
I had this in the drivers at one time in 2.4, I don't know where
it went ......
The drivers themselves must do the dma_consistent_alloc
on large buffers, and also keep track of both physical and
virtual addresses. Again, this was done once before ....
Thanks.
-- Dan
^ permalink raw reply
* Re: [ANNOUNCE] mini_fo-0.6.0 overlay file system
From: Markus Klotzbuecher @ 2005-05-10 17:29 UTC (permalink / raw)
To: Pantelis Antoniou; +Cc: linuxppc-embedded
In-Reply-To: <4280937C.8000709@intracom.gr>
Hi Pantelis
On Tue, May 10, 2005 at 01:57:00PM +0300, Pantelis Antoniou wrote:
> This sounds interesting.
>
> Could you post the patches in the list so that we can discuss
> them properly?
The module is currently built outside of the kernel sources, how would
you like me to post it?
Markus
^ permalink raw reply
* Re: RFC: PHY Abstraction Layer II
From: Andy Fleming @ 2005-05-10 17:04 UTC (permalink / raw)
To: James Chapman; +Cc: netdev, David S. Miller, linuxppc-embedded
In-Reply-To: <42625DDB.4090600@katalix.com>
[-- Attachment #1: Type: text/plain, Size: 1520 bytes --]
On Apr 17, 2005, at 08:00, James Chapman wrote:
> Andy Fleming wrote:
>> Ok, here's the new patch with changes suggested by James Chapman:
>
> I guess I still have questions about the way interrupts are used.
>
> Using an interrupt to schedule a work queue which then sets a variable
> that is used by a timer seems odd. Why not do all the work in the work
> queue and schedule it from the interrupt handler or timer?
Ok, I've set up a new system for handling interrupts. There are now
two "special" interrupt values, PHY_POLL, and PHY_IGNORE_INTERRUPT.
The first one is used to indicate that the PHY layer will poll the PHY
for state changes, and won't enable interrupts. The second indicates
that the PHY layer will neither poll, nor enable interrupts, and thus
will allow the driver to handle interrupts. The PHY layer will still
operate its state machine, though.
The driver must insure a couple things:
1) It must set phydev->state to PHY_CHANGELINK
2) It must do that in a work queue (or other non-interrupt time)
The first one tells the PHY layer that the link state changed (it has
to grab a lock to do this). The second one is required in order to
properly take the lock.
>
> Also, did you mean to leave the #if 0 code in davicom.c?
For now. It worked around a problem some people were reporting, so I'd
like to see if they report it again now that the code's out. If so,
they have a fairly easy fix, and I can reinsert it (or at least
reevaluate it) in the future.
>
> /james
Andy
[-- Attachment #2: phy_05_09_2005.patch --]
[-- Type: application/octet-stream, Size: 89495 bytes --]
diff -Nru a/Documentation/networking/phy.txt b/Documentation/networking/phy.txt
--- /dev/null Wed Dec 31 16:00:00 196900
+++ b/Documentation/networking/phy.txt 2005-05-09 13:19:26 -05:00
@@ -0,0 +1,203 @@
+
+-------
+PHY Abstraction Layer
+
+Purpose
+
+ Most network devices consist of set of registers which provide an interface
+ to a MAC layer, which communicates with the physical connection through a
+ PHY. The PHY concerns itself with negotiating link parameters with the link
+ partner on the other side of the network connection (typically, an ethernet
+ cable), and provides a register interface to allow drivers to determine what
+ settings were chosen, and to configure what settings are allowed.
+
+ While these devices are distinct from the network devices, and conform to a
+ standard layout for the registers, it has been common practice to integrate
+ the PHY management code with the network driver. This has resulted in large
+ amounts of redundant code. Also, on embedded systems with multiple (and
+ sometimes quite different) ethernet controllers connected to the same
+ management bus, it is difficult to ensure safe use of the bus.
+
+ Since the PHYs are devices, and the management busses through which they are
+ accessed are, in fact, busses, the PHY Abstraction Layer treats them as such.
+ In doing so, it has these goals:
+
+ 1) Increase code-reuse
+ 2) Increase overall code-maintainability
+ 3) Speed development time for new network drivers, and for new systems
+
+ Basically, this layer is meant to provide an interface to PHY devices which
+ allows network driver writers to write as little code as possible, while
+ still providing a full feature set.
+
+The MDIO bus
+
+ Most network devices are connected to a PHY by means of a management bus.
+ Different devices use different busses (though some share common interfaces).
+ In order to take advantage of the PAL, each bus interface needs to be
+ registered as a distinct device.
+
+ 1) read and write functions must be implemented. Their prototypes are:
+
+ int write(struct mii_bus *bus, int mii_id, int regnum, u16 value);
+ int read(struct mii_bus *bus, int mii_id, int regnum);
+
+ mii_id is the address on the bus for the PHY, and regnum is the register
+ number. These functions are guaranteed not to be called from interrupt
+ time, so it is safe for them to block, waiting for an interrupt to signal
+ the operation is complete
+
+ 2) A reset function is necessary. This is used to return the bus to an
+ initialized state.
+
+ 3) A probe function is needed. This function should set up anything the bus
+ driver needs, setup the mii_bus structure, and register with the PAL using
+ mdiobus_register. Similarly, there's a remove function to undo all of
+ that (use mdiobus_unregister).
+
+ 4) Like any driver, the device_driver structure must be configured, and init
+ exit functions are used to register the driver.
+
+ 5) The bus must also be declared somewhere as a device, and registered.
+
+ As an example for how one driver implemented an mdio bus driver, see
+ drivers/net/gianfar_mii.c and arch/ppc/syslib/mpc85xx_devices.c
+
+Connecting to a PHY
+
+ Sometime during startup, the network driver needs to establish a connection
+ between the PHY device, and the network device. At this time, the PHY's bus
+ and drivers need to all have been loaded, so it is ready for the connection.
+ At this point, there are several ways to connect to the PHY:
+
+ 1) The PAL handles everything, and only calls the network driver when
+ the link state changes, so it can react.
+
+ 2) The PAL handles everything except interrupts (usually because the
+ controller has the interrupt registers).
+
+ 3) The PAL handles everything, but checks in with the driver every second,
+ allowing the network driver to react first to any changes before the PAL
+ does.
+
+ 4) The PAL serves only as a library of functions, with the network device
+ manually calling functions to update status, and configure the PHY
+
+
+Letting the PHY Abstraction Layer do Everything
+
+ If you choose option 1 (The hope is that every driver can, but to still be
+ useful to drivers that can't), connecting to the PHY is simple:
+
+ First, you need a function to react to changes in the link state. This
+ function follows this protocol:
+
+ static void adjust_link(struct net_device *dev);
+
+ Next, you need to know the device name of the PHY connected to this device.
+ The name will look something like, "phy0:0", where the first number is the
+ bus id, and the second is the PHY's address on that bus.
+
+ Now, to connect, just call this function:
+
+ phydev = phy_connect(dev, phy_name, &adjust_link);
+
+ It is possible you don't need to react to changes in link state. If so, you
+ can pass in NULL as the last argument, and the PAL will not bother your
+ driver.
+
+ phydev is a pointer to the phy_device structure which represents the PHY. If
+ phy_connect is successful, it will return the pointer. dev, here, is the
+ pointer to your net_device. Once done, this function will have started the
+ PHY's software state machine, and registered for the PHY's interrupt, if it
+ has one. The phydev structure will be populated with information about the
+ current state, though the PHY will not yet be truly operational at this
+ point.
+
+ Now just make sure that phydev->supported and phydev->advertising have any
+ values pruned from them which don't make sense for your controller (a 10/100
+ controller may be connected to a gigabit capable PHY, so you would need to
+ mask off SUPPORTED_1000baseT*). See include/linux/ethtool.h for definitions
+ for these bitfields. Note that you should not SET any bits, or the PHY may
+ get put into an unsupported state.
+
+ Lastly, once the controller is ready to handle network traffic, you call
+ phy_start(phydev). This tells the PAL that you are ready, and configures the
+ PHY to connect to the network. If you want to handle your own interrupts,
+ just set phydev->irq to PHY_IGNORE_INTERRUPT before you call phy_start.
+ Similarly, if you don't want to use interrupts, set phydev->irq to PHY_POLL.
+
+ When you want to disconnect from the network (even if just briefly), you call
+ phy_stop(phydev).
+
+Keeping Close Tabs on the PAL
+
+ It is possible that the PAL's built-in state machine needs a little help to
+ keep your network device and the PHY properly in sync. If so, you can
+ register a helper function when connecting to the PHY, which will be called
+ every second before the state machine reacts to any changes. To do this, you
+ need to manually call phy_attach() and phy_prepare_link(), and then call
+ phy_start_machine() with the second argument set to point to your special
+ handler.
+
+ Currently there are no examples of how to use this functionality, and testing
+ on it has been limited because the author does not have any drivers which use
+ it (they all use option 1). So Caveat Emptor.
+
+Doing it all yourself
+
+ There's a remote chance that the PAL's built-in state machine cannot track
+ the complex interactions between the PHY and your network device. If this is
+ so, you can simply call phy_attach(), and not call phy_start_machine or
+ phy_prepare_link(). This will mean that phydev->state is entirely yours to
+ handle (phy_start and phy_stop toggle between some of the states, so you
+ might need to avoid them).
+
+ An effort has been made to make sure that useful functionality can be
+ accessed without the state-machine running, and most of these functions are
+ descended from functions which did not interact with a complex state-machine.
+ However, again, no effort has been made so far to test running without the
+ state machine, so tryer beware.
+
+ Here is a brief rundown of the functions:
+
+ int phy_read(struct phy_device *phydev, u16 regnum);
+ int phy_write(struct phy_device *phydev, u16 regnum, u16 val);
+
+ Simple read/write primitives. They invoke the bus's read/write function
+ pointers.
+
+ int phy_clear_interrupt(struct phy_device *phydev);
+ int phy_config_interrupt(struct phy_device *phydev, u32 interrupts);
+
+ Clear the PHY's interrupt, and configure which ones are allowed,
+ respectively. Currently only supports all on, or all off.
+
+ struct phy_device * phy_attach(struct net_device *dev, const char *phy_id);
+
+ Attaches a network device to a particular PHY, binding the PHY to a generic
+ driver if none was found during bus initialization.
+
+ int phy_start_aneg(struct phy_device *phydev);
+
+ Using variables inside the phydev structure, either configures advertising
+ and resets autonegotiation, or disables autonegotiation, and configures
+ forced settings.
+
+ static inline int phy_read_status(struct phy_device *phydev);
+
+ Fills the phydev structure with up-to-date information about the current
+ settings in the PHY.
+
+ int phy_ethtool_sset(struct phy_device *phydev, struct ethtool_cmd *cmd);
+ int phy_ethtool_gset(struct phy_device *phydev, struct ethtool_cmd *cmd);
+
+ Ethtool convenience functions.
+
+ int phy_mii_ioctl(struct phy_device *phydev,
+ struct mii_ioctl_data *mii_data, int cmd);
+
+ The MII ioctl. Note that this function will completely screw up the state
+ machine if you write registers like BMCR, BMSR, ADVERTISE, etc. Best to
+ use this only to write registers which are not standard, and don't set off
+ a renegotiation.
diff -Nru a/drivers/net/Kconfig b/drivers/net/Kconfig
--- a/drivers/net/Kconfig 2005-05-09 13:19:26 -05:00
+++ b/drivers/net/Kconfig 2005-05-09 13:19:26 -05:00
@@ -153,6 +153,8 @@
source "drivers/net/arcnet/Kconfig"
endif
+source "drivers/net/phy/Kconfig"
+
#
# Ethernet
#
@@ -2056,6 +2058,8 @@
config GIANFAR
tristate "Gianfar Ethernet"
depends on 85xx || 83xx
+ select PHYLIB
+ select PHYCONTROL
help
This driver supports the Gigabit TSEC on the MPC85xx
family of chips, and the FEC on the 8540
diff -Nru a/drivers/net/Makefile b/drivers/net/Makefile
--- a/drivers/net/Makefile 2005-05-09 13:19:25 -05:00
+++ b/drivers/net/Makefile 2005-05-09 13:19:25 -05:00
@@ -12,7 +12,7 @@
obj-$(CONFIG_BONDING) += bonding/
obj-$(CONFIG_GIANFAR) += gianfar_driver.o
-gianfar_driver-objs := gianfar.o gianfar_ethtool.o gianfar_phy.o
+gianfar_driver-objs := gianfar.o gianfar_ethtool.o gianfar_mii.o
#
# link order important here
@@ -63,6 +63,7 @@
#
obj-$(CONFIG_MII) += mii.o
+obj-$(CONFIG_PHYLIB) += phy/
obj-$(CONFIG_SUNDANCE) += sundance.o
obj-$(CONFIG_HAMACHI) += hamachi.o
diff -Nru a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
--- /dev/null Wed Dec 31 16:00:00 196900
+++ b/drivers/net/phy/Kconfig 2005-05-09 13:19:26 -05:00
@@ -0,0 +1,57 @@
+#
+# PHY Layer Configuration
+#
+
+menu "PHY device support"
+
+config PHYLIB
+ bool "PHY Device support and infrastructure"
+ depends on NET_ETHERNET
+ help
+ Ethernet controllers are usually attached to PHY
+ devices. This option provides infrastructure for
+ managing PHY devices.
+
+config PHYCONTROL
+ bool "Support for automatically handling PHY state changes"
+ depends on PHYLIB
+ help
+ Adds code to perform all the work for keeping PHY link
+ state (speed/duplex/etc) up-to-date. Also handles
+ interrupts.
+
+comment "MII PHY device drivers"
+ depends on PHYLIB
+
+config MARVELL_PHY
+ bool "Drivers for Marvell PHYs"
+ depends on PHYLIB
+ ---help---
+ Currently has a driver for the 88E1011S
+
+config DAVICOM_PHY
+ bool "Drivers for Davicom PHYs"
+ depends on PHYLIB
+ ---help---
+ Currently supports dm9161e and dm9131
+
+config QSEMI_PHY
+ bool "Drivers for Quality Semiconductor PHYs"
+ depends on PHYLIB
+ ---help---
+ Currently supports the qs6612
+
+config LXT_PHY
+ bool "Drivers for the Intel LXT PHYs"
+ depends on PHYLIB
+ ---help---
+ Currently supports the lxt970, lxt971
+
+config CICADA_PHY
+ bool "Drivers for the Cicada PHYs"
+ depends on PHYLIB
+ ---help---
+ Currently supports the cis8204
+
+endmenu
+
diff -Nru a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
--- /dev/null Wed Dec 31 16:00:00 196900
+++ b/drivers/net/phy/Makefile 2005-05-09 13:19:26 -05:00
@@ -0,0 +1,9 @@
+# Makefile for Linux PHY drivers
+
+obj-$(CONFIG_PHYLIB) += phy.o phy_device.o mdio_bus.o
+
+obj-$(CONFIG_MARVELL_PHY) += marvell.o
+obj-$(CONFIG_DAVICOM_PHY) += davicom.o
+obj-$(CONFIG_CICADA_PHY) += cicada.o
+obj-$(CONFIG_LXT_PHY) += lxt.o
+obj-$(CONFIG_QSEMI_PHY) += qsemi.o
diff -Nru a/drivers/net/phy/cicada.c b/drivers/net/phy/cicada.c
--- /dev/null Wed Dec 31 16:00:00 196900
+++ b/drivers/net/phy/cicada.c 2005-05-09 13:19:26 -05:00
@@ -0,0 +1,134 @@
+/*
+ * drivers/net/phy/cicada.c
+ *
+ * Driver for Cicada PHYs
+ *
+ * Author: Andy Fleming
+ *
+ * Copyright (c) 2004 Freescale Semiconductor, 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/unistd.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/module.h>
+#include <linux/version.h>
+#include <linux/mii.h>
+#include <linux/ethtool.h>
+#include <linux/phy.h>
+
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/uaccess.h>
+
+/* Cicada Extended Control Register 1 */
+#define MII_CIS8201_EXT_CON1 0x17
+#define MII_CIS8201_EXTCON1_INIT 0x0000
+
+/* Cicada Interrupt Mask Register */
+#define MII_CIS8201_IMASK 0x19
+#define MII_CIS8201_IMASK_IEN 0x8000
+#define MII_CIS8201_IMASK_SPEED 0x4000
+#define MII_CIS8201_IMASK_LINK 0x2000
+#define MII_CIS8201_IMASK_DUPLEX 0x1000
+#define MII_CIS8201_IMASK_MASK 0xf000
+
+/* Cicada Interrupt Status Register */
+#define MII_CIS8201_ISTAT 0x1a
+#define MII_CIS8201_ISTAT_STATUS 0x8000
+#define MII_CIS8201_ISTAT_SPEED 0x4000
+#define MII_CIS8201_ISTAT_LINK 0x2000
+#define MII_CIS8201_ISTAT_DUPLEX 0x1000
+
+/* Cicada Auxiliary Control/Status Register */
+#define MII_CIS8201_AUX_CONSTAT 0x1c
+#define MII_CIS8201_AUXCONSTAT_INIT 0x0004
+#define MII_CIS8201_AUXCONSTAT_DUPLEX 0x0020
+#define MII_CIS8201_AUXCONSTAT_SPEED 0x0018
+#define MII_CIS8201_AUXCONSTAT_GBIT 0x0010
+#define MII_CIS8201_AUXCONSTAT_100 0x0008
+
+
+static int cis820x_probe(struct phy_device *phydev)
+{
+ int err;
+
+ err = phy_write(phydev, MII_CIS8201_AUX_CONSTAT,
+ MII_CIS8201_AUXCONSTAT_INIT);
+
+ if (err < 0)
+ return err;
+
+ err = phy_write(phydev, MII_CIS8201_EXT_CON1,
+ MII_CIS8201_EXTCON1_INIT);
+
+ return err;
+}
+
+static int cis820x_ack_interrupt(struct phy_device *phydev)
+{
+ int err = phy_read(phydev, MII_CIS8201_ISTAT);
+
+ return (err < 0) ? err : 0;
+}
+
+static int cis820x_config_intr(struct phy_device *phydev)
+{
+ int err;
+
+ if(phydev->interrupts == PHY_INTERRUPT_ENABLED)
+ err = phy_write(phydev, MII_CIS8201_IMASK,
+ MII_CIS8201_IMASK_MASK);
+ else
+ err = phy_write(phydev, MII_CIS8201_IMASK, 0);
+
+ return err;
+}
+
+/* Cicada 820x */
+static struct phy_driver cis8204_driver = {
+ 0x000fc440,
+ "Cicada Cis8204",
+ 0x000fffc0,
+ .features = PHY_GBIT_FEATURES,
+ .flags = PHY_HAS_INTERRUPT,
+ .probe = &cis820x_probe,
+ .config_aneg = &genphy_config_aneg,
+ .read_status = &genphy_read_status,
+ .ack_interrupt = &cis820x_ack_interrupt,
+ .config_intr = &cis820x_config_intr,
+};
+
+int __init cis8204_init(void)
+{
+ int retval;
+
+ retval = phy_driver_register(&cis8204_driver);
+
+ return retval;
+}
+
+static void __exit cis8204_exit(void)
+{
+ phy_driver_unregister(&cis8204_driver);
+}
+
+module_init(cis8204_init);
+module_exit(cis8204_exit);
diff -Nru a/drivers/net/phy/davicom.c b/drivers/net/phy/davicom.c
--- /dev/null Wed Dec 31 16:00:00 196900
+++ b/drivers/net/phy/davicom.c 2005-05-09 13:19:26 -05:00
@@ -0,0 +1,275 @@
+/*
+ * drivers/net/phy/davicom.c
+ *
+ * Driver for Davicom PHYs
+ *
+ * Author: Andy Fleming
+ *
+ * Copyright (c) 2004 Freescale Semiconductor, 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/unistd.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/module.h>
+#include <linux/version.h>
+#include <linux/mii.h>
+#include <linux/ethtool.h>
+#include <linux/phy.h>
+
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/uaccess.h>
+
+#define MII_DM9161_SCR 0x10
+#define MII_DM9161_SCR_INIT 0x0610
+
+/* DM9161 Interrupt Register */
+#define MII_DM9161_INTR 0x15
+#define MII_DM9161_INTR_PEND 0x8000
+#define MII_DM9161_INTR_DPLX_MASK 0x0800
+#define MII_DM9161_INTR_SPD_MASK 0x0400
+#define MII_DM9161_INTR_LINK_MASK 0x0200
+#define MII_DM9161_INTR_MASK 0x0100
+#define MII_DM9161_INTR_DPLX_CHANGE 0x0010
+#define MII_DM9161_INTR_SPD_CHANGE 0x0008
+#define MII_DM9161_INTR_LINK_CHANGE 0x0004
+#define MII_DM9161_INTR_INIT 0x0000
+#define MII_DM9161_INTR_STOP \
+(MII_DM9161_INTR_DPLX_MASK | MII_DM9161_INTR_SPD_MASK \
+ | MII_DM9161_INTR_LINK_MASK | MII_DM9161_INTR_MASK)
+
+/* DM9161 10BT Configuration/Status */
+#define MII_DM9161_10BTCSR 0x12
+#define MII_DM9161_10BTCSR_INIT 0x7800
+
+struct dm9161_private {
+ struct timer_list timer;
+ int resetdone;
+};
+
+#define DM9161_DELAY 1
+int dm9161_config_intr(struct phy_device *phydev)
+{
+ int temp;
+
+ temp = phy_read(phydev, MII_DM9161_INTR);
+
+ if (temp < 0)
+ return temp;
+
+ if(PHY_INTERRUPT_ENABLED == phydev->interrupts )
+ temp &= ~(MII_DM9161_INTR_STOP);
+ else
+ temp |= MII_DM9161_INTR_STOP;
+
+ temp = phy_write(phydev, MII_DM9161_INTR, temp);
+
+ return temp;
+}
+
+
+#if 0
+static void dm9161_timer(unsigned long data)
+{
+ struct phy_device *phydev = (struct phy_device *)data;
+ struct dm9161_private *priv = phydev->priv;
+ int status = phy_read(phydev, MII_BMSR);
+
+ if (status < 0) {
+ mod_timer(&priv->timer, jiffies + DM9161_DELAY * HZ);
+ return;
+ }
+
+ spin_lock(&phydev->lock);
+ if (status & BMSR_ANEGCOMPLETE) {
+ if (PHY_PENDING == phydev->state)
+ phydev->state = PHY_UP;
+ else
+ phydev->state = PHY_READY;
+ } else
+ mod_timer(&priv->timer, jiffies + DM9161_DELAY * HZ);
+
+ spin_unlock(&phydev->lock);
+}
+#endif
+
+
+static int dm9161_config_aneg(struct phy_device *phydev)
+{
+ int err;
+
+ /* Isolate the PHY */
+ err = phy_write(phydev, MII_BMCR, BMCR_ISOLATE);
+
+ if (err < 0)
+ return err;
+
+ /* Configure the new settings */
+ err = genphy_config_advert(phydev);
+
+ if (err < 0)
+ return err;
+
+ /* Reconnect the PHY, and enable Autonegotiation */
+ err = phy_write(phydev, MII_BMCR, BMCR_ANENABLE);
+
+ if (err < 0)
+ return err;
+
+#if 0
+ /* Start a timer for DM9161_DELAY seconds to wait
+ * for the PHY to be ready */
+ init_timer(&priv->timer);
+ priv->timer.function = &dm9161_timer;
+ priv->timer.data = (unsigned long) phydev;
+ mod_timer(&priv->timer, jiffies + DM9161_DELAY * HZ);
+#endif
+
+ return 0;
+}
+
+static int dm9161_probe(struct phy_device *phydev)
+{
+ struct dm9161_private *priv;
+ int err;
+
+ /* Allocate the private data structure */
+ priv = kmalloc(sizeof(struct dm9161_private), GFP_KERNEL);
+
+ if (NULL == priv)
+ return -ENOMEM;
+
+ phydev->priv = priv;
+
+ /* Reset is not done yet */
+ priv->resetdone = 0;
+
+ /* Isolate the PHY */
+ err = phy_write(phydev, MII_BMCR, BMCR_ISOLATE);
+
+ if (err < 0)
+ return err;
+
+ /* Do not bypass the scrambler/descrambler */
+ err = phy_write(phydev, MII_DM9161_SCR, MII_DM9161_SCR_INIT);
+
+ if (err < 0)
+ return err;
+
+ /* Clear 10BTCSR to default */
+ err = phy_write(phydev, MII_DM9161_10BTCSR, MII_DM9161_10BTCSR_INIT);
+
+ if (err < 0)
+ return err;
+
+ /* Reconnect the PHY, and enable Autonegotiation */
+ err = phy_write(phydev, MII_BMCR, BMCR_ANENABLE);
+
+ if (err < 0)
+ return err;
+
+#if 0
+ phydev->state = PHY_STARTING;
+
+ /* Start a timer for DM9161_DELAY seconds to wait
+ * for the PHY to be ready */
+ init_timer(&priv->timer);
+ priv->timer.function = &dm9161_timer;
+ priv->timer.data = (unsigned long) phydev;
+ mod_timer(&priv->timer, jiffies + DM9161_DELAY * HZ);
+
+ pr_info("Bringing up a Davicom PHY, "
+ "this could take a while...\n");
+#endif
+ return 0;
+}
+
+static void dm9161_remove(struct phy_device *phydev)
+{
+ struct dm9161_private *priv = phydev->priv;
+
+// del_timer_sync(&priv->timer);
+ kfree(priv);
+}
+
+static int dm9161_ack_interrupt(struct phy_device *phydev)
+{
+ int err = phy_read(phydev, MII_DM9161_INTR);
+
+ return (err < 0) ? err : 0;
+}
+
+static struct phy_driver dm9161_driver = {
+ .phy_id = 0x0181b880,
+ .name = "Davicom DM9161E",
+ .phy_id_mask = 0x0ffffff0,
+ .features = PHY_BASIC_FEATURES,
+ .probe = dm9161_probe,
+ .config_aneg = dm9161_config_aneg,
+ .read_status = genphy_read_status,
+ .remove = dm9161_remove,
+};
+
+static struct phy_driver dm9131_driver = {
+ .phy_id = 0x00181b80,
+ .name = "Davicom DM9131",
+ .phy_id_mask = 0x0ffffff0,
+ .features = PHY_BASIC_FEATURES,
+ .flags = PHY_HAS_INTERRUPT,
+ .config_aneg = genphy_config_aneg,
+ .read_status = genphy_read_status,
+ .ack_interrupt = dm9161_ack_interrupt,
+ .config_intr = dm9161_config_intr,
+};
+
+int __init dm9161_init(void)
+{
+ int retval;
+
+ retval = phy_driver_register(&dm9161_driver);
+
+ return retval;
+}
+
+static void __exit dm9161_exit(void)
+{
+ phy_driver_unregister(&dm9161_driver);
+}
+
+module_init(dm9161_init);
+module_exit(dm9161_exit);
+
+int __init dm9131_init(void)
+{
+ int retval;
+
+ retval = phy_driver_register(&dm9131_driver);
+
+ return retval;
+}
+
+static void __exit dm9131_exit(void)
+{
+ phy_driver_unregister(&dm9131_driver);
+}
+
+module_init(dm9131_init);
+module_exit(dm9131_exit);
diff -Nru a/drivers/net/phy/lxt.c b/drivers/net/phy/lxt.c
--- /dev/null Wed Dec 31 16:00:00 196900
+++ b/drivers/net/phy/lxt.c 2005-05-09 13:19:26 -05:00
@@ -0,0 +1,179 @@
+/*
+ * drivers/net/phy/lxt.c
+ *
+ * Driver for Intel LXT PHYs
+ *
+ * Author: Andy Fleming
+ *
+ * Copyright (c) 2004 Freescale Semiconductor, 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/unistd.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/module.h>
+#include <linux/version.h>
+#include <linux/mii.h>
+#include <linux/ethtool.h>
+#include <linux/phy.h>
+
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/uaccess.h>
+
+/* The Level one LXT970 is used by many boards */
+
+#define MII_LXT970_IER 17 /* Interrupt Enable Register */
+
+#define MII_LXT970_IER_IEN 0x0002
+
+#define MII_LXT970_ISR 18 /* Interrupt Status Register */
+
+#define MII_LXT970_CONFIG 19 /* Configuration Register */
+
+/* ------------------------------------------------------------------------- */
+/* The Level one LXT971 is used on some of my custom boards */
+
+/* register definitions for the 971 */
+#define MII_LXT971_IER 18 /* Interrupt Enable Register */
+#define MII_LXT971_IER_IEN 0x00f2
+
+#define MII_LXT971_ISR 19 /* Interrupt Status Register */
+
+
+static int lxt970_ack_interrupt(struct phy_device *phydev)
+{
+ int err;
+
+ err = phy_read(phydev, MII_BMSR);
+
+ if (err < 0)
+ return err;
+
+ err = phy_read(phydev, MII_LXT970_ISR);
+
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+static int lxt970_config_intr(struct phy_device *phydev)
+{
+ int err;
+
+ if(phydev->interrupts == PHY_INTERRUPT_ENABLED)
+ err = phy_write(phydev, MII_LXT970_IER, MII_LXT970_IER_IEN);
+ else
+ err = phy_write(phydev, MII_LXT970_IER, 0);
+
+ return err;
+}
+
+static int lxt970_probe(struct phy_device *phydev)
+{
+ int err;
+
+ err = phy_write(phydev, MII_LXT970_CONFIG, 0);
+
+ return err;
+}
+
+
+static int lxt971_ack_interrupt(struct phy_device *phydev)
+{
+ int err = phy_read(phydev, MII_LXT971_ISR);
+
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+static int lxt971_config_intr(struct phy_device *phydev)
+{
+ int err;
+
+ if(phydev->interrupts == PHY_INTERRUPT_ENABLED)
+ err = phy_write(phydev, MII_LXT971_IER, MII_LXT971_IER_IEN);
+ else
+ err = phy_write(phydev, MII_LXT971_IER, 0);
+
+ return err;
+}
+
+static struct phy_driver lxt970_driver = {
+ .phy_id = 0x07810000,
+ .name = "LXT970",
+ .phy_id_mask = 0x0fffffff,
+ .features = PHY_BASIC_FEATURES,
+ .flags = PHY_HAS_INTERRUPT,
+ .probe = lxt970_probe,
+ .config_aneg = genphy_config_aneg,
+ .read_status = genphy_read_status,
+ .ack_interrupt = lxt970_ack_interrupt,
+ .config_intr = lxt970_config_intr,
+};
+
+static struct phy_driver lxt971_driver = {
+ .phy_id = 0x0001378e,
+ .name = "LXT971",
+ .phy_id_mask = 0x0fffffff,
+ .features = PHY_BASIC_FEATURES,
+ .flags = PHY_HAS_INTERRUPT,
+ .config_aneg = genphy_config_aneg,
+ .read_status = genphy_read_status,
+ .ack_interrupt = lxt971_ack_interrupt,
+ .config_intr = lxt971_config_intr,
+};
+
+int __init lxt970_init(void)
+{
+ int retval;
+
+ retval = phy_driver_register(&lxt970_driver);
+
+ return retval;
+}
+
+static void __exit lxt970_exit(void)
+{
+ phy_driver_unregister(&lxt970_driver);
+}
+
+module_init(lxt970_init);
+module_exit(lxt970_exit);
+
+int __init lxt971_init(void)
+{
+ int retval;
+
+ retval = phy_driver_register(&lxt971_driver);
+
+ return retval;
+}
+
+static void __exit lxt971_exit(void)
+{
+ phy_driver_unregister(&lxt971_driver);
+}
+
+module_init(lxt971_init);
+module_exit(lxt971_exit);
diff -Nru a/drivers/net/phy/marvell.c b/drivers/net/phy/marvell.c
--- /dev/null Wed Dec 31 16:00:00 196900
+++ b/drivers/net/phy/marvell.c 2005-05-09 13:19:26 -05:00
@@ -0,0 +1,139 @@
+/*
+ * drivers/net/phy/marvell.c
+ *
+ * Driver for Marvell PHYs
+ *
+ * Author: Andy Fleming
+ *
+ * Copyright (c) 2004 Freescale Semiconductor, 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/unistd.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/module.h>
+#include <linux/version.h>
+#include <linux/mii.h>
+#include <linux/ethtool.h>
+#include <linux/phy.h>
+
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/uaccess.h>
+
+#define MII_M1011_IEVENT 0x13
+#define MII_M1011_IEVENT_CLEAR 0x0000
+
+#define MII_M1011_IMASK 0x12
+#define MII_M1011_IMASK_INIT 0x6400
+#define MII_M1011_IMASK_CLEAR 0x0000
+
+static int marvell_ack_interrupt(struct phy_device *phydev)
+{
+ int err;
+
+ /* Clear the interrupts by reading the reg */
+ err = phy_read(phydev, MII_M1011_IEVENT);
+
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+static int marvell_config_intr(struct phy_device *phydev)
+{
+ int err;
+
+ if(phydev->interrupts == PHY_INTERRUPT_ENABLED)
+ err = phy_write(phydev, MII_M1011_IMASK, MII_M1011_IMASK_INIT);
+ else
+ err = phy_write(phydev, MII_M1011_IMASK, MII_M1011_IMASK_CLEAR);
+
+ return err;
+}
+
+static int marvell_config_aneg(struct phy_device *phydev)
+{
+ int err;
+
+ /* The Marvell PHY has an errata which requires
+ * that certain registers get written in order
+ * to restart autonegotiation */
+ err = phy_write(phydev, MII_BMCR, BMCR_RESET);
+
+ if (err < 0)
+ return err;
+
+ err = phy_write(phydev, 0x1d, 0x1f);
+ if (err < 0)
+ return err;
+
+ err = phy_write(phydev, 0x1e, 0x200c);
+ if (err < 0)
+ return err;
+
+ err = phy_write(phydev, 0x1d, 0x5);
+ if (err < 0)
+ return err;
+
+ err = phy_write(phydev, 0x1e, 0);
+ if (err < 0)
+ return err;
+
+ err = phy_write(phydev, 0x1e, 0x100);
+ if (err < 0)
+ return err;
+
+
+ err = genphy_config_aneg(phydev);
+
+ return err;
+}
+
+
+static struct phy_driver m88e1101_driver = {
+ .phy_id = 0x01410c00,
+ .phy_id_mask = 0xffffff00,
+ .name = "Marvell 88E1101",
+ .features = PHY_GBIT_FEATURES,
+ .flags = PHY_HAS_INTERRUPT,
+ .config_aneg = &marvell_config_aneg,
+ .read_status = &genphy_read_status,
+ .ack_interrupt = &marvell_ack_interrupt,
+ .config_intr = &marvell_config_intr,
+};
+
+int __init marvell_init(void)
+{
+ int retval;
+
+ retval = phy_driver_register(&m88e1101_driver);
+
+ return retval;
+}
+
+static void __exit marvell_exit(void)
+{
+ phy_driver_unregister(&m88e1101_driver);
+}
+
+module_init(marvell_init);
+module_exit(marvell_exit);
diff -Nru a/drivers/net/phy/mdio_bus.c b/drivers/net/phy/mdio_bus.c
--- /dev/null Wed Dec 31 16:00:00 196900
+++ b/drivers/net/phy/mdio_bus.c 2005-05-09 13:19:26 -05:00
@@ -0,0 +1,175 @@
+/*
+ * drivers/net/phy/mdio_bus.c
+ *
+ * MDIO Bus interface
+ *
+ * Author: Andy Fleming
+ *
+ * Copyright (c) 2004 Freescale Semiconductor, 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/unistd.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/module.h>
+#include <linux/version.h>
+#include <linux/mii.h>
+#include <linux/ethtool.h>
+#include <linux/phy.h>
+
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/uaccess.h>
+
+/* register_mdiobus
+ *
+ * description: Called by a bus driver to bring up all the PHYs
+ * on a given bus, and attach them to the bus
+ */
+int mdiobus_register(struct mii_bus *bus)
+{
+ int i;
+ int err = 0;
+
+ spin_lock_init(&bus->mdio_lock);
+
+ if (NULL == bus || NULL == bus->name ||
+ NULL == bus->read ||
+ NULL == bus->write)
+ return -EINVAL;
+
+ if (bus->reset)
+ bus->reset(bus);
+
+ for (i=0; i < PHY_MAX_ADDR; i++) {
+ struct phy_device *phydev;
+
+ phydev = get_phy_device(bus, i);
+
+ /* There's a PHY at this address
+ * We need to set:
+ * 1) IRQ
+ * 2) bus_id
+ * 3) parent
+ * 4) bus
+ * 5) mii_bus
+ * And, we need to register it */
+ if (phydev) {
+ phydev->irq = bus->irq[i];
+
+ phydev->dev.parent = bus->dev;
+
+ phydev->dev.bus = &mdio_bus_type;
+
+ phydev->bus = bus;
+
+ sprintf(phydev->dev.bus_id, "phy%d:%d", bus->id, i);
+
+ err = device_register(&phydev->dev);
+
+ if (err)
+ printk("phy %d did not register (%d)\n",
+ i, err);
+
+ /* If get_phy_device returned NULL, it may be
+ * because an error occurred. If so, we return
+ * that error */
+ } else if (errno)
+ return errno;
+
+ bus->phy_map[i] = phydev;
+ }
+
+ pr_info("%s: probed\n", bus->name);
+
+ return err;
+}
+EXPORT_SYMBOL(mdiobus_register);
+
+void mdiobus_unregister(struct mii_bus *bus)
+{
+ int i;
+
+ for (i=0; i < PHY_MAX_ADDR; i++)
+ if (bus->phy_map[i]) {
+ device_unregister(&bus->phy_map[i]->dev);
+ kfree(bus->phy_map[i]);
+ }
+
+}
+EXPORT_SYMBOL(mdiobus_unregister);
+
+/* mdio_bus_match
+ *
+ * description: Given a PHY device, and a PHY driver, return 1 if
+ * the driver supports the device. Otherwise, return 0
+ */
+int mdio_bus_match(struct device *dev, struct device_driver *drv)
+{
+ struct phy_device *phydev = to_phy_device(dev);
+ struct phy_driver *phydrv = to_phy_driver(drv);
+
+ return (phydrv->phy_id == (phydev->phy_id & phydrv->phy_id_mask));
+}
+
+/* Suspend and resume. Copied from platform_suspend and
+ * platform_resume
+ */
+static int mdio_bus_suspend(struct device * dev, u32 state)
+{
+ int ret = 0;
+
+ if (dev->driver && dev->driver->suspend) {
+ ret = dev->driver->suspend(dev, state, SUSPEND_DISABLE);
+ if (ret == 0)
+ ret = dev->driver->suspend(dev, state, SUSPEND_SAVE_STATE);
+ if (ret == 0)
+ ret = dev->driver->suspend(dev, state, SUSPEND_POWER_DOWN);
+ }
+ return ret;
+}
+
+static int mdio_bus_resume(struct device * dev)
+{
+ int ret = 0;
+
+ if (dev->driver && dev->driver->resume) {
+ ret = dev->driver->resume(dev, RESUME_POWER_ON);
+ if (ret == 0)
+ ret = dev->driver->resume(dev, RESUME_RESTORE_STATE);
+ if (ret == 0)
+ ret = dev->driver->resume(dev, RESUME_ENABLE);
+ }
+ return ret;
+}
+
+struct bus_type mdio_bus_type = {
+ .name = "mdio_bus",
+ .match = mdio_bus_match,
+ .suspend= mdio_bus_suspend,
+ .resume = mdio_bus_resume,
+};
+
+int __init mdio_bus_init(void)
+{
+ return bus_register(&mdio_bus_type);
+}
+
+subsys_initcall(mdio_bus_init);
diff -Nru a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c
--- /dev/null Wed Dec 31 16:00:00 196900
+++ b/drivers/net/phy/phy.c 2005-05-09 13:19:26 -05:00
@@ -0,0 +1,819 @@
+/*
+ * drivers/net/phy/phy.c
+ *
+ * Framework for configuring and reading PHY devices
+ * Based on code in sungem_phy.c and gianfar_phy.c
+ *
+ * Author: Andy Fleming
+ *
+ * Copyright (c) 2004 Freescale Semiconductor, 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/unistd.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/module.h>
+#include <linux/version.h>
+#include <linux/mii.h>
+#include <linux/ethtool.h>
+#include <linux/phy.h>
+
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/uaccess.h>
+
+int phy_read(struct phy_device *phydev, u16 regnum);
+int phy_write(struct phy_device *phydev, u16 regnum, u16 val);
+void phy_change(void *data);
+void phy_timer(unsigned long data);
+
+/* Convenience functions for reading/writing a given PHY
+ * register. They MUST NOT be called from interrupt context,
+ * because the bus read/write functions may wait for an interrupt
+ * to conclude the operation. */
+int phy_read(struct phy_device *phydev, u16 regnum)
+{
+ int retval;
+ struct mii_bus *bus = phydev->bus;
+
+ spin_lock_bh(&bus->mdio_lock);
+ retval = bus->read(bus, phydev->addr, regnum);
+ spin_unlock_bh(&bus->mdio_lock);
+
+ return retval;
+}
+
+int phy_write(struct phy_device *phydev, u16 regnum, u16 val)
+{
+ int err;
+ struct mii_bus *bus = phydev->bus;
+
+ spin_lock_bh(&bus->mdio_lock);
+ err = bus->write(bus, phydev->addr, regnum, val);
+ spin_unlock_bh(&bus->mdio_lock);
+
+ return err;
+}
+
+
+int phy_clear_interrupt(struct phy_device *phydev)
+{
+ int err = 0;
+
+ if (phydev->drv->ack_interrupt)
+ err = phydev->drv->ack_interrupt(phydev);
+
+ return err;
+}
+
+
+int phy_config_interrupt(struct phy_device *phydev, u32 interrupts)
+{
+ int err = 0;
+
+ phydev->interrupts = interrupts;
+ if (phydev->drv->config_intr)
+ err = phydev->drv->config_intr(phydev);
+
+ return err;
+}
+
+
+static inline int phy_read_status(struct phy_device *phydev)
+{
+ return phydev->drv->read_status(phydev);
+}
+
+/* phy_aneg_done
+ *
+ * description: Reads the status register and returns 0 either if
+ * auto-negotiation is incomplete, or if there was an error.
+ * Returns BMSR_ANEGCOMPLETE if auto-negotiation is done.
+ */
+static inline int phy_aneg_done(struct phy_device *phydev)
+{
+ int retval;
+
+ retval = phy_read(phydev, MII_BMSR);
+
+ if (retval < 0)
+ return retval;
+
+ return retval & BMSR_ANEGCOMPLETE;
+}
+
+/* phy_start_aneg
+ *
+ * description: Calls the PHY driver's config_aneg, and then
+ * sets the PHY state to PHY_AN if auto-negotiation is enabled,
+ * and to PHY_FORCING if auto-negotiation is disabled. Unless
+ * the PHY is currently HALTED.
+ */
+int phy_start_aneg(struct phy_device *phydev)
+{
+ int err = 0;
+
+ spin_lock(&phydev->lock);
+
+ if (AUTONEG_DISABLE == phydev->autoneg)
+ phy_sanitize_settings(phydev);
+
+ err = phydev->drv->config_aneg(phydev);
+
+ if (err < 0)
+ return err;
+
+ if (phydev->state != PHY_HALTED) {
+ if (AUTONEG_ENABLE == phydev->autoneg) {
+ phydev->state = PHY_AN;
+ phydev->link_timeout = PHY_AN_TIMEOUT;
+ } else {
+ phydev->state = PHY_FORCING;
+ phydev->link_timeout = PHY_FORCE_TIMEOUT;
+ }
+ }
+
+ spin_unlock(&phydev->lock);
+
+ return err;
+}
+
+
+/* A structure for mapping a particular speed and duplex
+ * combination to a particular SUPPORTED and ADVERTISED value */
+struct phy_setting {
+ int speed;
+ int duplex;
+ u32 setting;
+};
+
+/* A mapping of all SUPPORTED settings to speed/duplex */
+static struct phy_setting settings[] = {
+ { .speed = 10000, .duplex = DUPLEX_FULL,
+ .setting = SUPPORTED_10000baseT_Full,
+ },
+ { .speed = SPEED_1000, .duplex = DUPLEX_FULL,
+ .setting = SUPPORTED_1000baseT_Full,
+ },
+ { .speed = SPEED_1000, .duplex = DUPLEX_HALF,
+ .setting = SUPPORTED_1000baseT_Half,
+ },
+ { .speed = SPEED_100, .duplex = DUPLEX_FULL,
+ .setting = SUPPORTED_100baseT_Full,
+ },
+ { .speed = SPEED_100, .duplex = DUPLEX_HALF,
+ .setting = SUPPORTED_100baseT_Half,
+ },
+ { .speed = SPEED_10, .duplex = DUPLEX_FULL,
+ .setting = SUPPORTED_10baseT_Full,
+ },
+ { .speed = SPEED_10, .duplex = DUPLEX_HALF,
+ .setting = SUPPORTED_10baseT_Half,
+ },
+};
+
+#define MAX_NUM_SETTINGS (sizeof(settings)/sizeof(struct phy_setting))
+
+/* phy_find_setting
+ *
+ * description: Searches the settings array for the setting which
+ * matches the desired speed and duplex, and returns the index
+ * of that setting. Returns the index of the last setting if
+ * none of the others match.
+ */
+static inline int phy_find_setting(int speed, int duplex)
+{
+ int idx = 0;
+
+ while (idx < MAX_NUM_SETTINGS &&
+ (settings[idx].speed != speed ||
+ settings[idx].duplex != duplex))
+ idx++;
+
+ return idx < MAX_NUM_SETTINGS ? idx : MAX_NUM_SETTINGS - 1;
+}
+
+/* phy_find_valid
+ * idx: The first index in settings[] to search
+ * features: A mask of the valid settings
+ *
+ * description: Returns the index of the first valid setting less
+ * than or equal to the one pointed to by idx, as determined by
+ * the mask in features. Returns the index of the last setting
+ * if nothing else matches.
+ */
+static inline int phy_find_valid(int idx, u32 features)
+{
+ while (idx < MAX_NUM_SETTINGS && !(settings[idx].setting & features))
+ idx++;
+
+ return idx < MAX_NUM_SETTINGS ? idx : MAX_NUM_SETTINGS - 1;
+}
+
+/* phy_sanitize_settings
+ *
+ * description: Make sure the PHY is set to supported speeds and
+ * duplexes. Drop down by one in this order: 1000/FULL,
+ * 1000/HALF, 100/FULL, 100/HALF, 10/FULL, 10/HALF
+ */
+void phy_sanitize_settings(struct phy_device *phydev)
+{
+ u32 features = phydev->supported;
+ int idx;
+
+ /* Sanitize settings based on PHY capabilities */
+ if ((features & SUPPORTED_Autoneg) == 0)
+ phydev->autoneg = 0;
+
+ idx = phy_find_valid(phy_find_setting(phydev->speed, phydev->duplex),
+ features);
+
+ phydev->speed = settings[idx].speed;
+ phydev->duplex = settings[idx].duplex;
+}
+
+/* phy_force_reduction
+ *
+ * description: Reduces the speed/duplex settings by
+ * one notch. The order is so:
+ * 1000/FULL, 1000/HALF, 100/FULL, 100/HALF,
+ * 10/FULL, 10/HALF. The function bottoms out at 10/HALF.
+ */
+void phy_force_reduction(struct phy_device *phydev)
+{
+ int idx;
+
+ idx = phy_find_setting(phydev->speed, phydev->duplex);
+
+ idx++;
+
+ idx = phy_find_valid(idx, phydev->supported);
+
+ phydev->speed = settings[idx].speed;
+ phydev->duplex = settings[idx].duplex;
+
+ pr_info("Trying %d/%s\n", phydev->speed,
+ DUPLEX_FULL == phydev->duplex ?
+ "FULL" : "HALF");
+}
+
+/* phy_ethtool_sset:
+ * A generic ethtool sset function. Handles all the details
+ *
+ * A few notes about parameter checking:
+ * - We don't set port or transceiver, so we don't care what they
+ * were set to.
+ * - phy_start_aneg() will make sure forced settings are sane, and
+ * choose the next best ones from the ones selected, so we don't
+ * care if ethtool tries to give us bad values
+ */
+int phy_ethtool_sset(struct phy_device *phydev, struct ethtool_cmd *cmd)
+{
+ if (cmd->phy_address != phydev->addr)
+ return -EINVAL;
+
+ /* We make sure that we don't pass unsupported
+ * values in to the PHY */
+ cmd->advertising &= phydev->supported;
+
+ /* Verify the settings we care about. */
+ if (cmd->autoneg != AUTONEG_ENABLE && cmd->autoneg != AUTONEG_DISABLE)
+ return -EINVAL;
+
+ if (cmd->autoneg == AUTONEG_ENABLE && cmd->advertising == 0)
+ return -EINVAL;
+
+ if (cmd->autoneg == AUTONEG_DISABLE
+ && ((cmd->speed != SPEED_1000
+ && cmd->speed != SPEED_100
+ && cmd->speed != SPEED_10)
+ || (cmd->duplex != DUPLEX_HALF
+ && cmd->duplex != DUPLEX_FULL)))
+ return -EINVAL;
+
+ phydev->autoneg = cmd->autoneg;
+
+ phydev->speed = cmd->speed;
+
+ phydev->advertising = cmd->advertising;
+
+ if (AUTONEG_ENABLE == cmd->autoneg)
+ phydev->advertising |= ADVERTISED_Autoneg;
+ else
+ phydev->advertising &= ~ADVERTISED_Autoneg;
+
+ phydev->duplex = cmd->duplex;
+
+ /* Restart the PHY */
+ phy_start_aneg(phydev);
+
+ return 0;
+}
+
+int phy_ethtool_gset(struct phy_device *phydev, struct ethtool_cmd *cmd)
+{
+ cmd->supported = phydev->supported;
+
+ cmd->advertising = phydev->advertising;
+
+ cmd->speed = phydev->speed;
+ cmd->duplex = phydev->duplex;
+ cmd->port = PORT_MII;
+ cmd->phy_address = phydev->addr;
+ cmd->transceiver = XCVR_EXTERNAL;
+ cmd->autoneg = phydev->autoneg;
+
+ return 0;
+}
+
+
+/* Note that this function is currently incompatible with the
+ * PHYCONTROL layer. It changes registers without regard to
+ * current state. Use at own risk
+ */
+int phy_mii_ioctl(struct phy_device *phydev,
+ struct mii_ioctl_data *mii_data, int cmd)
+{
+ u16 val = mii_data->val_in;
+
+ switch (cmd) {
+ case SIOCGMIIPHY:
+ mii_data->phy_id = phydev->addr;
+ break;
+ case SIOCGMIIREG:
+ mii_data->val_out = phy_read(phydev, mii_data->reg_num);
+ break;
+
+ case SIOCSMIIREG:
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+
+ if (mii_data->phy_id == phydev->addr) {
+ switch(mii_data->reg_num) {
+ case MII_BMCR:
+ if (val & (BMCR_RESET|BMCR_ANENABLE))
+ phydev->autoneg = AUTONEG_DISABLE;
+ else
+ phydev->autoneg = AUTONEG_ENABLE;
+ if ((!phydev->autoneg) && (val & BMCR_FULLDPLX))
+ phydev->duplex = DUPLEX_FULL;
+ else
+ phydev->duplex = DUPLEX_HALF;
+ break;
+ case MII_ADVERTISE:
+ phydev->advertising = val;
+ break;
+ default:
+ /* do nothing */
+ break;
+ }
+ }
+
+ phy_write(phydev, mii_data->reg_num, val);
+ break;
+ }
+
+ return 0;
+}
+
+/* phy_start_machine:
+ *
+ * description: The PHY infrastructure can run a state machine
+ * which tracks whether the PHY is starting up, negotiating,
+ * etc. This function starts the timer which tracks the state
+ * of the PHY. If you want to be notified when the state
+ * changes, pass in the callback, otherwise, pass NULL. If you
+ * want to maintain your own state machine, do not call this
+ * function. */
+void phy_start_machine(struct phy_device *phydev,
+ void (*handler)(struct net_device *))
+{
+ phydev->adjust_state = handler;
+
+ init_timer(&phydev->phy_timer);
+ phydev->phy_timer.function = &phy_timer;
+ phydev->phy_timer.data = (unsigned long) phydev;
+ mod_timer(&phydev->phy_timer, jiffies + HZ);
+}
+
+/* phy_stop_machine
+ *
+ * description: Stops the state machine timer, sets the state to
+ * UP (unless it wasn't up yet), and then frees the interrupt,
+ * if it is in use. This function must be called BEFORE
+ * phy_detach.
+ */
+void phy_stop_machine(struct phy_device *phydev)
+{
+ del_timer_sync(&phydev->phy_timer);
+
+ spin_lock(&phydev->lock);
+ if (phydev->state > PHY_UP)
+ phydev->state = PHY_UP;
+ spin_unlock(&phydev->lock);
+
+ if (phydev->irq != PHY_POLL) {
+ phy_config_interrupt(phydev, PHY_INTERRUPT_DISABLED);
+ phy_clear_interrupt(phydev);
+ free_irq(phydev->irq, phydev);
+ }
+
+ phydev->adjust_state = NULL;
+}
+
+
+#ifdef CONFIG_PHYCONTROL
+/* phy_error:
+ *
+ * Moves the PHY to the HALTED state in response to a read
+ * or write error, and tells the controller the link is down.
+ * Must not be called from interrupt context, or while the
+ * phydev->lock is held.
+ */
+void phy_error(struct phy_device *phydev)
+{
+ spin_lock(&phydev->lock);
+ phydev->state = PHY_HALTED;
+ spin_unlock(&phydev->lock);
+}
+
+/* phy_interrupt
+ *
+ * description: When a PHY interrupt occurs, the handler disables
+ * interrupts, and schedules a work task to clear the interrupt.
+ */
+static irqreturn_t phy_interrupt(int irq, void *phy_dat, struct pt_regs *regs)
+{
+ struct phy_device *phydev = phy_dat;
+
+ /* The MDIO bus is not allowed to be written in interrupt
+ * context, so we need to disable the irq here. A work
+ * queue will write the PHY to disable and clear the
+ * interrupt, and then reenable the irq line. */
+ disable_irq_nosync(irq);
+
+ schedule_work(&phydev->phy_queue);
+
+ return IRQ_HANDLED;
+}
+
+/* Enable the interrupts from the PHY side */
+int phy_enable_interrupts(struct phy_device *phydev)
+{
+ int err;
+
+ err = phy_clear_interrupt(phydev);
+
+ if (err < 0)
+ return err;
+
+ err = phy_config_interrupt(phydev, PHY_INTERRUPT_ENABLED);
+
+ return err;
+}
+
+/* Disable the PHY interrupts from the PHY side */
+int phy_disable_interrupts(struct phy_device *phydev)
+{
+ int err;
+
+ /* Disable PHY interrupts */
+ err = phy_config_interrupt(phydev, PHY_INTERRUPT_DISABLED);
+
+ if (err)
+ goto phy_err;
+
+ /* Clear the interrupt */
+ err = phy_clear_interrupt(phydev);
+
+ if (err)
+ goto phy_err;
+
+phy_err:
+ phy_error(phydev);
+}
+
+/* phy_start_interrupts
+ *
+ * description: Request the interrupt for the given PHY. If
+ * this fails, then we set irq to PHY_POLL.
+ * Otherwise, we enable the interrupts in the PHY.
+ * Returns 0 on success.
+ * This should only be called with a valid IRQ number.
+ */
+int phy_start_interrupts(struct phy_device *phydev)
+{
+ int err = 0;
+
+ INIT_WORK(&phydev->phy_queue, phy_change, phydev);
+
+ if (request_irq(phydev->irq, phy_interrupt,
+ SA_SHIRQ,
+ "phy_interrupt",
+ phydev) < 0) {
+ printk(KERN_ERR "%s: Can't get IRQ %d (PHY)\n",
+ phydev->bus->name,
+ phydev->irq);
+ phydev->irq = PHY_POLL;
+ return 0;
+ }
+
+ err = phy_enable_interrupts(phydev);
+
+ return err;
+}
+
+/* Scheduled by the phy_interrupt/timer to handle PHY changes */
+void phy_change(void *data)
+{
+ int err;
+ struct phy_device *phydev = data;
+
+ err = phy_disable_interrupts(phydev);
+
+ if (err)
+ goto phy_err;
+
+ spin_lock(&phydev->lock);
+ if ((PHY_RUNNING == phydev->state) || (PHY_NOLINK == phydev->state))
+ phydev->state = PHY_CHANGELINK;
+ spin_unlock(&phydev->lock);
+
+ enable_irq(phydev->irq);
+
+ /* Reenable interrupts */
+ err = phy_config_interrupt(phydev, PHY_INTERRUPT_ENABLED);
+
+ if (err)
+ goto irq_enable_err;
+
+ return;
+
+irq_enable_err:
+ disable_irq(phydev->irq);
+phy_err:
+ phy_error(phydev);
+}
+
+/* Bring down the PHY link, and stop checking the status. */
+void phy_stop(struct phy_device *phydev)
+{
+ spin_lock(&phydev->lock);
+
+ if (PHY_HALTED == phydev->state) {
+ spin_unlock(&phydev->lock);
+ return;
+ }
+
+ if (phydev->irq != PHY_POLL) {
+ /* Clear any pending interrupts */
+ phy_clear_interrupt(phydev);
+
+ /* Disable PHY Interrupts */
+ phy_config_interrupt(phydev, PHY_INTERRUPT_DISABLED);
+ }
+
+ phydev->state = PHY_HALTED;
+
+ spin_unlock(&phydev->lock);
+}
+
+
+/* phy_start
+ *
+ * description: Indicates the attached device's readiness to
+ * handle PHY-related work. Used during startup to start the
+ * PHY, and after a call to phy_stop() to resume operation.
+ * Also used to indicate the MDIO bus has cleared an error
+ * condition.
+ */
+void phy_start(struct phy_device *phydev)
+{
+ spin_lock(&phydev->lock);
+
+ switch (phydev->state) {
+ case PHY_STARTING:
+ phydev->state = PHY_PENDING;
+ break;
+ case PHY_READY:
+ phydev->state = PHY_UP;
+ break;
+ case PHY_HALTED:
+ phydev->state = PHY_RESUMING;
+ default:
+ break;
+ }
+ spin_unlock(&phydev->lock);
+}
+EXPORT_SYMBOL(phy_stop);
+EXPORT_SYMBOL(phy_start);
+
+/* PHY timer which handles the state machine */
+void phy_timer(unsigned long data)
+{
+ struct phy_device *phydev = (struct phy_device *)data;
+ int needs_aneg = 0;
+ int err = 0;
+
+ spin_lock(&phydev->lock);
+
+ if (phydev->adjust_state)
+ phydev->adjust_state(phydev->attached_dev);
+
+ switch(phydev->state) {
+ case PHY_DOWN:
+ case PHY_STARTING:
+ case PHY_READY:
+ case PHY_PENDING:
+ break;
+ case PHY_UP:
+ needs_aneg = 1;
+
+ phydev->link_timeout = PHY_AN_TIMEOUT;
+
+ /* Start interrupts if we're handling them */
+ if (phydev->irq >= 0)
+ err = phy_start_interrupts(phydev);
+
+ break;
+ case PHY_AN:
+ /* Check if negotiation is done. Break
+ * if there's an error */
+ err = phy_aneg_done(phydev);
+ if (err < 0)
+ break;
+
+ /* If auto-negotiation is done, we change to
+ * either RUNNING, or NOLINK */
+ if (err > 0) {
+ err = phy_read_status(phydev);
+
+ if (err)
+ break;
+
+ if (phydev->link) {
+ phydev->state = PHY_RUNNING;
+ netif_carrier_on(phydev->attached_dev);
+ } else {
+ phydev->state = PHY_NOLINK;
+ netif_carrier_off(phydev->attached_dev);
+ }
+
+ phydev->adjust_link(phydev->attached_dev);
+
+ } else if (0 == phydev->link_timeout--) {
+ /* The counter expired, so either we
+ * switch to forced mode, or the
+ * magic_aneg bit exists, and we try aneg
+ * again */
+ if (!(phydev->drv->flags & PHY_HAS_MAGICANEG)) {
+ int idx;
+
+ /* We'll start from the
+ * fastest speed, and work
+ * our way down */
+ idx = phy_find_valid(0,
+ phydev->supported);
+
+ phydev->speed = settings[idx].speed;
+ phydev->duplex = settings[idx].duplex;
+
+ phydev->autoneg = AUTONEG_DISABLE;
+ phydev->state = PHY_FORCING;
+ phydev->link_timeout =
+ PHY_FORCE_TIMEOUT;
+
+ pr_info("Trying %d/%s\n",
+ phydev->speed,
+ DUPLEX_FULL ==
+ phydev->duplex ?
+ "FULL" : "HALF");
+ }
+
+ needs_aneg = 1;
+ }
+ break;
+ case PHY_NOLINK:
+ err = phy_read_status(phydev);
+
+ if (err)
+ break;
+
+ if (phydev->link) {
+ phydev->state = PHY_RUNNING;
+ netif_carrier_on(phydev->attached_dev);
+ phydev->adjust_link(phydev->attached_dev);
+ }
+ break;
+ case PHY_FORCING:
+ err = phy_read_status(phydev);
+
+ if (err)
+ break;
+
+ if (phydev->link) {
+ phydev->state = PHY_RUNNING;
+ netif_carrier_on(phydev->attached_dev);
+ } else {
+ if (0 == phydev->link_timeout--) {
+ phy_force_reduction(phydev);
+ needs_aneg = 1;
+ }
+ }
+
+ phydev->adjust_link(phydev->attached_dev);
+ break;
+ case PHY_RUNNING:
+ /* Only register a CHANGE if we are
+ * polling */
+ if (PHY_POLL == phydev->irq)
+ phydev->state = PHY_CHANGELINK;
+ break;
+ case PHY_CHANGELINK:
+ err = phy_read_status(phydev);
+
+ if (err)
+ break;
+
+ if (phydev->link) {
+ phydev->state = PHY_RUNNING;
+ netif_carrier_on(phydev->attached_dev);
+ } else {
+ phydev->state = PHY_NOLINK;
+ netif_carrier_off(phydev->attached_dev);
+ }
+
+ phydev->adjust_link(phydev->attached_dev);
+
+ if (PHY_POLL != phydev->irq)
+ err = phy_config_interrupt(phydev,
+ PHY_INTERRUPT_ENABLED);
+ break;
+ case PHY_HALTED:
+ if (phydev->link) {
+ phydev->link = 0;
+ netif_carrier_off(phydev->attached_dev);
+ phydev->adjust_link(phydev->attached_dev);
+ }
+ break;
+ case PHY_RESUMING:
+
+ err = phy_clear_interrupt(phydev);
+
+ if (err)
+ break;
+
+ err = phy_config_interrupt(phydev,
+ PHY_INTERRUPT_ENABLED);
+
+ if (err)
+ break;
+
+ if (AUTONEG_ENABLE == phydev->autoneg) {
+ err = phy_aneg_done(phydev);
+ if (err < 0)
+ break;
+
+ /* err > 0 if AN is done.
+ * Otherwise, it's 0, and we're
+ * still waiting for AN */
+ if (err > 0) {
+ phydev->state = PHY_RUNNING;
+ } else {
+ phydev->state = PHY_AN;
+ phydev->link_timeout = PHY_AN_TIMEOUT;
+ }
+ } else
+ phydev->state = PHY_RUNNING;
+ break;
+ }
+
+ spin_unlock(&phydev->lock);
+
+ if (needs_aneg)
+ err = phy_start_aneg(phydev);
+
+ if (err < 0)
+ phy_error(phydev);
+
+ mod_timer(&phydev->phy_timer, jiffies + PHY_STATE_TIME * HZ);
+}
+
+#endif /* CONFIG_PHYCONTROL */
diff -Nru a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
--- /dev/null Wed Dec 31 16:00:00 196900
+++ b/drivers/net/phy/phy_device.c 2005-05-09 13:19:26 -05:00
@@ -0,0 +1,677 @@
+/*
+ * drivers/net/phy/phy_device.c
+ *
+ * Framework for finding and configuring PHYs.
+ * Also contains generic PHY driver
+ *
+ * Author: Andy Fleming
+ *
+ * Copyright (c) 2004 Freescale Semiconductor, 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/unistd.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/module.h>
+#include <linux/version.h>
+#include <linux/mii.h>
+#include <linux/ethtool.h>
+#include <linux/phy.h>
+
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/uaccess.h>
+
+/* get_phy_device
+ *
+ * description: Reads the ID registers of the PHY at addr on the
+ * bus, then allocates and returns the phy_device to
+ * represent it.
+ */
+struct phy_device * get_phy_device(struct mii_bus *bus, uint addr)
+{
+ int phy_reg;
+ u32 phy_id;
+ struct phy_device *dev = NULL;
+
+ errno = 0;
+
+ /* Grab the bits from PHYIR1, and put them
+ * in the upper half */
+ phy_reg = bus->read(bus, addr, MII_PHYSID1);
+
+ if (phy_reg < 0) {
+ errno = phy_reg;
+ return NULL;
+ }
+
+ phy_id = (phy_reg & 0xffff) << 16;
+
+ /* Grab the bits from PHYIR2, and put them in the lower half */
+ phy_reg = bus->read(bus, addr, MII_PHYSID2);
+
+ if (phy_reg < 0) {
+ errno = phy_reg;
+ return NULL;
+ }
+
+ phy_id |= (phy_reg & 0xffff);
+
+ /* If the phy_id is all Fs, there is no device there */
+ if (0xffffffff == phy_id)
+ return NULL;
+
+ /* Otherwise, we allocate the device, and initialize the
+ * default values */
+ dev = kmalloc(sizeof(*dev), GFP_KERNEL);
+
+ if (NULL == dev) {
+ errno = -ENOMEM;
+ return NULL;
+ }
+
+ memset(dev, 0, sizeof(*dev));
+
+ dev->speed = 0;
+ dev->duplex = -1;
+ dev->pause = dev->asym_pause = 0;
+ dev->link = 1;
+
+ dev->autoneg = AUTONEG_ENABLE;
+
+ dev->addr = addr;
+ dev->phy_id = phy_id;
+ dev->bus = bus;
+
+ dev->state = PHY_DOWN;
+
+ spin_lock_init(&dev->lock);
+
+ return dev;
+}
+
+/* phy_prepare_link:
+ *
+ * description: Tells the PHY infrastructure to handle the
+ * gory details on monitoring link status (whether through
+ * polling or an interrupt), and to call back to the
+ * connected device driver when the link status changes.
+ * If you want to monitor your own link state, don't call
+ * this function */
+void phy_prepare_link(struct phy_device *phydev,
+ void (*handler)(struct net_device *))
+{
+ phydev->adjust_link = handler;
+}
+
+#ifdef CONFIG_PHYCONTROL
+/* phy_connect:
+ *
+ * description: Convenience function for connecting ethernet
+ * devices to PHY devices. The default behavior is for
+ * the PHY infrastructure to handle everything, and only notify
+ * the connected driver when the link status changes. If you
+ * don't want, or can't use the provided functionality, you may
+ * choose to call only the subset of functions which provide
+ * the desired functionality.
+ */
+struct phy_device * phy_connect(struct net_device *dev, const char *phy_id,
+ void (*handler)(struct net_device *))
+{
+ struct phy_device *phydev;
+
+ phydev = phy_attach(dev, phy_id);
+
+ if (NULL == phydev)
+ return phydev;
+
+ phy_prepare_link(phydev, handler);
+
+ phy_start_machine(phydev, NULL);
+
+ return phydev;
+}
+EXPORT_SYMBOL(phy_connect);
+
+void phy_disconnect(struct phy_device *phydev)
+{
+ phy_stop_machine(phydev);
+
+ phydev->adjust_link = NULL;
+
+ phy_detach(phydev);
+}
+EXPORT_SYMBOL(phy_disconnect);
+
+#endif /* CONFIG_PHYCONTROL */
+
+/* phy_attach:
+ *
+ * description: Called by drivers to attach to a particular PHY
+ * device. The phy_device is found, and properly hooked up
+ * to the phy_driver. If no driver is attached, then the
+ * genphy_driver is used. The phy_device is given a ptr to
+ * the attaching device, and given a callback for link status
+ * change. The phy_device is returned to the attaching
+ * driver.
+ */
+struct phy_device *phy_attach(struct net_device *dev, const char *phy_id)
+{
+ struct phy_device *phydev = NULL;
+ struct bus_type *bus = &mdio_bus_type;
+ struct list_head *entry;
+
+ /* Search the list of PHY devices on the mdio bus for the
+ * PHY with the requested name */
+ list_for_each(entry, &bus->devices.list)
+ {
+ struct device *d = container_of(entry, struct device, bus_list);
+
+ if (!strcmp(phy_id, d->bus_id)) {
+ phydev = to_phy_device(d);
+ break;
+ }
+ }
+
+ if (NULL == phydev) {
+ printk(KERN_ERR "%s not found\n", phy_id);
+ errno = -ENODEV;
+ return NULL;
+ }
+
+ /* Assume that if there is no driver, that it doesn't
+ * exist, and we should use the genphy driver. */
+ if (NULL == phydev->dev.driver) {
+ int err;
+ down_write(&phydev->dev.bus->subsys.rwsem);
+ phydev->dev.driver = &genphy_driver.driver;
+
+ err = phydev->dev.driver->probe(&phydev->dev);
+
+ if (err < 0) {
+ errno = err;
+ return NULL;
+ }
+
+ device_bind_driver(&phydev->dev);
+ up_write(&phydev->dev.bus->subsys.rwsem);
+ }
+
+ if (phydev->attached_dev) {
+ printk(KERN_ERR "%s: %s already attached\n",
+ dev->name, phy_id);
+ errno = -EBUSY;
+ return NULL;
+ }
+
+ phydev->attached_dev = dev;
+
+ return phydev;
+}
+EXPORT_SYMBOL(phy_attach);
+
+void phy_detach(struct phy_device *phydev)
+{
+ phydev->attached_dev = NULL;
+
+ /* If the device had no specific driver before (i.e. - it
+ * was using the generic driver), we unbind the device
+ * from the generic driver so that there's a chance a
+ * real driver could be loaded */
+ if (phydev->dev.driver == &genphy_driver.driver) {
+ down_write(&phydev->dev.bus->subsys.rwsem);
+ device_release_driver(&phydev->dev);
+ up_write(&phydev->dev.bus->subsys.rwsem);
+ }
+}
+EXPORT_SYMBOL(phy_detach);
+
+
+/* Generic PHY support and helper functions */
+
+/* genphy_config_advert
+ *
+ * description: Writes MII_ADVERTISE with the appropriate values,
+ * after sanitizing the values to make sure we only advertise
+ * what is supported
+ */
+int genphy_config_advert(struct phy_device *phydev)
+{
+ u32 advertise;
+ int adv;
+ int err;
+
+ /* Only allow advertising what
+ * this PHY supports */
+ phydev->advertising &= phydev->supported;
+ advertise = phydev->advertising;
+
+ /* Setup standard advertisement */
+ adv = phy_read(phydev, MII_ADVERTISE);
+
+ if (adv < 0)
+ return adv;
+
+ adv &= ~(ADVERTISE_ALL | ADVERTISE_100BASE4 | ADVERTISE_PAUSE_CAP |
+ ADVERTISE_PAUSE_ASYM);
+ if (advertise & ADVERTISED_10baseT_Half)
+ adv |= ADVERTISE_10HALF;
+ if (advertise & ADVERTISED_10baseT_Full)
+ adv |= ADVERTISE_10FULL;
+ if (advertise & ADVERTISED_100baseT_Half)
+ adv |= ADVERTISE_100HALF;
+ if (advertise & ADVERTISED_100baseT_Full)
+ adv |= ADVERTISE_100FULL;
+ if (advertise & ADVERTISED_Pause)
+ adv |= ADVERTISE_PAUSE_CAP;
+ if (advertise & ADVERTISED_Asym_Pause)
+ adv |= ADVERTISE_PAUSE_ASYM;
+
+ err = phy_write(phydev, MII_ADVERTISE, adv);
+
+ if (err < 0)
+ return err;
+
+ /* Configure gigabit if it's supported */
+ if (phydev->supported & (SUPPORTED_1000baseT_Half |
+ SUPPORTED_1000baseT_Full)) {
+ adv = phy_read(phydev, MII_CTRL1000);
+
+ if (adv < 0)
+ return adv;
+
+ adv &= ~(ADVERTISE_1000FULL | ADVERTISE_1000HALF);
+ if (advertise & SUPPORTED_1000baseT_Half)
+ adv |= ADVERTISE_1000HALF;
+ if (advertise & SUPPORTED_1000baseT_Full)
+ adv |= ADVERTISE_1000FULL;
+ err = phy_write(phydev, MII_CTRL1000, adv);
+
+ if (err < 0)
+ return err;
+ }
+
+ return adv;
+}
+
+
+/* genphy_setup_forced
+ *
+ * description: Configures MII_BMCR to force speed/duplex
+ * to the values in phydev. Assumes that the values are valid.
+ * Please see phy_sanitize_settings() */
+int genphy_setup_forced(struct phy_device *phydev)
+{
+ int ctl = phy_read(phydev, MII_BMCR);
+
+ if (ctl < 0)
+ return ctl;
+
+ phydev->pause = phydev->asym_pause = 0;
+
+ ctl &= ~(BMCR_FULLDPLX|BMCR_SPEED100|BMCR_SPEED1000|BMCR_ANENABLE);
+ ctl |= BMCR_RESET;
+
+ if (SPEED_1000 == phydev->speed)
+ ctl |= BMCR_SPEED1000;
+ else if (SPEED_100 == phydev->speed)
+ ctl |= BMCR_SPEED100;
+
+ if (DUPLEX_FULL == phydev->duplex)
+ ctl |= BMCR_FULLDPLX;
+
+ ctl = phy_write(phydev, MII_BMCR, ctl);
+
+ return ctl;
+}
+
+
+/* Enable and Restart Autonegotiation */
+int genphy_restart_aneg(struct phy_device *phydev)
+{
+ int ctl;
+
+ ctl = phy_read(phydev, MII_BMCR);
+
+ if (ctl < 0)
+ return ctl;
+
+ ctl |= (BMCR_ANENABLE | BMCR_ANRESTART);
+
+ ctl = phy_write(phydev, MII_BMCR, ctl);
+
+ return ctl;
+}
+
+
+/* genphy_config_aneg
+ *
+ * description: If auto-negotiation is enabled, we configure the
+ * advertising, and then restart auto-negotiation. If it is not
+ * enabled, then we write the BMCR
+ */
+int genphy_config_aneg(struct phy_device *phydev)
+{
+ int err = 0;
+
+ if (AUTONEG_ENABLE == phydev->autoneg) {
+ err = genphy_config_advert(phydev);
+
+ if (err < 0)
+ return err;
+
+ err = genphy_restart_aneg(phydev);
+ } else
+ err = genphy_setup_forced(phydev);
+
+ return err;
+}
+
+
+/* genphy_update_link
+ *
+ * description: Update the value in phydev->link to reflect the
+ * current link value. In order to do this, we need to read
+ * the status register twice, keeping the second value
+ */
+int genphy_update_link(struct phy_device *phydev)
+{
+ int status;
+
+ /* Do a fake read */
+ status = phy_read(phydev, MII_BMSR);
+
+ if (status < 0)
+ return status;
+
+ /* Read link and autonegotiation status */
+ status = phy_read(phydev, MII_BMSR);
+
+ if (status < 0)
+ return status;
+
+ if ((status & BMSR_LSTATUS) == 0)
+ phydev->link = 0;
+ else
+ phydev->link = 1;
+
+ return 0;
+}
+
+/* genphy_read_status
+ *
+ * description: Check the link, then figure out the current state
+ * by comparing what we advertise with what the link partner
+ * advertises. Start by checking the gigabit possibilities,
+ * then move on to 10/100.
+ */
+int genphy_read_status(struct phy_device *phydev)
+{
+ int adv;
+ int err;
+ int lpa;
+ int lpagb = 0;
+
+ /* Update the link, but return if there
+ * was an error */
+ err = genphy_update_link(phydev);
+ if (err)
+ return err;
+
+ if (AUTONEG_ENABLE == phydev->autoneg) {
+ if (phydev->supported & (SUPPORTED_1000baseT_Half
+ | SUPPORTED_1000baseT_Full)) {
+ lpagb = phy_read(phydev, MII_STAT1000);
+
+ if (lpagb < 0)
+ return lpagb;
+
+ adv = phy_read(phydev, MII_CTRL1000);
+
+ if (adv < 0)
+ return adv;
+
+ lpagb &= adv << 2;
+ }
+
+ lpa = phy_read(phydev, MII_LPA);
+
+ if (lpa < 0)
+ return lpa;
+
+ adv = phy_read(phydev, MII_ADVERTISE);
+
+ if (adv < 0)
+ return adv;
+
+ lpa &= adv;
+
+ phydev->speed = SPEED_10;
+ phydev->duplex = DUPLEX_HALF;
+ phydev->pause = phydev->asym_pause = 0;
+
+ if (lpagb & (LPA_1000FULL | LPA_1000HALF)) {
+ phydev->speed = SPEED_1000;
+
+ if (lpagb & LPA_1000FULL)
+ phydev->duplex = DUPLEX_FULL;
+ } else if (lpa & (LPA_100FULL | LPA_100HALF)) {
+ phydev->speed = SPEED_100;
+
+ if (lpa & LPA_100FULL)
+ phydev->duplex = DUPLEX_FULL;
+ } else
+ if (lpa & LPA_10FULL)
+ phydev->duplex = DUPLEX_FULL;
+
+ if (phydev->duplex == DUPLEX_FULL){
+ phydev->pause = lpa & LPA_PAUSE_CAP ? 1 : 0;
+ phydev->asym_pause = lpa & LPA_PAUSE_ASYM ? 1 : 0;
+ }
+ } else {
+ int bmcr = phy_read(phydev, MII_BMCR);
+ if (bmcr < 0)
+ return bmcr;
+
+ if (bmcr & BMCR_FULLDPLX)
+ phydev->duplex = DUPLEX_FULL;
+ else
+ phydev->duplex = DUPLEX_HALF;
+
+ if (bmcr & BMCR_SPEED1000)
+ phydev->speed = SPEED_1000;
+ else if (bmcr & BMCR_SPEED100)
+ phydev->speed = SPEED_100;
+ else
+ phydev->speed = SPEED_10;
+
+ phydev->pause = phydev->asym_pause = 0;
+ }
+
+ return 0;
+}
+
+
+static int genphy_probe(struct phy_device *phydev)
+{
+ u32 val;
+ u32 features;
+
+ /* For now, I'll claim that the generic driver supports
+ * all possible port types */
+ features = (SUPPORTED_TP | SUPPORTED_MII
+ | SUPPORTED_AUI | SUPPORTED_FIBRE |
+ SUPPORTED_BNC);
+
+ /* Do we support autonegotiation? */
+ val = phy_read(phydev, MII_BMSR);
+
+ if (val < 0)
+ return val;
+
+ if (val & BMSR_ANEGCAPABLE)
+ features |= SUPPORTED_Autoneg;
+
+ if (val & BMSR_100FULL)
+ features |= SUPPORTED_100baseT_Full;
+ if (val & BMSR_100HALF)
+ features |= SUPPORTED_100baseT_Half;
+ if (val & BMSR_10FULL)
+ features |= SUPPORTED_10baseT_Full;
+ if (val & BMSR_10HALF)
+ features |= SUPPORTED_10baseT_Half;
+
+ if (val & BMSR_ESTATEN) {
+ val = phy_read(phydev, MII_ESTATUS);
+
+ if (val < 0)
+ return val;
+
+ if (val & ESTATUS_1000_TFULL)
+ features |= SUPPORTED_1000baseT_Full;
+ if (val & ESTATUS_1000_THALF)
+ features |= SUPPORTED_1000baseT_Half;
+ }
+
+ phydev->supported = features;
+ phydev->advertising = features;
+
+ return 0;
+}
+
+
+/* phy_probe
+ *
+ * description: Take care of setting up the phy_device structure,
+ * set the state to READY (the driver's probe function should
+ * set it to STARTING if needed).
+ */
+int phy_probe(struct device *dev)
+{
+ struct phy_device *phydev;
+ struct phy_driver *phydrv;
+ struct device_driver *drv;
+ int err = 0;
+
+ phydev = to_phy_device(dev);
+
+ /* Make sure the driver is held.
+ * XXX -- Is this correct? */
+ drv = get_driver(phydev->dev.driver);
+ phydrv = to_phy_driver(drv);
+ phydev->drv = phydrv;
+
+ /* Disable the interrupt if the PHY doesn't support it */
+ if (!(phydrv->flags & PHY_HAS_INTERRUPT))
+ phydev->irq = PHY_POLL;
+
+ spin_lock(&phydev->lock);
+
+ /* Start out supporting everything. Eventually,
+ * a controller will attach, and may modify one
+ * or both of these values */
+ phydev->supported = phydrv->features;
+ phydev->advertising = phydrv->features;
+
+ /* Set the state to READY by default */
+ phydev->state = PHY_READY;
+
+ if (phydev->drv->probe)
+ err = phydev->drv->probe(phydev);
+
+ spin_unlock(&phydev->lock);
+
+ return err;
+}
+
+int phy_remove(struct device *dev)
+{
+ struct phy_device *phydev;
+
+ phydev = to_phy_device(dev);
+
+ spin_lock(&phydev->lock);
+ phydev->state = PHY_DOWN;
+ spin_unlock(&phydev->lock);
+
+ if (phydev->drv->remove)
+ phydev->drv->remove(phydev);
+
+ put_driver(phydev->dev.driver);
+ phydev->drv = NULL;
+
+ return 0;
+}
+
+int phy_driver_register(struct phy_driver *new_driver)
+{
+ int retval;
+
+ memset(&new_driver->driver, 0, sizeof(new_driver->driver));
+ new_driver->driver.name = new_driver->name;
+ new_driver->driver.bus = &mdio_bus_type;
+ new_driver->driver.probe = phy_probe;
+ new_driver->driver.remove = phy_remove;
+
+ retval = driver_register(&new_driver->driver);
+
+ if (retval) {
+ printk(KERN_ERR "%s: Error %d in registering driver\n",
+ new_driver->name, retval);
+
+ return retval;
+ }
+
+ pr_info("%s: Registered new driver\n", new_driver->name);
+
+ return 0;
+}
+
+void phy_driver_unregister(struct phy_driver *drv)
+{
+ driver_unregister(&drv->driver);
+}
+
+struct phy_driver genphy_driver = {
+ .phy_id = 0xffffffff,
+ .phy_id_mask = 0xffffffff,
+ .name = "Generic PHY",
+ .probe = genphy_probe,
+ .features = 0,
+ .config_aneg = genphy_config_aneg,
+ .read_status = genphy_read_status,
+};
+
+static int __init genphy_init(void)
+{
+ int retval;
+
+ retval = phy_driver_register(&genphy_driver);
+
+ return retval;
+}
+
+static void __exit genphy_exit(void)
+{
+ phy_driver_unregister(&genphy_driver);
+}
+
+module_init(genphy_init);
+module_exit(genphy_exit);
diff -Nru a/drivers/net/phy/qsemi.c b/drivers/net/phy/qsemi.c
--- /dev/null Wed Dec 31 16:00:00 196900
+++ b/drivers/net/phy/qsemi.c 2005-05-09 13:19:26 -05:00
@@ -0,0 +1,143 @@
+/*
+ * drivers/net/phy/qsemi.c
+ *
+ * Driver for Quality Semiconductor PHYs
+ *
+ * Author: Andy Fleming
+ *
+ * Copyright (c) 2004 Freescale Semiconductor, 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/unistd.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/module.h>
+#include <linux/version.h>
+#include <linux/mii.h>
+#include <linux/ethtool.h>
+#include <linux/phy.h>
+
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/uaccess.h>
+
+/* ------------------------------------------------------------------------- */
+/* The Quality Semiconductor QS6612 is used on the RPX CLLF */
+
+/* register definitions */
+
+#define MII_QS6612_MCR 17 /* Mode Control Register */
+#define MII_QS6612_FTR 27 /* Factory Test Register */
+#define MII_QS6612_MCO 28 /* Misc. Control Register */
+#define MII_QS6612_ISR 29 /* Interrupt Source Register */
+#define MII_QS6612_IMR 30 /* Interrupt Mask Register */
+#define MII_QS6612_IMR_INIT 0x003a
+#define MII_QS6612_PCR 31 /* 100BaseTx PHY Control Reg. */
+
+#define QS6612_PCR_AN_COMPLETE 0x1000
+#define QS6612_PCR_RLBEN 0x0200
+#define QS6612_PCR_DCREN 0x0100
+#define QS6612_PCR_4B5BEN 0x0040
+#define QS6612_PCR_TX_ISOLATE 0x0020
+#define QS6612_PCR_MLT3_DIS 0x0002
+#define QS6612_PCR_SCRM_DESCRM 0x0001
+
+
+/* Returns 0, unless there's a write error */
+int qs6612_probe(struct phy_device *phydev)
+{
+ /* The PHY powers up isolated on the RPX,
+ * so send a command to allow operation.
+ * XXX - My docs indicate this should be 0x0940
+ * ...or something. The current value sets three
+ * reserved bits, bit 11, which specifies it should be
+ * set to one, bit 10, which specifies it should be set
+ * to 0, and bit 7, which doesn't specify. However, my
+ * docs are preliminary, and I will leave it like this
+ * until someone more knowledgable corrects me or it.
+ * -- Andy Fleming
+ */
+ return phy_write(phydev, MII_QS6612_PCR, 0x0dc0);
+}
+
+int qs6612_ack_interrupt(struct phy_device *phydev)
+{
+ int err;
+
+ err = phy_read(phydev, MII_QS6612_ISR);
+
+ if (err < 0)
+ return err;
+
+ err = phy_read(phydev, MII_BMSR);
+
+ if (err < 0)
+ return err;
+
+ err = phy_read(phydev, MII_EXPANSION);
+
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+int qs6612_config_intr(struct phy_device *phydev)
+{
+ int err;
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
+ err = phy_write(phydev, MII_QS6612_IMR,
+ MII_QS6612_IMR_INIT);
+ else
+ err = phy_write(phydev, MII_QS6612_IMR, 0);
+
+ return err;
+
+}
+
+static struct phy_driver qs6612_driver = {
+ .phy_id = 0x00181440,
+ .name = "QS6612",
+ .phy_id_mask = 0xfffffff0,
+ .features = PHY_BASIC_FEATURES,
+ .flags = PHY_HAS_INTERRUPT,
+ .probe = qs6612_probe,
+ .config_aneg = genphy_config_aneg,
+ .read_status = genphy_read_status,
+ .ack_interrupt = qs6612_ack_interrupt,
+ .config_intr = qs6612_config_intr,
+};
+
+int __init qs6612_init(void)
+{
+ int retval;
+
+ retval = phy_driver_register(&qs6612_driver);
+
+ return retval;
+}
+
+static void __exit qs6612_exit(void)
+{
+ phy_driver_unregister(&qs6612_driver);
+}
+
+module_init(qs6612_init);
+module_exit(qs6612_exit);
diff -Nru a/include/linux/mii.h b/include/linux/mii.h
--- a/include/linux/mii.h 2005-05-09 13:19:25 -05:00
+++ b/include/linux/mii.h 2005-05-09 13:19:25 -05:00
@@ -22,6 +22,7 @@
#define MII_EXPANSION 0x06 /* Expansion register */
#define MII_CTRL1000 0x09 /* 1000BASE-T control */
#define MII_STAT1000 0x0a /* 1000BASE-T status */
+#define MII_ESTATUS 0x0f /* Extended Status */
#define MII_DCOUNTER 0x12 /* Disconnect counter */
#define MII_FCSCOUNTER 0x13 /* False carrier counter */
#define MII_NWAYTEST 0x14 /* N-way auto-neg test reg */
@@ -54,7 +55,10 @@
#define BMSR_ANEGCAPABLE 0x0008 /* Able to do auto-negotiation */
#define BMSR_RFAULT 0x0010 /* Remote fault detected */
#define BMSR_ANEGCOMPLETE 0x0020 /* Auto-negotiation complete */
-#define BMSR_RESV 0x07c0 /* Unused... */
+#define BMSR_RESV 0x00c0 /* Unused... */
+#define BMSR_ESTATEN 0x0100 /* Extended Status in R15 */
+#define BMSR_100FULL2 0x0200 /* Can do 100BASE-T2 HDX */
+#define BMSR_100HALF2 0x0400 /* Can do 100BASE-T2 FDX */
#define BMSR_10HALF 0x0800 /* Can do 10mbps, half-duplex */
#define BMSR_10FULL 0x1000 /* Can do 10mbps, full-duplex */
#define BMSR_100HALF 0x2000 /* Can do 100mbps, half-duplex */
@@ -105,6 +109,9 @@
#define EXPANSION_NPCAPABLE 0x0008 /* Link partner supports npage */
#define EXPANSION_MFAULTS 0x0010 /* Multiple faults detected */
#define EXPANSION_RESV 0xffe0 /* Unused... */
+
+#define ESTATUS_1000_TFULL 0x2000 /* Can do 1000BT Full */
+#define ESTATUS_1000_THALF 0x1000 /* Can do 1000BT Half */
/* N-way test register. */
#define NWAYTEST_RESV1 0x00ff /* Unused... */
diff -Nru a/include/linux/phy.h b/include/linux/phy.h
--- /dev/null Wed Dec 31 16:00:00 196900
+++ b/include/linux/phy.h 2005-05-09 13:19:26 -05:00
@@ -0,0 +1,362 @@
+/*
+ * include/linux/phy.h
+ *
+ * Framework and drivers for configuring and reading different PHYs
+ * Based on code in sungem_phy.c and gianfar_phy.c
+ *
+ * Author: Andy Fleming
+ *
+ * Copyright (c) 2004 Freescale Semiconductor, 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.
+ *
+ */
+
+#ifndef __PHY_H
+#define __PHY_H
+
+#include <linux/spinlock.h>
+#include <linux/device.h>
+
+#define PHY_BASIC_FEATURES (SUPPORTED_10baseT_Half | \
+ SUPPORTED_10baseT_Full | \
+ SUPPORTED_100baseT_Half | \
+ SUPPORTED_100baseT_Full | \
+ SUPPORTED_Autoneg | \
+ SUPPORTED_TP | \
+ SUPPORTED_MII)
+
+#define PHY_GBIT_FEATURES (PHY_BASIC_FEATURES | \
+ SUPPORTED_1000baseT_Half | \
+ SUPPORTED_1000baseT_Full)
+
+/* Set phydev->irq to PHY_POLL if interrupts are not supported,
+ * or not desired for this PHY. Set to PHY_IGNORE_INTERRUPT if
+ * the attached driver handles the interrupt
+ */
+#define PHY_POLL -1
+#define PHY_IGNORE_INTERRUPT -2
+
+#define PHY_HAS_INTERRUPT 0x00000001
+#define PHY_HAS_MAGICANEG 0x00000002
+
+#define MII_BUS_MAX 4
+
+
+#define PHY_INIT_TIMEOUT 100000
+#define PHY_STATE_TIME 1
+#define PHY_FORCE_TIMEOUT 10
+#define PHY_AN_TIMEOUT 10
+
+#define PHY_MAX_ADDR 32
+
+/* The Bus class for PHYs. Devices which provide access to
+ * PHYs should register using this structure */
+struct mii_bus {
+ const char *name;
+ int id;
+ void *priv;
+ int (*read)(struct mii_bus *bus, int phy_id, int regnum);
+ int (*write)(struct mii_bus *bus, int phy_id, int regnum, u16 val);
+ int (*reset)(struct mii_bus *bus);
+
+ /* A lock to ensure that only one thing can read/write
+ * the MDIO bus at a time */
+ spinlock_t mdio_lock;
+
+ struct device *dev;
+
+ /* list of all PHYs on bus */
+ struct phy_device *phy_map[PHY_MAX_ADDR];
+
+ /* Pointer to an array of interrupts, each PHY's
+ * interrupt at the index matching its address */
+ int *irq;
+};
+
+#define PHY_INTERRUPT_DISABLED 0x0
+#define PHY_INTERRUPT_ENABLED 0x80000000
+
+/* PHY state machine states:
+ *
+ * DOWN: PHY device and driver are not ready for anything. probe
+ * should be called if and only if the PHY is in this state,
+ * given that the PHY device exists.
+ * - PHY driver probe function will, depending on the PHY, set
+ * the state to STARTING or READY
+ *
+ * STARTING: PHY device is coming up, and the ethernet driver is
+ * not ready. PHY drivers may set this in the probe function.
+ * If they do, they are responsible for making sure the state is
+ * eventually set to indicate whether the PHY is UP or READY,
+ * depending on the state when the PHY is done starting up.
+ * - PHY driver will set the state to READY
+ * - start will set the state to PENDING
+ *
+ * READY: PHY is ready to send and receive packets, but the
+ * controller is not. By default, PHYs which do not implement
+ * probe will be set to this state by phy_probe(). If the PHY
+ * driver knows the PHY is ready, and the PHY state is STARTING,
+ * then it sets this STATE.
+ * - start will set the state to UP
+ *
+ * PENDING: PHY device is coming up, but the ethernet driver is
+ * ready. phy_start will set this state if the PHY state is
+ * STARTING.
+ * - PHY driver will set the state to UP when the PHY is ready
+ *
+ * UP: The PHY and attached device are ready to do work.
+ * Interrupts should be started here.
+ * - timer moves to AN
+ *
+ * AN: The PHY is currently negotiating the link state. Link is
+ * therefore down for now. phy_timer will set this state when it
+ * detects the state is UP. config_aneg will set this state
+ * whenever called with phydev->autoneg set to AUTONEG_ENABLE.
+ * - If autonegotiation finishes, but there's no link, it sets
+ * the state to NOLINK.
+ * - If aneg finishes with link, it sets the state to RUNNING,
+ * and calls adjust_link
+ * - If autonegotiation did not finish after an arbitrary amount
+ * of time, autonegotiation should be tried again if the PHY
+ * supports "magic" autonegotiation (back to AN)
+ * - If it didn't finish, and no magic_aneg, move to FORCING.
+ *
+ * NOLINK: PHY is up, but not currently plugged in.
+ * - If the timer notes that the link comes back, we move to RUNNING
+ * - config_aneg moves to AN
+ * - phy_stop moves to HALTED
+ *
+ * FORCING: PHY is being configured with forced settings
+ * - if link is up, move to RUNNING
+ * - If link is down, we drop to the next highest setting, and
+ * retry (FORCING) after a timeout
+ * - phy_stop moves to HALTED
+ *
+ * RUNNING: PHY is currently up, running, and possibly sending
+ * and/or receiving packets
+ * - timer will set CHANGELINK if we're polling (this ensures the
+ * link state is polled every other cycle of this state machine,
+ * which makes it every other second)
+ * - irq will set CHANGELINK
+ * - config_aneg will set AN
+ * - phy_stop moves to HALTED
+ *
+ * CHANGELINK: PHY experienced a change in link state
+ * - timer moves to RUNNING if link
+ * - timer moves to NOLINK if the link is down
+ * - phy_stop moves to HALTED
+ *
+ * HALTED: PHY is up, but no polling or interrupts are done. Or
+ * PHY is in an error state.
+ *
+ * - phy_start moves to RESUMING
+ *
+ * RESUMING: PHY was halted, but now wants to run again.
+ * - If we are forcing, or aneg is done, timer moves to RUNNING
+ * - If aneg is not done, timer moves to AN
+ * - phy_stop moves to HALTED
+ */
+enum phy_state {
+ PHY_DOWN=0,
+ PHY_STARTING,
+ PHY_READY,
+ PHY_PENDING,
+ PHY_UP,
+ PHY_AN,
+ PHY_RUNNING,
+ PHY_NOLINK,
+ PHY_FORCING,
+ PHY_CHANGELINK,
+ PHY_HALTED,
+ PHY_RESUMING
+};
+
+/* phy_device: An instance of a PHY
+ *
+ * drv: Pointer to the driver for this PHY instance
+ * bus: Pointer to the bus this PHY is on
+ * dev: driver model device structure for this PHY
+ * phy_id: UID for this device found during discovery
+ * state: state of the PHY for management purposes
+ * addr: Bus address of PHY
+ * link_timeout: The number of timer firings to wait before the
+ * giving up on the current attempt at acquiring a link
+ * irq: IRQ number of the PHY's interrupt (-1 if none)
+ * phy_timer: The timer for handling the state machine
+ * phy_queue: A work_queue for the interrupt
+ * attached_dev: The attached enet driver's device instance ptr
+ * adjust_link: Callback for the enet controller to respond to
+ * changes in the link state.
+ * adjust_state: Callback for the enet driver to respond to
+ * changes in the state machine.
+ *
+ * speed, duplex, pause, supported, advertising, and
+ * autoneg are used like in mii_if_info
+ *
+ * interrupts currently only supports enabled or disabled,
+ * but could be changed in the future to support enabling
+ * and disabling specific interrupts
+ *
+ * Contains some infrastructure for polling and interrupt
+ * handling, as well as handling shifts in PHY hardware state
+ */
+struct phy_device {
+ /* Information about the PHY type */
+ /* And management functions */
+ struct phy_driver *drv;
+
+ struct mii_bus *bus;
+
+ struct device dev;
+
+ u32 phy_id;
+
+ enum phy_state state;
+
+ /* Bus address of the PHY (0-32) */
+ int addr;
+
+ /* forced speed & duplex (no autoneg)
+ * partner speed & duplex & pause (autoneg)
+ */
+ int speed;
+ int duplex;
+ int pause;
+ int asym_pause;
+
+ /* The most recently read link state */
+ int link;
+
+ /* Enabled Interrupts */
+ u32 interrupts;
+
+ /* Union of PHY and Attached devices' supported modes */
+ /* See mii.h for more info */
+ u32 supported;
+ u32 advertising;
+
+ int autoneg;
+
+ int link_timeout;
+
+ /* Interrupt number for this PHY
+ * -1 means no interrupt */
+ int irq;
+
+ /* private data pointer */
+ /* For use by PHYs to maintain extra state */
+ void *priv;
+
+ /* Interrupt and Polling infrastructure */
+ struct work_struct phy_queue;
+ struct timer_list phy_timer;
+
+ spinlock_t lock;
+
+ struct net_device *attached_dev;
+
+ void (*adjust_link)(struct net_device *dev);
+
+ void (*adjust_state)(struct net_device *dev);
+};
+#define to_phy_device(d) container_of(d, struct phy_device, dev)
+
+/* struct phy_driver: Driver structure for a particular PHY type
+ *
+ * phy_id: The result of reading the UID registers of this PHY
+ * type, and ANDing them with the phy_id_mask. This driver
+ * only works for PHYs with IDs which match this field
+ * name: The friendly name of this PHY type
+ * phy_id_mask: Defines the important bits of the phy_id
+ * features: A list of features (speed, duplex, etc) supported
+ * by this PHY
+ * flags: A bitfield defining certain other features this PHY
+ * supports (like interrupts)
+ *
+ * The drivers must implement config_aneg and read_status. All
+ * other functions are optional. Note that none of these
+ * functions should be called from interrupt time. The goal is
+ * for the bus read/write functions to be able to block when the
+ * bus transaction is happening, and be freed up by an interrupt
+ * (The MPC85xx has this ability, though it is not currently
+ * supported in the driver).
+ */
+struct phy_driver {
+ u32 phy_id;
+ char *name;
+ unsigned int phy_id_mask;
+ u32 features;
+ u32 flags;
+
+ /* Called to initialize the PHY */
+ int (*probe)(struct phy_device *phydev);
+
+ /* PHY Power Management */
+ int (*suspend)(struct phy_device *phydev);
+ int (*resume)(struct phy_device *phydev);
+
+ /* Configures the advertisement and resets
+ * autonegotiation if phydev->autoneg is on,
+ * forces the speed to the current settings in phydev
+ * if phydev->autoneg is off */
+ int (*config_aneg)(struct phy_device *phydev);
+
+ /* Determines the negotiated speed and duplex */
+ int (*read_status)(struct phy_device *phydev);
+
+ /* Clears any pending interrupts */
+ int (*ack_interrupt)(struct phy_device *phydev);
+
+ /* Enables or disables interrupts */
+ int (*config_intr)(struct phy_device *phydev);
+
+ /* Clears up any memory if needed */
+ void (*remove)(struct phy_device *phydev);
+
+ struct device_driver driver;
+};
+#define to_phy_driver(d) container_of(d, struct phy_driver, driver)
+
+int phy_read(struct phy_device *phydev, u16 regnum);
+int phy_write(struct phy_device *phydev, u16 regnum, u16 val);
+struct phy_device* get_phy_device(struct mii_bus *bus, uint addr);
+int phy_clear_interrupt(struct phy_device *phydev);
+int phy_config_interrupt(struct phy_device *phydev, u32 interrupts);
+struct phy_device * phy_attach(struct net_device *dev, const char *phy_id);
+struct phy_device * phy_connect(struct net_device *dev, const char *phy_id,
+ void (*handler)(struct net_device *));
+void phy_disconnect(struct phy_device *phydev);
+void phy_detach(struct phy_device *phydev);
+void phy_start(struct phy_device *phydev);
+void phy_stop(struct phy_device *phydev);
+int phy_start_aneg(struct phy_device *phydev);
+int phy_read_status(struct phy_device *phydev);
+int mdiobus_register(struct mii_bus *bus);
+void mdiobus_unregister(struct mii_bus *bus);
+void phy_sanitize_settings(struct phy_device *phydev);
+
+int genphy_config_advert(struct phy_device *phydev);
+int genphy_setup_forced(struct phy_device *phydev);
+int genphy_restart_aneg(struct phy_device *phydev);
+int genphy_config_aneg(struct phy_device *phydev);
+int genphy_update_link(struct phy_device *phydev);
+int genphy_read_status(struct phy_device *phydev);
+void phy_driver_unregister(struct phy_driver *drv);
+int phy_driver_register(struct phy_driver *new_driver);
+void phy_prepare_link(struct phy_device *phydev,
+ void (*adjust_link)(struct net_device *));
+void phy_start_machine(struct phy_device *phydev,
+ void (*handler)(struct net_device *));
+void phy_stop_machine(struct phy_device *phydev);
+int phy_ethtool_sset(struct phy_device *phydev, struct ethtool_cmd *cmd);
+int phy_ethtool_gset(struct phy_device *phydev, struct ethtool_cmd *cmd);
+int phy_mii_ioctl(struct phy_device *phydev,
+ struct mii_ioctl_data *mii_data, int cmd);
+
+extern struct bus_type mdio_bus_type;
+extern struct phy_driver genphy_driver;
+#endif /* __PHY_H */
diff -Nru a/include/linux/ethtool.h b/include/linux/ethtool.h
--- a/include/linux/ethtool.h 2005-05-09 13:19:25 -05:00
+++ b/include/linux/ethtool.h 2005-05-09 13:19:25 -05:00
@@ -389,6 +389,7 @@
#define ETHTOOL_GTSO 0x0000001e /* Get TSO enable (ethtool_value) */
#define ETHTOOL_STSO 0x0000001f /* Set TSO enable (ethtool_value) */
+
/* compatibility with older code */
#define SPARC_ETH_GSET ETHTOOL_GSET
#define SPARC_ETH_SSET ETHTOOL_SSET
@@ -407,6 +408,8 @@
#define SUPPORTED_FIBRE (1 << 10)
#define SUPPORTED_BNC (1 << 11)
#define SUPPORTED_10000baseT_Full (1 << 12)
+#define SUPPORTED_Pause (1 << 13)
+#define SUPPORTED_Asym_Pause (1 << 14)
/* Indicates what features are advertised by the interface. */
#define ADVERTISED_10baseT_Half (1 << 0)
@@ -422,6 +425,8 @@
#define ADVERTISED_FIBRE (1 << 10)
#define ADVERTISED_BNC (1 << 11)
#define ADVERTISED_10000baseT_Full (1 << 12)
+#define ADVERTISED_Pause (1 << 13)
+#define ADVERTISED_Asym_Pause (1 << 14)
/* The following are all involved in forcing a particular link
* mode for the device for setting things. When getting the
^ permalink raw reply
* SMP kernel testing
From: Stuart Yoder @ 2005-05-10 15:11 UTC (permalink / raw)
To: linuxppc-dev
[-- Attachment #1: Type: text/plain, Size: 360 bytes --]
So we have our SMP kernel up and going on our system. Are there any
particular tests or applications out there that kernel developers use to
really beat and stress an SMP kernel-- lots of threads, spinlocks, etc?
I didn't see anything SMP specific in the LTP tests.
Or, are you basically 99.9% of the way there if the system works at all?
Thanks,
Stuart
[-- Attachment #2: Type: text/html, Size: 1171 bytes --]
^ permalink raw reply
* RE: PPC uImage build not reporting correctly
From: Stephen Warren @ 2005-05-10 15:14 UTC (permalink / raw)
To: Sam Ravnborg, Kumar Gala
Cc: Tom Rini, cpclark, Linux Kernel list, linuxppc-embedded list
From: linux-kernel-owner@vger.kernel.org
[mailto:linux-kernel-owner@vger.kernel.org] On Behalf Of Sam Ravnborg
> On Mon, May 09, 2005 at 10:19:01AM -0500, Kumar Gala wrote:
> > On May 6, 2005, at 6:22 PM, <cpclark@xmission.com> wrote:
> > > Couldn't you eliminate the ($shell ..) construct altogether, like=20
> > > this?:
> > >
> > > $(obj)/uImage: $(obj)/vmlinux.gz
> > > ??????? $(Q)rm -f $@
> > > ??????? $(call if_changed,uimage)
> > > ??????? @echo -n '? Image: $@'
> > > ??????? @if [ -f $@ ]; then echo 'is ready' ; else echo 'not
made'; fi
> >=20
> > Yes, and this seems to actually work.
> >=20
> > Sam, does this look reasonable to you. If so I will work up a
patch.
>
> Looks ok - but I do not see why use of $(shell ...) did not work out.
> Please bring your working version forward.
=20
It's because both any $(xxx) in the command will be expanded prior to
the command being executed ("command" meaning all lines in the complete
command script for the target in question - not on a line-by-line
basis).
Thus, the original $(wildcard), and also the $(shell) above are
evaluated/expanded by gmake prior to running any of the the "rm -rf",
"if_changed", and "echo" commands, and hence run before the uImage file
is created, and hence always think that it doesn't exist.
The only solution is to get the shell to do the evaluation of whether
uImage exists - that way, the evaluation is guaranteed to happen after
the uImage is (hopefully) created.
--=20
Stephen Warren, Software Engineer, NVIDIA, Fort Collins, CO
swarren@nvidia.com http://www.nvidia.com/
swarren@wwwdotorg.org http://www.wwwdotorg.org/pgp.html
^ permalink raw reply
* RE: [PATCH 03/04] Freescale Ethernet combined driver
From: Rune Torgersen @ 2005-05-10 14:53 UTC (permalink / raw)
To: Matt Porter; +Cc: linuxppc-embedded
=20
> > What's the recommended function to call to go from a
> > virtual -> physical address, but without doing a cache
> > flush/invalidate?
>=20
> There is no generic function to do that in a driver since
> no mainstream drivers in the kernel need to do it. Generally
> you can rework the driver such that you cache the DMA address
> as Dan suggested already. I don't know your exact usage, however,
> you can allocate memory with dma_alloc_noncoherent() that is
> cached on ppc32 NOT_CACHE_COHERENT prcoessors and stash the
> dma_addr_t/void * for later use. The other way is to kmalloc and
> dma_map_single() (stashing the same way) which is basically the
> same thing.
For 8xx/82xx (and other PQ cpus) there is a lot of drivers liberally
using
__pa and __va to get physical and virtual adresses for structures and
buffer descriptor tables for the CPM.
What would the preferred way of handling both of these be?
__pa() -> dma_map_single() ?
__va() -> ?
^ permalink raw reply
* Re: [PATCH 03/04] Freescale Ethernet combined driver
From: Pantelis Antoniou @ 2005-05-10 13:36 UTC (permalink / raw)
To: Matt Porter; +Cc: Tom Rini, linuxppc-embedded
In-Reply-To: <20050510064702.B21835@cox.net>
Matt Porter wrote:
> On Tue, May 10, 2005 at 04:14:51PM +0300, Pantelis Antoniou wrote:
>
>>Matt Porter wrote:
>>
>>>On Tue, May 10, 2005 at 08:13:48AM -0400, Dan Malek wrote:
>>>
>>>
>>>>On May 10, 2005, at 7:17 AM, Pantelis Antoniou wrote:
>>>>
>>>>
>>>>
>>>>>This patch replace iopa use with virt_to_phys.
>>>>
>>>>Not gonna work .....
>>>>
>>>>When you map uncached on 8xx you get a new vmalloc()
>>>>space. The virt_to_xxx macros don't work on those addresses.
>>>>You need to use the dma_consistent() function, stash the
>>>>real physical address it returns and then use it where
>>>>appropriate.
>>>
>>>
>>>That and the use of virt_to_* and friends is deprecated by
>>>the DMA API. You'll never get that upstream even if it were
>>>a case where it did work. That's a good thing to know for
>>>anybody doing other drivers...
>>>
>>>-Matt
>>>
>>>
>>
>>OK then.
>>
>>What's the recommended function to call to go from a
>>virtual -> physical address, but without doing a cache
>>flush/invalidate?
>
>
> There is no generic function to do that in a driver since
> no mainstream drivers in the kernel need to do it. Generally
> you can rework the driver such that you cache the DMA address
> as Dan suggested already. I don't know your exact usage, however,
> you can allocate memory with dma_alloc_noncoherent() that is
> cached on ppc32 NOT_CACHE_COHERENT prcoessors and stash the
> dma_addr_t/void * for later use. The other way is to kmalloc and
> dma_map_single() (stashing the same way) which is basically the
> same thing.
>
> Do you have a case where this doesn't work?
>
No that will do just fine.
I just want to know the recommended way to do it.
> -Matt
>
>
Regards
Pantelis
^ permalink raw reply
* Re: [PATCH 03/04] Freescale Ethernet combined driver
From: Matt Porter @ 2005-05-10 13:47 UTC (permalink / raw)
To: Pantelis Antoniou; +Cc: Tom Rini, linuxppc-embedded
In-Reply-To: <4280B3CB.8030007@intracom.gr>
On Tue, May 10, 2005 at 04:14:51PM +0300, Pantelis Antoniou wrote:
> Matt Porter wrote:
> > On Tue, May 10, 2005 at 08:13:48AM -0400, Dan Malek wrote:
> >
> >>On May 10, 2005, at 7:17 AM, Pantelis Antoniou wrote:
> >>
> >>
> >>>This patch replace iopa use with virt_to_phys.
> >>
> >>Not gonna work .....
> >>
> >>When you map uncached on 8xx you get a new vmalloc()
> >>space. The virt_to_xxx macros don't work on those addresses.
> >>You need to use the dma_consistent() function, stash the
> >>real physical address it returns and then use it where
> >>appropriate.
> >
> >
> > That and the use of virt_to_* and friends is deprecated by
> > the DMA API. You'll never get that upstream even if it were
> > a case where it did work. That's a good thing to know for
> > anybody doing other drivers...
> >
> > -Matt
> >
> >
>
> OK then.
>
> What's the recommended function to call to go from a
> virtual -> physical address, but without doing a cache
> flush/invalidate?
There is no generic function to do that in a driver since
no mainstream drivers in the kernel need to do it. Generally
you can rework the driver such that you cache the DMA address
as Dan suggested already. I don't know your exact usage, however,
you can allocate memory with dma_alloc_noncoherent() that is
cached on ppc32 NOT_CACHE_COHERENT prcoessors and stash the
dma_addr_t/void * for later use. The other way is to kmalloc and
dma_map_single() (stashing the same way) which is basically the
same thing.
Do you have a case where this doesn't work?
-Matt
^ permalink raw reply
* Serial console settings nightmare...
From: Garcia Jérémie @ 2005-05-10 13:45 UTC (permalink / raw)
To: linuxppc-dev
Hi everybody,
I'm trying to get a montavista hardhat pro 3.1 (kernel 2.4.20) running =
on our hardware. This one uses a ppc 405 EP.
In order to do that, we use a vxWorks bootloader to download via ftp the =
linux image (zImage.treeboot) in memory.=20
We had to modify some parts of linux code to achieve that. Indeed, linux =
bootstrap (which relocate & decompress) waits a board info structure =
from the bootloader;
in our case the vxWorks bootloader was not able to do that so we had =
this in a static way :
/**********************/
/* ppc4xx_setup.c */
/**********************/
void __init
platform_init(unsigned long r3, unsigned long r4, unsigned long r5,
unsigned long r6, unsigned long r7)
{
parse_bootinfo(find_bootinfo());
/* We use VxWorks bootrom, so we have to create ourselves the boot info =
structure */
bd_t my_bd_t;
=20
strcpy(my_bd_t.bi_s_version,"v1"); /* Version of =
this structure */
strcpy(my_bd_t.bi_r_version,"bootrom linux 1.0") ; /* Version of the =
IBM ROM */
my_bd_t.bi_memsize =3D 16*1024*1024 ; /* DRAM =
installed, in bytes */
my_bd_t.bi_intfreq =3D 200000000 ; /* Processor speed, in Hz =
*/
my_bd_t.bi_busfreq =3D 100000000 ; /* PLB Bus speed, in Hz =
*/
my_bd_t.bi_pllouta_freq =3D 400000000 ; /* PLL OUTA speed, in Hz =
*/
my_bd_t.bi_opb_busfreq =3D 50000000 ; /* OPB Bus speed, in Hz =
*/
my_bd_t.bi_iic_fast[0] =3D 0 ; /* Use fast i2c mode =
*/ =20
=20
memcpy((void *) __res, (void *) &my_bd_t,sizeof (bd_t));
...
/**********************/
/* embed_config.c */
/**********************/
bd_t * get_board_info(void)
{
my_strcpy(my_bd_t.bi_s_version,"v1"); /* Version of =
this structure */
my_strcpy(my_bd_t.bi_r_version,"bootrom linux 1.0") ; /* Version of =
the IBM ROM */
my_bd_t.bi_memsize =3D 16*1024*1024 ; /* DRAM =
installed, in bytes */
my_bd_t.bi_intfreq =3D 200000000 ; /* Processor speed, in Hz =
*/
my_bd_t.bi_busfreq =3D 100000000 ; /* PLB Bus speed, in Hz =
*/
my_bd_t.bi_pllouta_freq =3D 400000000 ; /* PLL OUTA speed, in Hz =
*/
/*my_bd_t.bi_opb_busfreq =3D 50000000 ; /* OPB Bus speed, in Hz =
*/
/*my_bd_t.bi_iic_fast[0] =3D 0 ; /* Use fast i2c mode =
*/
/* We return the address of our global structure */ =20
return &my_bd_t;
} =20
After that modifications the kernel boot process seemed to begin =
properly. But I can't see it properly on my serial console after
the linux bootstrap give the control to the kernel execution entry point =
(head_4xx.S).=20
Here is what I see on it:
loaded at: 00C7B250 00D374C4
relocated to: 00400000 004BC274
board data at: 004B913C 004B9190
relocated to: 0040547C 004054D0
zimage at: 00405AFF 004B8B22
avail ram: 004BD000 01000000
Linux/PPC load: console=3DttyS0,38400 root=3D/dev/nfs rw ip=3Don
Uncompressing Linux...done.
Now booting the kernel
.?w..tg?..*/)1j..-b?./?...V..?..-..W..E..?..4.)-W.?.-CV....i\Q!n.?v.-....=
;rs5k.5%.?i-.?-.-..V.....?!.-.?.Cn..?-.Fh-..g-.?wH.k... =
.[1;156H.[1;156H.
As you can see, I'd like my linux to have a serial setting of 38400 8n1. =
To achieve that, I modified the following things:
- in "ns16550.c": #define SERIAL_BAUD 38400 //(instead of 9600)
- in "tty_io.c" (console_init() function ): =20
tty_std_termios.c_iflag =3D ICRNL | IXON;
tty_std_termios.c_oflag =3D OPOST | ONLCR;
tty_std_termios.c_cflag =3D B38400 | CS8 | CREAD | HUPCL; =
//(instead of 9600)
tty_std_termios.c_lflag =3D ISIG | ICANON | ECHO | ECHOE | ECHOK =
|ECHOCTL | ECHOKE | IEXTEN;
- in "arch/ppc/platforms/myplatform.h" : #define BASE_BAUD xxxxxx
As you can see I haven't given a value for the BASE_BAUD value. Indeed, =
I can not find the right method to calculate it.
Could someone give me the formula please given the following infos:
- UART_DIV =3D 0x15 (so 21 in decimal)
- all other clocking info are given above in the "get_board_info"
Do you see sometrhing wrong there cause without any "readable" console =
I'm totally blind and I can't debug anything.
Please help me cause I've read so many forums,docs, and others that I'm =
really desperate... HELP!!!
J=E9r=E9mie
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox