* [PATCH 10/16] drivers/net/wireless/wl12xx: Use available error codes
From: Julia Lawall @ 2010-08-16 16:27 UTC (permalink / raw)
To: Luciano Coelho, John W. Linville, linux-wireless, netdev,
linux-kernel, kernel-ja
From: Julia Lawall <julia@diku.dk>
Error codes are stored in ret, but the return value is always 0. Return
ret instead.
The semantic match that finds this problem is as follows:
(http://coccinelle.lip6.fr/)
// <smpl>
@r@
local idexpression x;
constant C;
@@
if (...) { ...
x = -C
... when != x
(
return <+...x...+>;
|
return NULL;
|
return;
|
* return ...;
)
}
// </smpl>
Signed-off-by: Julia Lawall <julia@diku.dk>
---
This changes the semantics and has not been tested.
drivers/net/wireless/wl12xx/wl1271_acx.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/wireless/wl12xx/wl1271_acx.c b/drivers/net/wireless/wl12xx/wl1271_acx.c
index bb245f0..f03ad08 100644
--- a/drivers/net/wireless/wl12xx/wl1271_acx.c
+++ b/drivers/net/wireless/wl12xx/wl1271_acx.c
@@ -269,7 +269,7 @@ int wl1271_acx_pd_threshold(struct wl1271 *wl)
out:
kfree(pd);
- return 0;
+ return ret;
}
int wl1271_acx_slot(struct wl1271 *wl, enum acx_slot_type slot_time)
^ permalink raw reply related
* [PATCH 8/16] net/ax25: Use available error codes
From: Julia Lawall @ 2010-08-16 16:26 UTC (permalink / raw)
To: Ralf Baechle, David S. Miller, linux-hams, linux-kernel, netdev,
kernel-janitors
From: Julia Lawall <julia@diku.dk>
Error codes are stored in err, but the return value is always 0. Return
err instead.
The semantic match that finds this problem is as follows:
(http://coccinelle.lip6.fr/)
// <smpl>
@r@
local idexpression x;
constant C;
@@
if (...) { ...
x = -C
... when != x
(
return <+...x...+>;
|
return NULL;
|
return;
|
* return ...;
)
}
// </smpl>
Signed-off-by: Julia Lawall <julia@diku.dk>
---
This changes the semantics and has not been tested.
net/ax25/ax25_route.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/net/ax25/ax25_route.c b/net/ax25/ax25_route.c
index 7805945..a169084 100644
--- a/net/ax25/ax25_route.c
+++ b/net/ax25/ax25_route.c
@@ -412,7 +412,7 @@ int ax25_rt_autobind(ax25_cb *ax25, ax25_address *addr)
{
ax25_uid_assoc *user;
ax25_route *ax25_rt;
- int err;
+ int err = 0;
if ((ax25_rt = ax25_get_route(addr, NULL)) == NULL)
return -EHOSTUNREACH;
@@ -453,7 +453,7 @@ int ax25_rt_autobind(ax25_cb *ax25, ax25_address *addr)
put:
ax25_put_route(ax25_rt);
- return 0;
+ return err;
}
struct sk_buff *ax25_rt_build_path(struct sk_buff *skb, ax25_address *src,
^ permalink raw reply related
* [PATCH 3/16] drivers/net/wireless/wl12xx: Use available error codes
From: Julia Lawall @ 2010-08-16 16:25 UTC (permalink / raw)
To: Kalle Valo, John W. Linville, linux-wireless, netdev,
linux-kernel, kernel-janitors
From: Julia Lawall <julia@diku.dk>
In each case, error codes are stored in ret, but the return value is always
0. Return ret instead.
The semantic match that finds this problem is as follows:
(http://coccinelle.lip6.fr/)
// <smpl>
@r@
local idexpression x;
constant C;
@@
if (...) { ...
x = -C
... when != x
(
return <+...x...+>;
|
return NULL;
|
return;
|
* return ...;
)
}
// </smpl>
Signed-off-by: Julia Lawall <julia@diku.dk>
---
This changes the semantics and has not been tested.
drivers/net/wireless/wl12xx/wl1251_acx.c | 2 +-
drivers/net/wireless/wl12xx/wl1251_cmd.c | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/net/wireless/wl12xx/wl1251_acx.c b/drivers/net/wireless/wl12xx/wl1251_acx.c
index 91891f9..2f8a2ba 100644
--- a/drivers/net/wireless/wl12xx/wl1251_acx.c
+++ b/drivers/net/wireless/wl12xx/wl1251_acx.c
@@ -380,7 +380,7 @@ int wl1251_acx_pd_threshold(struct wl1251 *wl)
out:
kfree(pd);
- return 0;
+ return ret;
}
int wl1251_acx_slot(struct wl1251 *wl, enum acx_slot_type slot_time)
diff --git a/drivers/net/wireless/wl12xx/wl1251_cmd.c b/drivers/net/wireless/wl12xx/wl1251_cmd.c
index a37b30c..949b6a3 100644
--- a/drivers/net/wireless/wl12xx/wl1251_cmd.c
+++ b/drivers/net/wireless/wl12xx/wl1251_cmd.c
@@ -200,7 +200,7 @@ int wl1251_cmd_vbm(struct wl1251 *wl, u8 identity,
out:
kfree(vbm);
- return 0;
+ return ret;
}
int wl1251_cmd_data_path(struct wl1251 *wl, u8 channel, bool enable)
^ permalink raw reply related
* Re: [iproute2] iproute2: Fix 'addr flush secondary' logic.
From: Brian Haley @ 2010-08-16 15:58 UTC (permalink / raw)
To: Ben Greear; +Cc: netdev
In-Reply-To: <4C65A1CD.5090800@candelatech.com>
On 08/13/2010 03:49 PM, Ben Greear wrote:
> Attached is an updated patch to fix some of the errors you
> mentioned. It also fixes a bug when '-s -s' is used: The old
> code modified the ifa flags, which made it not handle the check
> for primary v/s secondary correctly in the next filter.
Thanks for the update, nitpicking comments below.
BTW, promote_secondaries was the reason for some of my testing
issues as I don't usually run with that enabled.
diff --git a/ip/ipaddress.c b/ip/ipaddress.c
index 5f0789c..51b59b4 100644
--- a/ip/ipaddress.c
+++ b/ip/ipaddress.c
> @@ -571,40 +572,41 @@ int print_addrinfo(const struct sockaddr_nl *who, struct nlmsghdr *n,
> abuf, sizeof(abuf)));
> }
> fprintf(fp, "scope %s ", rtnl_rtscope_n2a(ifa->ifa_scope, b1, sizeof(b1)));
> + ifa_flags = ifa->ifa_flags;
> if (ifa->ifa_flags&IFA_F_SECONDARY) {
Maybe put a comment as to the reason, so someone doesn't change it
back in the future:
/* Use local copy of flags to not interfere with filtering code */
> @@ -848,6 +850,7 @@ static int ipaddr_list_or_flush(int argc, char **argv, int flush)
> exit(1);
> }
> if (filter.flushed == 0) {
> + flush_done:
> if (show_stats) {
> if (round == 0)
> printf("Nothing to flush.\n");
Can you put "flush_done" at the beginning of the line?
> @@ -865,6 +868,13 @@ static int ipaddr_list_or_flush(int argc, char **argv, int flush)
> printf("\n*** Round %d, deleting %d addresses ***\n", round, filter.flushed);
> fflush(stdout);
> }
> +
> + /* If we are flushing, and specifying primary, then we want to flush only a single round.
> + * Otherwise, we'll start flushing secondaries that were promoted to primaries
> + */
> + if (!(filter.flags & IFA_F_SECONDARY) && (filter.flagmask & IFA_F_SECONDARY)) {
> + goto flush_done;
> + }
You don't need the {} here. Also, the comment line-wraps since it's
> 80 chars.
Everything seems to work correctly in my limited testing otherwise, thanks.
Tested-by: Brian Haley <brian.haley@hp.com>
-Brian
^ permalink raw reply
* Re: [PATCH 1/5] ptp: Added a brand new class driver for ptp clocks.
From: Arnd Bergmann @ 2010-08-16 14:26 UTC (permalink / raw)
To: Richard Cochran
Cc: Rodolfo Giometti, netdev-u79uwXL29TY76Z2rM5mHXA,
devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linuxppc-dev-uLR06cmDAlY/bJ5BZ2RsiQ,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
Krzysztof Halasa
In-Reply-To: <363bd749a38d0b785d8431e591bf54c38db4c2d7.1281956490.git.richard.cochran-3mrvs1K0uXizZXS1Dc/lvw@public.gmane.org>
On Monday 16 August 2010, Richard Cochran wrote:
> This patch adds an infrastructure for hardware clocks that implement
> IEEE 1588, the Precision Time Protocol (PTP). A class driver offers a
> registration method to particular hardware clock drivers. Each clock is
> exposed to user space as a character device with ioctls that allow tuning
> of the PTP clock.
Have you considered integrating the subsystem into the Posix clock/timer
framework?
I can't really tell from reading the source if this is possible or
not, but my feeling is that if it can be done, that would be a much
nicer interface. We already have clock_gettime/clock_settime/
timer_settime/... system calls, and while you'd need to add another
clockid and some syscalls, my feeling is that it will be more
usable in the end.
> +static const struct file_operations ptp_fops = {
> + .owner = THIS_MODULE,
> + .ioctl = ptp_ioctl,
> + .open = ptp_open,
> + .poll = ptp_poll,
> + .read = ptp_read,
> + .release = ptp_release,
> +};
.ioctl is gone, you have to use .unlocked_ioctl and make sure that
your ptp_ioctl function can handle being called concurrently.
You should also add a .compat_ioctl function, ideally one that
points to ptp_ioctl so you don't have to list every command as
being compatible. Right now, some commands are incompatible,
which means you need more changes to the interface.
> +struct ptp_clock_timer {
> + int alarm_index; /* Which alarm to query or configure. */
> + int signum; /* Requested signal. */
> + int flags; /* Zero or TIMER_ABSTIME, see TIMER_SETTIME(2) */
> + struct itimerspec tsp; /* See TIMER_SETTIME(2) */
> +};
This data structure is incompatible between 32 and 64 bit user space,
so you would need a compat_ioctl() function to convert between the
two formats. Better define the structure in a compatible way, avoiding
variable-length fields and implicit padding.
> +struct ptp_clock_request {
> + int type; /* One of the ptp_request_types enumeration values. */
> + int index; /* Which channel to configure. */
> + struct timespec ts; /* For period signals, the desired period. */
> + int flags; /* Bit field for PTP_ENABLE_FEATURE or other flags. */
> +};
Same problem here, timespec is either 64 or 128 bits long and has different
alignment.
> +struct ptp_extts_event {
> + int index;
> + struct timespec ts;
> +};
here too.
> +#define PTP_CLOCK_VERSION 0x00000001
> +
> +#define PTP_CLK_MAGIC '='
> +
> +#define PTP_CLOCK_APIVERS _IOR (PTP_CLK_MAGIC, 1, __u32)
Try avoiding versioned ABIs. It's better to just add new ioctls
or syscalls when you need some extra functionality and leave the
old ones around.
> +#define PTP_CLOCK_ADJTIME _IOW (PTP_CLK_MAGIC, 3, struct timespec)
> +#define PTP_CLOCK_GETTIME _IOR (PTP_CLK_MAGIC, 4, struct timespec)
> +#define PTP_CLOCK_SETTIME _IOW (PTP_CLK_MAGIC, 5, struct timespec)
These are also incompatible.
Arnd
^ permalink raw reply
* Re: TAHI CN-6-4-1 failed on Linux 2.6.32 kernel
From: Steve Chen @ 2010-08-16 14:07 UTC (permalink / raw)
To: Brian Haley; +Cc: usagi-users-ctl, netdev
In-Reply-To: <AANLkTik9=TcMWzNyKWYyDu+bbGscPne6++gD42aju9s_@mail.gmail.com>
On Fri, Aug 13, 2010 at 5:25 PM, Steve Chen <schen@mvista.com> wrote:
> On Fri, Aug 13, 2010 at 12:55 PM, Brian Haley <brian.haley@hp.com> wrote:
>> On 08/13/2010 01:34 PM, Steve Chen wrote:
>>>>>>> The TAHI correspondent node tests CN-6-4-1 (Processing in upper layer
>>>>>>> - Echo Checksum) failed for me in the 2.6.32 kernel. It appears that
>>>>>>> the Linux kernel is replying the ICMP echo request in
>>>>>>> icmpv6_echo_reply without much checking. Is this an intentional
>>>>>>> non-conformance to RFC3775 section 9.3.1?
>> [snip]
>>
>>> It appears that skb->ip_summed is always 1 (CHECKSUM_UNNECESSARY).
>>> I'm using e1000e. Looking at the driver, there is a checksum offload
>>> hardware. I think the code is doing frame check on the entire
>>> Ethernet packet. Since no error was found, it assume everything
>>> inside is correct.
>>
>> # ethtool -K ethX rx off
>>
>> Does that help? Does using a different NIC help?
>>
>
> ethtool did not help.
>
> I tried to run the test with a different NIC, but I keep getting
>
> ...
>
> IPSEC_AKEY : --------------------
> IPSEC_EALGO : ---------
> IPSEC_EKEY : ------------------------
> IPSEC_SUPPORT : OFF
> RR_TIMEOUT : 3
> TIMEOUT : 2
> WAIT_RATELIMIT : 1
> send Ra_R0_AllNd
> Wait 3 sec.
> Clear Captured Packets (Link0)
> Clear Captured Packets (Link0)
> send Ns_R0_AllNd
> ######### Packe Name(RH) Field Value(NextHeader) is NULL
> CNT_SendAndRecv Status=UnKnown
> NG UnKnown
> -> Initialization Fail
> ...
Brian,
Oops, set the Link0 in nut.def to the wrong value. However, I'm still
getting the same failure with different NIC. I'll start looking into
the code. Please let me know your test results.
Thanks,
Steve
^ permalink raw reply
* [PATCH] rds: fix a leak of kernel memory
From: Eric Dumazet @ 2010-08-16 13:25 UTC (permalink / raw)
To: David Miller; +Cc: Andy Grover, netdev
struct rds_rdma_notify contains a 32 bits hole on 64bit arches,
make sure it is zeroed before copying it to user.
Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>
CC: Andy Grover <andy.grover@oracle.com>
---
net/rds/recv.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/net/rds/recv.c b/net/rds/recv.c
index 795a00b..c93588c 100644
--- a/net/rds/recv.c
+++ b/net/rds/recv.c
@@ -297,7 +297,7 @@ static int rds_still_queued(struct rds_sock *rs, struct rds_incoming *inc,
int rds_notify_queue_get(struct rds_sock *rs, struct msghdr *msghdr)
{
struct rds_notifier *notifier;
- struct rds_rdma_notify cmsg;
+ struct rds_rdma_notify cmsg = { 0 }; /* fill holes with zero */
unsigned int count = 0, max_messages = ~0U;
unsigned long flags;
LIST_HEAD(copy);
^ permalink raw reply related
* [net-2.6 1/2] bnx2x: Fix PHY locking problem
From: Yaniv Rosner @ 2010-08-16 16:34 UTC (permalink / raw)
To: davem; +Cc: netdev
PHY locking is required between two ports for some external PHYs. Since
initialization was done in the common init function (called only on the
first port initialization) rather than in the port init function, there
was in fact no PHY locking between the ports.
Signed-off-by: Yaniv Rosner <yanivr@broadcom.com>
Signed-off-by: Eilon Greenstein <eilong@broadcom.com>
---
drivers/net/bnx2x/bnx2x_main.c | 9 +++++++--
1 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/drivers/net/bnx2x/bnx2x_main.c b/drivers/net/bnx2x/bnx2x_main.c
index b4ec2b0..f8c3f08 100644
--- a/drivers/net/bnx2x/bnx2x_main.c
+++ b/drivers/net/bnx2x/bnx2x_main.c
@@ -4328,10 +4328,12 @@ static int bnx2x_init_port(struct bnx2x *bp)
val |= aeu_gpio_mask;
REG_WR(bp, offset, val);
}
+ bp->port.need_hw_lock = 1;
break;
- case PORT_HW_CFG_XGXS_EXT_PHY_TYPE_SFX7101:
case PORT_HW_CFG_XGXS_EXT_PHY_TYPE_BCM8727:
+ bp->port.need_hw_lock = 1;
+ case PORT_HW_CFG_XGXS_EXT_PHY_TYPE_SFX7101:
/* add SPIO 5 to group 0 */
{
u32 reg_addr = (port ? MISC_REG_AEU_ENABLE1_FUNC_1_OUT_0 :
@@ -4341,7 +4343,10 @@ static int bnx2x_init_port(struct bnx2x *bp)
REG_WR(bp, reg_addr, val);
}
break;
-
+ case PORT_HW_CFG_XGXS_EXT_PHY_TYPE_BCM8072:
+ case PORT_HW_CFG_XGXS_EXT_PHY_TYPE_BCM8073:
+ bp->port.need_hw_lock = 1;
+ break;
default:
break;
}
^ permalink raw reply related
* [net-2.6 2/2] bnx2x: Update bnx2x version to 1.52.53-4
From: Yaniv Rosner @ 2010-08-16 16:34 UTC (permalink / raw)
To: davem; +Cc: netdev
Update bnx2x version to 1.52.53-4
Signed-off-by: Yaniv Rosner <yanivr@broadcom.com>
Signed-off-by: Eilon Greenstein <eilong@broadcom.com>
---
drivers/net/bnx2x/bnx2x.h | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/drivers/net/bnx2x/bnx2x.h b/drivers/net/bnx2x/bnx2x.h
index 53af9c9..5fe78f7 100644
--- a/drivers/net/bnx2x/bnx2x.h
+++ b/drivers/net/bnx2x/bnx2x.h
@@ -20,7 +20,7 @@
* (you will need to reboot afterwards) */
/* #define BNX2X_STOP_ON_ERROR */
-#define DRV_MODULE_VERSION "1.52.53-3"
-#define DRV_MODULE_RELDATE "2010/18/04"
+#define DRV_MODULE_VERSION "1.52.53-4"
+#define DRV_MODULE_RELDATE "2010/16/08"
#define BNX2X_BC_VER 0x040200
^ permalink raw reply related
* Re: [PATCH] netlink: fix compat recvmsg
From: Eric Dumazet @ 2010-08-16 13:01 UTC (permalink / raw)
To: Johannes Berg; +Cc: David Miller, netdev, Kalle Valo
In-Reply-To: <1281963025.2524.43.camel@edumazet-laptop>
Le lundi 16 août 2010 à 14:50 +0200, Eric Dumazet a écrit :
> Le lundi 16 août 2010 à 09:20 +0200, Johannes Berg a écrit :
> > From: Johannes Berg <johannes.berg@intel.com>
> >
> > Since
> > commit 1dacc76d0014a034b8aca14237c127d7c19d7726
> > Author: Johannes Berg <johannes@sipsolutions.net>
> > Date: Wed Jul 1 11:26:02 2009 +0000
> >
> > net/compat/wext: send different messages to compat tasks
> >
> > we had a race condition when setting and then
> > restoring frag_list. Eric attempted to fix it,
> > but the fix created even worse problems.
> >
> > However, the original motivation I had when I
> > added the code that turned out to be racy is
> > no longer clear to me, since we only copy up
> > to skb->len to userspace, which doesn't include
> > the frag_list length. As a result, not doing
> > any frag_list clearing and restoring avoids
> > the race condition, while not introducing any
> > other problems.
> >
> > Additionally, while preparing this patch I found
> > that since none of the remaining netlink code is
> > really aware of the frag_list, we need to use the
> > original skb's information for packet information
> > and credentials. This fixes, for example, the
> > group information received by compat tasks.
> >
> > Cc: Eric Dumazet <eric.dumazet@gmail.com>
> > Cc: stable@kernel.org [2.6.31+, for 2.6.35 revert 1235f504aa]
> > Signed-off-by: Johannes Berg <johannes.berg@intel.com>
> > ---
>
> As a side effect, it might fix the
>
> if (nlk->flags & NETLINK_RECV_PKTINFO)
> netlink_cmsg_recv_pktinfo(msg, skb);
>
> That tried to use pktinfo from the slave skb. After this
> patch, skb points to master skb.
>
As you already mentioned it in your changelog, I missed it ;)
^ permalink raw reply
* Re: [PATCH] netlink: fix compat recvmsg
From: Johannes Berg @ 2010-08-16 12:54 UTC (permalink / raw)
To: Eric Dumazet; +Cc: David Miller, netdev, Kalle Valo
In-Reply-To: <1281963025.2524.43.camel@edumazet-laptop>
On Mon, 2010-08-16 at 14:50 +0200, Eric Dumazet wrote:
> > Additionally, while preparing this patch I found
> > that since none of the remaining netlink code is
> > really aware of the frag_list, we need to use the
> > original skb's information for packet information
> > and credentials. This fixes, for example, the
> > group information received by compat tasks.
> As a side effect, it might fix the
>
> if (nlk->flags & NETLINK_RECV_PKTINFO)
> netlink_cmsg_recv_pktinfo(msg, skb);
>
> That tried to use pktinfo from the slave skb. After this
> patch, skb points to master skb.
Yeah, that's what I was looking at regarding the "packet information" I
wrote about. The group information was wrong (0 rather than 1), I could
see that in strace (though I had to compile a strace64 binary first...)
> Acked-by: Eric Dumazet <eric.dumazet@gmail.com>
>
> Thanks !
Thanks for looking, and finding the bug to start with :-)
johannes
^ permalink raw reply
* Re: [PATCH] netlink: fix compat recvmsg
From: Eric Dumazet @ 2010-08-16 12:50 UTC (permalink / raw)
To: Johannes Berg; +Cc: David Miller, netdev, Kalle Valo
In-Reply-To: <1281943244.3683.8.camel@jlt3.sipsolutions.net>
Le lundi 16 août 2010 à 09:20 +0200, Johannes Berg a écrit :
> From: Johannes Berg <johannes.berg@intel.com>
>
> Since
> commit 1dacc76d0014a034b8aca14237c127d7c19d7726
> Author: Johannes Berg <johannes@sipsolutions.net>
> Date: Wed Jul 1 11:26:02 2009 +0000
>
> net/compat/wext: send different messages to compat tasks
>
> we had a race condition when setting and then
> restoring frag_list. Eric attempted to fix it,
> but the fix created even worse problems.
>
> However, the original motivation I had when I
> added the code that turned out to be racy is
> no longer clear to me, since we only copy up
> to skb->len to userspace, which doesn't include
> the frag_list length. As a result, not doing
> any frag_list clearing and restoring avoids
> the race condition, while not introducing any
> other problems.
>
> Additionally, while preparing this patch I found
> that since none of the remaining netlink code is
> really aware of the frag_list, we need to use the
> original skb's information for packet information
> and credentials. This fixes, for example, the
> group information received by compat tasks.
>
> Cc: Eric Dumazet <eric.dumazet@gmail.com>
> Cc: stable@kernel.org [2.6.31+, for 2.6.35 revert 1235f504aa]
> Signed-off-by: Johannes Berg <johannes.berg@intel.com>
> ---
As a side effect, it might fix the
if (nlk->flags & NETLINK_RECV_PKTINFO)
netlink_cmsg_recv_pktinfo(msg, skb);
That tried to use pktinfo from the slave skb. After this
patch, skb points to master skb.
Acked-by: Eric Dumazet <eric.dumazet@gmail.com>
Thanks !
^ permalink raw reply
* [PATCH 5/5] ptp: Added a clock driver for the National Semiconductor PHYTER.
From: Richard Cochran @ 2010-08-16 11:19 UTC (permalink / raw)
To: netdev
Cc: linux-kernel, linuxppc-dev, devicetree-discuss, linux-arm-kernel,
Krzysztof Halasa, Rodolfo Giometti
In-Reply-To: <cover.1281956490.git.richard.cochran@omicron.at>
This patch adds support for the PTP clock found on the DP83640.
The basic clock operations and one external time stamp have
been implemented.
Signed-off-by: Richard Cochran <richard.cochran@omicron.at>
---
drivers/net/phy/Kconfig | 29 ++
drivers/net/phy/Makefile | 1 +
drivers/net/phy/dp83640.c | 904 +++++++++++++++++++++++++++++++++++++++++
drivers/net/phy/dp83640_reg.h | 261 ++++++++++++
4 files changed, 1195 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/phy/dp83640.c
create mode 100644 drivers/net/phy/dp83640_reg.h
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index a527e37..a2d0753 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -77,6 +77,35 @@ config NATIONAL_PHY
---help---
Currently supports the DP83865 PHY.
+config DP83640_PHY
+ tristate "Driver for the National Semiconductor DP83640 PHYTER"
+ depends on PTP_1588_CLOCK
+ depends on NETWORK_PHY_TIMESTAMPING
+ ---help---
+ Supports the DP83640 PHYTER with IEEE 1588 features.
+
+ This driver adds support for using the DP83640 as a PTP
+ clock. This clock is only useful if your PTP programs are
+ getting hardware time stamps on the PTP Ethernet packets
+ using the SO_TIMESTAMPING API.
+
+ In order for this to work, your MAC driver must also
+ implement the skb_tx_timetamp() function.
+
+config DP83640_PHY_STATUS_FRAMES
+ bool "DP83640 Status Frames"
+ default y
+ depends on DP83640_PHY
+ ---help---
+ This option allows the DP83640 PHYTER driver to obtain time
+ stamps from the PHY via special status frames, rather than
+ reading over the MDIO bus. Using status frames is therefore
+ more efficient. However, if enabled, this option will cause
+ the driver to add a mutlicast address to the MAC.
+
+ Say Y here, unless your MAC does not support multicast
+ destination addresses.
+
config STE10XP
depends on PHYLIB
tristate "Driver for STMicroelectronics STe10Xp PHYs"
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index 13bebab..2333215 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -19,6 +19,7 @@ obj-$(CONFIG_FIXED_PHY) += fixed.o
obj-$(CONFIG_MDIO_BITBANG) += mdio-bitbang.o
obj-$(CONFIG_MDIO_GPIO) += mdio-gpio.o
obj-$(CONFIG_NATIONAL_PHY) += national.o
+obj-$(CONFIG_DP83640_PHY) += dp83640.o
obj-$(CONFIG_STE10XP) += ste10Xp.o
obj-$(CONFIG_MICREL_PHY) += micrel.o
obj-$(CONFIG_MDIO_OCTEON) += mdio-octeon.o
diff --git a/drivers/net/phy/dp83640.c b/drivers/net/phy/dp83640.c
new file mode 100644
index 0000000..38a7202
--- /dev/null
+++ b/drivers/net/phy/dp83640.c
@@ -0,0 +1,904 @@
+/*
+ * Driver for the National Semiconductor DP83640 PHYTER
+ *
+ * Copyright (C) 2010 OMICRON electronics GmbH
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include <linux/ethtool.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/mii.h>
+#include <linux/module.h>
+#include <linux/net_tstamp.h>
+#include <linux/netdevice.h>
+#include <linux/phy.h>
+#include <linux/ptp_classify.h>
+#include <linux/ptp_clock_kernel.h>
+
+#include "dp83640_reg.h"
+
+#ifdef CONFIG_DP83640_PHY_STATUS_FRAMES
+#define USE_STATUS_FRAMES
+#endif
+
+#define DP83640_PHY_ID 0x20005ce1
+#define PAGESEL 0x13
+#define LAYER4 0x02
+#define LAYER2 0x01
+#define MAX_RXTS 4
+#define MAX_TXTS 4
+#define N_EXT_TS 1
+#define PSF_PTPVER 2
+#define PSF_EVNT 0x4000
+#define PSF_RX 0x2000
+#define PSF_TX 0x1000
+#define EXT_EVENT 1
+#define EXT_GPIO 1
+
+#if defined(__BIG_ENDIAN)
+#define ENDIAN_FLAG 0
+#elif defined(__LITTLE_ENDIAN)
+#define ENDIAN_FLAG PSF_ENDIAN
+#endif
+
+#define SKB_PTP_TYPE(__skb) (*(unsigned int *)((__skb)->cb))
+
+struct phy_rxts {
+ u16 ns_lo; /* ns[15:0] */
+ u16 ns_hi; /* overflow[1:0], ns[29:16] */
+ u16 sec_lo; /* sec[15:0] */
+ u16 sec_hi; /* sec[31:16] */
+ u16 seqid; /* sequenceId[15:0] */
+ u16 msgtype; /* messageType[3:0], hash[11:0] */
+};
+
+struct phy_txts {
+ u16 ns_lo; /* ns[15:0] */
+ u16 ns_hi; /* overflow[1:0], ns[29:16] */
+ u16 sec_lo; /* sec[15:0] */
+ u16 sec_hi; /* sec[31:16] */
+};
+
+struct rxts {
+ struct list_head list;
+ unsigned long tmo;
+ u64 ns;
+ u16 seqid;
+ u8 msgtype;
+ u16 hash;
+};
+
+struct dp83640_private {
+ struct phy_device *phydev;
+ struct work_struct ts_work;
+ int hwts_tx_en;
+ int hwts_rx_en;
+ int layer;
+ int version;
+ /* protects extended registers from concurrent access */
+ struct mutex extreg_mux;
+ int page;
+ /* remember the last event time stamp */
+ struct phy_txts edata;
+ /* list of rx timestamps */
+ struct list_head rxts;
+ struct list_head rxpool;
+ struct rxts rx_pool_data[MAX_RXTS];
+ /* protects above three fields from concurrent access */
+ spinlock_t rx_lock;
+
+ struct sk_buff_head rx_queue;
+ struct sk_buff_head tx_queue;
+};
+
+/* globals */
+
+static struct ptp_clock *dp83640_clock;
+DEFINE_MUTEX(clock_lock); /* protects the one and only dp83640_clock */
+
+static void do_timestamp_work(struct work_struct *work);
+
+/* extended register access functions */
+
+static int ext_read(struct phy_device *phydev, int page, u32 regnum)
+{
+ struct dp83640_private *dp83640 = phydev->priv;
+ int val;
+
+ if (dp83640->page != page) {
+ phy_write(phydev, PAGESEL, page);
+ dp83640->page = page;
+ }
+ val = phy_read(phydev, regnum);
+
+ return val;
+}
+
+static void ext_write(struct phy_device *phydev, int page, u32 regnum, u16 val)
+{
+ struct dp83640_private *dp83640 = phydev->priv;
+
+ if (dp83640->page != page) {
+ phy_write(phydev, PAGESEL, page);
+ dp83640->page = page;
+ }
+ phy_write(phydev, regnum, val);
+}
+
+static int tdr_write(struct phy_device *phydev, struct timespec *ts, u16 cmd)
+{
+ ext_write(phydev, PAGE4, PTP_TDR, ts->tv_nsec & 0xffff);/* ns[15:0] */
+ ext_write(phydev, PAGE4, PTP_TDR, ts->tv_nsec >> 16); /* ns[31:16] */
+ ext_write(phydev, PAGE4, PTP_TDR, ts->tv_sec & 0xffff); /* sec[15:0] */
+ ext_write(phydev, PAGE4, PTP_TDR, ts->tv_sec >> 16); /* sec[31:16] */
+
+ ext_write(phydev, PAGE4, PTP_CTL, cmd);
+
+ return 0;
+}
+
+/* convert phy timestamps into driver timestamps */
+
+static void phy2rxts(struct phy_rxts *p, struct rxts *rxts)
+{
+ u32 sec;
+
+ sec = p->sec_lo;
+ sec |= p->sec_hi << 16;
+
+ rxts->ns = p->ns_lo;
+ rxts->ns |= (p->ns_hi & 0x3fff) << 16;
+ rxts->ns += ((u64)sec) * 1000000000ULL;
+ rxts->seqid = p->seqid;
+ rxts->msgtype = (p->msgtype >> 12) & 0xf;
+ rxts->hash = p->msgtype & 0x0fff;
+}
+
+static u64 phy2txts(struct phy_txts *p)
+{
+ u64 ns;
+ u32 sec;
+
+ sec = p->sec_lo;
+ sec |= p->sec_hi << 16;
+
+ ns = p->ns_lo;
+ ns |= (p->ns_hi & 0x3fff) << 16;
+ ns += ((u64)sec) * 1000000000ULL;
+
+ return ns;
+}
+
+/* ptp clock methods */
+
+static int ptp_dp83640_adjfreq(void *priv, s32 ppb)
+{
+ struct dp83640_private *dp83640 = priv;
+ struct phy_device *phydev = dp83640->phydev;
+ u64 rate;
+ int neg_adj = 0;
+ u16 hi, lo;
+
+ if (!ppb)
+ return 0;
+
+ if (ppb < 0) {
+ neg_adj = 1;
+ ppb = -ppb;
+ }
+ rate = ppb;
+ rate <<= 26;
+ rate = div_u64(rate, 1953125);
+
+ hi = (rate >> 16) & PTP_RATE_HI_MASK;
+ if (neg_adj)
+ hi |= PTP_RATE_DIR;
+
+ lo = rate & 0xffff;
+
+ mutex_lock(&dp83640->extreg_mux);
+
+ ext_write(phydev, PAGE4, PTP_RATEH, hi);
+ ext_write(phydev, PAGE4, PTP_RATEL, lo);
+
+ mutex_unlock(&dp83640->extreg_mux);
+
+ return 0;
+}
+
+static int ptp_dp83640_adjtime(void *priv, struct timespec *ts)
+{
+ struct dp83640_private *dp83640 = priv;
+ int err;
+
+ mutex_lock(&dp83640->extreg_mux);
+
+ err = tdr_write(dp83640->phydev, ts, PTP_STEP_CLK);
+
+ mutex_unlock(&dp83640->extreg_mux);
+
+ return err;
+}
+
+static int ptp_dp83640_gettime(void *priv, struct timespec *ts)
+{
+ struct dp83640_private *dp83640 = priv;
+ struct phy_device *phydev = dp83640->phydev;
+ unsigned int val[4];
+
+ mutex_lock(&dp83640->extreg_mux);
+
+ ext_write(phydev, PAGE4, PTP_CTL, PTP_RD_CLK);
+
+ val[0] = ext_read(phydev, PAGE4, PTP_TDR); /* ns[15:0] */
+ val[1] = ext_read(phydev, PAGE4, PTP_TDR); /* ns[31:16] */
+ val[2] = ext_read(phydev, PAGE4, PTP_TDR); /* sec[15:0] */
+ val[3] = ext_read(phydev, PAGE4, PTP_TDR); /* sec[31:16] */
+
+ mutex_unlock(&dp83640->extreg_mux);
+
+ ts->tv_nsec = val[0] | (val[1] << 16);
+ ts->tv_sec = val[2] | (val[3] << 16);
+
+ return 0;
+}
+
+static int ptp_dp83640_settime(void *priv, struct timespec *ts)
+{
+ struct dp83640_private *dp83640 = priv;
+ int err;
+
+ mutex_lock(&dp83640->extreg_mux);
+
+ err = tdr_write(dp83640->phydev, ts, PTP_LOAD_CLK);
+
+ mutex_unlock(&dp83640->extreg_mux);
+
+ return err;
+}
+
+static int ptp_dp83640_gettimer(void *priv, int index, struct itimerspec *ts)
+{
+ /* We do not offer any ancillary timer features. */
+ return -EOPNOTSUPP;
+}
+
+static int ptp_dp83640_settimer(void *p, int i, int abs, struct itimerspec *ts)
+{
+ /* We do not offer any ancillary timer features. */
+ return -EOPNOTSUPP;
+}
+
+static int ptp_dp83640_enable(void *priv, struct ptp_clock_request *rq, int on)
+{
+ struct dp83640_private *dp83640 = priv;
+ struct phy_device *phydev = dp83640->phydev;
+ u16 evnt;
+
+ switch (rq->type) {
+ case PTP_REQUEST_EXTTS:
+ if (rq->index != 0)
+ return -EINVAL;
+ evnt = EVNT_WR | (EXT_EVENT & EVNT_SEL_MASK) << EVNT_SEL_SHIFT;
+ if (on) {
+ evnt |= (EXT_GPIO & EVNT_GPIO_MASK) << EVNT_GPIO_SHIFT;
+ evnt |= EVNT_RISE;
+ }
+ ext_write(phydev, PAGE5, PTP_EVNT, evnt);
+ return 0;
+ default:
+ break;
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static struct ptp_clock_info ptp_dp83640_caps = {
+ .owner = THIS_MODULE,
+ .name = "dp83640 timer",
+ .max_adj = 1953124,
+ .n_alarm = 0,
+ .n_ext_ts = N_EXT_TS,
+ .n_per_out = 0,
+ .pps = 0,
+ .priv = NULL,
+ .adjfreq = ptp_dp83640_adjfreq,
+ .adjtime = ptp_dp83640_adjtime,
+ .gettime = ptp_dp83640_gettime,
+ .settime = ptp_dp83640_settime,
+ .gettimer = ptp_dp83640_gettimer,
+ .settimer = ptp_dp83640_settimer,
+ .enable = ptp_dp83640_enable,
+};
+
+/* status frame conditional code */
+
+#ifdef USE_STATUS_FRAMES
+
+static u8 status_frame_dst[6] = { 0x01, 0x1B, 0x19, 0x00, 0x00, 0x00 };
+static u8 status_frame_src[6] = { 0x08, 0x00, 0x17, 0x0B, 0x6B, 0x0F };
+
+static void enable_status_frames(struct phy_device *phydev, bool on)
+{
+ u16 cfg0 = 0, ver;
+
+ if (on)
+ cfg0 = PSF_EVNT_EN | PSF_RXTS_EN | PSF_TXTS_EN | ENDIAN_FLAG;
+
+ ver = (PSF_PTPVER & VERSIONPTP_MASK) << VERSIONPTP_SHIFT;
+
+ ext_write(phydev, PAGE5, PSF_CFG0, cfg0);
+ ext_write(phydev, PAGE6, PSF_CFG1, ver);
+
+ if (!phydev->attached_dev) {
+ pr_err("expected to find an attached netdevice\n");
+ BUG();
+ }
+
+ if (on) {
+ if (dev_mc_add(phydev->attached_dev, status_frame_dst))
+ pr_warning("dp83640: failed to add mc address\n");
+ } else {
+ if (dev_mc_del(phydev->attached_dev, status_frame_dst))
+ pr_warning("dp83640: failed to delete mc address\n");
+ }
+}
+
+static bool is_status_frame(struct sk_buff *skb, int type)
+{
+ struct ethhdr *h = eth_hdr(skb);
+
+ if (PTP_CLASS_V2_L2 == type &&
+ !memcmp(h->h_source, status_frame_src, sizeof(status_frame_src)))
+ return true;
+ else
+ return false;
+}
+
+static void rx_reading_work(struct dp83640_private *dp83640)
+{
+}
+
+static void tx_timestamp_work(struct dp83640_private *dp83640)
+{
+}
+
+#else /* USE_STATUS_FRAMES */
+
+static void enable_status_frames(struct phy_device *phydev, bool on)
+{
+}
+
+static bool is_status_frame(struct sk_buff *skb, int type)
+{
+ return false;
+}
+
+static void read_rxts(struct phy_device *phydev, struct rxts *rxts)
+{
+ struct phy_rxts p;
+
+ p.ns_lo = ext_read(phydev, PAGE4, PTP_RXTS);
+ p.ns_hi = ext_read(phydev, PAGE4, PTP_RXTS);
+ p.sec_lo = ext_read(phydev, PAGE4, PTP_RXTS);
+ p.sec_hi = ext_read(phydev, PAGE4, PTP_RXTS);
+ p.seqid = ext_read(phydev, PAGE4, PTP_RXTS);
+ p.msgtype = ext_read(phydev, PAGE4, PTP_RXTS);
+
+ rxts->tmo = jiffies + HZ;
+ phy2rxts(&p, rxts);
+}
+
+static u64 read_txts(struct phy_device *phydev)
+{
+ struct phy_txts p;
+
+ p.ns_lo = ext_read(phydev, PAGE4, PTP_TXTS);
+ p.ns_hi = ext_read(phydev, PAGE4, PTP_TXTS);
+ p.sec_lo = ext_read(phydev, PAGE4, PTP_TXTS);
+ p.sec_hi = ext_read(phydev, PAGE4, PTP_TXTS);
+
+ return phy2txts(&p);
+}
+
+static void rx_reading_work(struct dp83640_private *dp83640)
+{
+ struct rxts *rxts;
+
+ mutex_lock(&dp83640->extreg_mux);
+
+ for (;;) {
+ int val = ext_read(dp83640->phydev, PAGE4, PTP_STS);
+ if (!(val & RXTS_RDY))
+ break;
+ if (list_empty(&dp83640->rxpool)) {
+ pr_warning("dp83640: rx timestamp pool is empty\n");
+ break;
+ }
+ rxts = list_first_entry(&dp83640->rxpool, struct rxts, list);
+ list_del_init(&rxts->list);
+ read_rxts(dp83640->phydev, rxts);
+ list_add_tail(&rxts->list, &dp83640->rxts);
+ }
+
+ mutex_unlock(&dp83640->extreg_mux);
+}
+
+static void tx_timestamp_work(struct dp83640_private *dp83640)
+{
+ struct skb_shared_hwtstamps shhwtstamps;
+ struct sk_buff *skb;
+ u64 ns;
+ int val;
+
+ mutex_lock(&dp83640->extreg_mux);
+
+ while ((skb = skb_dequeue(&dp83640->tx_queue)) != NULL) {
+
+ val = ext_read(dp83640->phydev, PAGE4, PTP_STS);
+ if (!(val & TXTS_RDY)) {
+ skb_queue_head(&dp83640->tx_queue, skb);
+ break;
+ }
+ ns = read_txts(dp83640->phydev);
+ memset(&shhwtstamps, 0, sizeof(shhwtstamps));
+ shhwtstamps.hwtstamp = ns_to_ktime(ns);
+ skb_complete_tx_timestamp(skb, &shhwtstamps);
+ }
+
+ mutex_unlock(&dp83640->extreg_mux);
+}
+
+#endif /* !USE_STATUS_FRAMES */
+
+/* time stamping methods */
+
+static void decode_evnt(struct dp83640_private *dp83640,
+ struct phy_txts *phy_txts, u16 ests)
+{
+ struct ptp_clock_event event;
+ int words = (ests >> EVNT_TS_LEN_SHIFT) & EVNT_TS_LEN_MASK;
+
+ switch (words) { /* fall through in every case */
+ case 3:
+ dp83640->edata.sec_hi = phy_txts->sec_hi;
+ case 2:
+ dp83640->edata.sec_lo = phy_txts->sec_lo;
+ case 1:
+ dp83640->edata.ns_hi = phy_txts->ns_hi;
+ case 0:
+ dp83640->edata.ns_lo = phy_txts->ns_lo;
+ }
+
+ event.type = PTP_CLOCK_EXTTS;
+ event.index = 0;
+ event.timestamp = phy2txts(&dp83640->edata);
+
+ ptp_clock_event(dp83640_clock, &event);
+}
+
+static void decode_rxts(struct dp83640_private *dp83640,
+ struct phy_rxts *phy_rxts)
+{
+ struct rxts *rxts;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dp83640->rx_lock, flags);
+
+ if (list_empty(&dp83640->rxpool)) {
+ pr_warning("dp83640: rx timestamp pool is empty\n");
+ goto out;
+ }
+ rxts = list_first_entry(&dp83640->rxpool, struct rxts, list);
+ list_del_init(&rxts->list);
+ phy2rxts(phy_rxts, rxts);
+ list_add_tail(&rxts->list, &dp83640->rxts);
+out:
+ spin_unlock_irqrestore(&dp83640->rx_lock, flags);
+}
+
+static void decode_txts(struct dp83640_private *dp83640,
+ struct phy_txts *phy_txts)
+{
+ struct skb_shared_hwtstamps shhwtstamps;
+ struct sk_buff *skb;
+ u64 ns;
+
+ /* We must already have the skb that triggered this. */
+
+ skb = skb_dequeue(&dp83640->tx_queue);
+
+ if (!skb) {
+ pr_warning("dp83640: have timestamp but tx_queue empty\n");
+ return;
+ }
+ ns = phy2txts(phy_txts);
+ memset(&shhwtstamps, 0, sizeof(shhwtstamps));
+ shhwtstamps.hwtstamp = ns_to_ktime(ns);
+ skb_complete_tx_timestamp(skb, &shhwtstamps);
+}
+
+static void decode_status_frame(struct dp83640_private *dp83640,
+ struct sk_buff *skb)
+{
+ struct phy_rxts *phy_rxts;
+ struct phy_txts *phy_txts;
+ u8 *ptr;
+ int len, size;
+ u16 ests, type;
+
+ ptr = skb->data + 2;
+
+ for (len = skb_headlen(skb) - 2; len > sizeof(type); len -= size) {
+
+ type = *(u16 *)ptr;
+ ests = type & 0x0fff;
+ type = type & 0xf000;
+ len -= sizeof(type);
+ ptr += sizeof(type);
+
+ if (PSF_RX == type && len >= sizeof(*phy_rxts)) {
+
+ phy_rxts = (struct phy_rxts *) ptr;
+ decode_rxts(dp83640, phy_rxts);
+ size = sizeof(*phy_rxts);
+
+ } else if (PSF_TX == type && len >= sizeof(*phy_txts)) {
+
+ phy_txts = (struct phy_txts *) ptr;
+ decode_txts(dp83640, phy_txts);
+ size = sizeof(*phy_txts);
+
+ } else if (PSF_EVNT == type && len >= sizeof(*phy_txts)) {
+
+ phy_txts = (struct phy_txts *) ptr;
+ decode_evnt(dp83640, phy_txts, ests);
+ size = sizeof(*phy_txts);
+
+ } else {
+ size = 0;
+ break;
+ }
+ ptr += size;
+ }
+}
+
+static int expired(struct rxts *rxts)
+{
+ return time_after(jiffies, rxts->tmo);
+}
+
+static int match(struct sk_buff *skb, unsigned int type, struct rxts *rxts)
+{
+ u16 *seqid;
+ u8 *msgtype, *data = skb_mac_header(skb);
+
+ /* check sequenceID, messageType, 12 bit hash of offset 20-29 */
+ /* We assume that the IPv4 header has no options. */
+
+ switch (type) {
+ case PTP_CLASS_V1_IPV4:
+ msgtype = data + 42 + 32;
+ seqid = (u16 *)(data + 42 + 30);
+ break;
+ case PTP_CLASS_V1_IPV6:
+ msgtype = data + 62 + 32;
+ seqid = (u16 *)(data + 62 + 30);
+ break;
+ case PTP_CLASS_V2_IPV4:
+ msgtype = data + 42 + 0;
+ seqid = (u16 *)(data + 42 + 30);
+ break;
+ case PTP_CLASS_V2_IPV6:
+ msgtype = data + 62 + 0;
+ seqid = (u16 *)(data + 62 + 30);
+ break;
+ case PTP_CLASS_V2_L2:
+ msgtype = data + 14 + 0;
+ seqid = (u16 *)(data + 14 + 30);
+ break;
+ case PTP_CLASS_V2_VLAN:
+ msgtype = data + 18 + 0;
+ seqid = (u16 *)(data + 18 + 30);
+ break;
+ default:
+ return 0;
+ }
+
+ return ((*msgtype & 0xf) == rxts->msgtype && *seqid == rxts->seqid);
+}
+
+static int dp83640_probe(struct phy_device *phydev)
+{
+ struct dp83640_private *dp83640;
+ int i;
+
+ dp83640 = kzalloc(sizeof(struct dp83640_private), GFP_KERNEL);
+ if (!dp83640)
+ return -ENOMEM;
+
+ dp83640->phydev = phydev;
+ INIT_WORK(&dp83640->ts_work, do_timestamp_work);
+ mutex_init(&dp83640->extreg_mux);
+
+ INIT_LIST_HEAD(&dp83640->rxts);
+ INIT_LIST_HEAD(&dp83640->rxpool);
+ for (i = 0; i < MAX_RXTS; i++)
+ list_add(&dp83640->rx_pool_data[i].list, &dp83640->rxpool);
+
+ phydev->priv = dp83640;
+
+ spin_lock_init(&dp83640->rx_lock);
+ skb_queue_head_init(&dp83640->rx_queue);
+ skb_queue_head_init(&dp83640->tx_queue);
+
+ mutex_lock(&clock_lock);
+
+ if (!dp83640_clock) {
+ ptp_dp83640_caps.priv = dp83640;
+ dp83640_clock = ptp_clock_register(&ptp_dp83640_caps);
+ if (IS_ERR(dp83640_clock)) {
+ mutex_unlock(&clock_lock);
+ kfree(dp83640);
+ return PTR_ERR(dp83640_clock);
+ }
+ }
+ mutex_unlock(&clock_lock);
+
+ return 0;
+}
+
+static void dp83640_remove(struct phy_device *phydev)
+{
+ struct dp83640_private *dp83640 = phydev->priv;
+
+ enable_status_frames(phydev, false);
+
+ cancel_work_sync(&dp83640->ts_work);
+
+ mutex_lock(&clock_lock);
+
+ if (ptp_dp83640_caps.priv == dp83640) {
+ ptp_clock_unregister(dp83640_clock);
+ dp83640_clock = NULL;
+ ptp_dp83640_caps.priv = NULL;
+ }
+ mutex_unlock(&clock_lock);
+
+ mutex_destroy(&dp83640->extreg_lock);
+
+ kfree(dp83640);
+}
+
+static int dp83640_hwtstamp(struct phy_device *phydev, struct ifreq *ifr)
+{
+ struct dp83640_private *dp83640 = phydev->priv;
+ struct hwtstamp_config cfg;
+ u16 txcfg0, rxcfg0;
+
+ if (copy_from_user(&cfg, ifr->ifr_data, sizeof(cfg)))
+ return -EFAULT;
+
+ if (cfg.flags) /* reserved for future extensions */
+ return -EINVAL;
+
+ switch (cfg.tx_type) {
+ case HWTSTAMP_TX_OFF:
+ dp83640->hwts_tx_en = 0;
+ break;
+ case HWTSTAMP_TX_ON:
+ dp83640->hwts_tx_en = 1;
+ break;
+ default:
+ return -ERANGE;
+ }
+
+ switch (cfg.rx_filter) {
+ case HWTSTAMP_FILTER_NONE:
+ dp83640->hwts_rx_en = 0;
+ dp83640->layer = 0;
+ dp83640->version = 0;
+ break;
+ case HWTSTAMP_FILTER_PTP_V1_L4_EVENT:
+ case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
+ case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
+ dp83640->hwts_rx_en = 1;
+ dp83640->layer = LAYER4;
+ dp83640->version = 1;
+ break;
+ case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
+ dp83640->hwts_rx_en = 1;
+ dp83640->layer = LAYER4;
+ dp83640->version = 2;
+ break;
+ case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
+ dp83640->hwts_rx_en = 1;
+ dp83640->layer = LAYER2;
+ dp83640->version = 2;
+ break;
+ case HWTSTAMP_FILTER_PTP_V2_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
+ dp83640->hwts_rx_en = 1;
+ dp83640->layer = LAYER4|LAYER2;
+ dp83640->version = 2;
+ break;
+ default:
+ return -ERANGE;
+ }
+
+ txcfg0 = (dp83640->version & TX_PTP_VER_MASK) << TX_PTP_VER_SHIFT;
+ rxcfg0 = (dp83640->version & TX_PTP_VER_MASK) << TX_PTP_VER_SHIFT;
+
+ if (dp83640->layer & LAYER2) {
+ txcfg0 |= TX_L2_EN;
+ rxcfg0 |= RX_L2_EN;
+ }
+ if (dp83640->layer & LAYER4) {
+ txcfg0 |= TX_IPV6_EN | TX_IPV4_EN;
+ rxcfg0 |= RX_IPV6_EN | RX_IPV4_EN;
+ }
+
+ if (dp83640->hwts_tx_en)
+ txcfg0 |= TX_TS_EN;
+
+ if (dp83640->hwts_rx_en)
+ rxcfg0 |= RX_TS_EN;
+
+ mutex_lock(&dp83640->extreg_mux);
+
+ if (dp83640->hwts_tx_en || dp83640->hwts_rx_en) {
+ enable_status_frames(phydev, true);
+ ext_write(phydev, PAGE4, PTP_CTL, PTP_ENABLE);
+ }
+
+ ext_write(phydev, PAGE5, PTP_TXCFG0, txcfg0);
+ ext_write(phydev, PAGE5, PTP_RXCFG0, rxcfg0);
+
+ mutex_unlock(&dp83640->extreg_mux);
+
+ return copy_to_user(ifr->ifr_data, &cfg, sizeof(cfg)) ? -EFAULT : 0;
+}
+
+static void rx_timestamp_work(struct dp83640_private *dp83640)
+{
+ struct list_head *this, *next;
+ struct rxts *rxts;
+ struct skb_shared_hwtstamps *shhwtstamps;
+ struct sk_buff *skb;
+ unsigned int type;
+ unsigned long flags;
+
+ /* Deliver each deferred packet, with or without a time stamp. */
+
+ while ((skb = skb_dequeue(&dp83640->rx_queue)) != NULL) {
+ type = SKB_PTP_TYPE(skb);
+ spin_lock_irqsave(&dp83640->rx_lock, flags);
+ list_for_each_safe(this, next, &dp83640->rxts) {
+ rxts = list_entry(this, struct rxts, list);
+ if (match(skb, type, rxts)) {
+ shhwtstamps = skb_hwtstamps(skb);
+ memset(shhwtstamps, 0, sizeof(*shhwtstamps));
+ shhwtstamps->hwtstamp = ns_to_ktime(rxts->ns);
+ list_del_init(&rxts->list);
+ list_add(&rxts->list, &dp83640->rxpool);
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&dp83640->rx_lock, flags);
+ netif_rx(skb);
+ }
+
+ /* Clear out expired time stamps. */
+
+ spin_lock_irqsave(&dp83640->rx_lock, flags);
+ list_for_each_safe(this, next, &dp83640->rxts) {
+ rxts = list_entry(this, struct rxts, list);
+ if (expired(rxts)) {
+ list_del_init(&rxts->list);
+ list_add(&rxts->list, &dp83640->rxpool);
+ }
+ }
+ spin_unlock_irqrestore(&dp83640->rx_lock, flags);
+}
+
+static void do_timestamp_work(struct work_struct *work)
+{
+ struct dp83640_private *dp83640 =
+ container_of(work, struct dp83640_private, ts_work);
+
+ rx_reading_work(dp83640);
+ rx_timestamp_work(dp83640);
+ tx_timestamp_work(dp83640);
+}
+
+static bool dp83640_rxtstamp(struct phy_device *phydev,
+ struct sk_buff *skb, int type)
+{
+ struct dp83640_private *dp83640 = phydev->priv;
+
+ if (!dp83640->hwts_rx_en)
+ return false;
+
+ if (is_status_frame(skb, type)) {
+ decode_status_frame(dp83640, skb);
+ /* Let the stack drop this frame. */
+ return false;
+ }
+
+ SKB_PTP_TYPE(skb) = type;
+ skb_queue_tail(&dp83640->rx_queue, skb);
+ schedule_work(&dp83640->ts_work);
+
+ return true;
+}
+
+static void dp83640_txtstamp(struct phy_device *phydev,
+ struct sk_buff *skb, int type)
+{
+ struct dp83640_private *dp83640 = phydev->priv;
+
+ if (!dp83640->hwts_tx_en) {
+ kfree_skb(skb);
+ return;
+ }
+ skb_queue_tail(&dp83640->tx_queue, skb);
+ schedule_work(&dp83640->ts_work);
+}
+
+static struct phy_driver dp83640_driver = {
+ .phy_id = DP83640_PHY_ID,
+ .phy_id_mask = 0xfffffff0,
+ .name = "NatSemi DP83640",
+ .features = PHY_BASIC_FEATURES,
+ .flags = 0,
+ .probe = dp83640_probe,
+ .remove = dp83640_remove,
+ .config_aneg = genphy_config_aneg,
+ .read_status = genphy_read_status,
+ .hwtstamp = dp83640_hwtstamp,
+ .rxtstamp = dp83640_rxtstamp,
+ .txtstamp = dp83640_txtstamp,
+ .driver = {.owner = THIS_MODULE,}
+};
+
+static int __init dp83640_init(void)
+{
+ return phy_driver_register(&dp83640_driver);
+}
+
+static void __exit dp83640_exit(void)
+{
+ phy_driver_unregister(&dp83640_driver);
+}
+
+MODULE_DESCRIPTION("National Semiconductor DP83640 PHY driver");
+MODULE_AUTHOR("Richard Cochran <richard.cochran@omicron.at>");
+MODULE_LICENSE("GPL");
+
+module_init(dp83640_init);
+module_exit(dp83640_exit);
+
+static struct mdio_device_id dp83640_tbl[] = {
+ { DP83640_PHY_ID, 0xfffffff0 },
+ { }
+};
+
+MODULE_DEVICE_TABLE(mdio, dp83640_tbl);
diff --git a/drivers/net/phy/dp83640_reg.h b/drivers/net/phy/dp83640_reg.h
new file mode 100644
index 0000000..9d34bb6
--- /dev/null
+++ b/drivers/net/phy/dp83640_reg.h
@@ -0,0 +1,261 @@
+/* dp83640_reg.h
+ * Generated by regen.tcl on Fri Jul 23 09:45:18 AM CEST 2010
+ */
+#ifndef HAVE_DP83640_REGISTERS
+#define HAVE_DP83640_REGISTERS
+
+#define PAGE4 0x0004
+#define PTP_CTL 0x0014 /* PTP Control Register */
+#define PTP_TDR 0x0015 /* PTP Time Data Register */
+#define PTP_STS 0x0016 /* PTP Status Register */
+#define PTP_TSTS 0x0017 /* PTP Trigger Status Register */
+#define PTP_RATEL 0x0018 /* PTP Rate Low Register */
+#define PTP_RATEH 0x0019 /* PTP Rate High Register */
+#define PTP_RDCKSUM 0x001a /* PTP Read Checksum */
+#define PTP_WRCKSUM 0x001b /* PTP Write Checksum */
+#define PTP_TXTS 0x001c /* PTP Transmit Timestamp Register, in four 16-bit reads */
+#define PTP_RXTS 0x001d /* PTP Receive Timestamp Register, in six? 16-bit reads */
+#define PTP_ESTS 0x001e /* PTP Event Status Register */
+#define PTP_EDATA 0x001f /* PTP Event Data Register */
+
+#define PAGE5 0x0005
+#define PTP_TRIG 0x0014 /* PTP Trigger Configuration Register */
+#define PTP_EVNT 0x0015 /* PTP Event Configuration Register */
+#define PTP_TXCFG0 0x0016 /* PTP Transmit Configuration Register 0 */
+#define PTP_TXCFG1 0x0017 /* PTP Transmit Configuration Register 1 */
+#define PSF_CFG0 0x0018 /* PHY Status Frame Configuration Register 0 */
+#define PTP_RXCFG0 0x0019 /* PTP Receive Configuration Register 0 */
+#define PTP_RXCFG1 0x001a /* PTP Receive Configuration Register 1 */
+#define PTP_RXCFG2 0x001b /* PTP Receive Configuration Register 2 */
+#define PTP_RXCFG3 0x001c /* PTP Receive Configuration Register 3 */
+#define PTP_RXCFG4 0x001d /* PTP Receive Configuration Register 4 */
+#define PTP_TRDL 0x001e /* PTP Temporary Rate Duration Low Register */
+#define PTP_TRDH 0x001f /* PTP Temporary Rate Duration High Register */
+
+#define PAGE6 0x0006
+#define PTP_COC 0x0014 /* PTP Clock Output Control Register */
+#define PSF_CFG1 0x0015 /* PHY Status Frame Configuration Register 1 */
+#define PSF_CFG2 0x0016 /* PHY Status Frame Configuration Register 2 */
+#define PSF_CFG3 0x0017 /* PHY Status Frame Configuration Register 3 */
+#define PSF_CFG4 0x0018 /* PHY Status Frame Configuration Register 4 */
+#define PTP_SFDCFG 0x0019 /* PTP SFD Configuration Register */
+#define PTP_INTCTL 0x001a /* PTP Interrupt Control Register */
+#define PTP_CLKSRC 0x001b /* PTP Clock Source Register */
+#define PTP_ETR 0x001c /* PTP Ethernet Type Register */
+#define PTP_OFF 0x001d /* PTP Offset Register */
+#define PTP_GPIOMON 0x001e /* PTP GPIO Monitor Register */
+#define PTP_RXHASH 0x001f /* PTP Receive Hash Register */
+
+/* Bit definitions for the PTP_CTL register */
+#define TRIG_SEL_SHIFT (10) /* PTP Trigger Select */
+#define TRIG_SEL_MASK (0x7)
+#define TRIG_DIS (1<<9) /* Disable PTP Trigger */
+#define TRIG_EN (1<<8) /* Enable PTP Trigger */
+#define TRIG_READ (1<<7) /* Read PTP Trigger */
+#define TRIG_LOAD (1<<6) /* Load PTP Trigger */
+#define PTP_RD_CLK (1<<5) /* Read PTP Clock */
+#define PTP_LOAD_CLK (1<<4) /* Load PTP Clock */
+#define PTP_STEP_CLK (1<<3) /* Step PTP Clock */
+#define PTP_ENABLE (1<<2) /* Enable PTP Clock */
+#define PTP_DISABLE (1<<1) /* Disable PTP Clock */
+#define PTP_RESET (1<<0) /* Reset PTP Clock */
+
+/* Bit definitions for the PTP_STS register */
+#define TXTS_RDY (1<<11) /* Transmit Timestamp Ready */
+#define RXTS_RDY (1<<10) /* Receive Timestamp Ready */
+#define TRIG_DONE (1<<9) /* PTP Trigger Done */
+#define EVENT_RDY (1<<8) /* PTP Event Timestamp Ready */
+#define TXTS_IE (1<<3) /* Transmit Timestamp Interrupt Enable */
+#define RXTS_IE (1<<2) /* Receive Timestamp Interrupt Enable */
+#define TRIG_IE (1<<1) /* Trigger Interrupt Enable */
+#define EVENT_IE (1<<0) /* Event Interrupt Enable */
+
+/* Bit definitions for the PTP_TSTS register */
+#define TRIG7_ERROR (1<<15) /* Trigger 7 Error */
+#define TRIG7_ACTIVE (1<<14) /* Trigger 7 Active */
+#define TRIG6_ERROR (1<<13) /* Trigger 6 Error */
+#define TRIG6_ACTIVE (1<<12) /* Trigger 6 Active */
+#define TRIG5_ERROR (1<<11) /* Trigger 5 Error */
+#define TRIG5_ACTIVE (1<<10) /* Trigger 5 Active */
+#define TRIG4_ERROR (1<<9) /* Trigger 4 Error */
+#define TRIG4_ACTIVE (1<<8) /* Trigger 4 Active */
+#define TRIG3_ERROR (1<<7) /* Trigger 3 Error */
+#define TRIG3_ACTIVE (1<<6) /* Trigger 3 Active */
+#define TRIG2_ERROR (1<<5) /* Trigger 2 Error */
+#define TRIG2_ACTIVE (1<<4) /* Trigger 2 Active */
+#define TRIG1_ERROR (1<<3) /* Trigger 1 Error */
+#define TRIG1_ACTIVE (1<<2) /* Trigger 1 Active */
+#define TRIG0_ERROR (1<<1) /* Trigger 0 Error */
+#define TRIG0_ACTIVE (1<<0) /* Trigger 0 Active */
+
+/* Bit definitions for the PTP_RATEH register */
+#define PTP_RATE_DIR (1<<15) /* PTP Rate Direction */
+#define PTP_TMP_RATE (1<<14) /* PTP Temporary Rate */
+#define PTP_RATE_HI_SHIFT (0) /* PTP Rate High 10-bits */
+#define PTP_RATE_HI_MASK (0x3ff)
+
+/* Bit definitions for the PTP_ESTS register */
+#define EVNTS_MISSED_SHIFT (8) /* Indicates number of events missed */
+#define EVNTS_MISSED_MASK (0x7)
+#define EVNT_TS_LEN_SHIFT (6) /* Indicates length of the Timestamp field in 16-bit words minus 1 */
+#define EVNT_TS_LEN_MASK (0x3)
+#define EVNT_RF (1<<5) /* Indicates whether the event is a rise or falling event */
+#define EVNT_NUM_SHIFT (2) /* Indicates Event Timestamp Unit which detected an event */
+#define EVNT_NUM_MASK (0x7)
+#define MULT_EVNT (1<<1) /* Indicates multiple events were detected at the same time */
+#define EVENT_DET (1<<0) /* PTP Event Detected */
+
+/* Bit definitions for the PTP_EDATA register */
+#define E7_RISE (1<<15) /* Indicates direction of Event 7 */
+#define E7_DET (1<<14) /* Indicates Event 7 detected */
+#define E6_RISE (1<<13) /* Indicates direction of Event 6 */
+#define E6_DET (1<<12) /* Indicates Event 6 detected */
+#define E5_RISE (1<<11) /* Indicates direction of Event 5 */
+#define E5_DET (1<<10) /* Indicates Event 5 detected */
+#define E4_RISE (1<<9) /* Indicates direction of Event 4 */
+#define E4_DET (1<<8) /* Indicates Event 4 detected */
+#define E3_RISE (1<<7) /* Indicates direction of Event 3 */
+#define E3_DET (1<<6) /* Indicates Event 3 detected */
+#define E2_RISE (1<<5) /* Indicates direction of Event 2 */
+#define E2_DET (1<<4) /* Indicates Event 2 detected */
+#define E1_RISE (1<<3) /* Indicates direction of Event 1 */
+#define E1_DET (1<<2) /* Indicates Event 1 detected */
+#define E0_RISE (1<<1) /* Indicates direction of Event 0 */
+#define E0_DET (1<<0) /* Indicates Event 0 detected */
+
+/* Bit definitions for the PTP_TRIG register */
+#define TRIG_PULSE (1<<15) /* generate a Pulse rather than a single edge */
+#define TRIG_PER (1<<14) /* generate a periodic signal */
+#define TRIG_IF_LATE (1<<13) /* trigger immediately if already past */
+#define TRIG_NOTIFY (1<<12) /* Trigger Notification Enable */
+#define TRIG_GPIO_SHIFT (8) /* Trigger GPIO Connection, value 1-12 */
+#define TRIG_GPIO_MASK (0xf)
+#define TRIG_TOGGLE (1<<7) /* Trigger Toggle Mode Enable */
+#define TRIG_CSEL_SHIFT (1) /* Trigger Configuration Select */
+#define TRIG_CSEL_MASK (0x7)
+#define TRIG_WR (1<<0) /* Trigger Configuration Write */
+
+/* Bit definitions for the PTP_EVNT register */
+#define EVNT_RISE (1<<14) /* Event Rise Detect Enable */
+#define EVNT_FALL (1<<13) /* Event Fall Detect Enable */
+#define EVNT_SINGLE (1<<12) /* enable single event capture operation */
+#define EVNT_GPIO_SHIFT (8) /* Event GPIO Connection, value 1-12 */
+#define EVNT_GPIO_MASK (0xf)
+#define EVNT_SEL_SHIFT (1) /* Event Select */
+#define EVNT_SEL_MASK (0x7)
+#define EVNT_WR (1<<0) /* Event Configuration Write */
+
+/* Bit definitions for the PTP_TXCFG0 register */
+#define SYNC_1STEP (1<<15) /* insert timestamp into transmit Sync Messages */
+#define DR_INSERT (1<<13) /* Insert Delay_Req Timestamp in Delay_Resp (dangerous) */
+#define NTP_TS_EN (1<<12) /* Enable Timestamping of NTP Packets */
+#define IGNORE_2STEP (1<<11) /* Ignore Two_Step flag for One-Step operation */
+#define CRC_1STEP (1<<10) /* Disable checking of CRC for One-Step operation */
+#define CHK_1STEP (1<<9) /* Enable UDP Checksum correction for One-Step Operation */
+#define IP1588_EN (1<<8) /* Enable IEEE 1588 defined IP address filter */
+#define TX_L2_EN (1<<7) /* Layer2 Timestamp Enable */
+#define TX_IPV6_EN (1<<6) /* IPv6 Timestamp Enable */
+#define TX_IPV4_EN (1<<5) /* IPv4 Timestamp Enable */
+#define TX_PTP_VER_SHIFT (1) /* Enable Timestamp capture for IEEE 1588 version X */
+#define TX_PTP_VER_MASK (0xf)
+#define TX_TS_EN (1<<0) /* Transmit Timestamp Enable */
+
+/* Bit definitions for the PTP_TXCFG1 register */
+#define BYTE0_MASK_SHIFT (8) /* Bit mask to be used for matching Byte0 of the PTP Message */
+#define BYTE0_MASK_MASK (0xff)
+#define BYTE0_DATA_SHIFT (0) /* Data to be used for matching Byte0 of the PTP Message */
+#define BYTE0_DATA_MASK (0xff)
+
+/* Bit definitions for the PSF_CFG0 register */
+#define MAC_SRC_ADD_SHIFT (11) /* Status Frame Mac Source Address */
+#define MAC_SRC_ADD_MASK (0x3)
+#define MIN_PRE_SHIFT (8) /* Status Frame Minimum Preamble */
+#define MIN_PRE_MASK (0x7)
+#define PSF_ENDIAN (1<<7) /* Status Frame Endian Control */
+#define PSF_IPV4 (1<<6) /* Status Frame IPv4 Enable */
+#define PSF_PCF_RD (1<<5) /* Control Frame Read PHY Status Frame Enable */
+#define PSF_ERR_EN (1<<4) /* Error PHY Status Frame Enable */
+#define PSF_TXTS_EN (1<<3) /* Transmit Timestamp PHY Status Frame Enable */
+#define PSF_RXTS_EN (1<<2) /* Receive Timestamp PHY Status Frame Enable */
+#define PSF_TRIG_EN (1<<1) /* Trigger PHY Status Frame Enable */
+#define PSF_EVNT_EN (1<<0) /* Event PHY Status Frame Enable */
+
+/* Bit definitions for the PTP_RXCFG0 register */
+#define DOMAIN_EN (1<<15) /* Domain Match Enable */
+#define ALT_MAST_DIS (1<<14) /* Alternate Master Timestamp Disable */
+#define USER_IP_SEL (1<<13) /* Selects portion of IP address accessible thru PTP_RXCFG2 */
+#define USER_IP_EN (1<<12) /* Enable User-programmed IP address filter */
+#define RX_SLAVE (1<<11) /* Receive Slave Only */
+#define IP1588_EN_SHIFT (8) /* Enable IEEE 1588 defined IP address filters */
+#define IP1588_EN_MASK (0xf)
+#define RX_L2_EN (1<<7) /* Layer2 Timestamp Enable */
+#define RX_IPV6_EN (1<<6) /* IPv6 Timestamp Enable */
+#define RX_IPV4_EN (1<<5) /* IPv4 Timestamp Enable */
+#define RX_PTP_VER_SHIFT (1) /* Enable Timestamp capture for IEEE 1588 version X */
+#define RX_PTP_VER_MASK (0xf)
+#define RX_TS_EN (1<<0) /* Receive Timestamp Enable */
+
+/* Bit definitions for the PTP_RXCFG1 register */
+#define BYTE0_MASK_SHIFT (8) /* Bit mask to be used for matching Byte0 of the PTP Message */
+#define BYTE0_MASK_MASK (0xff)
+#define BYTE0_DATA_SHIFT (0) /* Data to be used for matching Byte0 of the PTP Message */
+#define BYTE0_DATA_MASK (0xff)
+
+/* Bit definitions for the PTP_RXCFG3 register */
+#define TS_MIN_IFG_SHIFT (12) /* Minimum Inter-frame Gap */
+#define TS_MIN_IFG_MASK (0xf)
+#define ACC_UDP (1<<11) /* Record Timestamp if UDP Checksum Error */
+#define ACC_CRC (1<<10) /* Record Timestamp if CRC Error */
+#define TS_APPEND (1<<9) /* Append Timestamp for L2 */
+#define TS_INSERT (1<<8) /* Enable Timestamp Insertion */
+#define PTP_DOMAIN_SHIFT (0) /* PTP Message domainNumber field */
+#define PTP_DOMAIN_MASK (0xff)
+
+/* Bit definitions for the PTP_RXCFG4 register */
+#define IPV4_UDP_MOD (1<<15) /* Enable IPV4 UDP Modification */
+#define TS_SEC_EN (1<<14) /* Enable Timestamp Seconds */
+#define TS_SEC_LEN_SHIFT (12) /* Inserted Timestamp Seconds Length */
+#define TS_SEC_LEN_MASK (0x3)
+#define RXTS_NS_OFF_SHIFT (6) /* Receive Timestamp Nanoseconds offset */
+#define RXTS_NS_OFF_MASK (0x3f)
+#define RXTS_SEC_OFF_SHIFT (0) /* Receive Timestamp Seconds offset */
+#define RXTS_SEC_OFF_MASK (0x3f)
+
+/* Bit definitions for the PTP_COC register */
+#define PTP_CLKOUT_EN (1<<15) /* PTP Clock Output Enable */
+#define PTP_CLKOUT_SEL (1<<14) /* PTP Clock Output Source Select */
+#define PTP_CLKOUT_SPEEDSEL (1<<13) /* PTP Clock Output I/O Speed Select */
+#define PTP_CLKDIV_SHIFT (0) /* PTP Clock Divide-by Value */
+#define PTP_CLKDIV_MASK (0xff)
+
+/* Bit definitions for the PSF_CFG1 register */
+#define PTPRESERVED_SHIFT (12) /* PTP v2 reserved field */
+#define PTPRESERVED_MASK (0xf)
+#define VERSIONPTP_SHIFT (8) /* PTP v2 versionPTP field */
+#define VERSIONPTP_MASK (0xf)
+#define TRANSPORT_SPECIFIC_SHIFT (4) /* PTP v2 Header transportSpecific field */
+#define TRANSPORT_SPECIFIC_MASK (0xf)
+#define MESSAGETYPE_SHIFT (0) /* PTP v2 messageType field */
+#define MESSAGETYPE_MASK (0xf)
+
+/* Bit definitions for the PTP_SFDCFG register */
+#define TX_SFD_GPIO_SHIFT (4) /* TX SFD GPIO Select, value 1-12 */
+#define TX_SFD_GPIO_MASK (0xf)
+#define RX_SFD_GPIO_SHIFT (0) /* RX SFD GPIO Select, value 1-12 */
+#define RX_SFD_GPIO_MASK (0xf)
+
+/* Bit definitions for the PTP_INTCTL register */
+#define PTP_INT_GPIO_SHIFT (0) /* PTP Interrupt GPIO Select */
+#define PTP_INT_GPIO_MASK (0xf)
+
+/* Bit definitions for the PTP_CLKSRC register */
+#define CLK_SRC_SHIFT (14) /* PTP Clock Source Select */
+#define CLK_SRC_MASK (0x3)
+#define CLK_SRC_PER_SHIFT (0) /* PTP Clock Source Period */
+#define CLK_SRC_PER_MASK (0x7f)
+
+/* Bit definitions for the PTP_OFF register */
+#define PTP_OFFSET_SHIFT (0) /* PTP Message offset from preceding header */
+#define PTP_OFFSET_MASK (0xff)
+
+#endif
--
1.7.0.4
^ permalink raw reply related
* [PATCH 4/5] ptp: Added a clock driver for the IXP46x.
From: Richard Cochran @ 2010-08-16 11:18 UTC (permalink / raw)
To: netdev
Cc: linuxppc-dev, devicetree-discuss, linux-kernel, Rodolfo Giometti,
linux-arm-kernel, Krzysztof Halasa
In-Reply-To: <cover.1281956490.git.richard.cochran@omicron.at>
This patch adds a driver for the hardware time stamping unit found on the
IXP465. The basic clock operations and an external trigger are implemented.
Signed-off-by: Richard Cochran <richard.cochran@omicron.at>
---
arch/arm/mach-ixp4xx/include/mach/ixp46x_ts.h | 78 ++++++
drivers/net/arm/ixp4xx_eth.c | 191 +++++++++++++
drivers/ptp/Kconfig | 13 +
drivers/ptp/Makefile | 1 +
drivers/ptp/ptp_ixp46x.c | 359 +++++++++++++++++++++++++
5 files changed, 642 insertions(+), 0 deletions(-)
create mode 100644 arch/arm/mach-ixp4xx/include/mach/ixp46x_ts.h
create mode 100644 drivers/ptp/ptp_ixp46x.c
diff --git a/arch/arm/mach-ixp4xx/include/mach/ixp46x_ts.h b/arch/arm/mach-ixp4xx/include/mach/ixp46x_ts.h
new file mode 100644
index 0000000..729a6b2
--- /dev/null
+++ b/arch/arm/mach-ixp4xx/include/mach/ixp46x_ts.h
@@ -0,0 +1,78 @@
+/*
+ * PTP 1588 clock using the IXP46X
+ *
+ * Copyright (C) 2010 OMICRON electronics GmbH
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef _IXP46X_TS_H_
+#define _IXP46X_TS_H_
+
+#define DEFAULT_ADDEND 0xF0000029
+#define TICKS_NS_SHIFT 4
+
+struct ixp46x_channel_ctl {
+ u32 Ch_Control; /* 0x40 Time Synchronization Channel Control */
+ u32 Ch_Event; /* 0x44 Time Synchronization Channel Event */
+ u32 TxSnapLo; /* 0x48 Transmit Snapshot Low Register */
+ u32 TxSnapHi; /* 0x4C Transmit Snapshot High Register */
+ u32 RxSnapLo; /* 0x50 Receive Snapshot Low Register */
+ u32 RxSnapHi; /* 0x54 Receive Snapshot High Register */
+ u32 SrcUUIDLo; /* 0x58 Source UUID0 Low Register */
+ u32 SrcUUIDHi; /* 0x5C Sequence Identifier/Source UUID0 High */
+};
+
+struct ixp46x_ts_regs {
+ u32 Control; /* 0x00 Time Sync Control Register */
+ u32 Event; /* 0x04 Time Sync Event Register */
+ u32 Addend; /* 0x08 Time Sync Addend Register */
+ u32 Accum; /* 0x0C Time Sync Accumulator Register */
+ u32 Test; /* 0x10 Time Sync Test Register */
+ u32 Unused; /* 0x14 */
+ u32 RSysTime_Lo; /* 0x18 RawSystemTime_Low Register */
+ u32 RSysTimeHi; /* 0x1C RawSystemTime_High Register */
+ u32 SysTimeLo; /* 0x20 SystemTime_Low Register */
+ u32 SysTimeHi; /* 0x24 SystemTime_High Register */
+ u32 TrgtLo; /* 0x28 TargetTime_Low Register */
+ u32 TrgtHi; /* 0x2C TargetTime_High Register */
+ u32 ASMSLo; /* 0x30 Auxiliary Slave Mode Snapshot Low */
+ u32 ASMSHi; /* 0x34 Auxiliary Slave Mode Snapshot High */
+ u32 AMMSLo; /* 0x38 Auxiliary Master Mode Snapshot Low */
+ u32 AMMSHi; /* 0x3C Auxiliary Master Mode Snapshot High */
+
+ struct ixp46x_channel_ctl channel[3];
+};
+
+/* 0x00 Time Sync Control Register Bits */
+#define TSCR_AMM (1<<3)
+#define TSCR_ASM (1<<2)
+#define TSCR_TTM (1<<1)
+#define TSCR_RST (1<<0)
+
+/* 0x04 Time Sync Event Register Bits */
+#define TSER_SNM (1<<3)
+#define TSER_SNS (1<<2)
+#define TTIPEND (1<<1)
+
+/* 0x40 Time Synchronization Channel Control Register Bits */
+#define MASTER_MODE (1<<0)
+#define TIMESTAMP_ALL (1<<1)
+
+/* 0x44 Time Synchronization Channel Event Register Bits */
+#define TX_SNAPSHOT_LOCKED (1<<0)
+#define RX_SNAPSHOT_LOCKED (1<<1)
+
+#endif
diff --git a/drivers/net/arm/ixp4xx_eth.c b/drivers/net/arm/ixp4xx_eth.c
index 4f1cc71..3d36154 100644
--- a/drivers/net/arm/ixp4xx_eth.c
+++ b/drivers/net/arm/ixp4xx_eth.c
@@ -30,9 +30,12 @@
#include <linux/etherdevice.h>
#include <linux/io.h>
#include <linux/kernel.h>
+#include <linux/net_tstamp.h>
#include <linux/phy.h>
#include <linux/platform_device.h>
+#include <linux/ptp_classify.h>
#include <linux/slab.h>
+#include <mach/ixp46x_ts.h>
#include <mach/npe.h>
#include <mach/qmgr.h>
@@ -67,6 +70,14 @@
#define RXFREE_QUEUE(port_id) (NPE_ID(port_id) + 26)
#define TXDONE_QUEUE 31
+#define PTP_SLAVE_MODE 1
+#define PTP_MASTER_MODE 2
+#define PORT2CHANNEL(p) 1
+/*
+ * PHYSICAL_ID(p->id) ?
+ * TODO - Figure out correct mapping.
+ */
+
/* TX Control Registers */
#define TX_CNTRL0_TX_EN 0x01
#define TX_CNTRL0_HALFDUPLEX 0x02
@@ -171,6 +182,8 @@ struct port {
int id; /* logical port ID */
int speed, duplex;
u8 firmware[4];
+ int hwts_tx_en;
+ int hwts_rx_en;
};
/* NPE message structure */
@@ -246,6 +259,171 @@ static int ports_open;
static struct port *npe_port_tab[MAX_NPES];
static struct dma_pool *dma_pool;
+static struct sock_filter ptp_filter[] = {
+ PTP_FILTER
+};
+
+static int match(struct sk_buff *skb, u16 uid_hi, u32 uid_lo, u16 seq)
+{
+ unsigned int type;
+ u16 *hi, *id;
+ u8 *lo, *data = skb->data;
+
+ type = sk_run_filter(skb, ptp_filter, ARRAY_SIZE(ptp_filter));
+
+ if (PTP_CLASS_V1_IPV4 == type) {
+
+ id = (u16 *)(data + 42 + 30);
+ hi = (u16 *)(data + 42 + 22);
+ lo = data + 42 + 24;
+
+ return (uid_hi == *hi &&
+ 0 == memcmp(&uid_lo, lo, sizeof(uid_lo)) &&
+ seq == *id);
+ }
+
+ return 0;
+}
+
+static void do_rx_timestamp(struct port *port, struct sk_buff *skb)
+{
+ struct skb_shared_hwtstamps *shhwtstamps;
+ struct ixp46x_ts_regs *regs;
+ u64 ns;
+ u32 ch, hi, lo, val;
+ u16 uid, seq;
+
+ if (!port->hwts_rx_en)
+ return;
+
+ ch = PORT2CHANNEL(port);
+
+ regs = (struct ixp46x_ts_regs __iomem *) IXP4XX_TIMESYNC_BASE_VIRT;
+
+ val = __raw_readl(®s->channel[ch].Ch_Event);
+
+ if (!(val & RX_SNAPSHOT_LOCKED))
+ return;
+
+ lo = __raw_readl(®s->channel[ch].SrcUUIDLo);
+ hi = __raw_readl(®s->channel[ch].SrcUUIDHi);
+
+ uid = hi & 0xffff;
+ seq = (hi >> 16) & 0xffff;
+
+ if (!match(skb, htons(uid), htonl(lo), htons(seq)))
+ goto out;
+
+ lo = __raw_readl(®s->channel[ch].RxSnapLo);
+ hi = __raw_readl(®s->channel[ch].RxSnapHi);
+ ns = ((u64) hi) << 32;
+ ns |= lo;
+ ns <<= TICKS_NS_SHIFT;
+
+ shhwtstamps = skb_hwtstamps(skb);
+ memset(shhwtstamps, 0, sizeof(*shhwtstamps));
+ shhwtstamps->hwtstamp = ns_to_ktime(ns);
+out:
+ __raw_writel(RX_SNAPSHOT_LOCKED, ®s->channel[ch].Ch_Event);
+}
+
+static void do_tx_timestamp(struct port *port, struct sk_buff *skb)
+{
+#ifdef __ARMEB__
+ struct skb_shared_hwtstamps shhwtstamps;
+ struct ixp46x_ts_regs *regs;
+ union skb_shared_tx *shtx;
+ u64 ns;
+ u32 ch, cnt, hi, lo, val;
+
+ shtx = skb_tx(skb);
+ if (unlikely(shtx->hardware && port->hwts_tx_en))
+ shtx->in_progress = 1;
+ else
+ return;
+
+ ch = PORT2CHANNEL(port);
+
+ regs = (struct ixp46x_ts_regs __iomem *) IXP4XX_TIMESYNC_BASE_VIRT;
+
+ /*
+ * This really stinks, but we have to poll for the Tx time stamp.
+ * Usually, the time stamp is ready after 4 to 6 microseconds.
+ */
+ for (cnt = 0; cnt < 100; cnt++) {
+ val = __raw_readl(®s->channel[ch].Ch_Event);
+ if (val & TX_SNAPSHOT_LOCKED)
+ break;
+ udelay(1);
+ }
+ if (!(val & TX_SNAPSHOT_LOCKED)) {
+ shtx->in_progress = 0;
+ return;
+ }
+
+ lo = __raw_readl(®s->channel[ch].TxSnapLo);
+ hi = __raw_readl(®s->channel[ch].TxSnapHi);
+ ns = ((u64) hi) << 32;
+ ns |= lo;
+ ns <<= TICKS_NS_SHIFT;
+
+ memset(&shhwtstamps, 0, sizeof(shhwtstamps));
+ shhwtstamps.hwtstamp = ns_to_ktime(ns);
+ skb_tstamp_tx(skb, &shhwtstamps);
+
+ __raw_writel(TX_SNAPSHOT_LOCKED, ®s->channel[ch].Ch_Event);
+#endif
+}
+
+static int hwtstamp_ioctl(struct net_device *netdev, struct ifreq *ifr, int cmd)
+{
+ struct hwtstamp_config cfg;
+ struct ixp46x_ts_regs *regs;
+ struct port *port = netdev_priv(netdev);
+ int ch;
+
+ if (copy_from_user(&cfg, ifr->ifr_data, sizeof(cfg)))
+ return -EFAULT;
+
+ if (cfg.flags) /* reserved for future extensions */
+ return -EINVAL;
+
+ ch = PORT2CHANNEL(port);
+ regs = (struct ixp46x_ts_regs __iomem *) IXP4XX_TIMESYNC_BASE_VIRT;
+
+ switch (cfg.tx_type) {
+ case HWTSTAMP_TX_OFF:
+ port->hwts_tx_en = 0;
+ break;
+ case HWTSTAMP_TX_ON:
+ port->hwts_tx_en = 1;
+ break;
+ default:
+ return -ERANGE;
+ }
+
+ switch (cfg.rx_filter) {
+ case HWTSTAMP_FILTER_NONE:
+ port->hwts_rx_en = 0;
+ break;
+ case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
+ port->hwts_rx_en = PTP_SLAVE_MODE;
+ __raw_writel(0, ®s->channel[ch].Ch_Control);
+ break;
+ case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
+ port->hwts_rx_en = PTP_MASTER_MODE;
+ __raw_writel(MASTER_MODE, ®s->channel[ch].Ch_Control);
+ break;
+ default:
+ return -ERANGE;
+ }
+
+ /* Clear out any old time stamps. */
+ __raw_writel(TX_SNAPSHOT_LOCKED | RX_SNAPSHOT_LOCKED,
+ ®s->channel[ch].Ch_Event);
+
+ return copy_to_user(ifr->ifr_data, &cfg, sizeof(cfg)) ? -EFAULT : 0;
+}
static int ixp4xx_mdio_cmd(struct mii_bus *bus, int phy_id, int location,
int write, u16 cmd)
@@ -573,6 +751,7 @@ static int eth_poll(struct napi_struct *napi, int budget)
debug_pkt(dev, "eth_poll", skb->data, skb->len);
+ do_rx_timestamp(port, skb);
skb->protocol = eth_type_trans(skb, dev);
dev->stats.rx_packets++;
dev->stats.rx_bytes += skb->len;
@@ -728,6 +907,10 @@ static int eth_xmit(struct sk_buff *skb, struct net_device *dev)
#if DEBUG_TX
printk(KERN_DEBUG "%s: eth_xmit end\n", dev->name);
#endif
+
+ do_tx_timestamp(port, skb);
+ skb_tx_timestamp(skb);
+
return NETDEV_TX_OK;
}
@@ -783,6 +966,9 @@ static int eth_ioctl(struct net_device *dev, struct ifreq *req, int cmd)
if (!netif_running(dev))
return -EINVAL;
+ if (cpu_is_ixp46x() && cmd == SIOCSHWTSTAMP)
+ return hwtstamp_ioctl(dev, req, cmd);
+
return phy_mii_ioctl(port->phydev, req, cmd);
}
@@ -1171,6 +1357,11 @@ static int __devinit eth_init_one(struct platform_device *pdev)
char phy_id[MII_BUS_ID_SIZE + 3];
int err;
+ if (ptp_filter_init(ptp_filter, ARRAY_SIZE(ptp_filter))) {
+ pr_err("ixp4xx_eth: bad ptp filter\n");
+ return -EINVAL;
+ }
+
if (!(dev = alloc_etherdev(sizeof(struct port))))
return -ENOMEM;
diff --git a/drivers/ptp/Kconfig b/drivers/ptp/Kconfig
index aaf957f..cc2d3a1 100644
--- a/drivers/ptp/Kconfig
+++ b/drivers/ptp/Kconfig
@@ -49,4 +49,17 @@ config PTP_1588_CLOCK_GIANFAR
To compile this driver as a module, choose M here: the module
will be called gianfar_ptp.
+config PTP_1588_CLOCK_IXP46X
+ tristate "Intel IXP46x as PTP clock"
+ depends on PTP_1588_CLOCK
+ depends on IXP4XX_ETH
+ help
+ This driver adds support for using the IXP46X as a PTP
+ clock. This clock is only useful if your PTP programs are
+ getting hardware time stamps on the PTP Ethernet packets
+ using the SO_TIMESTAMPING API.
+
+ To compile this driver as a module, choose M here: the module
+ will be called ptp_ixp46x.
+
endmenu
diff --git a/drivers/ptp/Makefile b/drivers/ptp/Makefile
index 1651d52..5018f58 100644
--- a/drivers/ptp/Makefile
+++ b/drivers/ptp/Makefile
@@ -4,3 +4,4 @@
obj-$(CONFIG_PTP_1588_CLOCK) += ptp_clock.o
obj-$(CONFIG_PTP_1588_CLOCK_LINUX) += ptp_linux.o
+obj-$(CONFIG_PTP_1588_CLOCK_IXP46X) += ptp_ixp46x.o
diff --git a/drivers/ptp/ptp_ixp46x.c b/drivers/ptp/ptp_ixp46x.c
new file mode 100644
index 0000000..4530256
--- /dev/null
+++ b/drivers/ptp/ptp_ixp46x.c
@@ -0,0 +1,359 @@
+/*
+ * PTP 1588 clock using the IXP46X
+ *
+ * Copyright (C) 2010 OMICRON electronics GmbH
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include <linux/ptp_clock_kernel.h>
+#include <mach/ixp46x_ts.h>
+
+#define DRIVER "ptp_ixp46x"
+#define N_EXT_TS 2
+#define MASTER_GPIO 8
+#define MASTER_IRQ 25
+#define SLAVE_GPIO 7
+#define SLAVE_IRQ 24
+
+struct ixp_clock {
+ struct ixp46x_ts_regs *regs;
+ struct ptp_clock *ptp_clock;
+ int exts0_enabled;
+ int exts1_enabled;
+};
+
+DEFINE_SPINLOCK(register_lock);
+
+/*
+ * Register access functions
+ */
+
+static inline u32 ixp_read(volatile unsigned __iomem *addr)
+{
+ u32 val;
+ val = __raw_readl(addr);
+ return val;
+}
+
+static inline void ixp_write(volatile unsigned __iomem *addr, u32 val)
+{
+ __raw_writel(val, addr);
+}
+
+static u64 sys_time_read(struct ixp46x_ts_regs *regs)
+{
+ u64 ns;
+ u32 lo, hi;
+
+ lo = ixp_read(®s->SysTimeLo);
+ hi = ixp_read(®s->SysTimeHi);
+
+ ns = ((u64) hi) << 32;
+ ns |= lo;
+ ns <<= TICKS_NS_SHIFT;
+
+ return ns;
+}
+
+static void sys_time_write(struct ixp46x_ts_regs *regs, u64 ns)
+{
+ u32 hi, lo;
+
+ ns >>= TICKS_NS_SHIFT;
+ hi = ns >> 32;
+ lo = ns & 0xffffffff;
+
+ ixp_write(®s->SysTimeLo, lo);
+ ixp_write(®s->SysTimeHi, hi);
+}
+
+/*
+ * Interrupt service routine
+ */
+
+static irqreturn_t isr(int irq, void *priv)
+{
+ struct ixp_clock *ixp_clock = priv;
+ struct ixp46x_ts_regs *regs = ixp_clock->regs;
+ struct ptp_clock_event event;
+ u32 ack = 0, lo, hi, val;
+
+ val = ixp_read(®s->Event);
+
+ if (val & TSER_SNS) {
+ ack |= TSER_SNS;
+ if (ixp_clock->exts0_enabled) {
+ hi = ixp_read(®s->ASMSHi);
+ lo = ixp_read(®s->ASMSLo);
+ event.type = PTP_CLOCK_EXTTS;
+ event.index = 0;
+ event.timestamp = ((u64) hi) << 32;
+ event.timestamp |= lo;
+ event.timestamp <<= TICKS_NS_SHIFT;
+ ptp_clock_event(ixp_clock->ptp_clock, &event);
+ }
+ }
+
+ if (val & TSER_SNM) {
+ ack |= TSER_SNM;
+ if (ixp_clock->exts1_enabled) {
+ hi = ixp_read(®s->AMMSHi);
+ lo = ixp_read(®s->AMMSLo);
+ event.type = PTP_CLOCK_EXTTS;
+ event.index = 1;
+ event.timestamp = ((u64) hi) << 32;
+ event.timestamp |= lo;
+ event.timestamp <<= TICKS_NS_SHIFT;
+ ptp_clock_event(ixp_clock->ptp_clock, &event);
+ }
+ }
+
+ if (val & TTIPEND)
+ ack |= TTIPEND; /* this bit seems to be always set */
+
+ if (ack) {
+ ixp_write(®s->Event, ack);
+ return IRQ_HANDLED;
+ } else
+ return IRQ_NONE;
+}
+
+/*
+ * PTP clock operations
+ */
+
+static int ptp_ixp_adjfreq(void *priv, s32 ppb)
+{
+ u64 adj;
+ u32 diff, addend;
+ int neg_adj = 0;
+ struct ixp_clock *ixp_clock = priv;
+ struct ixp46x_ts_regs *regs = ixp_clock->regs;
+
+ if (!ppb)
+ return 0;
+
+ if (ppb < 0) {
+ neg_adj = 1;
+ ppb = -ppb;
+ }
+ addend = DEFAULT_ADDEND;
+ adj = addend;
+ adj *= ppb;
+ diff = div_u64(adj, 1000000000ULL);
+
+ addend = neg_adj ? addend - diff : addend + diff;
+
+ ixp_write(®s->Addend, addend);
+
+ return 0;
+}
+
+static int ptp_ixp_adjtime(void *priv, struct timespec *ts)
+{
+ s64 delta, now;
+ unsigned long flags;
+ struct ixp_clock *ixp_clock = priv;
+ struct ixp46x_ts_regs *regs = ixp_clock->regs;
+
+ delta = 1000000000LL * ts->tv_sec;
+ delta += ts->tv_nsec;
+
+ spin_lock_irqsave(®ister_lock, flags);
+
+ now = sys_time_read(regs);
+ now += delta;
+ sys_time_write(regs, now);
+
+ spin_unlock_irqrestore(®ister_lock, flags);
+
+ return 0;
+}
+
+static int ptp_ixp_gettime(void *priv, struct timespec *ts)
+{
+ u64 ns;
+ u32 remainder;
+ unsigned long flags;
+ struct ixp_clock *ixp_clock = priv;
+ struct ixp46x_ts_regs *regs = ixp_clock->regs;
+
+ spin_lock_irqsave(®ister_lock, flags);
+
+ ns = sys_time_read(regs);
+
+ spin_unlock_irqrestore(®ister_lock, flags);
+
+ ts->tv_sec = div_u64_rem(ns, 1000000000, &remainder);
+ ts->tv_nsec = remainder;
+ return 0;
+}
+
+static int ptp_ixp_settime(void *priv, struct timespec *ts)
+{
+ u64 ns;
+ unsigned long flags;
+ struct ixp_clock *ixp_clock = priv;
+ struct ixp46x_ts_regs *regs = ixp_clock->regs;
+
+ ns = ts->tv_sec * 1000000000ULL;
+ ns += ts->tv_nsec;
+
+ spin_lock_irqsave(®ister_lock, flags);
+
+ sys_time_write(regs, ns);
+
+ spin_unlock_irqrestore(®ister_lock, flags);
+
+ return 0;
+}
+
+static int ptp_ixp_gettimer(void *priv, int index, struct itimerspec *ts)
+{
+ /* We do not offer any ancillary timer features. */
+ return -EOPNOTSUPP;
+}
+
+static int ptp_ixp_settimer(void *p, int i, int abs, struct itimerspec *ts)
+{
+ /* We do not offer any ancillary timer features. */
+ return -EOPNOTSUPP;
+}
+
+static int ptp_ixp_enable(void *priv, struct ptp_clock_request *rq, int on)
+{
+ struct ixp_clock *ixp_clock = priv;
+
+ switch (rq->type) {
+ case PTP_REQUEST_EXTTS:
+ switch (rq->index) {
+ case 0:
+ ixp_clock->exts0_enabled = on ? 1 : 0;
+ break;
+ case 1:
+ ixp_clock->exts1_enabled = on ? 1 : 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+ default:
+ break;
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static struct ptp_clock_info ptp_ixp_caps = {
+ .owner = THIS_MODULE,
+ .name = "IXP46X timer",
+ .max_adj = 66666655,
+ .n_ext_ts = N_EXT_TS,
+ .pps = 0,
+ .priv = NULL,
+ .adjfreq = ptp_ixp_adjfreq,
+ .adjtime = ptp_ixp_adjtime,
+ .gettime = ptp_ixp_gettime,
+ .settime = ptp_ixp_settime,
+ .gettimer = ptp_ixp_gettimer,
+ .settimer = ptp_ixp_settimer,
+ .enable = ptp_ixp_enable,
+};
+
+/* module operations */
+
+static struct ixp_clock ixp_clock;
+
+static int setup_interrupt(int gpio)
+{
+ int irq;
+
+ gpio_line_config(gpio, IXP4XX_GPIO_IN);
+
+ irq = gpio_to_irq(gpio);
+
+ if (NO_IRQ == irq)
+ return NO_IRQ;
+
+ if (set_irq_type(irq, IRQF_TRIGGER_FALLING)) {
+ pr_err("cannot set trigger type for irq %d\n", irq);
+ return NO_IRQ;
+ }
+
+ if (request_irq(irq, isr, 0, DRIVER, &ixp_clock)) {
+ pr_err("request_irq failed for irq %d\n", irq);
+ return NO_IRQ;
+ }
+
+ return irq;
+}
+
+static void __exit ptp_ixp_exit(void)
+{
+ free_irq(MASTER_IRQ, &ixp_clock);
+ free_irq(SLAVE_IRQ, &ixp_clock);
+ ptp_clock_unregister(ixp_clock.ptp_clock);
+}
+
+static int __init ptp_ixp_init(void)
+{
+ ixp_clock.regs =
+ (struct ixp46x_ts_regs __iomem *)IXP4XX_TIMESYNC_BASE_VIRT;
+
+ ptp_ixp_caps.priv = &ixp_clock;
+
+ ixp_clock.ptp_clock = ptp_clock_register(&ptp_ixp_caps);
+
+ if (IS_ERR(ixp_clock.ptp_clock))
+ return PTR_ERR(ixp_clock.ptp_clock);
+
+ ixp_write(&ixp_clock.regs->Addend, DEFAULT_ADDEND);
+ ixp_write(&ixp_clock.regs->TrgtLo, 1);
+ ixp_write(&ixp_clock.regs->TrgtHi, 0);
+ ixp_write(&ixp_clock.regs->Event, TTIPEND);
+
+ if (MASTER_IRQ != setup_interrupt(MASTER_GPIO)) {
+ pr_err("failed to setup gpio %d as irq\n", MASTER_GPIO);
+ goto no_master;
+ }
+ if (SLAVE_IRQ != setup_interrupt(SLAVE_GPIO)) {
+ pr_err("failed to setup gpio %d as irq\n", SLAVE_GPIO);
+ goto no_slave;
+ }
+
+ return 0;
+no_slave:
+ free_irq(MASTER_IRQ, &ixp_clock);
+no_master:
+ ptp_clock_unregister(ixp_clock.ptp_clock);
+ return -ENODEV;
+}
+
+module_init(ptp_ixp_init);
+module_exit(ptp_ixp_exit);
+
+MODULE_AUTHOR("Richard Cochran <richard.cochran@omicron.at>");
+MODULE_DESCRIPTION("PTP clock using the IXP46X timer");
+MODULE_LICENSE("GPL");
--
1.7.0.4
^ permalink raw reply related
* [PATCH 3/5] ptp: Added a clock that uses the eTSEC found on the MPC85xx.
From: Richard Cochran @ 2010-08-16 11:18 UTC (permalink / raw)
To: netdev
Cc: linuxppc-dev, devicetree-discuss, linux-kernel, Rodolfo Giometti,
linux-arm-kernel, Krzysztof Halasa
In-Reply-To: <cover.1281956490.git.richard.cochran@omicron.at>
The eTSEC includes a PTP clock with quite a few features. This patch adds
support for the basic clock adjustment functions, plus two external time
stamps and one alarm.
Signed-off-by: Richard Cochran <richard.cochran@omicron.at>
---
Documentation/powerpc/dts-bindings/fsl/tsec.txt | 57 +++
arch/powerpc/boot/dts/mpc8313erdb.dts | 14 +
arch/powerpc/boot/dts/mpc8572ds.dts | 14 +
arch/powerpc/boot/dts/p2020ds.dts | 14 +
arch/powerpc/boot/dts/p2020rdb.dts | 14 +
drivers/net/Makefile | 1 +
drivers/net/gianfar_ptp.c | 527 +++++++++++++++++++++++
drivers/net/gianfar_ptp_reg.h | 113 +++++
drivers/ptp/Kconfig | 13 +
9 files changed, 767 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/gianfar_ptp.c
create mode 100644 drivers/net/gianfar_ptp_reg.h
diff --git a/Documentation/powerpc/dts-bindings/fsl/tsec.txt b/Documentation/powerpc/dts-bindings/fsl/tsec.txt
index edb7ae1..f6edbb8 100644
--- a/Documentation/powerpc/dts-bindings/fsl/tsec.txt
+++ b/Documentation/powerpc/dts-bindings/fsl/tsec.txt
@@ -74,3 +74,60 @@ Example:
interrupt-parent = <&mpic>;
phy-handle = <&phy0>
};
+
+* Gianfar PTP clock nodes
+
+General Properties:
+
+ - compatible Should be "fsl,etsec-ptp"
+ - reg Offset and length of the register set for the device
+ - interrupts There should be at least two interrupts. Some devices
+ have as many as four PTP related interrupts.
+
+Clock Properties:
+
+ - tclk-period Timer reference clock period in nanoseconds.
+ - tmr-prsc Prescaler, divides the output clock.
+ - tmr-add Frequency compensation value.
+ - cksel 0= external clock, 1= eTSEC system clock, 3= RTC clock input.
+ Currently the driver only supports choice "1".
+ - tmr-fiper1 Fixed interval period pulse generator.
+ - tmr-fiper2 Fixed interval period pulse generator.
+ - max-adj Maximum frequency adjustment in parts per billion.
+
+ These properties set the operational parameters for the PTP
+ clock. You must choose these carefully for the clock to work right.
+ Here is how to figure good values:
+
+ TimerOsc = system clock MHz
+ tclk_period = desired clock period nanoseconds
+ NominalFreq = 1000 / tclk_period MHz
+ FreqDivRatio = TimerOsc / NominalFreq (must be greater that 1.0)
+ tmr_add = ceil(2^32 / FreqDivRatio)
+ OutputClock = NominalFreq / tmr_prsc MHz
+ PulseWidth = 1 / OutputClock microseconds
+ FiperFreq1 = desired frequency in Hz
+ FiperDiv1 = 1000000 * OutputClock / FiperFreq1
+ tmr_fiper1 = tmr_prsc * tclk_period * FiperDiv1 - tclk_period
+ max_adj = 1000000000 * (FreqDivRatio - 1.0) - 1
+
+ The calculation for tmr_fiper2 is the same as for tmr_fiper1. The
+ driver expects that tmr_fiper1 will be correctly set to produce a 1
+ Pulse Per Second (PPS) signal, since this will be offered to the PPS
+ subsystem to synchronize the Linux clock.
+
+Example:
+
+ ptp_clock@24E00 {
+ compatible = "fsl,etsec-ptp";
+ reg = <0x24E00 0xB0>;
+ interrupts = <12 0x8 13 0x8>;
+ interrupt-parent = < &ipic >;
+ tclk-period = <10>;
+ tmr-prsc = <100>;
+ tmr-add = <0x999999A4>;
+ cksel = <0x1>;
+ tmr-fiper1 = <0x3B9AC9F6>;
+ tmr-fiper2 = <0x00018696>;
+ max-adj = <659999998>;
+ };
diff --git a/arch/powerpc/boot/dts/mpc8313erdb.dts b/arch/powerpc/boot/dts/mpc8313erdb.dts
index 183f2aa..85a7eaa 100644
--- a/arch/powerpc/boot/dts/mpc8313erdb.dts
+++ b/arch/powerpc/boot/dts/mpc8313erdb.dts
@@ -208,6 +208,20 @@
sleep = <&pmc 0x00300000>;
};
+ ptp_clock@24E00 {
+ compatible = "fsl,etsec-ptp";
+ reg = <0x24E00 0xB0>;
+ interrupts = <12 0x8 13 0x8>;
+ interrupt-parent = < &ipic >;
+ tclk-period = <10>;
+ tmr-prsc = <100>;
+ tmr-add = <0x999999A4>;
+ cksel = <0x1>;
+ tmr-fiper1 = <0x3B9AC9F6>;
+ tmr-fiper2 = <0x00018696>;
+ max-adj = <659999998>;
+ };
+
enet0: ethernet@24000 {
#address-cells = <1>;
#size-cells = <1>;
diff --git a/arch/powerpc/boot/dts/mpc8572ds.dts b/arch/powerpc/boot/dts/mpc8572ds.dts
index cafc128..74208cd 100644
--- a/arch/powerpc/boot/dts/mpc8572ds.dts
+++ b/arch/powerpc/boot/dts/mpc8572ds.dts
@@ -324,6 +324,20 @@
};
};
+ ptp_clock@24E00 {
+ compatible = "fsl,etsec-ptp";
+ reg = <0x24E00 0xB0>;
+ interrupts = <68 2 69 2 70 2 71 2>;
+ interrupt-parent = < &mpic >;
+ tclk-period = <5>;
+ tmr-prsc = <200>;
+ tmr-add = <0xAAAAAAAB>;
+ cksel = <1>;
+ tmr-fiper1 = <0x3B9AC9FB>;
+ tmr-fiper2 = <0x3B9AC9FB>;
+ max-adj = <499999999>;
+ };
+
enet0: ethernet@24000 {
#address-cells = <1>;
#size-cells = <1>;
diff --git a/arch/powerpc/boot/dts/p2020ds.dts b/arch/powerpc/boot/dts/p2020ds.dts
index 1101914..39d73bb 100644
--- a/arch/powerpc/boot/dts/p2020ds.dts
+++ b/arch/powerpc/boot/dts/p2020ds.dts
@@ -336,6 +336,20 @@
phy_type = "ulpi";
};
+ ptp_clock@24E00 {
+ compatible = "fsl,etsec-ptp";
+ reg = <0x24E00 0xB0>;
+ interrupts = <68 2 69 2 70 2>;
+ interrupt-parent = < &mpic >;
+ tclk-period = <5>;
+ tmr-prsc = <200>;
+ tmr-add = <0xCCCCCCCD>;
+ cksel = <1>;
+ tmr-fiper1 = <0x3B9AC9FB>;
+ tmr-fiper2 = <0x0001869B>;
+ max-adj = <249999999>;
+ };
+
enet0: ethernet@24000 {
#address-cells = <1>;
#size-cells = <1>;
diff --git a/arch/powerpc/boot/dts/p2020rdb.dts b/arch/powerpc/boot/dts/p2020rdb.dts
index da4cb0d..5498fb9 100644
--- a/arch/powerpc/boot/dts/p2020rdb.dts
+++ b/arch/powerpc/boot/dts/p2020rdb.dts
@@ -396,6 +396,20 @@
phy_type = "ulpi";
};
+ ptp_clock@24E00 {
+ compatible = "fsl,etsec-ptp";
+ reg = <0x24E00 0xB0>;
+ interrupts = <68 2 69 2 70 2>;
+ interrupt-parent = < &mpic >;
+ tclk-period = <5>;
+ tmr-prsc = <200>;
+ tmr-add = <0xCCCCCCCD>;
+ cksel = <1>;
+ tmr-fiper1 = <0x3B9AC9FB>;
+ tmr-fiper2 = <0x0001869B>;
+ max-adj = <249999999>;
+ };
+
enet0: ethernet@24000 {
#address-cells = <1>;
#size-cells = <1>;
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index 56e8c27..ea164ba 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -29,6 +29,7 @@ obj-$(CONFIG_ATL2) += atlx/
obj-$(CONFIG_ATL1E) += atl1e/
obj-$(CONFIG_ATL1C) += atl1c/
obj-$(CONFIG_GIANFAR) += gianfar_driver.o
+obj-$(CONFIG_PTP_1588_CLOCK_GIANFAR) += gianfar_ptp.o
obj-$(CONFIG_TEHUTI) += tehuti.o
obj-$(CONFIG_ENIC) += enic/
obj-$(CONFIG_JME) += jme.o
diff --git a/drivers/net/gianfar_ptp.c b/drivers/net/gianfar_ptp.c
new file mode 100644
index 0000000..637c8a7
--- /dev/null
+++ b/drivers/net/gianfar_ptp.c
@@ -0,0 +1,527 @@
+/*
+ * PTP 1588 clock using the eTSEC
+ *
+ * Copyright (C) 2010 OMICRON electronics GmbH
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include <linux/device.h>
+#include <linux/hrtimer.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/timex.h>
+#include <linux/io.h>
+
+#include <linux/ptp_clock_kernel.h>
+
+#include "gianfar_ptp_reg.h"
+#include "gianfar.h"
+
+#define DRIVER "gianfar_ptp"
+#define N_ALARM 1 /* first alarm is used internally to reset fipers */
+#define N_EXT_TS 2
+#define REG_SIZE sizeof(struct gianfar_ptp_registers)
+
+struct etsects {
+ struct gianfar_ptp_registers *regs;
+ struct ptp_clock *clock;
+ int irq;
+ u64 alarm_interval; /* for periodic alarm */
+ u64 alarm_value;
+ u32 tclk_period; /* nanoseconds */
+ u32 tmr_prsc;
+ u32 tmr_add;
+ u32 cksel;
+ u32 tmr_fiper1;
+ u32 tmr_fiper2;
+};
+
+/* Private globals */
+static struct etsects the_clock;
+DEFINE_SPINLOCK(register_lock);
+
+/*
+ * Register access functions
+ */
+
+static u64 tmr_cnt_read(struct etsects *etsects)
+{
+ u64 ns;
+ u32 lo, hi;
+
+ lo = gfar_read(&etsects->regs->tmr_cnt_l);
+ hi = gfar_read(&etsects->regs->tmr_cnt_h);
+ ns = ((u64) hi) << 32;
+ ns |= lo;
+ return ns;
+}
+
+static void tmr_cnt_write(struct etsects *etsects, u64 ns)
+{
+ u32 hi = ns >> 32;
+ u32 lo = ns & 0xffffffff;
+
+ gfar_write(&etsects->regs->tmr_cnt_l, lo);
+ gfar_write(&etsects->regs->tmr_cnt_h, hi);
+}
+
+static void set_alarm(struct etsects *etsects)
+{
+ u64 ns;
+ u32 lo, hi;
+
+ ns = tmr_cnt_read(etsects) + 1500000000ULL;
+ ns = div_u64(ns, 1000000000UL) * 1000000000ULL;
+ ns -= etsects->tclk_period;
+ hi = ns >> 32;
+ lo = ns & 0xffffffff;
+ gfar_write(&etsects->regs->tmr_alarm1_l, lo);
+ gfar_write(&etsects->regs->tmr_alarm1_h, hi);
+}
+
+static void set_fipers(struct etsects *etsects)
+{
+ u32 tmr_ctrl = gfar_read(&etsects->regs->tmr_ctrl);
+
+ gfar_write(&etsects->regs->tmr_ctrl, tmr_ctrl & (~TE));
+ gfar_write(&etsects->regs->tmr_prsc, etsects->tmr_prsc);
+ gfar_write(&etsects->regs->tmr_fiper1, etsects->tmr_fiper1);
+ gfar_write(&etsects->regs->tmr_fiper2, etsects->tmr_fiper2);
+ set_alarm(etsects);
+ gfar_write(&etsects->regs->tmr_ctrl, tmr_ctrl|TE);
+}
+
+/*
+ * Interrupt service routine
+ */
+
+static irqreturn_t isr(int irq, void *priv)
+{
+ struct etsects *etsects = priv;
+ struct ptp_clock_event event;
+ u64 ns;
+ u32 ack = 0, lo, hi, mask, val;
+
+ val = gfar_read(&etsects->regs->tmr_tevent);
+
+ if (val & ETS1) {
+ ack |= ETS1;
+ hi = gfar_read(&etsects->regs->tmr_etts1_h);
+ lo = gfar_read(&etsects->regs->tmr_etts1_l);
+ event.type = PTP_CLOCK_EXTTS;
+ event.index = 0;
+ event.timestamp = ((u64) hi) << 32;
+ event.timestamp |= lo;
+ ptp_clock_event(etsects->clock, &event);
+ }
+
+ if (val & ETS2) {
+ ack |= ETS2;
+ hi = gfar_read(&etsects->regs->tmr_etts2_h);
+ lo = gfar_read(&etsects->regs->tmr_etts2_l);
+ event.type = PTP_CLOCK_EXTTS;
+ event.index = 1;
+ event.timestamp = ((u64) hi) << 32;
+ event.timestamp |= lo;
+ ptp_clock_event(etsects->clock, &event);
+ }
+
+ if (val & ALM2) {
+ ack |= ALM2;
+ if (etsects->alarm_value) {
+ event.type = PTP_CLOCK_ALARM;
+ event.index = 0;
+ event.timestamp = etsects->alarm_value;
+ ptp_clock_event(etsects->clock, &event);
+ }
+ if (etsects->alarm_interval) {
+ ns = etsects->alarm_value + etsects->alarm_interval;
+ hi = ns >> 32;
+ lo = ns & 0xffffffff;
+ spin_lock(®ister_lock);
+ gfar_write(&etsects->regs->tmr_alarm2_l, lo);
+ gfar_write(&etsects->regs->tmr_alarm2_h, hi);
+ spin_unlock(®ister_lock);
+ etsects->alarm_value = ns;
+ } else {
+ gfar_write(&etsects->regs->tmr_tevent, ALM2);
+ spin_lock(®ister_lock);
+ mask = gfar_read(&etsects->regs->tmr_temask);
+ mask &= ~ALM2EN;
+ gfar_write(&etsects->regs->tmr_temask, mask);
+ spin_unlock(®ister_lock);
+ etsects->alarm_value = 0;
+ etsects->alarm_interval = 0;
+ }
+ }
+
+ if (val & PP1) {
+ ack |= PP1;
+ event.type = PTP_CLOCK_PPS;
+ ptp_clock_event(etsects->clock, &event);
+ }
+
+ if (ack) {
+ gfar_write(&etsects->regs->tmr_tevent, ack);
+ return IRQ_HANDLED;
+ } else
+ return IRQ_NONE;
+}
+
+/*
+ * PTP clock operations
+ */
+
+static int ptp_gianfar_adjfreq(void *priv, s32 ppb)
+{
+ u64 adj;
+ u32 diff, tmr_add;
+ int neg_adj = 0;
+ struct etsects *etsects = priv;
+
+ if (!ppb)
+ return 0;
+
+ if (ppb < 0) {
+ neg_adj = 1;
+ ppb = -ppb;
+ }
+ tmr_add = etsects->tmr_add;
+ adj = tmr_add;
+ adj *= ppb;
+ diff = div_u64(adj, 1000000000ULL);
+
+ tmr_add = neg_adj ? tmr_add - diff : tmr_add + diff;
+
+ gfar_write(&etsects->regs->tmr_add, tmr_add);
+
+ return 0;
+}
+
+static int ptp_gianfar_adjtime(void *priv, struct timespec *ts)
+{
+ s64 delta, now;
+ unsigned long flags;
+ struct etsects *etsects = priv;
+
+ delta = 1000000000LL * ts->tv_sec;
+ delta += ts->tv_nsec;
+
+ spin_lock_irqsave(®ister_lock, flags);
+
+ now = tmr_cnt_read(etsects);
+ now += delta;
+ tmr_cnt_write(etsects, now);
+
+ spin_unlock_irqrestore(®ister_lock, flags);
+
+ set_fipers(etsects);
+
+ return 0;
+}
+
+static int ptp_gianfar_gettime(void *priv, struct timespec *ts)
+{
+ u64 ns;
+ u32 remainder;
+ unsigned long flags;
+ struct etsects *etsects = priv;
+
+ spin_lock_irqsave(®ister_lock, flags);
+
+ ns = tmr_cnt_read(etsects);
+
+ spin_unlock_irqrestore(®ister_lock, flags);
+
+ ts->tv_sec = div_u64_rem(ns, 1000000000, &remainder);
+ ts->tv_nsec = remainder;
+ return 0;
+}
+
+static int ptp_gianfar_settime(void *priv, struct timespec *ts)
+{
+ u64 ns;
+ unsigned long flags;
+ struct etsects *etsects = priv;
+
+ ns = ts->tv_sec * 1000000000ULL;
+ ns += ts->tv_nsec;
+
+ spin_lock_irqsave(®ister_lock, flags);
+
+ tmr_cnt_write(etsects, ns);
+ set_fipers(etsects);
+
+ spin_unlock_irqrestore(®ister_lock, flags);
+
+ return 0;
+}
+
+static int ptp_gianfar_gettimer(void *priv, int index, struct itimerspec *ts)
+{
+ u64 now, ns;
+ u32 remainder;
+ unsigned long flags;
+ struct etsects *etsects = priv;
+
+ ns = etsects->alarm_interval;
+
+ ts->it_interval.tv_sec = div_u64_rem(ns, 1000000000, &remainder);
+ ts->it_interval.tv_nsec = remainder;
+
+ spin_lock_irqsave(®ister_lock, flags);
+ now = tmr_cnt_read(etsects);
+ spin_unlock_irqrestore(®ister_lock, flags);
+
+ ns = etsects->alarm_value - now;
+
+ ts->it_value.tv_sec = div_u64_rem(ns, 1000000000, &remainder);
+ ts->it_value.tv_nsec = remainder;
+
+ return 0;
+}
+
+static int ptp_gianfar_settimer(void *p, int i, int abs, struct itimerspec *ts)
+{
+ u64 ns;
+ u32 lo, hi, mask;
+ unsigned long flags;
+ struct etsects *etsects = p;
+
+ ns = ts->it_interval.tv_sec * 1000000000ULL;
+ ns += ts->it_interval.tv_nsec;
+
+ etsects->alarm_interval = ns;
+
+ ns = ts->it_value.tv_sec * 1000000000ULL;
+ ns += ts->it_value.tv_nsec;
+
+ if (!ns) {
+ /* Cancel the timer. */
+ etsects->alarm_value = 0;
+ etsects->alarm_interval = 0;
+ return 0;
+ }
+
+ if (!abs) {
+ spin_lock_irqsave(®ister_lock, flags);
+ ns += tmr_cnt_read(etsects);
+ spin_unlock_irqrestore(®ister_lock, flags);
+ }
+
+ etsects->alarm_value = ns;
+
+ hi = ns >> 32;
+ lo = ns & 0xffffffff;
+
+ spin_lock_irqsave(®ister_lock, flags);
+
+ gfar_write(&etsects->regs->tmr_alarm2_l, lo);
+ gfar_write(&etsects->regs->tmr_alarm2_h, hi);
+
+ mask = gfar_read(&etsects->regs->tmr_temask);
+ mask |= ALM2EN;
+ gfar_write(&etsects->regs->tmr_temask, mask);
+
+ spin_unlock_irqrestore(®ister_lock, flags);
+
+ return 0;
+}
+
+static int ptp_gianfar_enable(void *priv, struct ptp_clock_request *rq, int on)
+{
+ struct etsects *etsects = priv;
+ unsigned long flags;
+ u32 bit, mask;
+
+ switch (rq->type) {
+ case PTP_REQUEST_EXTTS:
+ switch (rq->index) {
+ case 0:
+ bit = ETS1EN;
+ break;
+ case 1:
+ bit = ETS2EN;
+ break;
+ default:
+ return -EINVAL;
+ }
+ spin_lock_irqsave(®ister_lock, flags);
+ mask = gfar_read(&etsects->regs->tmr_temask);
+ if (on)
+ mask |= bit;
+ else
+ mask &= ~bit;
+ gfar_write(&etsects->regs->tmr_temask, mask);
+ spin_unlock_irqrestore(®ister_lock, flags);
+ return 0;
+
+ case PTP_REQUEST_PPS:
+ spin_lock_irqsave(®ister_lock, flags);
+ mask = gfar_read(&etsects->regs->tmr_temask);
+ if (on)
+ mask |= PP1EN;
+ else
+ mask &= ~PP1EN;
+ gfar_write(&etsects->regs->tmr_temask, mask);
+ spin_unlock_irqrestore(®ister_lock, flags);
+ return 0;
+
+ default:
+ break;
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static struct ptp_clock_info ptp_gianfar_caps = {
+ .owner = THIS_MODULE,
+ .name = "gianfar clock",
+ .max_adj = 512000,
+ .n_alarm = N_ALARM,
+ .n_ext_ts = N_EXT_TS,
+ .n_per_out = 0,
+ .pps = 1,
+ .priv = &the_clock,
+ .adjfreq = ptp_gianfar_adjfreq,
+ .adjtime = ptp_gianfar_adjtime,
+ .gettime = ptp_gianfar_gettime,
+ .settime = ptp_gianfar_settime,
+ .gettimer = ptp_gianfar_gettimer,
+ .settimer = ptp_gianfar_settimer,
+ .enable = ptp_gianfar_enable,
+};
+
+/* OF device tree */
+
+static int get_of_u32(struct device_node *node, char *str, u32 *val)
+{
+ int plen;
+ const u32 *prop = of_get_property(node, str, &plen);
+
+ if (!prop || plen != sizeof(*prop))
+ return -1;
+ *val = *prop;
+ return 0;
+}
+
+static int gianfar_ptp_probe(struct of_device *dev,
+ const struct of_device_id *match)
+{
+ struct device_node *node = dev->dev.of_node;
+ struct etsects *etsects = &the_clock;
+ struct timespec now;
+ u32 tmr_ctrl;
+
+ if (get_of_u32(node, "tclk-period", &etsects->tclk_period) ||
+ get_of_u32(node, "tmr-prsc", &etsects->tmr_prsc) ||
+ get_of_u32(node, "tmr-add", &etsects->tmr_add) ||
+ get_of_u32(node, "cksel", &etsects->cksel) ||
+ get_of_u32(node, "tmr-fiper1", &etsects->tmr_fiper1) ||
+ get_of_u32(node, "tmr-fiper2", &etsects->tmr_fiper2) ||
+ get_of_u32(node, "max-adj", &ptp_gianfar_caps.max_adj)) {
+ pr_err("device tree node missing required elements\n");
+ return -ENODEV;
+ }
+
+ etsects->irq = irq_of_parse_and_map(node, 0);
+
+ if (etsects->irq == NO_IRQ) {
+ pr_err("irq not in device tree\n");
+ return -ENODEV;
+ }
+ if (request_irq(etsects->irq, isr, 0, DRIVER, etsects)) {
+ pr_err("request_irq failed\n");
+ return -ENODEV;
+ }
+
+ etsects->regs = of_iomap(node, 0);
+ if (!etsects->regs) {
+ pr_err("of_iomap ptp registers failed\n");
+ return -EINVAL;
+ }
+
+ tmr_ctrl =
+ (etsects->tclk_period & TCLK_PERIOD_MASK) << TCLK_PERIOD_SHIFT |
+ (etsects->cksel & CKSEL_MASK) << CKSEL_SHIFT;
+
+ getnstimeofday(&now);
+ ptp_gianfar_settime(etsects, &now);
+
+ gfar_write(&etsects->regs->tmr_ctrl, tmr_ctrl);
+ gfar_write(&etsects->regs->tmr_add, etsects->tmr_add);
+ gfar_write(&etsects->regs->tmr_prsc, etsects->tmr_prsc);
+ gfar_write(&etsects->regs->tmr_fiper1, etsects->tmr_fiper1);
+ gfar_write(&etsects->regs->tmr_fiper2, etsects->tmr_fiper2);
+ set_alarm(etsects);
+ gfar_write(&etsects->regs->tmr_ctrl, tmr_ctrl|FS|RTPE|TE);
+
+ etsects->clock = ptp_clock_register(&ptp_gianfar_caps);
+
+ return IS_ERR(etsects->clock) ? PTR_ERR(etsects->clock) : 0;
+}
+
+static int gianfar_ptp_remove(struct of_device *dev)
+{
+ gfar_write(&the_clock.regs->tmr_temask, 0);
+ gfar_write(&the_clock.regs->tmr_ctrl, 0);
+
+ ptp_clock_unregister(the_clock.clock);
+
+ free_irq(the_clock.irq, &the_clock);
+
+ iounmap(the_clock.regs);
+
+ return 0;
+}
+
+static struct of_device_id match_table[] = {
+ { .compatible = "fsl,etsec-ptp" },
+ {},
+};
+
+static struct of_platform_driver gianfar_ptp_driver = {
+ .driver = {
+ .name = "gianfar_ptp",
+ .of_match_table = match_table,
+ .owner = THIS_MODULE,
+ },
+ .probe = gianfar_ptp_probe,
+ .remove = gianfar_ptp_remove,
+};
+
+/* module operations */
+
+static int __init ptp_gianfar_init(void)
+{
+ return of_register_platform_driver(&gianfar_ptp_driver);
+}
+
+module_init(ptp_gianfar_init);
+
+static void __exit ptp_gianfar_exit(void)
+{
+ of_unregister_platform_driver(&gianfar_ptp_driver);
+}
+
+module_exit(ptp_gianfar_exit);
+
+MODULE_AUTHOR("Richard Cochran <richard.cochran@omicron.at>");
+MODULE_DESCRIPTION("PTP clock using the eTSEC");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/gianfar_ptp_reg.h b/drivers/net/gianfar_ptp_reg.h
new file mode 100644
index 0000000..95e171f
--- /dev/null
+++ b/drivers/net/gianfar_ptp_reg.h
@@ -0,0 +1,113 @@
+/* gianfar_ptp_reg.h
+ * Generated by regen.tcl on Thu May 13 01:38:57 PM CEST 2010
+ *
+ * PTP 1588 clock using the gianfar eTSEC
+ *
+ * Copyright (C) 2010 OMICRON electronics GmbH
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#ifndef _GIANFAR_PTP_REG_H_
+#define _GIANFAR_PTP_REG_H_
+
+struct gianfar_ptp_registers {
+ u32 tmr_ctrl; /* Timer control register */
+ u32 tmr_tevent; /* Timestamp event register */
+ u32 tmr_temask; /* Timer event mask register */
+ u32 tmr_pevent; /* Timestamp event register */
+ u32 tmr_pemask; /* Timer event mask register */
+ u32 tmr_stat; /* Timestamp status register */
+ u32 tmr_cnt_h; /* Timer counter high register */
+ u32 tmr_cnt_l; /* Timer counter low register */
+ u32 tmr_add; /* Timer drift compensation addend register */
+ u32 tmr_acc; /* Timer accumulator register */
+ u32 tmr_prsc; /* Timer prescale */
+ u8 res1[4];
+ u32 tmroff_h; /* Timer offset high */
+ u32 tmroff_l; /* Timer offset low */
+ u8 res2[8];
+ u32 tmr_alarm1_h; /* Timer alarm 1 high register */
+ u32 tmr_alarm1_l; /* Timer alarm 1 high register */
+ u32 tmr_alarm2_h; /* Timer alarm 2 high register */
+ u32 tmr_alarm2_l; /* Timer alarm 2 high register */
+ u8 res3[48];
+ u32 tmr_fiper1; /* Timer fixed period interval */
+ u32 tmr_fiper2; /* Timer fixed period interval */
+ u32 tmr_fiper3; /* Timer fixed period interval */
+ u8 res4[20];
+ u32 tmr_etts1_h; /* Timestamp of general purpose external trigger */
+ u32 tmr_etts1_l; /* Timestamp of general purpose external trigger */
+ u32 tmr_etts2_h; /* Timestamp of general purpose external trigger */
+ u32 tmr_etts2_l; /* Timestamp of general purpose external trigger */
+};
+
+/* Bit definitions for the TMR_CTRL register */
+#define ALM1P (1<<31) /* Alarm1 output polarity */
+#define ALM2P (1<<30) /* Alarm2 output polarity */
+#define FS (1<<28) /* FIPER start indication */
+#define PP1L (1<<27) /* Fiper1 pulse loopback mode enabled. */
+#define PP2L (1<<26) /* Fiper2 pulse loopback mode enabled. */
+#define TCLK_PERIOD_SHIFT (16) /* 1588 timer reference clock period. */
+#define TCLK_PERIOD_MASK (0x3ff)
+#define RTPE (1<<15) /* Record Tx Timestamp to PAL Enable. */
+#define FRD (1<<14) /* FIPER Realignment Disable */
+#define ESFDP (1<<11) /* External Tx/Rx SFD Polarity. */
+#define ESFDE (1<<10) /* External Tx/Rx SFD Enable. */
+#define ETEP2 (1<<9) /* External trigger 2 edge polarity */
+#define ETEP1 (1<<8) /* External trigger 1 edge polarity */
+#define COPH (1<<7) /* Generated clock (TSEC_1588_GCLK) output phase. */
+#define CIPH (1<<6) /* External oscillator input clock phase. */
+#define TMSR (1<<5) /* Timer soft reset. When enabled, it resets all the timer registers and state machines. */
+#define BYP (1<<3) /* Bypass drift compensated clock */
+#define TE (1<<2) /* 1588 timer enable. If not enabled, all the timer registers and state machines are disabled. */
+#define CKSEL_SHIFT (0) /* 1588 Timer reference clock source select. */
+#define CKSEL_MASK (0x3)
+
+/* Bit definitions for the TMR_TEVENT register */
+#define ETS2 (1<<25) /* External trigger 2 timestamp sampled */
+#define ETS1 (1<<24) /* External trigger 1 timestamp sampled */
+#define ALM2 (1<<17) /* Current time equaled alarm time register 2 */
+#define ALM1 (1<<16) /* Current time equaled alarm time register 1 */
+#define PP1 (1<<7) /* Indicates that a periodic pulse has been generated based on FIPER1 register */
+#define PP2 (1<<6) /* Indicates that a periodic pulse has been generated based on FIPER2 register */
+#define PP3 (1<<5) /* Indicates that a periodic pulse has been generated based on FIPER3 register */
+
+/* Bit definitions for the TMR_TEMASK register */
+#define ETS2EN (1<<25) /* External trigger 2 timestamp sample event enable */
+#define ETS1EN (1<<24) /* External trigger 1 timestamp sample event enable */
+#define ALM2EN (1<<17) /* Timer ALM2 event enable */
+#define ALM1EN (1<<16) /* Timer ALM1 event enable */
+#define PP1EN (1<<7) /* Periodic pulse event 1 enable */
+#define PP2EN (1<<6) /* Periodic pulse event 2 enable */
+
+/* Bit definitions for the TMR_PEVENT register */
+#define TXP2 (1<<9) /* Indicates that a PTP frame has been transmitted and its timestamp is stored in TXTS2 register */
+#define TXP1 (1<<8) /* Indicates that a PTP frame has been transmitted and its timestamp is stored in TXTS1 register */
+#define RXP (1<<0) /* Indicates that a PTP frame has been received */
+
+/* Bit definitions for the TMR_PEMASK register */
+#define TXP2EN (1<<9) /* Transmit PTP packet event 2 enable */
+#define TXP1EN (1<<8) /* Transmit PTP packet event 1 enable */
+#define RXPEN (1<<0) /* Receive PTP packet event enable */
+
+/* Bit definitions for the TMR_STAT register */
+#define STAT_VEC_SHIFT (0) /* Timer general purpose status vector */
+#define STAT_VEC_MASK (0x3f)
+
+/* Bit definitions for the TMR_PRSC register */
+#define PRSC_OCK_SHIFT (0) /* Output clock division/prescale factor. */
+#define PRSC_OCK_MASK (0xffff)
+
+#endif
diff --git a/drivers/ptp/Kconfig b/drivers/ptp/Kconfig
index 3aa517a..aaf957f 100644
--- a/drivers/ptp/Kconfig
+++ b/drivers/ptp/Kconfig
@@ -36,4 +36,17 @@ config PTP_1588_CLOCK_LINUX
To compile this driver as a module, choose M here: the module
will be called ptp_linux.
+config PTP_1588_CLOCK_GIANFAR
+ tristate "Freescale eTSEC as PTP clock"
+ depends on PTP_1588_CLOCK
+ depends on GIANFAR
+ help
+ This driver adds support for using the eTSEC as a PTP
+ clock. This clock is only useful if your PTP programs are
+ getting hardware time stamps on the PTP Ethernet packets
+ using the SO_TIMESTAMPING API.
+
+ To compile this driver as a module, choose M here: the module
+ will be called gianfar_ptp.
+
endmenu
--
1.7.0.4
^ permalink raw reply related
* [PATCH 2/5] ptp: Added a clock that uses the Linux system time.
From: Richard Cochran @ 2010-08-16 11:18 UTC (permalink / raw)
To: netdev
Cc: linuxppc-dev, devicetree-discuss, linux-kernel, Rodolfo Giometti,
linux-arm-kernel, Krzysztof Halasa
In-Reply-To: <cover.1281956490.git.richard.cochran@omicron.at>
This PTP clock simply uses the NTP time adjustment system calls. The
driver can be used in systems that lack a special hardware PTP clock.
Signed-off-by: Richard Cochran <richard.cochran@omicron.at>
---
drivers/ptp/Kconfig | 12 ++++
drivers/ptp/Makefile | 1 +
drivers/ptp/ptp_linux.c | 136 +++++++++++++++++++++++++++++++++++++++++++++++
kernel/time/ntp.c | 2 +
4 files changed, 151 insertions(+), 0 deletions(-)
create mode 100644 drivers/ptp/ptp_linux.c
diff --git a/drivers/ptp/Kconfig b/drivers/ptp/Kconfig
index cd7becb..3aa517a 100644
--- a/drivers/ptp/Kconfig
+++ b/drivers/ptp/Kconfig
@@ -24,4 +24,16 @@ config PTP_1588_CLOCK
To compile this driver as a module, choose M here: the module
will be called ptp_clock.
+config PTP_1588_CLOCK_LINUX
+ tristate "Linux system timer as PTP clock"
+ depends on PTP_1588_CLOCK
+ help
+ This driver adds support for using the standard Linux time
+ source as a PTP clock. This clock is only useful if your PTP
+ programs are using software time stamps for the PTP Ethernet
+ packets.
+
+ To compile this driver as a module, choose M here: the module
+ will be called ptp_linux.
+
endmenu
diff --git a/drivers/ptp/Makefile b/drivers/ptp/Makefile
index b86695c..1651d52 100644
--- a/drivers/ptp/Makefile
+++ b/drivers/ptp/Makefile
@@ -3,3 +3,4 @@
#
obj-$(CONFIG_PTP_1588_CLOCK) += ptp_clock.o
+obj-$(CONFIG_PTP_1588_CLOCK_LINUX) += ptp_linux.o
diff --git a/drivers/ptp/ptp_linux.c b/drivers/ptp/ptp_linux.c
new file mode 100644
index 0000000..f93ae0c
--- /dev/null
+++ b/drivers/ptp/ptp_linux.c
@@ -0,0 +1,136 @@
+/*
+ * PTP 1588 clock using the Linux system clock
+ *
+ * Copyright (C) 2010 OMICRON electronics GmbH
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/hrtimer.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/timex.h>
+
+#include <linux/ptp_clock_kernel.h>
+
+static struct ptp_clock *linux_clock;
+
+DEFINE_SPINLOCK(adjtime_lock);
+
+static int ptp_linux_adjfreq(void *priv, s32 delta)
+{
+ struct timex txc;
+ s64 tmp = delta;
+ int err;
+ txc.freq = div_s64(tmp<<16, 1000);
+ txc.modes = ADJ_FREQUENCY;
+ err = do_adjtimex(&txc);
+ return err < 0 ? err : 0;
+}
+
+static int ptp_linux_adjtime(void *priv, struct timespec *ts)
+{
+ s64 delta;
+ ktime_t now;
+ struct timespec t2;
+ unsigned long flags;
+ int err;
+
+ delta = 1000000000LL * ts->tv_sec + ts->tv_nsec;
+
+ spin_lock_irqsave(&adjtime_lock, flags);
+
+ now = ktime_get_real();
+
+ now = delta < 0 ? ktime_sub_ns(now, -delta) : ktime_add_ns(now, delta);
+
+ t2 = ktime_to_timespec(now);
+
+ err = do_settimeofday(&t2);
+
+ spin_unlock_irqrestore(&adjtime_lock, flags);
+
+ return err;
+}
+
+static int ptp_linux_gettime(void *priv, struct timespec *ts)
+{
+ getnstimeofday(ts);
+ return 0;
+}
+
+static int ptp_linux_settime(void *priv, struct timespec *ts)
+{
+ return do_settimeofday(ts);
+}
+
+static int ptp_linux_gettimer(void *priv, int index, struct itimerspec *ts)
+{
+ /* We do not offer any ancillary features at all. */
+ return -EOPNOTSUPP;
+}
+
+static int ptp_linux_settimer(void *p, int i, int abs, struct itimerspec *ts)
+{
+ /* We do not offer any ancillary features at all. */
+ return -EOPNOTSUPP;
+}
+
+static int ptp_linux_enable(void *priv, struct ptp_clock_request *rq, int on)
+{
+ /* We do not offer any ancillary features at all. */
+ return -EOPNOTSUPP;
+}
+
+static struct ptp_clock_info ptp_linux_caps = {
+ .owner = THIS_MODULE,
+ .name = "Linux timer",
+ .max_adj = 512000,
+ .n_alarm = 0,
+ .n_ext_ts = 0,
+ .n_per_out = 0,
+ .pps = 0,
+ .priv = NULL,
+ .adjfreq = ptp_linux_adjfreq,
+ .adjtime = ptp_linux_adjtime,
+ .gettime = ptp_linux_gettime,
+ .settime = ptp_linux_settime,
+ .gettimer = ptp_linux_gettimer,
+ .settimer = ptp_linux_settimer,
+ .enable = ptp_linux_enable,
+};
+
+/* module operations */
+
+static void __exit ptp_linux_exit(void)
+{
+ ptp_clock_unregister(linux_clock);
+}
+
+static int __init ptp_linux_init(void)
+{
+ linux_clock = ptp_clock_register(&ptp_linux_caps);
+
+ return IS_ERR(linux_clock) ? PTR_ERR(linux_clock) : 0;
+}
+
+module_init(ptp_linux_init);
+module_exit(ptp_linux_exit);
+
+MODULE_AUTHOR("Richard Cochran <richard.cochran@omicron.at>");
+MODULE_DESCRIPTION("PTP clock using the Linux system timer");
+MODULE_LICENSE("GPL");
diff --git a/kernel/time/ntp.c b/kernel/time/ntp.c
index c631168..eba3bcf 100644
--- a/kernel/time/ntp.c
+++ b/kernel/time/ntp.c
@@ -14,6 +14,7 @@
#include <linux/timex.h>
#include <linux/time.h>
#include <linux/mm.h>
+#include <linux/module.h>
/*
* NTP timekeeping variables:
@@ -535,6 +536,7 @@ int do_adjtimex(struct timex *txc)
return result;
}
+EXPORT_SYMBOL(do_adjtimex);
static int __init ntp_tick_adj_setup(char *str)
{
--
1.7.0.4
^ permalink raw reply related
* [PATCH 1/5] ptp: Added a brand new class driver for ptp clocks.
From: Richard Cochran @ 2010-08-16 11:17 UTC (permalink / raw)
To: netdev
Cc: linux-kernel, linuxppc-dev, devicetree-discuss, linux-arm-kernel,
Krzysztof Halasa, Rodolfo Giometti
In-Reply-To: <cover.1281956490.git.richard.cochran@omicron.at>
This patch adds an infrastructure for hardware clocks that implement
IEEE 1588, the Precision Time Protocol (PTP). A class driver offers a
registration method to particular hardware clock drivers. Each clock is
exposed to user space as a character device with ioctls that allow tuning
of the PTP clock.
Signed-off-by: Richard Cochran <richard.cochran@omicron.at>
---
Documentation/ptp/ptp.txt | 95 +++++++
Documentation/ptp/testptp.c | 306 ++++++++++++++++++++++
Documentation/ptp/testptp.mk | 33 +++
drivers/Kconfig | 2 +
drivers/Makefile | 1 +
drivers/ptp/Kconfig | 27 ++
drivers/ptp/Makefile | 5 +
drivers/ptp/ptp_clock.c | 514 ++++++++++++++++++++++++++++++++++++++
include/linux/Kbuild | 1 +
include/linux/ptp_clock.h | 79 ++++++
include/linux/ptp_clock_kernel.h | 137 ++++++++++
11 files changed, 1200 insertions(+), 0 deletions(-)
create mode 100644 Documentation/ptp/ptp.txt
create mode 100644 Documentation/ptp/testptp.c
create mode 100644 Documentation/ptp/testptp.mk
create mode 100644 drivers/ptp/Kconfig
create mode 100644 drivers/ptp/Makefile
create mode 100644 drivers/ptp/ptp_clock.c
create mode 100644 include/linux/ptp_clock.h
create mode 100644 include/linux/ptp_clock_kernel.h
diff --git a/Documentation/ptp/ptp.txt b/Documentation/ptp/ptp.txt
new file mode 100644
index 0000000..46858b3
--- /dev/null
+++ b/Documentation/ptp/ptp.txt
@@ -0,0 +1,95 @@
+
+* PTP infrastructure for Linux
+
+ This patch set introduces support for IEEE 1588 PTP clocks in
+ Linux. Together with the SO_TIMESTAMPING socket options, this
+ presents a standardized method for developing PTP user space
+ programs, synchronizing Linux with external clocks, and using the
+ ancillary features of PTP hardware clocks.
+
+ A new class driver exports a kernel interface for specific clock
+ drivers and a user space interface. The infrastructure supports a
+ complete set of PTP functionality.
+
+ + Basic clock operations
+ - Set time
+ - Get time
+ - Shift the clock by a given offset atomically
+ - Adjust clock frequency
+
+ + Ancillary clock features
+ - One short or periodic alarms, with signal delivery to user program
+ - Time stamp external events
+ - Period output signals configurable from user space
+ - Synchronization of the Linux system time via the PPS subsystem
+
+** PTP kernel API
+
+ A PTP clock driver registers itself with the class driver. The
+ class driver handles all of the dealings with user space. The
+ author of a clock driver need only implement the details of
+ programming the clock hardware. The clock driver notifies the class
+ driver of asynchronous events (alarms and external time stamps) via
+ a simple message passing interface.
+
+ The class driver supports multiple PTP clock drivers. In normal use
+ cases, only one PTP clock is needed. However, for testing and
+ development, it can be useful to have more than one clock in a
+ single system, in order to allow performance comparisons.
+
+** PTP user space API
+
+ The class driver creates a character device for each registered PTP
+ clock. User space programs may control the clock using standardized
+ ioctls. A program may query, enable, configure, and disable the
+ ancillary clock features. User space can receive time stamped
+ events via blocking read() and poll(). One shot and periodic
+ signals may be configured via an ioctl API with semantics similar
+ to the POSIX timer_settime() system call.
+
+ As an real life example, the following two patches for ptpd version
+ 1.0.0 demonstrate how the API works.
+
+ https://sourceforge.net/tracker/?func=detail&aid=2992845&group_id=139814&atid=744634
+
+ https://sourceforge.net/tracker/?func=detail&aid=2992847&group_id=139814&atid=744634
+
+** Writing clock drivers
+
+ Clock drivers include include/linux/ptp_clock_kernel.h and register
+ themselves by presenting a 'struct ptp_clock_info' to the
+ registration method. Clock drivers must implement all of the
+ functions in the interface. If a clock does not offer a particular
+ ancillary feature, then the driver should just return -EOPNOTSUPP
+ from those functions.
+
+ Drivers must ensure that all of the methods in interface are
+ reentrant. Since most hardware implementations treat the time value
+ as a 64 bit integer accessed as two 32 bit registers, drivers
+ should use spin_lock_irqsave/spin_unlock_irqrestore to protect
+ against concurrent access. This locking cannot be accomplished in
+ class driver, since the lock may also be needed by the clock
+ driver's interrupt service routine.
+
+** Supported hardware
+
+ + Standard Linux system timer
+ - No special PTP features
+ - For use with software time stamping
+
+ + Freescale eTSEC gianfar
+ - 2 Time stamp external triggers, programmable polarity (opt. interrupt)
+ - 2 Alarm registers (optional interrupt)
+ - 3 Periodic signals (optional interrupt)
+
+ + National DP83640
+ - 6 GPIOs programmable as inputs or outputs
+ - 6 GPIOs with dedicated functions (LED/JTAG/clock) can also be
+ used as general inputs or outputs
+ - GPIO inputs can time stamp external triggers
+ - GPIO outputs can produce periodic signals
+ - 1 interrupt pin
+
+ + Intel IXP465
+ - Auxiliary Slave/Master Mode Snapshot (optional interrupt)
+ - Target Time (optional interrupt)
diff --git a/Documentation/ptp/testptp.c b/Documentation/ptp/testptp.c
new file mode 100644
index 0000000..41a9839
--- /dev/null
+++ b/Documentation/ptp/testptp.c
@@ -0,0 +1,306 @@
+/*
+ * PTP 1588 clock support - User space test program
+ *
+ * Copyright (C) 2010 OMICRON electronics GmbH
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <linux/ptp_clock.h>
+
+static void handle_alarm(int s)
+{
+ printf("received signal %d\n", s);
+}
+
+static int install_handler(int signum, void (*handler)(int))
+{
+ struct sigaction action;
+ sigset_t mask;
+
+ /* Unblock the signal. */
+ sigemptyset(&mask);
+ sigaddset(&mask, signum);
+ sigprocmask(SIG_UNBLOCK, &mask, NULL);
+
+ /* Install the signal handler. */
+ action.sa_handler = handler;
+ action.sa_flags = 0;
+ sigemptyset(&action.sa_mask);
+ sigaction(signum, &action, NULL);
+
+ return 0;
+}
+
+static void usage(char *progname)
+{
+ fprintf(stderr,
+ "usage: %s [options]\n"
+ " -a val request a one-shot alarm after 'val' seconds\n"
+ " -A val request a periodic alarm every 'val' seconds\n"
+ " -c query the ptp clock's capabilities\n"
+ " -d name device to open\n"
+ " -e val read 'val' external time stamp events\n"
+ " -f val adjust the ptp clock frequency by 'val' PPB\n"
+ " -g get the ptp clock time\n"
+ " -h prints this message\n"
+ " -p val enable output with a period of 'val' nanoseconds\n"
+ " -P val enable or disable (val=1|0) the system clock PPS\n"
+ " -s set the ptp clock time from the system time\n"
+ " -t val shift the ptp clock time by 'val' seconds\n"
+ " -v query the ptp clock api version\n",
+ progname);
+}
+
+int main(int argc, char *argv[])
+{
+ struct ptp_clock_caps caps;
+ struct ptp_clock_timer timer;
+ struct ptp_extts_event event;
+ struct ptp_clock_request request;
+ struct timespec ts;
+ char *progname;
+ int c, cnt, fd, val = 0;
+
+ char *device = "/dev/ptp_clock_0";
+ int adjfreq = 0x7fffffff;
+ int adjtime = 0;
+ int capabilities = 0;
+ int extts = 0;
+ int gettime = 0;
+ int oneshot = 0;
+ int periodic = 0;
+ int perout = -1;
+ int pps = -1;
+ int settime = 0;
+ int version = 0;
+
+ progname = strrchr(argv[0], '/');
+ progname = progname ? 1+progname : argv[0];
+ while (EOF != (c = getopt(argc, argv, "a:A:cd:e:f:ghp:P:st:v"))) {
+ switch (c) {
+ case 'a':
+ oneshot = atoi(optarg);
+ break;
+ case 'A':
+ periodic = atoi(optarg);
+ break;
+ case 'c':
+ capabilities = 1;
+ break;
+ case 'd':
+ device = optarg;
+ break;
+ case 'e':
+ extts = atoi(optarg);
+ break;
+ case 'f':
+ adjfreq = atoi(optarg);
+ break;
+ case 'g':
+ gettime = 1;
+ break;
+ case 'p':
+ perout = atoi(optarg);
+ break;
+ case 'P':
+ pps = atoi(optarg);
+ break;
+ case 's':
+ settime = 1;
+ break;
+ case 't':
+ adjtime = atoi(optarg);
+ break;
+ case 'v':
+ version = 1;
+ break;
+ case 'h':
+ usage(progname);
+ return 0;
+ case '?':
+ default:
+ usage(progname);
+ return -1;
+ }
+ }
+
+ fd = open(device, O_RDWR);
+ if (fd < 0) {
+ fprintf(stderr, "cannot open %s: %s", device, strerror(errno));
+ return -1;
+ }
+
+ if (version) {
+ if (ioctl(fd, PTP_CLOCK_APIVERS, &val)) {
+ perror("PTP_CLOCK_APIVERS");
+ } else {
+ printf("version = 0x%08x\n", val);
+ }
+ }
+
+ if (capabilities) {
+ if (ioctl(fd, PTP_CLOCK_GETCAPS, &caps)) {
+ perror("PTP_CLOCK_GETCAPS");
+ } else {
+ printf("capabilities:\n"
+ " %d maximum frequency adjustment (PPB)\n"
+ " %d programmable alarms\n"
+ " %d external time stamp channels\n"
+ " %d programmable periodic signals\n"
+ " %d pulse per second\n",
+ caps.max_adj,
+ caps.n_alarm,
+ caps.n_ext_ts,
+ caps.n_per_out,
+ caps.pps);
+ }
+ }
+
+ if (0x7fffffff != adjfreq) {
+ if (ioctl(fd, PTP_CLOCK_ADJFREQ, adjfreq)) {
+ perror("PTP_CLOCK_ADJFREQ");
+ } else {
+ puts("frequency adjustment okay");
+ }
+ }
+
+ if (adjtime) {
+ ts.tv_sec = adjtime;
+ ts.tv_nsec = 0;
+ if (ioctl(fd, PTP_CLOCK_ADJTIME, &ts)) {
+ perror("PTP_CLOCK_ADJTIME");
+ } else {
+ puts("time shift okay");
+ }
+ }
+
+ if (gettime) {
+ if (ioctl(fd, PTP_CLOCK_GETTIME, &ts)) {
+ perror("PTP_CLOCK_GETTIME");
+ } else {
+ printf("clock time: %ld.%09ld or %s",
+ ts.tv_sec, ts.tv_nsec, ctime(&ts.tv_sec));
+ }
+ }
+
+ if (settime) {
+ clock_gettime(CLOCK_REALTIME, &ts);
+ if (ioctl(fd, PTP_CLOCK_SETTIME, &ts)) {
+ perror("PTP_CLOCK_SETTIME");
+ } else {
+ puts("set time okay");
+ }
+ }
+
+ if (extts) {
+ memset(&request, 0, sizeof(request));
+ request.type = PTP_REQUEST_EXTTS;
+ request.index = 0;
+ request.flags = PTP_ENABLE_FEATURE;
+ if (ioctl(fd, PTP_FEATURE_REQUEST, &request)) {
+ perror("PTP_FEATURE_REQUEST");
+ extts = 0;
+ } else {
+ puts("external time stamp request okay");
+ }
+ for (; extts; extts--) {
+ cnt = read(fd, &event, sizeof(event));
+ if (cnt != sizeof(event)) {
+ perror("read");
+ break;
+ }
+ printf("event index %d at %ld.%09ld\n", event.index,
+ event.ts.tv_sec, event.ts.tv_nsec);
+ fflush(stdout);
+ }
+ /* Disable the feature again. */
+ request.flags = 0;
+ if (ioctl(fd, PTP_FEATURE_REQUEST, &request)) {
+ perror("PTP_FEATURE_REQUEST");
+ }
+ }
+
+ if (oneshot) {
+ install_handler(SIGALRM, handle_alarm);
+ memset(&timer, 0, sizeof(timer));
+ timer.signum = SIGALRM;
+ timer.tsp.it_value.tv_sec = oneshot;
+ if (ioctl(fd, PTP_CLOCK_SETTIMER, &timer)) {
+ perror("PTP_CLOCK_SETTIMER");
+ } else {
+ puts("set timer okay");
+ }
+ pause();
+ }
+
+ if (periodic) {
+ install_handler(SIGALRM, handle_alarm);
+ memset(&timer, 0, sizeof(timer));
+ timer.signum = SIGALRM;
+ timer.tsp.it_value.tv_sec = periodic;
+ timer.tsp.it_interval.tv_sec = periodic;
+ if (ioctl(fd, PTP_CLOCK_SETTIMER, &timer)) {
+ perror("PTP_CLOCK_SETTIMER");
+ } else {
+ puts("set timer okay");
+ }
+ while (1) {
+ pause();
+ }
+ }
+
+ if (perout >= 0) {
+ memset(&request, 0, sizeof(request));
+ request.type = PTP_REQUEST_PEROUT;
+ request.index = 0;
+ request.ts.tv_sec = 0;
+ request.ts.tv_nsec = perout;
+ request.flags = perout ? PTP_ENABLE_FEATURE : 0;
+ if (ioctl(fd, PTP_FEATURE_REQUEST, &request)) {
+ perror("PTP_FEATURE_REQUEST");
+ extts = 0;
+ } else {
+ puts("periodic output request okay");
+ }
+ }
+
+ if (pps != -1) {
+ memset(&request, 0, sizeof(request));
+ request.type = PTP_REQUEST_PPS;
+ request.flags = perout ? PTP_ENABLE_FEATURE : 0;
+ if (ioctl(fd, PTP_FEATURE_REQUEST, &request)) {
+ perror("PTP_FEATURE_REQUEST");
+ } else {
+ puts("pps for system time request okay");
+ }
+ }
+
+ close(fd);
+ return 0;
+}
diff --git a/Documentation/ptp/testptp.mk b/Documentation/ptp/testptp.mk
new file mode 100644
index 0000000..4ef2d97
--- /dev/null
+++ b/Documentation/ptp/testptp.mk
@@ -0,0 +1,33 @@
+# PTP 1588 clock support - User space test program
+#
+# Copyright (C) 2010 OMICRON electronics GmbH
+#
+# 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.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+CC = $(CROSS_COMPILE)gcc
+INC = -I$(KBUILD_OUTPUT)/usr/include
+CFLAGS = -Wall $(INC)
+LDLIBS = -lrt
+PROGS = testptp
+
+all: $(PROGS)
+
+testptp: testptp.o
+
+clean:
+ rm -f testptp.o
+
+distclean: clean
+ rm -f $(PROGS)
diff --git a/drivers/Kconfig b/drivers/Kconfig
index a2b902f..774fbd7 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -52,6 +52,8 @@ source "drivers/spi/Kconfig"
source "drivers/pps/Kconfig"
+source "drivers/ptp/Kconfig"
+
source "drivers/gpio/Kconfig"
source "drivers/w1/Kconfig"
diff --git a/drivers/Makefile b/drivers/Makefile
index 91874e0..6d12b48 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -76,6 +76,7 @@ obj-$(CONFIG_I2O) += message/
obj-$(CONFIG_RTC_LIB) += rtc/
obj-y += i2c/ media/
obj-$(CONFIG_PPS) += pps/
+obj-$(CONFIG_PTP_1588_CLOCK) += ptp/
obj-$(CONFIG_W1) += w1/
obj-$(CONFIG_POWER_SUPPLY) += power/
obj-$(CONFIG_HWMON) += hwmon/
diff --git a/drivers/ptp/Kconfig b/drivers/ptp/Kconfig
new file mode 100644
index 0000000..cd7becb
--- /dev/null
+++ b/drivers/ptp/Kconfig
@@ -0,0 +1,27 @@
+#
+# PTP clock support configuration
+#
+
+menu "PTP clock support"
+
+config PTP_1588_CLOCK
+ tristate "PTP clock support"
+ depends on EXPERIMENTAL
+ depends on PPS
+ help
+ The IEEE 1588 standard defines a method to precisely
+ synchronize distributed clocks over Ethernet networks. The
+ standard defines a Precision Time Protocol (PTP), which can
+ be used to achieve synchronization within a few dozen
+ microseconds. In addition, with the help of special hardware
+ time stamping units, it can be possible to achieve
+ synchronization to within a few hundred nanoseconds.
+
+ This driver adds support for PTP clocks as character
+ devices. If you want to use a PTP clock, then you should
+ also enable at least one clock driver as well.
+
+ To compile this driver as a module, choose M here: the module
+ will be called ptp_clock.
+
+endmenu
diff --git a/drivers/ptp/Makefile b/drivers/ptp/Makefile
new file mode 100644
index 0000000..b86695c
--- /dev/null
+++ b/drivers/ptp/Makefile
@@ -0,0 +1,5 @@
+#
+# Makefile for PTP 1588 clock support.
+#
+
+obj-$(CONFIG_PTP_1588_CLOCK) += ptp_clock.o
diff --git a/drivers/ptp/ptp_clock.c b/drivers/ptp/ptp_clock.c
new file mode 100644
index 0000000..1ebb4b0
--- /dev/null
+++ b/drivers/ptp/ptp_clock.c
@@ -0,0 +1,514 @@
+/*
+ * PTP 1588 clock support
+ *
+ * Partially adapted from the Linux PPS driver.
+ *
+ * Copyright (C) 2010 OMICRON electronics GmbH
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include <linux/bitops.h>
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/poll.h>
+#include <linux/pps_kernel.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#include <linux/ptp_clock_kernel.h>
+#include <linux/ptp_clock.h>
+
+#define PTP_MAX_ALARMS 4
+#define PTP_MAX_CLOCKS BITS_PER_LONG
+#define PTP_MAX_TIMESTAMPS 128
+#define PTP_PPS_DEFAULTS (PPS_CAPTUREASSERT | PPS_OFFSETASSERT)
+#define PTP_PPS_EVENT PPS_CAPTUREASSERT
+#define PTP_PPS_MODE (PTP_PPS_DEFAULTS | PPS_CANWAIT | PPS_TSFMT_TSPEC)
+
+struct alarm {
+ struct pid *pid;
+ int sig;
+};
+
+struct timestamp_event_queue {
+ struct ptp_extts_event buf[PTP_MAX_TIMESTAMPS];
+ int head;
+ int tail;
+ int overflow;
+};
+
+struct ptp_clock {
+ struct list_head list;
+ struct cdev cdev;
+ struct device *dev;
+ struct ptp_clock_info *info;
+ dev_t devid;
+ int index; /* index into clocks.map, also the minor number */
+ int pps_source;
+
+ struct alarm alarm[PTP_MAX_ALARMS];
+ struct mutex alarm_mux; /* one process at a time setting an alarm */
+
+ struct timestamp_event_queue tsevq; /* simple fifo for time stamps */
+ struct mutex tsevq_mux; /* one process at a time reading the fifo */
+ wait_queue_head_t tsev_wq;
+};
+
+/* private globals */
+
+static const struct file_operations ptp_fops;
+static dev_t ptp_devt;
+static struct class *ptp_class;
+
+static struct {
+ struct list_head list;
+ DECLARE_BITMAP(map, PTP_MAX_CLOCKS);
+} clocks;
+static DEFINE_MUTEX(clocks_mux); /* protects 'clocks' */
+
+/* time stamp event queue operations */
+
+static inline int queue_cnt(struct timestamp_event_queue *q)
+{
+ int cnt = q->tail - q->head;
+ return cnt < 0 ? PTP_MAX_TIMESTAMPS + cnt : cnt;
+}
+
+static inline int queue_free(struct timestamp_event_queue *q)
+{
+ return PTP_MAX_TIMESTAMPS - queue_cnt(q) - 1;
+}
+
+static void enqueue_external_timestamp(struct timestamp_event_queue *queue,
+ struct ptp_clock_event *src)
+{
+ struct ptp_extts_event *dst;
+ u32 remainder;
+
+ dst = &queue->buf[queue->tail];
+
+ dst->index = src->index;
+ dst->ts.tv_sec = div_u64_rem(src->timestamp, 1000000000, &remainder);
+ dst->ts.tv_nsec = remainder;
+
+ if (!queue_free(queue))
+ queue->overflow++;
+
+ queue->tail = (queue->tail + 1) % PTP_MAX_TIMESTAMPS;
+}
+
+/* public interface */
+
+struct ptp_clock *ptp_clock_register(struct ptp_clock_info *info)
+{
+ struct ptp_clock *ptp;
+ int err = 0, index, major = MAJOR(ptp_devt);
+
+ if (info->n_alarm > PTP_MAX_ALARMS)
+ return ERR_PTR(-EINVAL);
+
+ /* Find a free clock slot and reserve it. */
+ err = -EBUSY;
+ mutex_lock(&clocks_mux);
+ index = find_first_zero_bit(clocks.map, PTP_MAX_CLOCKS);
+ if (index < PTP_MAX_CLOCKS)
+ set_bit(index, clocks.map);
+ else
+ goto no_clock;
+
+ /* Initialize a clock structure. */
+ err = -ENOMEM;
+ ptp = kzalloc(sizeof(struct ptp_clock), GFP_KERNEL);
+ if (ptp == NULL)
+ goto no_memory;
+
+ ptp->info = info;
+ ptp->devid = MKDEV(major, index);
+ ptp->index = index;
+ mutex_init(&ptp->alarm_mux);
+ mutex_init(&ptp->tsevq_mux);
+ init_waitqueue_head(&ptp->tsev_wq);
+
+ /* Create a new device in our class. */
+ ptp->dev = device_create(ptp_class, NULL, ptp->devid, ptp,
+ "ptp_clock_%d", ptp->index);
+ if (IS_ERR(ptp->dev))
+ goto no_device;
+
+ dev_set_drvdata(ptp->dev, ptp);
+
+ /* Register a character device. */
+ cdev_init(&ptp->cdev, &ptp_fops);
+ ptp->cdev.owner = info->owner;
+ err = cdev_add(&ptp->cdev, ptp->devid, 1);
+ if (err)
+ goto no_cdev;
+
+ /* Register a new PPS source. */
+ if (info->pps) {
+ struct pps_source_info pps;
+ memset(&pps, 0, sizeof(pps));
+ snprintf(pps.name, PPS_MAX_NAME_LEN, "ptp%d", index);
+ pps.mode = PTP_PPS_MODE;
+ pps.owner = info->owner;
+ pps.dev = ptp->dev;
+ err = pps_register_source(&pps, PTP_PPS_DEFAULTS);
+ if (err < 0) {
+ pr_err("failed to register pps source\n");
+ goto no_pps;
+ } else
+ ptp->pps_source = err;
+ }
+
+ /* Clock is ready, add it into the list. */
+ list_add(&ptp->list, &clocks.list);
+
+ mutex_unlock(&clocks_mux);
+ return ptp;
+
+no_pps:
+no_cdev:
+ device_destroy(ptp_class, ptp->devid);
+no_device:
+ mutex_destroy(&ptp->alarm_mux);
+ mutex_destroy(&ptp->tsevq_mux);
+ kfree(ptp);
+no_memory:
+ clear_bit(index, clocks.map);
+no_clock:
+ mutex_unlock(&clocks_mux);
+ return ERR_PTR(err);
+}
+EXPORT_SYMBOL(ptp_clock_register);
+
+int ptp_clock_unregister(struct ptp_clock *ptp)
+{
+ /* Release the clock's resources. */
+ if (ptp->info->pps)
+ pps_unregister_source(ptp->pps_source);
+ cdev_del(&ptp->cdev);
+ device_destroy(ptp_class, ptp->devid);
+ mutex_destroy(&ptp->alarm_mux);
+ mutex_destroy(&ptp->tsevq_mux);
+
+ /* Remove the clock from the list. */
+ mutex_lock(&clocks_mux);
+ list_del(&ptp->list);
+ clear_bit(ptp->index, clocks.map);
+ mutex_unlock(&clocks_mux);
+
+ kfree(ptp);
+
+ return 0;
+}
+EXPORT_SYMBOL(ptp_clock_unregister);
+
+void ptp_clock_event(struct ptp_clock *ptp, struct ptp_clock_event *event)
+{
+ struct timespec ts;
+ struct pps_ktime pps_ts;
+
+ switch (event->type) {
+
+ case PTP_CLOCK_ALARM:
+ kill_pid(ptp->alarm[event->index].pid,
+ ptp->alarm[event->index].sig, 1);
+ break;
+
+ case PTP_CLOCK_EXTTS:
+ enqueue_external_timestamp(&ptp->tsevq, event);
+ wake_up_interruptible(&ptp->tsev_wq);
+ break;
+
+ case PTP_CLOCK_PPS:
+ getnstimeofday(&ts);
+ pps_ts.sec = ts.tv_sec;
+ pps_ts.nsec = ts.tv_nsec;
+ pps_event(ptp->pps_source, &pps_ts, PTP_PPS_EVENT, NULL);
+ break;
+ }
+}
+EXPORT_SYMBOL(ptp_clock_event);
+
+/* character device operations */
+
+static int ptp_ioctl(struct inode *node, struct file *fp,
+ unsigned int cmd, unsigned long arg)
+{
+ struct ptp_clock_caps caps;
+ struct ptp_clock_request req;
+ struct ptp_clock_timer timer;
+ struct ptp_clock *ptp = fp->private_data;
+ struct ptp_clock_info *ops = ptp->info;
+ void *priv = ops->priv;
+ struct timespec ts;
+ int flags, index;
+ int err = 0;
+ s32 ppb;
+
+ switch (cmd) {
+
+ case PTP_CLOCK_APIVERS:
+ err = put_user(PTP_CLOCK_VERSION, (u32 __user *)arg);
+ break;
+
+ case PTP_CLOCK_ADJFREQ:
+ if (!capable(CAP_SYS_TIME))
+ return -EPERM;
+ ppb = arg;
+ if (ppb > ops->max_adj || ppb < -ops->max_adj)
+ return -EINVAL;
+ err = ops->adjfreq(priv, ppb);
+ break;
+
+ case PTP_CLOCK_ADJTIME:
+ if (!capable(CAP_SYS_TIME))
+ return -EPERM;
+ if (copy_from_user(&ts, (void __user *)arg, sizeof(ts)))
+ err = -EFAULT;
+ else
+ err = ops->adjtime(priv, &ts);
+ break;
+
+ case PTP_CLOCK_GETTIME:
+ err = ops->gettime(priv, &ts);
+ if (err)
+ break;
+ err = copy_to_user((void __user *)arg, &ts, sizeof(ts));
+ break;
+
+ case PTP_CLOCK_SETTIME:
+ if (!capable(CAP_SYS_TIME))
+ return -EPERM;
+ if (copy_from_user(&ts, (void __user *)arg, sizeof(ts)))
+ err = -EFAULT;
+ else
+ err = ops->settime(priv, &ts);
+ break;
+
+ case PTP_CLOCK_GETCAPS:
+ memset(&caps, 0, sizeof(caps));
+ caps.max_adj = ptp->info->max_adj;
+ caps.n_alarm = ptp->info->n_alarm;
+ caps.n_ext_ts = ptp->info->n_ext_ts;
+ caps.n_per_out = ptp->info->n_per_out;
+ caps.pps = ptp->info->pps;
+ err = copy_to_user((void __user *)arg, &caps, sizeof(caps));
+ break;
+
+ case PTP_CLOCK_GETTIMER:
+ if (copy_from_user(&timer, (void __user *)arg, sizeof(timer))) {
+ err = -EFAULT;
+ break;
+ }
+ index = timer.alarm_index;
+ if (index < 0 || index >= ptp->info->n_alarm) {
+ err = -EINVAL;
+ break;
+ }
+ err = ops->gettimer(priv, index, &timer.tsp);
+ if (err)
+ break;
+ err = copy_to_user((void __user *)arg, &timer, sizeof(timer));
+ break;
+
+ case PTP_CLOCK_SETTIMER:
+ if (copy_from_user(&timer, (void __user *)arg, sizeof(timer))) {
+ err = -EFAULT;
+ break;
+ }
+ index = timer.alarm_index;
+ if (index < 0 || index >= ptp->info->n_alarm) {
+ err = -EINVAL;
+ break;
+ }
+ if (!valid_signal(timer.signum))
+ return -EINVAL;
+ flags = timer.flags;
+ if (flags & (flags != TIMER_ABSTIME)) {
+ err = -EINVAL;
+ break;
+ }
+ if (mutex_lock_interruptible(&ptp->alarm_mux))
+ return -ERESTARTSYS;
+
+ if (ptp->alarm[index].pid)
+ put_pid(ptp->alarm[index].pid);
+
+ ptp->alarm[index].pid = get_pid(task_pid(current));
+ ptp->alarm[index].sig = timer.signum;
+ err = ops->settimer(priv, index, flags, &timer.tsp);
+
+ mutex_unlock(&ptp->alarm_mux);
+ break;
+
+ case PTP_FEATURE_REQUEST:
+ if (copy_from_user(&req, (void __user *)arg, sizeof(req))) {
+ err = -EFAULT;
+ break;
+ }
+ switch (req.type) {
+ case PTP_REQUEST_EXTTS:
+ case PTP_REQUEST_PEROUT:
+ break;
+ case PTP_REQUEST_PPS:
+ if (!capable(CAP_SYS_TIME))
+ return -EPERM;
+ break;
+ default:
+ err = -EINVAL;
+ break;
+ }
+ if (err)
+ break;
+ err = ops->enable(priv, &req,
+ req.flags & PTP_ENABLE_FEATURE ? 1 : 0);
+ break;
+
+ default:
+ err = -ENOTTY;
+ break;
+ }
+ return err;
+}
+
+static int ptp_open(struct inode *inode, struct file *fp)
+{
+ struct ptp_clock *ptp;
+ ptp = container_of(inode->i_cdev, struct ptp_clock, cdev);
+
+ fp->private_data = ptp;
+
+ return 0;
+}
+
+static unsigned int ptp_poll(struct file *fp, poll_table *wait)
+{
+ struct ptp_clock *ptp = fp->private_data;
+
+ poll_wait(fp, &ptp->tsev_wq, wait);
+
+ return queue_cnt(&ptp->tsevq) ? POLLIN : 0;
+}
+
+static ssize_t ptp_read(struct file *fp, char __user *buf,
+ size_t cnt, loff_t *off)
+{
+ struct ptp_clock *ptp = fp->private_data;
+ struct timestamp_event_queue *queue = &ptp->tsevq;
+ struct ptp_extts_event *event;
+ size_t qcnt;
+
+ if (mutex_lock_interruptible(&ptp->tsevq_mux))
+ return -ERESTARTSYS;
+
+ cnt = cnt / sizeof(struct ptp_extts_event);
+
+ if (wait_event_interruptible(ptp->tsev_wq,
+ (qcnt = queue_cnt(&ptp->tsevq)))) {
+ mutex_unlock(&ptp->tsevq_mux);
+ return -ERESTARTSYS;
+ }
+
+ if (cnt > qcnt)
+ cnt = qcnt;
+
+ event = &queue->buf[queue->head];
+
+ if (copy_to_user(buf, event, cnt * sizeof(struct ptp_extts_event))) {
+ mutex_unlock(&ptp->tsevq_mux);
+ return -EFAULT;
+ }
+ queue->head = (queue->head + cnt) % PTP_MAX_TIMESTAMPS;
+
+ mutex_unlock(&ptp->tsevq_mux);
+
+ return cnt * sizeof(struct ptp_extts_event);
+}
+
+static int ptp_release(struct inode *inode, struct file *fp)
+{
+ struct ptp_clock *ptp;
+ struct itimerspec ts = {
+ {0, 0}, {0, 0}
+ };
+ int i;
+
+ ptp = container_of(inode->i_cdev, struct ptp_clock, cdev);
+
+ for (i = 0; i < ptp->info->n_alarm; i++) {
+ if (ptp->alarm[i].pid) {
+ ptp->info->settimer(ptp->info->priv, i, 0, &ts);
+ put_pid(ptp->alarm[i].pid);
+ ptp->alarm[i].pid = NULL;
+ }
+ }
+ return 0;
+}
+
+static const struct file_operations ptp_fops = {
+ .owner = THIS_MODULE,
+ .ioctl = ptp_ioctl,
+ .open = ptp_open,
+ .poll = ptp_poll,
+ .read = ptp_read,
+ .release = ptp_release,
+};
+
+/* module operations */
+
+static void __exit ptp_exit(void)
+{
+ class_destroy(ptp_class);
+ unregister_chrdev_region(ptp_devt, PTP_MAX_CLOCKS);
+}
+
+static int __init ptp_init(void)
+{
+ int err;
+
+ INIT_LIST_HEAD(&clocks.list);
+
+ ptp_class = class_create(THIS_MODULE, "ptp");
+ if (!ptp_class) {
+ printk(KERN_ERR "ptp: failed to allocate class\n");
+ return -ENOMEM;
+ }
+
+ err = alloc_chrdev_region(&ptp_devt, 0, PTP_MAX_CLOCKS, "ptp");
+ if (err < 0) {
+ printk(KERN_ERR "ptp: failed to allocate char device region\n");
+ goto no_region;
+ }
+
+ pr_info("PTP clock support registered\n");
+ return 0;
+
+no_region:
+ class_destroy(ptp_class);
+ return err;
+}
+
+subsys_initcall(ptp_init);
+module_exit(ptp_exit);
+
+MODULE_AUTHOR("Richard Cochran <richard.cochran@omicron.at>");
+MODULE_DESCRIPTION("PTP clocks support");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/Kbuild b/include/linux/Kbuild
index 9aa9bca..471205a 100644
--- a/include/linux/Kbuild
+++ b/include/linux/Kbuild
@@ -140,6 +140,7 @@ header-y += pkt_sched.h
header-y += posix_types.h
header-y += ppdev.h
header-y += prctl.h
+header-y += ptp_clock.h
header-y += qnxtypes.h
header-y += qnx4_fs.h
header-y += radeonfb.h
diff --git a/include/linux/ptp_clock.h b/include/linux/ptp_clock.h
new file mode 100644
index 0000000..5a509c5
--- /dev/null
+++ b/include/linux/ptp_clock.h
@@ -0,0 +1,79 @@
+/*
+ * PTP 1588 clock support - user space interface
+ *
+ * Copyright (C) 2010 OMICRON electronics GmbH
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef _PTP_CLOCK_H_
+#define _PTP_CLOCK_H_
+
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+#define PTP_ENABLE_FEATURE (1<<0)
+#define PTP_RISING_EDGE (1<<1)
+#define PTP_FALLING_EDGE (1<<2)
+
+enum ptp_request_types {
+ PTP_REQUEST_EXTTS,
+ PTP_REQUEST_PEROUT,
+ PTP_REQUEST_PPS,
+};
+
+struct ptp_clock_caps {
+ __s32 max_adj; /* Maximum frequency adjustment, parts per billon. */
+ int n_alarm; /* Number of programmable alarms. */
+ int n_ext_ts; /* Number of external time stamp channels. */
+ int n_per_out; /* Number of programmable periodic signals. */
+ int pps; /* Whether the clock supports a PPS callback. */
+};
+
+struct ptp_clock_timer {
+ int alarm_index; /* Which alarm to query or configure. */
+ int signum; /* Requested signal. */
+ int flags; /* Zero or TIMER_ABSTIME, see TIMER_SETTIME(2) */
+ struct itimerspec tsp; /* See TIMER_SETTIME(2) */
+};
+
+struct ptp_clock_request {
+ int type; /* One of the ptp_request_types enumeration values. */
+ int index; /* Which channel to configure. */
+ struct timespec ts; /* For period signals, the desired period. */
+ int flags; /* Bit field for PTP_ENABLE_FEATURE or other flags. */
+};
+
+struct ptp_extts_event {
+ int index;
+ struct timespec ts;
+};
+
+#define PTP_CLOCK_VERSION 0x00000001
+
+#define PTP_CLK_MAGIC '='
+
+#define PTP_CLOCK_APIVERS _IOR (PTP_CLK_MAGIC, 1, __u32)
+#define PTP_CLOCK_ADJFREQ _IO (PTP_CLK_MAGIC, 2)
+#define PTP_CLOCK_ADJTIME _IOW (PTP_CLK_MAGIC, 3, struct timespec)
+#define PTP_CLOCK_GETTIME _IOR (PTP_CLK_MAGIC, 4, struct timespec)
+#define PTP_CLOCK_SETTIME _IOW (PTP_CLK_MAGIC, 5, struct timespec)
+
+#define PTP_CLOCK_GETCAPS _IOR (PTP_CLK_MAGIC, 6, struct ptp_clock_caps)
+#define PTP_CLOCK_GETTIMER _IOWR (PTP_CLK_MAGIC, 7, struct ptp_clock_timer)
+#define PTP_CLOCK_SETTIMER _IOW (PTP_CLK_MAGIC, 8, struct ptp_clock_timer)
+#define PTP_FEATURE_REQUEST _IOW (PTP_CLK_MAGIC, 9, struct ptp_clock_request)
+
+#endif
diff --git a/include/linux/ptp_clock_kernel.h b/include/linux/ptp_clock_kernel.h
new file mode 100644
index 0000000..d6cc158
--- /dev/null
+++ b/include/linux/ptp_clock_kernel.h
@@ -0,0 +1,137 @@
+/*
+ * PTP 1588 clock support
+ *
+ * Copyright (C) 2010 OMICRON electronics GmbH
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef _PTP_CLOCK_KERNEL_H_
+#define _PTP_CLOCK_KERNEL_H_
+
+#include <linux/ptp_clock.h>
+
+/**
+ * struct ptp_clock_info - decribes a PTP hardware clock
+ *
+ * @owner: The clock driver should set to THIS_MODULE.
+ * @name: A short name to identify the clock.
+ * @max_adj: The maximum possible frequency adjustment, in parts per billon.
+ * @n_alarm: The number of programmable alarms.
+ * @n_ext_ts: The number of external time stamp channels.
+ * @n_per_out: The number of programmable periodic signals.
+ * @pps: Indicates whether the clock supports a PPS callback.
+ * @priv: Passed to the clock operations, for the driver's private use.
+ *
+ * clock operations
+ *
+ * @adjfreq: Adjusts the frequency of the hardware clock.
+ * parameter delta: Desired period change in parts per billion.
+ *
+ * @adjtime: Shifts the time of the hardware clock.
+ * parameter ts: Desired change in seconds and nanoseconds.
+ *
+ * @gettime: Reads the current time from the hardware clock.
+ * parameter ts: Holds the result.
+ *
+ * @settime: Set the current time on the hardware clock.
+ * parameter ts: Time value to set.
+ *
+ * @gettimer: Reads the time remaining from the given timer.
+ * parameter index: Which alarm to query.
+ * parameter ts: Holds the result.
+ *
+ * @settimer: Arms the given timer for periodic or one shot operation.
+ * parameter index: Which alarm to set.
+ * parameter abs: TIMER_ABSTIME, or zero for relative timer.
+ * parameter ts: Alarm time and period to set.
+ *
+ * @enable: Request driver to enable or disable an ancillary feature.
+ * parameter request: Desired resource to enable or disable.
+ * parameter on: Caller passes one to enable or zero to disable.
+ *
+ * The callbacks must all return zero on success, non-zero otherwise.
+ */
+
+struct ptp_clock_info {
+ struct module *owner;
+ char name[16];
+ s32 max_adj;
+ int n_alarm;
+ int n_ext_ts;
+ int n_per_out;
+ int pps;
+ void *priv;
+ int (*adjfreq)(void *priv, s32 delta);
+ int (*adjtime)(void *priv, struct timespec *ts);
+ int (*gettime)(void *priv, struct timespec *ts);
+ int (*settime)(void *priv, struct timespec *ts);
+ int (*gettimer)(void *priv, int index, struct itimerspec *ts);
+ int (*settimer)(void *priv, int index, int abs, struct itimerspec *ts);
+ int (*enable)(void *priv, struct ptp_clock_request *request, int on);
+};
+
+struct ptp_clock;
+
+/**
+ * ptp_clock_register() - register a PTP hardware clock driver
+ *
+ * @info: Structure describing the new clock.
+ */
+
+extern struct ptp_clock *ptp_clock_register(struct ptp_clock_info *info);
+
+/**
+ * ptp_clock_unregister() - unregister a PTP hardware clock driver
+ *
+ * @ptp: The clock to remove from service.
+ */
+
+extern int ptp_clock_unregister(struct ptp_clock *ptp);
+
+
+enum ptp_clock_events {
+ PTP_CLOCK_ALARM,
+ PTP_CLOCK_EXTTS,
+ PTP_CLOCK_PPS,
+};
+
+/**
+ * struct ptp_clock_event - decribes a PTP hardware clock event
+ *
+ * @type: One of the ptp_clock_events enumeration values.
+ * @index: Identifies the source of the event.
+ * @timestamp: When the event occured.
+ */
+
+struct ptp_clock_event {
+ int type;
+ int index;
+ u64 timestamp;
+};
+
+/**
+ * ptp_clock_event() - notify the PTP layer about an event
+ *
+ * This function should only be called from interrupt context.
+ *
+ * @ptp: The clock obtained from ptp_clock_register().
+ * @event: Message structure describing the event.
+ */
+
+extern void ptp_clock_event(struct ptp_clock *ptp,
+ struct ptp_clock_event *event);
+
+#endif
--
1.7.0.4
^ permalink raw reply related
* [PATCH v5 0/5] ptp: IEEE 1588 clock support
From: Richard Cochran @ 2010-08-16 11:17 UTC (permalink / raw)
To: netdev-u79uwXL29TY76Z2rM5mHXA
Cc: linuxppc-dev-uLR06cmDAlY/bJ5BZ2RsiQ,
devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
linux-kernel-u79uwXL29TY76Z2rM5mHXA, Rodolfo Giometti,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
Krzysztof Halasa
Now and again there has been talk on the netdev list of adding PTP
support into Linux. One part of the picture is already in place, the
SO_TIMESTAMPING API for hardware time stamping. This patch set offers
the missing second part needed for complete IEEE 1588 support.
* Why all the CCs?
1. IMHO, the patches should go through netdev.
2. A reviewer on netdev said, this should appear on lkml.
3. One driver is for PowerPC, and adds device tree stuff.
4. One driver is for the ARM Xscale IXP465.
* Open Issues:
** DP83640
In order to make this work, one line must be added into the MAC
driver. If you have the DP83640 and want to try the driver, you
need to add this one line to your MAC driver: In the
.ndo_start_xmit function, add skb_tx_timestamp(skb).
** IXP465
I do not know how to correctly choose the timestamp "channel" based
on the port identifier:
+#define PORT2CHANNEL(p) 1
+/*
+ * PHYSICAL_ID(p->id) ?
+ * TODO - Figure out correct mapping.
+ */
Krzysztof, can you help?
* PTP Patch ChangeLog
** v5
*** general
- Added a hook into the PPS subsystem
- Corrected max_adj in all drivers
- Removed unnecessary sysfs stuff
- Replaced spinlock with mutex in class driver
*** gianfar
- Added PPS support
- Changed underscore to minus in device tree bindings
- Use of_iomap instead of ioremap
*** ixp465
- Added an external trigger event
- Corrected in_progress logic
*** phyter
- Added an external trigger event
- Added support for phy status frames
** v4
*** general
- Added a clock driver for the National Semiconductor PHYTER.
- Added a clock driver for the Intel IXP465.
- Made more stylish according to checkstyle.pl.
*** gianfar
- Replace device_type and model with compatible string ("fsl,etsec-ptp")
- Register only one interrupt, since others are superfluous.
- Combine ptp clock instance with private variable structure.
- ISR now returns NONE or HANDLED properly.
- Print error message if something is missing from the device nodes.
** v3
*** general
- Added documentation on writing clock drivers.
- Added the ioctls for the ancillary clock features.
- Changed wrong subsys_initcall() to module_init() in clock drivers.
- Removed the (too coarse) character device mutex.
- Setting the clock now requires CAP_SYS_TIME.
*** gianfar
- Added alarm feature.
- Added device tree node binding description.
- Added fine grain locking of the clock registers.
- Added the external time stamp feature.
- Added white space for better style.
- Coverted base+offset to structure pointers for register access.
- When removing the driver, we now disable all PTP functions.
** v2
- Changed clock list from a static array into a dynamic list. Also,
use a bitmap to manage the clock's minor numbers.
- Replaced character device semaphore with a mutex.
- Drop .ko from module names in Kbuild help.
- Replace deprecated unifdef-y with header-y for user space header file.
- Added links to both of the ptpd patches on sourceforge.
- Gianfar driver now gets parameters from device tree.
- Added API documentation to Documentation/ptp/ptp.txt
Richard Cochran (5):
ptp: Added a brand new class driver for ptp clocks.
ptp: Added a clock that uses the Linux system time.
ptp: Added a clock that uses the eTSEC found on the MPC85xx.
ptp: Added a clock driver for the IXP46x.
ptp: Added a clock driver for the National Semiconductor PHYTER.
Documentation/powerpc/dts-bindings/fsl/tsec.txt | 57 ++
Documentation/ptp/ptp.txt | 95 +++
Documentation/ptp/testptp.c | 306 ++++++++
Documentation/ptp/testptp.mk | 33 +
arch/arm/mach-ixp4xx/include/mach/ixp46x_ts.h | 78 ++
arch/powerpc/boot/dts/mpc8313erdb.dts | 14 +
arch/powerpc/boot/dts/mpc8572ds.dts | 14 +
arch/powerpc/boot/dts/p2020ds.dts | 14 +
arch/powerpc/boot/dts/p2020rdb.dts | 14 +
drivers/Kconfig | 2 +
drivers/Makefile | 1 +
drivers/net/Makefile | 1 +
drivers/net/arm/ixp4xx_eth.c | 191 +++++
drivers/net/gianfar_ptp.c | 527 +++++++++++++
drivers/net/gianfar_ptp_reg.h | 113 +++
drivers/net/phy/Kconfig | 29 +
drivers/net/phy/Makefile | 1 +
drivers/net/phy/dp83640.c | 904 +++++++++++++++++++++++
drivers/net/phy/dp83640_reg.h | 261 +++++++
drivers/ptp/Kconfig | 65 ++
drivers/ptp/Makefile | 7 +
drivers/ptp/ptp_clock.c | 514 +++++++++++++
drivers/ptp/ptp_ixp46x.c | 359 +++++++++
drivers/ptp/ptp_linux.c | 136 ++++
include/linux/Kbuild | 1 +
include/linux/ptp_clock.h | 79 ++
include/linux/ptp_clock_kernel.h | 137 ++++
kernel/time/ntp.c | 2 +
28 files changed, 3955 insertions(+), 0 deletions(-)
create mode 100644 Documentation/ptp/ptp.txt
create mode 100644 Documentation/ptp/testptp.c
create mode 100644 Documentation/ptp/testptp.mk
create mode 100644 arch/arm/mach-ixp4xx/include/mach/ixp46x_ts.h
create mode 100644 drivers/net/gianfar_ptp.c
create mode 100644 drivers/net/gianfar_ptp_reg.h
create mode 100644 drivers/net/phy/dp83640.c
create mode 100644 drivers/net/phy/dp83640_reg.h
create mode 100644 drivers/ptp/Kconfig
create mode 100644 drivers/ptp/Makefile
create mode 100644 drivers/ptp/ptp_clock.c
create mode 100644 drivers/ptp/ptp_ixp46x.c
create mode 100644 drivers/ptp/ptp_linux.c
create mode 100644 include/linux/ptp_clock.h
create mode 100644 include/linux/ptp_clock_kernel.h
^ permalink raw reply
* Re: BUG: IPv6 stops working after a while, needs ip ne del command to reset
From: Thomas Habets @ 2010-08-16 10:59 UTC (permalink / raw)
To: Eric Dumazet; +Cc: linux-kernel, netdev, Thomas Habets
In-Reply-To: <1281953960.2524.23.camel@edumazet-laptop>
On Mon, 16 Aug 2010, Eric Dumazet wrote:
>> $ ip -6 r sh
>> 2a00:800:752:1::5c:0/112 dev eth0 proto kernel metric 256 mtu 1500
>> advmss 14 hoplimit 4294967295 unreachable
>
> advmss 14 ? or is it a copy/paste error ?
> unreachable ?
Copy/paste error, sorry.
advmss 1440, and no "unreachable"
Complete (correct) line:
2a00:800:752:1::5c:0/112 dev eth0 proto kernel metric 256 mtu 1500 advmss
1440 hoplimit 4294967295
> This route seems wrong.
Did you mean the below?
>> 2a00:800:1000:64::1 dev lo proto kernel metric 256 error -101 mtu
>> 16436 advmss 16376 hoplimit 4294967295
Yes, that's where the "unreachable" belonged.
unreachable 2a00:800:1000:64::1 dev lo proto kernel metric 256 error
-101 mtu 1500 advmss 1440 hoplimit 4294967295
That's an additional address on the lo interface, seen here again:
>> 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436
>> inet6 2a00:800:1000:64::1/128 scope global
>> valid_lft forever preferred_lft forever
>> inet6 ::1/128 scope host
>> valid_lft forever preferred_lft forever
Am I not allowed to add addresses to lo? That I've deconfigured this
address and rebooted the box to check if this matters.
> I am wondering if you have some lowlevel problem, say lost frames in an
> otherwise idle link, maybe a full/half duplex mismatch ?
But at first it works perfectly, and then it doesn't work at all. The link
is ~idle both before and after, and IPv4 is unaffected. When I run "ip ne
del ..." it *immediately* starts working again. From 100% packet loss to
0%.
Duplex is full according to dmesg and ethtool (mii-tool says 1000BaseT-HD,
but I suppose mii-tool is not as reliable?).
Cisco router also says "Full-duplex, 1000Mb/s", so there doesn't seem to
be a mismatch. No errors in "show int giX/Y" either.
ethtool info right after reboot (when ipv6 is still working):
$ sudo ethtool eth0
Settings for eth0:
Supported ports: [ TP ]
Supported link modes: 10baseT/Half 10baseT/Full
100baseT/Half 100baseT/Full
1000baseT/Half 1000baseT/Full
Supports auto-negotiation: Yes
Advertised link modes: 10baseT/Half 10baseT/Full
100baseT/Half 100baseT/Full
1000baseT/Half 1000baseT/Full
Advertised auto-negotiation: Yes
Speed: 1000Mb/s
Duplex: Full
Port: Twisted Pair
PHYAD: 1
Transceiver: internal
Auto-negotiation: on
Supports Wake-on: g
Wake-on: g
Current message level: 0x000000ff (255)
Link detected: yes
No errors show in "ethtool -S eth0 | grep -v ': 0$'" now that it's
working.
I will re-check ethtool and Cisco router output for mismatches when it
breaks again to make sure that there's no change or errors counting up.
---------
typedef struct me_s {
char name[] = { "Thomas Habets" };
char email[] = { "thomas@habets.pp.se" };
char kernel[] = { "Linux" };
char *pgpKey[] = { "http://www.habets.pp.se/pubkey.txt" };
char pgp[] = { "A8A3 D1DD 4AE0 8467 7FDE 0945 286A E90A AD48 E854" };
char coolcmd[] = { "echo '. ./_&. ./_'>_;. ./_" };
} me_t;
^ permalink raw reply
* Re: [GIT] Networking
From: Jarek Poplawski @ 2010-08-16 10:48 UTC (permalink / raw)
To: Eric Dumazet
Cc: David Miller, torvalds, akpm, netdev, linux-kernel,
Patrick McHardy
In-Reply-To: <1281955361.2524.35.camel@edumazet-laptop>
On Mon, Aug 16, 2010 at 12:42:41PM +0200, Eric Dumazet wrote:
> Le lundi 16 ao??t 2010 ?? 09:53 +0000, Jarek Poplawski a écrit :
> > Eric Dumazet wrote:
> > > Le dimanche 15 aou^t 2010 a` 12:55 +0200, Eric Dumazet a écrit :
> > ...
> > > [PATCH] netfilter: {ip,ip6,arp}_tables: avoid lockdep false positive
> > >
> > > After commit 24b36f019 (netfilter: {ip,ip6,arp}_tables: dont block
> > > bottom half more than necessary), lockdep can raise a warning
> > > because we attempt to lock a spinlock with BH enabled, while
> > > the same lock is usually locked by another cpu in a softirq context.
> >
> > Btw, could you remind us how get_counters() are serialized (I guess
> > you can't have them on 2 cpus at the same time)?
> >
>
> get_counters() is serialized by the xt_find_table_lock() done from
> get_entries(). This use a mutex to guard against changes.
>
> You are right that if we ever allow two concurrent "iptables -nvL"
> operations in the future (using a read lock on a rwlock instead of a
> mutex), then we must disable BH even for summing data from the other
> cpus.
OK, thanks for the explanation,
Jarek P.
^ permalink raw reply
* Re: [GIT] Networking
From: Eric Dumazet @ 2010-08-16 10:42 UTC (permalink / raw)
To: Jarek Poplawski
Cc: David Miller, torvalds, akpm, netdev, linux-kernel,
Patrick McHardy
In-Reply-To: <20100816095325.GA8547@ff.dom.local>
Le lundi 16 août 2010 à 09:53 +0000, Jarek Poplawski a écrit :
> Eric Dumazet wrote:
> > Le dimanche 15 aou^t 2010 a` 12:55 +0200, Eric Dumazet a écrit :
> ...
> > [PATCH] netfilter: {ip,ip6,arp}_tables: avoid lockdep false positive
> >
> > After commit 24b36f019 (netfilter: {ip,ip6,arp}_tables: dont block
> > bottom half more than necessary), lockdep can raise a warning
> > because we attempt to lock a spinlock with BH enabled, while
> > the same lock is usually locked by another cpu in a softirq context.
>
> Btw, could you remind us how get_counters() are serialized (I guess
> you can't have them on 2 cpus at the same time)?
>
get_counters() is serialized by the xt_find_table_lock() done from
get_entries(). This use a mutex to guard against changes.
You are right that if we ever allow two concurrent "iptables -nvL"
operations in the future (using a read lock on a rwlock instead of a
mutex), then we must disable BH even for summing data from the other
cpus.
Thanks
^ permalink raw reply
* Re: BUG: IPv6 stops working after a while, needs ip ne del command to reset
From: Eric Dumazet @ 2010-08-16 10:19 UTC (permalink / raw)
To: Thomas Habets; +Cc: linux-kernel, netdev
In-Reply-To: <alpine.DEB.1.10.1008131952470.19570@red.crap.retrofitta.se>
Le vendredi 13 août 2010 à 19:55 +0200, Thomas Habets a écrit :
> (originally sent to netdev on aug 6th)
>
CC netdev again
> IPv6 initially works, but when I leave it alone overnight I'm unable to ping
> even my default gw.
>
> Static global IPv6 addresses configured on both ends. No access lists on either
> end.
>
> Kernel version: 2.6.35 mainline (amd64) and 2.6.33.6.
> Kernel config: http://pastebin.com/raw.php?i=Y6S8iKW7
> Dist: Debian Lenny (5.0.5), nothing special to my knowledge.
>
> I seem to have the same issue that Mikael Abrahamsson encountered with Ubuntu
> kernels 2.6.26.3, 2.6.26-5-generic and 2.6.27-2-generic, and mainline kernels
> 2.6.25, 2.6.26 and 2.6.27:
> https://bugs.launchpad.net/ubuntu/+source/linux/+bug/263260
>
> He got IPv6 running again without rebooting using "networking stop, ifconfig
> eth0 down, networking start, kill dhclient", while I narrowed it down to just
> deleting the ipv6 neighbor (ip ne del..., see below). Rebooting also causes it
> to start working again.
>
> It's very reproducible. I just leave it overnight and it breaks every time.
>
> I am willing and able to try patches at any time, the box is not in production.
>
> No iptables, no ip6tables. IP6tables support is not even compiled in.
>
> NIC is "Broadcom Corporation NetXtreme BCM5715 Gigabit ethernet (rev a3)"
> according to lspci.
>
> Other end is a directly connected Cisco 7600 (routed port) that I have access
> to, but it's in production use. IPv4 works perfectly over this same port. Only
> lo and eth0 are UP.
>
>
> Output when broken
> ------------------
> $ uname -a
> Linux XXXXX 2.6.35 #1 SMP Tue Aug 3 09:25:51 CEST 2010 x86_64
> GNU/Linux
>
> $ ip -6 a sh
> 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436
> inet6 2a00:800:1000:64::1/128 scope global
> valid_lft forever preferred_lft forever
> inet6 ::1/128 scope host
> valid_lft forever preferred_lft forever
> 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qlen 1000
> inet6 2a00:800:752:1::5c:2/112 scope global
> valid_lft forever preferred_lft forever
> inet6 fe80::224:81ff:fea3:4424/64 scope link
> valid_lft forever preferred_lft forever
>
> (I have tried removing 2a00:800:1000:64::1/128 from lo, same issue)
>
> $ ip -6 r sh
> 2a00:800:752:1::5c:0/112 dev eth0 proto kernel metric 256 mtu 1500
> advmss 14 hoplimit 4294967295 unreachable
advmss 14 ? or is it a copy/paste error ?
unreachable ?
This route seems wrong.
> 2a00:800:1000:64::1 dev lo proto kernel metric 256 error -101 mtu 16436
> advmss 16376 hoplimit 4294967295
> fe80::/64 dev eth0 proto kernel metric 256 mtu 1500 advmss 1440
> hoplimit 4294967295
> default via 2a00:800:752:1::5c:1 dev eth0 metric 1024 mtu 1500 advmss 1440
> hoplimit 4294967295
>
> $ ping6 2a00:800:752:1::5c:1
> PING 2a00:800:752:1::5c:1(2a00:800:752:1::5c:1) 56 data bytes
> ^C
> --- 2a00:800:752:1::5c:1 ping statistics ---
> 22 packets transmitted, 0 received, 100% packet loss, time 21006ms
>
>
> # Tcpdpump on the problem machine shows mostly the pings, but also periodically
> some ND:
>
> [...]
> 12:54:02.683672 00:24:81:a3:44:24 > 00:22:55:17:4b:80, ethertype IPv6
> (0x86dd), length 118: 2a00:800:752:1::5c:2 > 2a00:800:752:1::5c:1: ICMP6, echo
> request, seq 12, length 64
> 12:54:02.693669 00:24:81:a3:44:24 > 00:22:55:17:4b:80, ethertype IPv6
> (0x86dd), length 86: fe80::224:81ff:fea3:4424 > 2a00:800:752:1::5c:1: ICMP6,
> neighbor solicitation, who has 2a00:800:752:1::5c:1, length 32
Sollicitation comes from fe80::224:81ff:fea3:4424 instead of
2a00:800:752:1::5c:2
> 12:54:02.693832 00:22:55:17:4b:80 > 00:24:81:a3:44:24, ethertype IPv6
> (0x86dd), length 78: 2a00:800:752:1::5c:1 > fe80::224:81ff:fea3:4424: ICMP6,
> neighbor advertisement, tgt is 2a00:800:752:1::5c:1, length 24
> 12:54:03.683672 00:24:81:a3:44:24 > 00:22:55:17:4b:80, ethertype IPv6
> (0x86dd), length 118: 2a00:800:752:1::5c:2 > 2a00:800:752:1::5c:1: ICMP6, echo
> request, seq 13, length 64
> [...]
>
> $ ip -6 ne
> fe80::222:55ff:fe17:4b80 dev eth0 lladdr 00:22:55:17:4b:80 router STALE
> 2a00:800:752:1::5c:1 dev eth0 lladdr 00:22:55:17:4b:80 router STALE
>
>
> Fixing the adjacency
> --------------------
> $ ping6 2a00:800:752:1::5c:1
> PING 2a00:800:752:1::5c:1(2a00:800:752:1::5c:1) 56 data bytes
> ^C
> --- 2a00:800:752:1::5c:1 ping statistics ---
> 51 packets transmitted, 0 received, 100% packet loss, time 50006ms
>
> $ sudo ip ne del 2a00:800:752:1::5c:1 dev eth0
> $ ping6 2a00:800:752:1::5c:1
> PING 2a00:800:752:1::5c:1(2a00:800:752:1::5c:1) 56 data bytes
> 64 bytes from 2a00:800:752:1::5c:1: icmp_seq=1 ttl=64 time=31.9 ms
> 64 bytes from 2a00:800:752:1::5c:1: icmp_seq=2 ttl=64 time=0.212 ms
>
> $ ip -6 ne
> fe80::222:55ff:fe17:4b80 dev eth0 lladdr 00:22:55:17:4b:80 router REACHABLE
> 2a00:800:752:1::5c:1 dev eth0 lladdr 00:22:55:17:4b:80 router REACHABLE
>
> (Note that after a few minutes it goes back to STALE, but pinging still works
> and brings back the state to REACHABLE, so it's not that it can't get out of
> STALE once there, it seems).
>
I am wondering if you have some lowlevel problem, say lost frames in an
otherwise idle link, maybe a full/half duplex mismatch ?
^ permalink raw reply
* Re: [patch] net/sched: remove unneeded NULL check
From: Jarek Poplawski @ 2010-08-16 10:14 UTC (permalink / raw)
To: Dan Carpenter
Cc: Jamal Hadi Salim, David S. Miller, Patrick McHardy, Eric Dumazet,
Stephen Hemminger, netdev, kernel-janitors
In-Reply-To: <20100814210949.GB645@bicker>
Dan Carpenter wrote:
> There is no need to check "s". nla_data() doesn't return NULL. Also we
> already dereferenced "s" at this point so it would have oopsed ealier if
> it were NULL.
Looks OK to me.
Jarek P.
>
> Signed-off-by: Dan Carpenter <error27@gmail.com>
>
> diff --git a/net/sched/sch_api.c b/net/sched/sch_api.c
> index 408eea7..6fb3d41 100644
> --- a/net/sched/sch_api.c
> +++ b/net/sched/sch_api.c
> @@ -360,7 +360,7 @@ static struct qdisc_size_table *qdisc_get_stab(struct nlattr *opt)
> tsize = nla_len(tb[TCA_STAB_DATA]) / sizeof(u16);
> }
>
> - if (!s || tsize != s->tsize || (!tab && tsize > 0))
> + if (tsize != s->tsize || (!tab && tsize > 0))
> return ERR_PTR(-EINVAL);
>
> spin_lock(&qdisc_stab_lock);
> --
^ permalink raw reply
* Re: [PATCH v3] net: add Fast Ethernet driver for PXA168.
From: Sachin Sanap @ 2010-08-16 10:07 UTC (permalink / raw)
To: Philip Rakity
Cc: Lennert Buytenhek, netdev@vger.kernel.org, Ashish Karkare,
Prabhanjan Sarnaik, eric.y.miao@gmail.com, Mark Brown
In-Reply-To: <16B97483-C552-479E-876E-019A4F217F87@marvell.com>
On Sat, 2010-08-14 at 13:47 -0700, Philip Rakity wrote:
>
> I was wondering why you need to take a tx interrupt done per packet. The only normal reason for the interrupt is to restart the higher layer when the tx queue is blocked (FULL) due to flow control or sending traffic too fast. The original code tried to minimize tx interrupts.
Restarting the higher layer is just one of the thing that we do, we also
kfree_skb() after the interrupt and by not generating any interrupt we
are delaying it indefinitely which is wrong.
>
> The other point was the you should set the DMA ownership bit as the LAST operation after deciding if you need to set the TX interrupt bit in the descriptor and not before making the decision.
>
I did not get your point. We are currently setting the DMA ownership at
last only.
> Philip
>
>
> On Aug 14, 2010, at 12:22 AM, Sachin Sanap wrote:
>
> > From df92c2959eb0e50ea63c90822fb9e1edef673ea6 Mon Sep 17 00:00:00 2001
> > From: Sachin Sanap <ssanap@marvell.com>
> > Date: Sat, 14 Aug 2010 17:11:05 +0530
> > Subject: [PATCH] net: add Fast Ethernet driver for PXA168.
> >
> > Signed-off-by: Sachin Sanap <ssanap@marvell.com>
> > ---
> > V3:
> > * TX_DONE interrupt is now generated for every packet.
> > * MTU can now be configured upto 9500 bytes.
> > * TX/RX ring sizes are now platform configurable.
> > * Remove the CPU hogging and yield the CPU while SMI operations take time.
> > * Cleanup the hash table for old MAC address entries in set_rx_mode function.
> >
> > drivers/net/Kconfig | 10 +
> > drivers/net/Makefile | 1 +
> > drivers/net/pxa168_eth.c | 1666 ++++++++++++++++++++++++++++++++++++++++++++
> > include/linux/pxa168_eth.h | 30 +
> > 4 files changed, 1707 insertions(+), 0 deletions(-)
> > create mode 100644 drivers/net/pxa168_eth.c
> > create mode 100644 include/linux/pxa168_eth.h
> >
> > diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
> > index ce2fcdd..78cd7e8 100644
> > --- a/drivers/net/Kconfig
> > +++ b/drivers/net/Kconfig
> > @@ -927,6 +927,16 @@ config SMC91X
> > The module will be called smc91x. If you want to compile it as a
> > module, say M here and read <file:Documentation/kbuild/modules.txt>.
> >
> > +config PXA168_ETH
> > + tristate "Marvell pxa168 ethernet support"
> > + depends on CPU_PXA168
> > + select PHYLIB
> > + help
> > + This driver supports the pxa168 Ethernet ports.
> > +
> > + To compile this driver as a module, choose M here. The module
> > + will be called pxa168_eth.
> > +
> > config NET_NETX
> > tristate "NetX Ethernet support"
> > select MII
> > diff --git a/drivers/net/Makefile b/drivers/net/Makefile
> > index 0a0512a..a42d437 100644
> > --- a/drivers/net/Makefile
> > +++ b/drivers/net/Makefile
> > @@ -244,6 +244,7 @@ obj-$(CONFIG_MYRI10GE) += myri10ge/
> > obj-$(CONFIG_SMC91X) += smc91x.o
> > obj-$(CONFIG_SMC911X) += smc911x.o
> > obj-$(CONFIG_SMSC911X) += smsc911x.o
> > +obj-$(CONFIG_PXA168_ETH) += pxa168_eth.o
> > obj-$(CONFIG_BFIN_MAC) += bfin_mac.o
> > obj-$(CONFIG_DM9000) += dm9000.o
> > obj-$(CONFIG_PASEMI_MAC) += pasemi_mac_driver.o
> > diff --git a/drivers/net/pxa168_eth.c b/drivers/net/pxa168_eth.c
> > new file mode 100644
> > index 0000000..ecc64d7
> > --- /dev/null
> > +++ b/drivers/net/pxa168_eth.c
> > @@ -0,0 +1,1666 @@
> > +/*
> > + * PXA168 ethernet driver.
> > + * Most of the code is derived from mv643xx ethernet driver.
> > + *
> > + * Copyright (C) 2010 Marvell International Ltd.
> > + * Sachin Sanap <ssanap@marvell.com>
> > + * Philip Rakity <prakity@marvell.com>
> > + * Mark Brown <markb@marvell.com>
> > + *
> > + * 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.
> > + *
> > + * This program is distributed in the hope that it will be useful,
> > + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> > + * GNU General Public License for more details.
> > + *
> > + * You should have received a copy of the GNU General Public License
> > + * along with this program; if not, write to the Free Software
> > + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
> > + */
> > +
> > +#include <linux/init.h>
> > +#include <linux/dma-mapping.h>
> > +#include <linux/in.h>
> > +#include <linux/ip.h>
> > +#include <linux/tcp.h>
> > +#include <linux/udp.h>
> > +#include <linux/etherdevice.h>
> > +#include <linux/bitops.h>
> > +#include <linux/delay.h>
> > +#include <linux/ethtool.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/module.h>
> > +#include <linux/kernel.h>
> > +#include <linux/workqueue.h>
> > +#include <linux/clk.h>
> > +#include <linux/phy.h>
> > +#include <linux/io.h>
> > +#include <linux/types.h>
> > +#include <asm/pgtable.h>
> > +#include <asm/system.h>
> > +#include <linux/delay.h>
> > +#include <linux/dma-mapping.h>
> > +#include <asm/cacheflush.h>
> > +#include <linux/pxa168_eth.h>
> > +
> > +#define DRIVER_NAME "pxa168-eth"
> > +#define DRIVER_VERSION "0.3"
> > +
> > +/*
> > + * Registers
> > + */
> > +
> > +#define PHY_ADDRESS 0x0000
> > +#define SMI 0x0010
> > +#define PORT_CONFIG 0x0400
> > +#define PORT_CONFIG_EXT 0x0408
> > +#define PORT_COMMAND 0x0410
> > +#define PORT_STATUS 0x0418
> > +#define HTPR 0x0428
> > +#define SDMA_CONFIG 0x0440
> > +#define SDMA_CMD 0x0448
> > +#define INT_CAUSE 0x0450
> > +#define INT_W_CLEAR 0x0454
> > +#define INT_MASK 0x0458
> > +#define ETH_F_RX_DESC_0 0x0480
> > +#define ETH_C_RX_DESC_0 0x04A0
> > +#define ETH_C_TX_DESC_1 0x04E4
> > +
> > +/* smi register */
> > +#define SMI_BUSY (1 << 28) /* 0 - Write, 1 - Read */
> > +#define SMI_R_VALID (1 << 27) /* 0 - Write, 1 - Read */
> > +#define SMI_OP_W (0 << 26) /* Write operation */
> > +#define SMI_OP_R (1 << 26) /* Read operation */
> > +
> > +#define PHY_WAIT_ITERATIONS 10
> > +
> > +#define PXA168_ETH_PHY_ADDR_DEFAULT 0
> > +/* RX & TX descriptor command */
> > +#define BUF_OWNED_BY_DMA (1 << 31)
> > +
> > +/* RX descriptor status */
> > +#define RX_EN_INT (1 << 23)
> > +#define RX_FIRST_DESC (1 << 17)
> > +#define RX_LAST_DESC (1 << 16)
> > +#define RX_ERROR (1 << 15)
> > +
> > +/* TX descriptor command */
> > +#define TX_EN_INT (1 << 23)
> > +#define TX_GEN_CRC (1 << 22)
> > +#define TX_ZERO_PADDING (1 << 18)
> > +#define TX_FIRST_DESC (1 << 17)
> > +#define TX_LAST_DESC (1 << 16)
> > +#define TX_ERROR (1 << 15)
> > +
> > +/* SDMA_CMD */
> > +#define SDMA_CMD_AT (1 << 31)
> > +#define SDMA_CMD_TXDL (1 << 24)
> > +#define SDMA_CMD_TXDH (1 << 23)
> > +#define SDMA_CMD_AR (1 << 15)
> > +#define SDMA_CMD_ERD (1 << 7)
> > +
> > +/* Bit definitions of the Port Config Reg */
> > +#define PCR_HS (1 << 12)
> > +#define PCR_EN (1 << 7)
> > +#define PCR_PM (1 << 0)
> > +
> > +/* Bit definitions of the Port Config Extend Reg */
> > +#define PCXR_2BSM (1 << 28)
> > +#define PCXR_DSCP_EN (1 << 21)
> > +#define PCXR_MFL_1518 (0 << 14)
> > +#define PCXR_MFL_1536 (1 << 14)
> > +#define PCXR_MFL_2048 (2 << 14)
> > +#define PCXR_MFL_64K (3 << 14)
> > +#define PCXR_FLP (1 << 11)
> > +#define PCXR_PRIO_TX_OFF 3
> > +#define PCXR_TX_HIGH_PRI (7 << PCXR_PRIO_TX_OFF)
> > +
> > +/* Bit definitions of the SDMA Config Reg */
> > +#define SDCR_BSZ_OFF 12
> > +#define SDCR_BSZ8 (3 << SDCR_BSZ_OFF)
> > +#define SDCR_BSZ4 (2 << SDCR_BSZ_OFF)
> > +#define SDCR_BSZ2 (1 << SDCR_BSZ_OFF)
> > +#define SDCR_BSZ1 (0 << SDCR_BSZ_OFF)
> > +#define SDCR_BLMR (1 << 6)
> > +#define SDCR_BLMT (1 << 7)
> > +#define SDCR_RIFB (1 << 9)
> > +#define SDCR_RC_OFF 2
> > +#define SDCR_RC_MAX_RETRANS (0xf << SDCR_RC_OFF)
> > +
> > +/*
> > + * Bit definitions of the Interrupt Cause Reg
> > + * and Interrupt MASK Reg is the same
> > + */
> > +#define ICR_RXBUF (1 << 0)
> > +#define ICR_TXBUF_H (1 << 2)
> > +#define ICR_TXBUF_L (1 << 3)
> > +#define ICR_TXEND_H (1 << 6)
> > +#define ICR_TXEND_L (1 << 7)
> > +#define ICR_RXERR (1 << 8)
> > +#define ICR_TXERR_H (1 << 10)
> > +#define ICR_TXERR_L (1 << 11)
> > +#define ICR_TX_UDR (1 << 13)
> > +#define ICR_MII_CH (1 << 28)
> > +
> > +#define ALL_INTS (ICR_TXBUF_H | ICR_TXBUF_L | ICR_TX_UDR |\
> > + ICR_TXERR_H | ICR_TXERR_L |\
> > + ICR_TXEND_H | ICR_TXEND_L |\
> > + ICR_RXBUF | ICR_RXERR | ICR_MII_CH)
> > +
> > +#define ETH_HW_IP_ALIGN 2 /* hw aligns IP header */
> > +
> > +#define NUM_RX_DESCS 64
> > +#define NUM_TX_DESCS 64
> > +
> > +#define HASH_ADD 0
> > +#define HASH_DELETE 1
> > +#define HASH_ADDR_TABLE_SIZE 0x4000 /* 16K (1/2K address - PCR_HS == 1) */
> > +#define HOP_NUMBER 12
> > +
> > +/* Bit definitions for Port status */
> > +#define PORT_SPEED_100 (1 << 0)
> > +#define FULL_DUPLEX (1 << 1)
> > +#define FLOW_CONTROL_ENABLED (1 << 2)
> > +#define LINK_UP (1 << 3)
> > +
> > +/* Bit definitions for work to be done */
> > +#define WORK_LINK (1 << 0)
> > +#define WORK_TX_DONE (1 << 1)
> > +
> > +/*
> > + * Misc definitions.
> > + */
> > +#define SKB_DMA_REALIGN ((PAGE_SIZE - NET_SKB_PAD) % SMP_CACHE_BYTES)
> > +
> > +struct rx_desc {
> > + u32 cmd_sts; /* Descriptor command status */
> > + u16 byte_cnt; /* Descriptor buffer byte count */
> > + u16 buf_size; /* Buffer size */
> > + u32 buf_ptr; /* Descriptor buffer pointer */
> > + u32 next_desc_ptr; /* Next descriptor pointer */
> > +};
> > +
> > +struct tx_desc {
> > + u32 cmd_sts; /* Command/status field */
> > + u16 reserved;
> > + u16 byte_cnt; /* buffer byte count */
> > + u32 buf_ptr; /* pointer to buffer for this descriptor */
> > + u32 next_desc_ptr; /* Pointer to next descriptor */
> > +};
> > +
> > +struct pxa168_eth_private {
> > + int port_num; /* User Ethernet port number */
> > +
> > + int rx_resource_err; /* Rx ring resource error flag */
> > +
> > + /* Next available and first returning Rx resource */
> > + int rx_curr_desc_q, rx_used_desc_q;
> > +
> > + /* Next available and first returning Tx resource */
> > + int tx_curr_desc_q, tx_used_desc_q;
> > +
> > + struct rx_desc *p_rx_desc_area;
> > + dma_addr_t rx_desc_dma;
> > + int rx_desc_area_size;
> > + struct sk_buff **rx_skb;
> > +
> > + struct tx_desc *p_tx_desc_area;
> > + dma_addr_t tx_desc_dma;
> > + int tx_desc_area_size;
> > + struct sk_buff **tx_skb;
> > +
> > + struct work_struct tx_timeout_task;
> > +
> > + struct net_device *dev;
> > + struct napi_struct napi;
> > + u8 work_todo;
> > + int skb_size;
> > +
> > + struct net_device_stats stats;
> > + /* Size of Tx Ring per queue */
> > + int tx_ring_size;
> > + /* Number of tx descriptors in use */
> > + int tx_desc_count;
> > + /* Size of Rx Ring per queue */
> > + int rx_ring_size;
> > + /* Number of rx descriptors in use */
> > + int rx_desc_count;
> > +
> > + /*
> > + * Used in case RX Ring is empty, which can occur when
> > + * system does not have resources (skb's)
> > + */
> > + struct timer_list timeout;
> > + struct mii_bus *smi_bus;
> > + struct phy_device *phy;
> > +
> > + /* clock */
> > + struct clk *clk;
> > + struct pxa168_eth_platform_data *pd;
> > + /*
> > + * Ethernet controller base address.
> > + */
> > + void __iomem *base;
> > +
> > + /* Pointer to the hardware address filter table */
> > + void *htpr;
> > + dma_addr_t htpr_dma;
> > +};
> > +
> > +struct addr_table_entry {
> > + __le32 lo;
> > + __le32 hi;
> > +};
> > +
> > +/* Bit fields of a Hash Table Entry */
> > +enum hash_table_entry {
> > + HASH_ENTRY_VALID = 1,
> > + SKIP = 2,
> > + HASH_ENTRY_RECEIVE_DISCARD = 4,
> > + HASH_ENTRY_RECEIVE_DISCARD_BIT = 2
> > +};
> > +
> > +static int pxa168_get_settings(struct net_device *dev, struct ethtool_cmd *cmd);
> > +static int pxa168_set_settings(struct net_device *dev, struct ethtool_cmd *cmd);
> > +static int pxa168_init_hw(struct pxa168_eth_private *pep);
> > +static void eth_port_reset(struct net_device *dev);
> > +static void eth_port_start(struct net_device *dev);
> > +static int pxa168_eth_open(struct net_device *dev);
> > +static int pxa168_eth_stop(struct net_device *dev);
> > +static int ethernet_phy_setup(struct net_device *dev);
> > +
> > +static inline u32 rdl(struct pxa168_eth_private *pep, int offset)
> > +{
> > + return readl(pep->base + offset);
> > +}
> > +
> > +static inline void wrl(struct pxa168_eth_private *pep, int offset, u32 data)
> > +{
> > + writel(data, pep->base + offset);
> > +}
> > +
> > +static void abort_dma(struct pxa168_eth_private *pep)
> > +{
> > + int delay;
> > + int max_retries = 40;
> > +
> > + do {
> > + wrl(pep, SDMA_CMD, SDMA_CMD_AR | SDMA_CMD_AT);
> > + udelay(100);
> > +
> > + delay = 10;
> > + while ((rdl(pep, SDMA_CMD) & (SDMA_CMD_AR | SDMA_CMD_AT))
> > + && delay-- > 0) {
> > + udelay(10);
> > + }
> > + } while (max_retries-- > 0 && delay <= 0);
> > +
> > + if (max_retries <= 0)
> > + printk(KERN_ERR "%s : DMA Stuck\n", __func__);
> > +}
> > +
> > +static int ethernet_phy_get(struct pxa168_eth_private *pep)
> > +{
> > + unsigned int reg_data;
> > +
> > + reg_data = rdl(pep, PHY_ADDRESS);
> > +
> > + return (reg_data >> (5 * pep->port_num)) & 0x1f;
> > +}
> > +
> > +static void ethernet_phy_set_addr(struct pxa168_eth_private *pep, int phy_addr)
> > +{
> > + u32 reg_data;
> > + int addr_shift = 5 * pep->port_num;
> > +
> > + reg_data = rdl(pep, PHY_ADDRESS);
> > + reg_data &= ~(0x1f << addr_shift);
> > + reg_data |= (phy_addr & 0x1f) << addr_shift;
> > + wrl(pep, PHY_ADDRESS, reg_data);
> > +}
> > +
> > +static void ethernet_phy_reset(struct pxa168_eth_private *pep)
> > +{
> > + int data;
> > +
> > + data = phy_read(pep->phy, MII_BMCR);
> > + if (data < 0)
> > + return;
> > +
> > + data |= BMCR_RESET;
> > + if (phy_write(pep->phy, MII_BMCR, data) < 0)
> > + return;
> > +
> > + do {
> > + data = phy_read(pep->phy, MII_BMCR);
> > + } while (data >= 0 && data & BMCR_RESET);
> > +}
> > +
> > +static void rxq_refill(struct net_device *dev)
> > +{
> > + struct pxa168_eth_private *pep = netdev_priv(dev);
> > + struct sk_buff *skb;
> > + struct rx_desc *p_used_rx_desc;
> > + int used_rx_desc;
> > +
> > + while (pep->rx_desc_count < pep->rx_ring_size) {
> > + int size;
> > +
> > + skb = dev_alloc_skb(pep->skb_size);
> > + if (!skb)
> > + break;
> > + if (SKB_DMA_REALIGN)
> > + skb_reserve(skb, SKB_DMA_REALIGN);
> > + pep->rx_desc_count++;
> > + /* Get 'used' Rx descriptor */
> > + used_rx_desc = pep->rx_used_desc_q;
> > + p_used_rx_desc = &pep->p_rx_desc_area[used_rx_desc];
> > + size = skb->end - skb->data;
> > + p_used_rx_desc->buf_ptr = dma_map_single(NULL,
> > + skb->data,
> > + size,
> > + DMA_FROM_DEVICE);
> > + p_used_rx_desc->buf_size = size;
> > + pep->rx_skb[used_rx_desc] = skb;
> > +
> > + /* Return the descriptor to DMA ownership */
> > + wmb();
> > + p_used_rx_desc->cmd_sts = BUF_OWNED_BY_DMA | RX_EN_INT;
> > + wmb();
> > +
> > + /* Move the used descriptor pointer to the next descriptor */
> > + pep->rx_used_desc_q = (used_rx_desc + 1) % pep->rx_ring_size;
> > +
> > + /* Any Rx return cancels the Rx resource error status */
> > + pep->rx_resource_err = 0;
> > +
> > + skb_reserve(skb, ETH_HW_IP_ALIGN);
> > + }
> > +
> > + /*
> > + * If RX ring is empty of SKB, set a timer to try allocating
> > + * again at a later time.
> > + */
> > + if (pep->rx_desc_count == 0) {
> > + pep->timeout.expires = jiffies + (HZ / 10);
> > + add_timer(&pep->timeout);
> > + }
> > +}
> > +
> > +static inline void rxq_refill_timer_wrapper(unsigned long data)
> > +{
> > + struct pxa168_eth_private *pep = (void *)data;
> > + napi_schedule(&pep->napi);
> > +}
> > +
> > +static inline u8 flip_8_bits(u8 x)
> > +{
> > + return (((x) & 0x01) << 3) | (((x) & 0x02) << 1)
> > + | (((x) & 0x04) >> 1) | (((x) & 0x08) >> 3)
> > + | (((x) & 0x10) << 3) | (((x) & 0x20) << 1)
> > + | (((x) & 0x40) >> 1) | (((x) & 0x80) >> 3);
> > +}
> > +
> > +static void nibble_swap_every_byte(unsigned char *mac_addr)
> > +{
> > + int i;
> > + for (i = 0; i < ETH_ALEN; i++) {
> > + mac_addr[i] = ((mac_addr[i] & 0x0f) << 4) |
> > + ((mac_addr[i] & 0xf0) >> 4);
> > + }
> > +}
> > +
> > +static void inverse_every_nibble(unsigned char *mac_addr)
> > +{
> > + int i;
> > + for (i = 0; i < ETH_ALEN; i++)
> > + mac_addr[i] = flip_8_bits(mac_addr[i]);
> > +}
> > +
> > +/*
> > + * ----------------------------------------------------------------------------
> > + * This function will calculate the hash function of the address.
> > + * Inputs
> > + * mac_addr_orig - MAC address.
> > + * Outputs
> > + * return the calculated entry.
> > + */
> > +static u32 hash_function(unsigned char *mac_addr_orig)
> > +{
> > + u32 hash_result;
> > + u32 addr0;
> > + u32 addr1;
> > + u32 addr2;
> > + u32 addr3;
> > + unsigned char mac_addr[ETH_ALEN];
> > +
> > + /* Make a copy of MAC address since we are going to performe bit
> > + * operations on it
> > + */
> > + memcpy(mac_addr, mac_addr_orig, ETH_ALEN);
> > +
> > + nibble_swap_every_byte(mac_addr);
> > + inverse_every_nibble(mac_addr);
> > +
> > + addr0 = (mac_addr[5] >> 2) & 0x3f;
> > + addr1 = (mac_addr[5] & 0x03) | (((mac_addr[4] & 0x7f)) << 2);
> > + addr2 = ((mac_addr[4] & 0x80) >> 7) | mac_addr[3] << 1;
> > + addr3 = (mac_addr[2] & 0xff) | ((mac_addr[1] & 1) << 8);
> > +
> > + hash_result = (addr0 << 9) | (addr1 ^ addr2 ^ addr3);
> > + hash_result = hash_result & 0x07ff;
> > + return hash_result;
> > +}
> > +
> > +/*
> > + * ----------------------------------------------------------------------------
> > + * This function will add/del an entry to the address table.
> > + * Inputs
> > + * pep - ETHERNET .
> > + * mac_addr - MAC address.
> > + * skip - if 1, skip this address.Used in case of deleting an entry which is a
> > + * part of chain in the hash table.We cant just delete the entry since
> > + * that will break the chain.We need to defragment the tables time to
> > + * time.
> > + * rd - 0 Discard packet upon match.
> > + * - 1 Receive packet upon match.
> > + * Outputs
> > + * address table entry is added/deleted.
> > + * 0 if success.
> > + * -ENOSPC if table full
> > + */
> > +static int add_del_hash_entry(struct pxa168_eth_private *pep,
> > + unsigned char *mac_addr,
> > + u32 rd, u32 skip, int del)
> > +{
> > + struct addr_table_entry *entry, *start;
> > + u32 new_high;
> > + u32 new_low;
> > + u32 i;
> > +
> > + new_low = (((mac_addr[1] >> 4) & 0xf) << 15)
> > + | (((mac_addr[1] >> 0) & 0xf) << 11)
> > + | (((mac_addr[0] >> 4) & 0xf) << 7)
> > + | (((mac_addr[0] >> 0) & 0xf) << 3)
> > + | (((mac_addr[3] >> 4) & 0x1) << 31)
> > + | (((mac_addr[3] >> 0) & 0xf) << 27)
> > + | (((mac_addr[2] >> 4) & 0xf) << 23)
> > + | (((mac_addr[2] >> 0) & 0xf) << 19)
> > + | (skip << SKIP) | (rd << HASH_ENTRY_RECEIVE_DISCARD_BIT)
> > + | HASH_ENTRY_VALID;
> > +
> > + new_high = (((mac_addr[5] >> 4) & 0xf) << 15)
> > + | (((mac_addr[5] >> 0) & 0xf) << 11)
> > + | (((mac_addr[4] >> 4) & 0xf) << 7)
> > + | (((mac_addr[4] >> 0) & 0xf) << 3)
> > + | (((mac_addr[3] >> 5) & 0x7) << 0);
> > +
> > + /*
> > + * Pick the appropriate table, start scanning for free/reusable
> > + * entries at the index obtained by hashing the specified MAC address
> > + */
> > + start = (struct addr_table_entry *)(pep->htpr);
> > + entry = start + hash_function(mac_addr);
> > + for (i = 0; i < HOP_NUMBER; i++) {
> > + if (!(le32_to_cpu(entry->lo) & HASH_ENTRY_VALID)) {
> > + break;
> > + } else {
> > + /* if same address put in same position */
> > + if (((le32_to_cpu(entry->lo) & 0xfffffff8) ==
> > + (new_low & 0xfffffff8)) &&
> > + (le32_to_cpu(entry->hi) == new_high)) {
> > + break;
> > + }
> > + }
> > + if (entry == start + 0x7ff)
> > + entry = start;
> > + else
> > + entry++;
> > + }
> > +
> > + if (((le32_to_cpu(entry->lo) & 0xfffffff8) != (new_low & 0xfffffff8)) &&
> > + (le32_to_cpu(entry->hi) != new_high) && del)
> > + return 0;
> > +
> > + if (i == HOP_NUMBER) {
> > + if (!del) {
> > + printk(KERN_INFO "%s: table section is full, need to "
> > + "move to 16kB implementation?\n",
> > + __FILE__);
> > + return -ENOSPC;
> > + } else
> > + return 0;
> > + }
> > +
> > + /*
> > + * Update the selected entry
> > + */
> > + if (del) {
> > + entry->hi = 0;
> > + entry->lo = 0;
> > + } else {
> > + entry->hi = cpu_to_le32(new_high);
> > + entry->lo = cpu_to_le32(new_low);
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +/*
> > + * ----------------------------------------------------------------------------
> > + * Create an addressTable entry from MAC address info
> > + * found in the specifed net_device struct
> > + *
> > + * Input : pointer to ethernet interface network device structure
> > + * Output : N/A
> > + */
> > +static void update_hash_table_mac_address(struct pxa168_eth_private *pep,
> > + unsigned char *oaddr,
> > + unsigned char *addr)
> > +{
> > + /* Delete old entry */
> > + if (oaddr)
> > + add_del_hash_entry(pep, oaddr, 1, 0, HASH_DELETE);
> > + /* Add new entry */
> > + add_del_hash_entry(pep, addr, 1, 0, HASH_ADD);
> > +}
> > +
> > +static int init_hash_table(struct pxa168_eth_private *pep)
> > +{
> > + /*
> > + * Hardware expects CPU to build a hash table based on a predefined
> > + * hash function and populate it based on hardware address. The
> > + * location of the hash table is identified by 32-bit pointer stored
> > + * in HTPR internal register. Two possible sizes exists for the hash
> > + * table 8kB (256kB of DRAM required (4 x 64 kB banks)) and 1/2kB
> > + * (16kB of DRAM required (4 x 4 kB banks)).We currently only support
> > + * 1/2kB.
> > + */
> > + /* TODO: Add support for 8kB hash table and alternative hash
> > + * function.Driver can dynamically switch to them if the 1/2kB hash
> > + * table is full.
> > + */
> > + if (pep->htpr == NULL) {
> > + pep->htpr = dma_alloc_coherent(pep->dev->dev.parent,
> > + HASH_ADDR_TABLE_SIZE,
> > + &pep->htpr_dma, GFP_KERNEL);
> > + if (pep->htpr == NULL)
> > + return -ENOMEM;
> > + }
> > + memset(pep->htpr, 0, HASH_ADDR_TABLE_SIZE);
> > + wrl(pep, HTPR, pep->htpr_dma);
> > + return 0;
> > +}
> > +
> > +static void pxa168_eth_set_rx_mode(struct net_device *dev)
> > +{
> > + struct pxa168_eth_private *pep = netdev_priv(dev);
> > + struct netdev_hw_addr *ha;
> > + u32 val;
> > +
> > + val = rdl(pep, PORT_CONFIG);
> > + if (dev->flags & IFF_PROMISC)
> > + val |= PCR_PM;
> > + else
> > + val &= ~PCR_PM;
> > + wrl(pep, PORT_CONFIG, val);
> > +
> > + /*
> > + * Remove the old list of MAC address and add dev->addr
> > + * and multicast address.
> > + */
> > + memset(pep->htpr, 0, HASH_ADDR_TABLE_SIZE);
> > + update_hash_table_mac_address(pep, NULL, dev->dev_addr);
> > +
> > + netdev_for_each_mc_addr(ha, dev)
> > + update_hash_table_mac_address(pep, NULL, ha->addr);
> > +}
> > +
> > +static int pxa168_eth_set_mac_address(struct net_device *dev, void *addr)
> > +{
> > + struct sockaddr *sa = addr;
> > + struct pxa168_eth_private *pep = netdev_priv(dev);
> > + unsigned char oldMac[ETH_ALEN];
> > +
> > + if (!is_valid_ether_addr(sa->sa_data))
> > + return -EINVAL;
> > + memcpy(oldMac, dev->dev_addr, ETH_ALEN);
> > + memcpy(dev->dev_addr, sa->sa_data, ETH_ALEN);
> > + netif_addr_lock_bh(dev);
> > + update_hash_table_mac_address(pep, oldMac, dev->dev_addr);
> > + netif_addr_unlock_bh(dev);
> > + return 0;
> > +}
> > +
> > +static void eth_port_start(struct net_device *dev)
> > +{
> > + unsigned int val = 0;
> > + struct pxa168_eth_private *pep = netdev_priv(dev);
> > + int tx_curr_desc, rx_curr_desc;
> > +
> > + /* Perform PHY reset, if there is a PHY. */
> > + if (pep->phy != NULL) {
> > + struct ethtool_cmd cmd;
> > +
> > + pxa168_get_settings(pep->dev, &cmd);
> > + ethernet_phy_reset(pep);
> > + pxa168_set_settings(pep->dev, &cmd);
> > + }
> > +
> > + /* Assignment of Tx CTRP of given queue */
> > + tx_curr_desc = pep->tx_curr_desc_q;
> > + wrl(pep, ETH_C_TX_DESC_1,
> > + (u32) ((struct tx_desc *)pep->tx_desc_dma + tx_curr_desc));
> > +
> > + /* Assignment of Rx CRDP of given queue */
> > + rx_curr_desc = pep->rx_curr_desc_q;
> > + wrl(pep, ETH_C_RX_DESC_0,
> > + (u32) ((struct rx_desc *)pep->rx_desc_dma + rx_curr_desc));
> > +
> > + wrl(pep, ETH_F_RX_DESC_0,
> > + (u32) ((struct rx_desc *)pep->rx_desc_dma + rx_curr_desc));
> > +
> > + /* Clear all interrupts */
> > + wrl(pep, INT_CAUSE, 0);
> > +
> > + /* Enable all interrupts for receive, transmit and error. */
> > + wrl(pep, INT_MASK, ALL_INTS);
> > +
> > + val = rdl(pep, PORT_CONFIG);
> > + val |= PCR_EN;
> > + wrl(pep, PORT_CONFIG, val);
> > +
> > + /* Start RX DMA engine */
> > + val = rdl(pep, SDMA_CMD);
> > + val |= SDMA_CMD_ERD;
> > + wrl(pep, SDMA_CMD, val);
> > +}
> > +
> > +static void eth_port_reset(struct net_device *dev)
> > +{
> > + struct pxa168_eth_private *pep = netdev_priv(dev);
> > + unsigned int val = 0;
> > +
> > + /* Stop all interrupts for receive, transmit and error. */
> > + wrl(pep, INT_MASK, 0);
> > +
> > + /* Clear all interrupts */
> > + wrl(pep, INT_CAUSE, 0);
> > +
> > + /* Stop RX DMA */
> > + val = rdl(pep, SDMA_CMD);
> > + val &= ~SDMA_CMD_ERD; /* abort dma command */
> > +
> > + /* Abort any transmit and receive operations and put DMA
> > + * in idle state.
> > + */
> > + abort_dma(pep);
> > +
> > + /* Disable port */
> > + val = rdl(pep, PORT_CONFIG);
> > + val &= ~PCR_EN;
> > + wrl(pep, PORT_CONFIG, val);
> > +}
> > +
> > +/*
> > + * txq_reclaim - Free the tx desc data for completed descriptors
> > + * If force is non-zero, frees uncompleted descriptors as well
> > + */
> > +static int txq_reclaim(struct net_device *dev, int force)
> > +{
> > + struct pxa168_eth_private *pep = netdev_priv(dev);
> > + struct tx_desc *desc;
> > + u32 cmd_sts;
> > + struct sk_buff *skb;
> > + int tx_index;
> > + dma_addr_t addr;
> > + int count;
> > + int released = 0;
> > +
> > + netif_tx_lock(dev);
> > +
> > + pep->work_todo &= ~WORK_TX_DONE;
> > + while (pep->tx_desc_count > 0) {
> > + tx_index = pep->tx_used_desc_q;
> > + desc = &pep->p_tx_desc_area[tx_index];
> > + cmd_sts = desc->cmd_sts;
> > + if (!force && (cmd_sts & BUF_OWNED_BY_DMA)) {
> > + if (released > 0) {
> > + goto txq_reclaim_end;
> > + } else {
> > + released = -1;
> > + goto txq_reclaim_end;
> > + }
> > + }
> > + pep->tx_used_desc_q = (tx_index + 1) % pep->tx_ring_size;
> > + pep->tx_desc_count--;
> > + addr = desc->buf_ptr;
> > + count = desc->byte_cnt;
> > + skb = pep->tx_skb[tx_index];
> > + if (skb)
> > + pep->tx_skb[tx_index] = NULL;
> > +
> > + if (cmd_sts & TX_ERROR) {
> > + if (net_ratelimit())
> > + printk(KERN_ERR "%s: Error in TX\n", dev->name);
> > + dev->stats.tx_errors++;
> > + }
> > + dma_unmap_single(NULL, addr, count, DMA_TO_DEVICE);
> > + if (skb)
> > + dev_kfree_skb_irq(skb);
> > + released++;
> > + }
> > +txq_reclaim_end:
> > + netif_tx_unlock(dev);
> > + return released;
> > +}
> > +
> > +static void pxa168_eth_tx_timeout(struct net_device *dev)
> > +{
> > + struct pxa168_eth_private *pep = netdev_priv(dev);
> > +
> > + printk(KERN_INFO "%s: TX timeout desc_count %d\n",
> > + dev->name, pep->tx_desc_count);
> > +
> > + schedule_work(&pep->tx_timeout_task);
> > +}
> > +
> > +static void pxa168_eth_tx_timeout_task(struct work_struct *work)
> > +{
> > + struct pxa168_eth_private *pep = container_of(work,
> > + struct pxa168_eth_private,
> > + tx_timeout_task);
> > + struct net_device *dev = pep->dev;
> > + pxa168_eth_stop(dev);
> > + pxa168_eth_open(dev);
> > +}
> > +
> > +static int rxq_process(struct net_device *dev, int budget)
> > +{
> > + struct pxa168_eth_private *pep = netdev_priv(dev);
> > + struct net_device_stats *stats = &dev->stats;
> > + unsigned int received_packets = 0;
> > + struct sk_buff *skb;
> > +
> > + while (budget-- > 0) {
> > + int rx_next_curr_desc, rx_curr_desc, rx_used_desc;
> > + struct rx_desc *rx_desc;
> > + unsigned int cmd_sts;
> > +
> > + /* Do not process Rx ring in case of Rx ring resource error */
> > + if (pep->rx_resource_err)
> > + break;
> > + rx_curr_desc = pep->rx_curr_desc_q;
> > + rx_used_desc = pep->rx_used_desc_q;
> > + rx_desc = &pep->p_rx_desc_area[rx_curr_desc];
> > + cmd_sts = rx_desc->cmd_sts;
> > + rmb();
> > + if (cmd_sts & (BUF_OWNED_BY_DMA))
> > + break;
> > + skb = pep->rx_skb[rx_curr_desc];
> > + pep->rx_skb[rx_curr_desc] = NULL;
> > +
> > + rx_next_curr_desc = (rx_curr_desc + 1) % pep->rx_ring_size;
> > + pep->rx_curr_desc_q = rx_next_curr_desc;
> > +
> > + /* Rx descriptors exhausted. */
> > + /* Set the Rx ring resource error flag */
> > + if (rx_next_curr_desc == rx_used_desc)
> > + pep->rx_resource_err = 1;
> > + pep->rx_desc_count--;
> > + dma_unmap_single(NULL, rx_desc->buf_ptr,
> > + rx_desc->buf_size,
> > + DMA_FROM_DEVICE);
> > + received_packets++;
> > + /*
> > + * Update statistics.
> > + * Note byte count includes 4 byte CRC count
> > + */
> > + stats->rx_packets++;
> > + stats->rx_bytes += rx_desc->byte_cnt;
> > + /*
> > + * In case received a packet without first / last bits on OR
> > + * the error summary bit is on, the packets needs to be droped.
> > + */
> > + if (((cmd_sts & (RX_FIRST_DESC | RX_LAST_DESC)) !=
> > + (RX_FIRST_DESC | RX_LAST_DESC))
> > + || (cmd_sts & RX_ERROR)) {
> > +
> > + stats->rx_dropped++;
> > + if ((cmd_sts & (RX_FIRST_DESC | RX_LAST_DESC)) !=
> > + (RX_FIRST_DESC | RX_LAST_DESC)) {
> > + if (net_ratelimit())
> > + printk(KERN_ERR
> > + "%s: Rx pkt on multiple desc\n",
> > + dev->name);
> > + }
> > + if (cmd_sts & RX_ERROR)
> > + stats->rx_errors++;
> > + dev_kfree_skb_irq(skb);
> > + } else {
> > + /*
> > + * The -4 is for the CRC in the trailer of the
> > + * received packet
> > + */
> > + skb_put(skb, rx_desc->byte_cnt - 4);
> > + skb->protocol = eth_type_trans(skb, dev);
> > + netif_receive_skb(skb);
> > + }
> > + dev->last_rx = jiffies;
> > + }
> > + /* Fill RX ring with skb's */
> > + rxq_refill(dev);
> > + return received_packets;
> > +}
> > +
> > +static int pxa168_eth_collect_events(struct pxa168_eth_private *pep,
> > + struct net_device *dev)
> > +{
> > + u32 icr;
> > + int ret = 0;
> > +
> > + icr = rdl(pep, INT_CAUSE);
> > + if (icr == 0)
> > + return IRQ_NONE;
> > +
> > + wrl(pep, INT_CAUSE, ~icr);
> > + if (icr & (ICR_TXBUF_H | ICR_TXBUF_L)) {
> > + pep->work_todo |= WORK_TX_DONE;
> > + ret = 1;
> > + }
> > + if (icr & ICR_RXBUF)
> > + ret = 1;
> > + if (icr & ICR_MII_CH) {
> > + pep->work_todo |= WORK_LINK;
> > + ret = 1;
> > + }
> > + return ret;
> > +}
> > +
> > +static void handle_link_event(struct pxa168_eth_private *pep)
> > +{
> > + struct net_device *dev = pep->dev;
> > + u32 port_status;
> > + int speed;
> > + int duplex;
> > + int fc;
> > +
> > + port_status = rdl(pep, PORT_STATUS);
> > + if (!(port_status & LINK_UP)) {
> > + if (netif_carrier_ok(dev)) {
> > + printk(KERN_INFO "%s: link down\n", dev->name);
> > + netif_carrier_off(dev);
> > + txq_reclaim(dev, 1);
> > + }
> > + return;
> > + }
> > + if (port_status & PORT_SPEED_100)
> > + speed = 100;
> > + else
> > + speed = 10;
> > +
> > + duplex = (port_status & FULL_DUPLEX) ? 1 : 0;
> > + fc = (port_status & FLOW_CONTROL_ENABLED) ? 1 : 0;
> > + printk(KERN_INFO "%s: link up, %d Mb/s, %s duplex, "
> > + "flow control %sabled\n", dev->name,
> > + speed, duplex ? "full" : "half", fc ? "en" : "dis");
> > + if (!netif_carrier_ok(dev))
> > + netif_carrier_on(dev);
> > +}
> > +
> > +static irqreturn_t pxa168_eth_int_handler(int irq, void *dev_id)
> > +{
> > + struct net_device *dev = (struct net_device *)dev_id;
> > + struct pxa168_eth_private *pep = netdev_priv(dev);
> > +
> > + if (unlikely(!pxa168_eth_collect_events(pep, dev)))
> > + return IRQ_NONE;
> > + /* Disable interrupts */
> > + wrl(pep, INT_MASK, 0);
> > + napi_schedule(&pep->napi);
> > + return IRQ_HANDLED;
> > +}
> > +
> > +static void pxa168_eth_recalc_skb_size(struct pxa168_eth_private *pep)
> > +{
> > + int skb_size;
> > +
> > + /*
> > + * Reserve 2+14 bytes for an ethernet header (the hardware
> > + * automatically prepends 2 bytes of dummy data to each
> > + * received packet), 16 bytes for up to four VLAN tags, and
> > + * 4 bytes for the trailing FCS -- 36 bytes total.
> > + */
> > + skb_size = pep->dev->mtu + 36;
> > +
> > + /*
> > + * Make sure that the skb size is a multiple of 8 bytes, as
> > + * the lower three bits of the receive descriptor's buffer
> > + * size field are ignored by the hardware.
> > + */
> > + pep->skb_size = (skb_size + 7) & ~7;
> > +
> > + /*
> > + * If NET_SKB_PAD is smaller than a cache line,
> > + * netdev_alloc_skb() will cause skb->data to be misaligned
> > + * to a cache line boundary. If this is the case, include
> > + * some extra space to allow re-aligning the data area.
> > + */
> > + pep->skb_size += SKB_DMA_REALIGN;
> > +
> > +}
> > +
> > +static int set_port_config_ext(struct pxa168_eth_private *pep)
> > +{
> > + int skb_size;
> > +
> > + pxa168_eth_recalc_skb_size(pep);
> > + if (pep->skb_size <= 1518)
> > + skb_size = PCXR_MFL_1518;
> > + else if (pep->skb_size <= 1536)
> > + skb_size = PCXR_MFL_1536;
> > + else if (pep->skb_size <= 2048)
> > + skb_size = PCXR_MFL_2048;
> > + else
> > + skb_size = PCXR_MFL_64K;
> > +
> > + /* Extended Port Configuration */
> > + wrl(pep,
> > + PORT_CONFIG_EXT, PCXR_2BSM | /* Two byte prefix aligns IP hdr */
> > + PCXR_DSCP_EN | /* Enable DSCP in IP */
> > + skb_size | PCXR_FLP | /* do not force link pass */
> > + PCXR_TX_HIGH_PRI); /* Transmit - high priority queue */
> > +
> > + return 0;
> > +}
> > +
> > +static int pxa168_init_hw(struct pxa168_eth_private *pep)
> > +{
> > + int err = 0;
> > +
> > + /* Disable interrupts */
> > + wrl(pep, INT_MASK, 0);
> > + wrl(pep, INT_CAUSE, 0);
> > + /* Write to ICR to clear interrupts. */
> > + wrl(pep, INT_W_CLEAR, 0);
> > + /* Abort any transmit and receive operations and put DMA
> > + * in idle state.
> > + */
> > + abort_dma(pep);
> > + /* Initialize address hash table */
> > + err = init_hash_table(pep);
> > + if (err)
> > + return err;
> > + /* SDMA configuration */
> > + wrl(pep, SDMA_CONFIG, SDCR_BSZ8 | /* Burst size = 32 bytes */
> > + SDCR_RIFB | /* Rx interrupt on frame */
> > + SDCR_BLMT | /* Little endian transmit */
> > + SDCR_BLMR | /* Little endian receive */
> > + SDCR_RC_MAX_RETRANS); /* Max retransmit count */
> > + /* Port Configuration */
> > + wrl(pep, PORT_CONFIG, PCR_HS); /* Hash size is 1/2kb */
> > + set_port_config_ext(pep);
> > +
> > + return err;
> > +}
> > +
> > +static int rxq_init(struct net_device *dev)
> > +{
> > + struct pxa168_eth_private *pep = netdev_priv(dev);
> > + struct rx_desc *p_rx_desc;
> > + int size = 0, i = 0;
> > + int rx_desc_num = pep->rx_ring_size;
> > +
> > + /* Allocate RX skb rings */
> > + pep->rx_skb = kmalloc(sizeof(*pep->rx_skb) * pep->rx_ring_size,
> > + GFP_KERNEL);
> > + if (!pep->rx_skb) {
> > + printk(KERN_ERR "%s: Cannot alloc RX skb ring\n", dev->name);
> > + return -ENOMEM;
> > + }
> > + /* Allocate RX ring */
> > + pep->rx_desc_count = 0;
> > + size = pep->rx_ring_size * sizeof(struct rx_desc);
> > + pep->rx_desc_area_size = size;
> > + pep->p_rx_desc_area = dma_alloc_coherent(pep->dev->dev.parent, size,
> > + &pep->rx_desc_dma, GFP_KERNEL);
> > + if (!pep->p_rx_desc_area) {
> > + printk(KERN_ERR "%s: Cannot alloc RX ring (size %d bytes)\n",
> > + dev->name, size);
> > + goto out;
> > + }
> > + memset((void *)pep->p_rx_desc_area, 0, size);
> > + /* initialize the next_desc_ptr links in the Rx descriptors ring */
> > + p_rx_desc = (struct rx_desc *)pep->p_rx_desc_area;
> > + for (i = 0; i < rx_desc_num; i++) {
> > + p_rx_desc[i].next_desc_ptr = pep->rx_desc_dma +
> > + ((i + 1) % rx_desc_num) * sizeof(struct rx_desc);
> > + }
> > + /* Save Rx desc pointer to driver struct. */
> > + pep->rx_curr_desc_q = 0;
> > + pep->rx_used_desc_q = 0;
> > + pep->rx_desc_area_size = rx_desc_num * sizeof(struct rx_desc);
> > + return 0;
> > +out:
> > + kfree(pep->rx_skb);
> > + return -ENOMEM;
> > +}
> > +
> > +static void rxq_deinit(struct net_device *dev)
> > +{
> > + struct pxa168_eth_private *pep = netdev_priv(dev);
> > + int curr;
> > +
> > + /* Free preallocated skb's on RX rings */
> > + for (curr = 0; pep->rx_desc_count && curr < pep->rx_ring_size; curr++) {
> > + if (pep->rx_skb[curr]) {
> > + dev_kfree_skb(pep->rx_skb[curr]);
> > + pep->rx_desc_count--;
> > + }
> > + }
> > + if (pep->rx_desc_count)
> > + printk(KERN_ERR
> > + "Error in freeing Rx Ring. %d skb's still\n",
> > + pep->rx_desc_count);
> > + /* Free RX ring */
> > + if (pep->p_rx_desc_area)
> > + dma_free_coherent(pep->dev->dev.parent, pep->rx_desc_area_size,
> > + pep->p_rx_desc_area, pep->rx_desc_dma);
> > + kfree(pep->rx_skb);
> > +}
> > +
> > +static int txq_init(struct net_device *dev)
> > +{
> > + struct pxa168_eth_private *pep = netdev_priv(dev);
> > + struct tx_desc *p_tx_desc;
> > + int size = 0, i = 0;
> > + int tx_desc_num = pep->tx_ring_size;
> > +
> > + pep->tx_skb = kmalloc(sizeof(*pep->tx_skb) * pep->tx_ring_size,
> > + GFP_KERNEL);
> > + if (!pep->tx_skb) {
> > + printk(KERN_ERR "%s: Cannot alloc TX skb ring\n", dev->name);
> > + return -ENOMEM;
> > + }
> > + /* Allocate TX ring */
> > + pep->tx_desc_count = 0;
> > + size = pep->tx_ring_size * sizeof(struct tx_desc);
> > + pep->tx_desc_area_size = size;
> > + pep->p_tx_desc_area = dma_alloc_coherent(pep->dev->dev.parent, size,
> > + &pep->tx_desc_dma, GFP_KERNEL);
> > + if (!pep->p_tx_desc_area) {
> > + printk(KERN_ERR "%s: Cannot allocate Tx Ring (size %d bytes)\n",
> > + dev->name, size);
> > + goto out;
> > + }
> > + memset((void *)pep->p_tx_desc_area, 0, pep->tx_desc_area_size);
> > + /* Initialize the next_desc_ptr links in the Tx descriptors ring */
> > + p_tx_desc = (struct tx_desc *)pep->p_tx_desc_area;
> > + for (i = 0; i < tx_desc_num; i++) {
> > + p_tx_desc[i].next_desc_ptr = pep->tx_desc_dma +
> > + ((i + 1) % tx_desc_num) * sizeof(struct tx_desc);
> > + }
> > + pep->tx_curr_desc_q = 0;
> > + pep->tx_used_desc_q = 0;
> > + pep->tx_desc_area_size = tx_desc_num * sizeof(struct tx_desc);
> > + return 0;
> > +out:
> > + kfree(pep->tx_skb);
> > + return -ENOMEM;
> > +}
> > +
> > +static void txq_deinit(struct net_device *dev)
> > +{
> > + struct pxa168_eth_private *pep = netdev_priv(dev);
> > +
> > + /* Free outstanding skb's on TX ring */
> > + txq_reclaim(dev, 1);
> > + BUG_ON(pep->tx_used_desc_q != pep->tx_curr_desc_q);
> > + /* Free TX ring */
> > + if (pep->p_tx_desc_area)
> > + dma_free_coherent(pep->dev->dev.parent, pep->tx_desc_area_size,
> > + pep->p_tx_desc_area, pep->tx_desc_dma);
> > + kfree(pep->tx_skb);
> > +}
> > +
> > +static int pxa168_eth_open(struct net_device *dev)
> > +{
> > + struct pxa168_eth_private *pep = netdev_priv(dev);
> > + int err;
> > +
> > + err = request_irq(dev->irq, pxa168_eth_int_handler,
> > + IRQF_DISABLED, dev->name, dev);
> > + if (err) {
> > + dev_printk(KERN_ERR, &dev->dev, "can't assign irq\n");
> > + return -EAGAIN;
> > + }
> > + pep->rx_resource_err = 0;
> > + err = rxq_init(dev);
> > + if (err != 0)
> > + goto out_free_irq;
> > + err = txq_init(dev);
> > + if (err != 0)
> > + goto out_free_rx_skb;
> > + pep->rx_used_desc_q = 0;
> > + pep->rx_curr_desc_q = 0;
> > +
> > + /* Fill RX ring with skb's */
> > + rxq_refill(dev);
> > + pep->rx_used_desc_q = 0;
> > + pep->rx_curr_desc_q = 0;
> > + netif_carrier_off(dev);
> > + eth_port_start(dev);
> > + napi_enable(&pep->napi);
> > + return 0;
> > +out_free_rx_skb:
> > + rxq_deinit(dev);
> > +out_free_irq:
> > + free_irq(dev->irq, dev);
> > + return err;
> > +}
> > +
> > +static int pxa168_eth_stop(struct net_device *dev)
> > +{
> > + struct pxa168_eth_private *pep = netdev_priv(dev);
> > + eth_port_reset(dev);
> > +
> > + /* Disable interrupts */
> > + wrl(pep, INT_MASK, 0);
> > + wrl(pep, INT_CAUSE, 0);
> > + /* Write to ICR to clear interrupts. */
> > + wrl(pep, INT_W_CLEAR, 0);
> > + napi_disable(&pep->napi);
> > + del_timer_sync(&pep->timeout);
> > + netif_carrier_off(dev);
> > + free_irq(dev->irq, dev);
> > + rxq_deinit(dev);
> > + txq_deinit(dev);
> > +
> > + return 0;
> > +}
> > +
> > +static int pxa168_eth_change_mtu(struct net_device *dev, int mtu)
> > +{
> > + int retval;
> > + struct pxa168_eth_private *pep = netdev_priv(dev);
> > +
> > + if ((mtu > 9500) || (mtu < 68))
> > + return -EINVAL;
> > +
> > + dev->mtu = mtu;
> > + retval = set_port_config_ext(pep);
> > +
> > + if (!netif_running(dev))
> > + return 0;
> > +
> > + /*
> > + * Stop and then re-open the interface. This will allocate RX
> > + * skbs of the new MTU.
> > + * There is a possible danger that the open will not succeed,
> > + * due to memory being full.
> > + */
> > + pxa168_eth_stop(dev);
> > + if (pxa168_eth_open(dev)) {
> > + dev_printk(KERN_ERR, &dev->dev,
> > + "fatal error on re-opening device after "
> > + "MTU change\n");
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int eth_alloc_tx_desc_index(struct pxa168_eth_private *pep)
> > +{
> > + int tx_desc_curr;
> > +
> > + tx_desc_curr = pep->tx_curr_desc_q;
> > + pep->tx_curr_desc_q = (tx_desc_curr + 1) % pep->tx_ring_size;
> > + BUG_ON(pep->tx_curr_desc_q == pep->tx_used_desc_q);
> > + pep->tx_desc_count++;
> > +
> > + return tx_desc_curr;
> > +}
> > +
> > +static int pxa168_rx_poll(struct napi_struct *napi, int budget)
> > +{
> > + struct pxa168_eth_private *pep =
> > + container_of(napi, struct pxa168_eth_private, napi);
> > + struct net_device *dev = pep->dev;
> > + int work_done = 0;
> > +
> > + if (unlikely(pep->work_todo & WORK_LINK)) {
> > + pep->work_todo &= ~(WORK_LINK);
> > + handle_link_event(pep);
> > + }
> > + /*
> > + * We call txq_reclaim every time since in NAPI interupts are disabled
> > + * and due to this we miss the TX_DONE interrupt,which is not updated in
> > + * interrupt status register.
> > + */
> > + txq_reclaim(dev, 0);
> > + if (netif_queue_stopped(dev)
> > + && pep->tx_ring_size - pep->tx_desc_count > 1) {
> > + netif_wake_queue(dev);
> > + }
> > + work_done = rxq_process(dev, budget);
> > + if (work_done < budget) {
> > + napi_complete(napi);
> > + wrl(pep, INT_MASK, ALL_INTS);
> > + }
> > +
> > + return work_done;
> > +}
> > +
> > +static int pxa168_eth_start_xmit(struct sk_buff *skb, struct net_device *dev)
> > +{
> > + struct pxa168_eth_private *pep = netdev_priv(dev);
> > + struct net_device_stats *stats = &dev->stats;
> > + struct tx_desc *desc;
> > + int tx_index;
> > + int length;
> > +
> > + tx_index = eth_alloc_tx_desc_index(pep);
> > + desc = &pep->p_tx_desc_area[tx_index];
> > + length = skb->len;
> > + pep->tx_skb[tx_index] = skb;
> > + desc->byte_cnt = length;
> > + desc->buf_ptr = dma_map_single(NULL, skb->data, length, DMA_TO_DEVICE);
> > + wmb();
> > + desc->cmd_sts = BUF_OWNED_BY_DMA | TX_GEN_CRC | TX_FIRST_DESC |
> > + TX_ZERO_PADDING | TX_LAST_DESC | TX_EN_INT;
> > + wmb();
> > + wrl(pep, SDMA_CMD, SDMA_CMD_TXDH | SDMA_CMD_ERD);
> > +
> > + stats->tx_bytes += skb->len;
> > + stats->tx_packets++;
> > + dev->trans_start = jiffies;
> > + if (pep->tx_ring_size - pep->tx_desc_count <= 1) {
> > + /* We handled the current skb, but now we are out of space.*/
> > + netif_stop_queue(dev);
> > + }
> > +
> > + return NETDEV_TX_OK;
> > +}
> > +
> > +static int smi_wait_ready(struct pxa168_eth_private *pep)
> > +{
> > + int i = 0;
> > +
> > + /* wait for the SMI register to become available */
> > + for (i = 0; rdl(pep, SMI) & SMI_BUSY; i++) {
> > + if (i == PHY_WAIT_ITERATIONS)
> > + return -ETIMEDOUT;
> > + msleep(10);
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int pxa168_smi_read(struct mii_bus *bus, int phy_addr, int regnum)
> > +{
> > + struct pxa168_eth_private *pep = bus->priv;
> > + int i = 0;
> > + int val;
> > +
> > + if (smi_wait_ready(pep)) {
> > + printk(KERN_WARNING "pxa168_eth: SMI bus busy timeout\n");
> > + return -ETIMEDOUT;
> > + }
> > + wrl(pep, SMI, (phy_addr << 16) | (regnum << 21) | SMI_OP_R);
> > + /* now wait for the data to be valid */
> > + for (i = 0; !((val = rdl(pep, SMI)) & SMI_R_VALID); i++) {
> > + if (i == PHY_WAIT_ITERATIONS) {
> > + printk(KERN_WARNING
> > + "pxa168_eth: SMI bus read not valid\n");
> > + return -ENODEV;
> > + }
> > + msleep(10);
> > + }
> > +
> > + return val & 0xffff;
> > +}
> > +
> > +static int pxa168_smi_write(struct mii_bus *bus, int phy_addr, int regnum,
> > + u16 value)
> > +{
> > + struct pxa168_eth_private *pep = bus->priv;
> > +
> > + if (smi_wait_ready(pep)) {
> > + printk(KERN_WARNING "pxa168_eth: SMI bus busy timeout\n");
> > + return -ETIMEDOUT;
> > + }
> > +
> > + wrl(pep, SMI, (phy_addr << 16) | (regnum << 21) |
> > + SMI_OP_W | (value & 0xffff));
> > +
> > + if (smi_wait_ready(pep)) {
> > + printk(KERN_ERR "pxa168_eth: SMI bus busy timeout\n");
> > + return -ETIMEDOUT;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int pxa168_eth_do_ioctl(struct net_device *dev, struct ifreq *ifr,
> > + int cmd)
> > +{
> > + struct pxa168_eth_private *pep = netdev_priv(dev);
> > + if (pep->phy != NULL)
> > + return phy_mii_ioctl(pep->phy, if_mii(ifr), cmd);
> > +
> > + return -EOPNOTSUPP;
> > +}
> > +
> > +static struct phy_device *phy_scan(struct pxa168_eth_private *pep, int phy_addr)
> > +{
> > + struct mii_bus *bus = pep->smi_bus;
> > + struct phy_device *phydev;
> > + int start;
> > + int num;
> > + int i;
> > +
> > + if (phy_addr == PXA168_ETH_PHY_ADDR_DEFAULT) {
> > + /* Scan entire range */
> > + start = ethernet_phy_get(pep);
> > + num = 32;
> > + } else {
> > + /* Use phy addr specific to platform */
> > + start = phy_addr & 0x1f;
> > + num = 1;
> > + }
> > + phydev = NULL;
> > + for (i = 0; i < num; i++) {
> > + int addr = (start + i) & 0x1f;
> > + if (bus->phy_map[addr] == NULL)
> > + mdiobus_scan(bus, addr);
> > +
> > + if (phydev == NULL) {
> > + phydev = bus->phy_map[addr];
> > + if (phydev != NULL)
> > + ethernet_phy_set_addr(pep, addr);
> > + }
> > + }
> > +
> > + return phydev;
> > +}
> > +
> > +static void phy_init(struct pxa168_eth_private *pep, int speed, int duplex)
> > +{
> > + struct phy_device *phy = pep->phy;
> > + ethernet_phy_reset(pep);
> > +
> > + phy_attach(pep->dev, dev_name(&phy->dev), 0, PHY_INTERFACE_MODE_MII);
> > +
> > + if (speed == 0) {
> > + phy->autoneg = AUTONEG_ENABLE;
> > + phy->speed = 0;
> > + phy->duplex = 0;
> > + phy->supported &= PHY_BASIC_FEATURES;
> > + phy->advertising = phy->supported | ADVERTISED_Autoneg;
> > + } else {
> > + phy->autoneg = AUTONEG_DISABLE;
> > + phy->advertising = 0;
> > + phy->speed = speed;
> > + phy->duplex = duplex;
> > + }
> > + phy_start_aneg(phy);
> > +}
> > +
> > +static int ethernet_phy_setup(struct net_device *dev)
> > +{
> > + struct pxa168_eth_private *pep = netdev_priv(dev);
> > +
> > + if (pep->pd != NULL) {
> > + if (pep->pd->init)
> > + pep->pd->init();
> > + }
> > + pep->phy = phy_scan(pep, pep->pd->phy_addr & 0x1f);
> > + if (pep->phy != NULL)
> > + phy_init(pep, pep->pd->speed, pep->pd->duplex);
> > + update_hash_table_mac_address(pep, NULL, dev->dev_addr);
> > +
> > + return 0;
> > +}
> > +
> > +static int pxa168_get_settings(struct net_device *dev, struct ethtool_cmd *cmd)
> > +{
> > + struct pxa168_eth_private *pep = netdev_priv(dev);
> > + int err;
> > +
> > + err = phy_read_status(pep->phy);
> > + if (err == 0)
> > + err = phy_ethtool_gset(pep->phy, cmd);
> > +
> > + return err;
> > +}
> > +
> > +static int pxa168_set_settings(struct net_device *dev, struct ethtool_cmd *cmd)
> > +{
> > + struct pxa168_eth_private *pep = netdev_priv(dev);
> > +
> > + return phy_ethtool_sset(pep->phy, cmd);
> > +}
> > +
> > +static void pxa168_get_drvinfo(struct net_device *dev,
> > + struct ethtool_drvinfo *info)
> > +{
> > + strncpy(info->driver, DRIVER_NAME, 32);
> > + strncpy(info->version, DRIVER_VERSION, 32);
> > + strncpy(info->fw_version, "N/A", 32);
> > + strncpy(info->bus_info, "N/A", 32);
> > +}
> > +
> > +static u32 pxa168_get_link(struct net_device *dev)
> > +{
> > + return !!netif_carrier_ok(dev);
> > +}
> > +
> > +static const struct ethtool_ops pxa168_ethtool_ops = {
> > + .get_settings = pxa168_get_settings,
> > + .set_settings = pxa168_set_settings,
> > + .get_drvinfo = pxa168_get_drvinfo,
> > + .get_link = pxa168_get_link,
> > +};
> > +
> > +static const struct net_device_ops pxa168_eth_netdev_ops = {
> > + .ndo_open = pxa168_eth_open,
> > + .ndo_stop = pxa168_eth_stop,
> > + .ndo_start_xmit = pxa168_eth_start_xmit,
> > + .ndo_set_rx_mode = pxa168_eth_set_rx_mode,
> > + .ndo_set_mac_address = pxa168_eth_set_mac_address,
> > + .ndo_validate_addr = eth_validate_addr,
> > + .ndo_do_ioctl = pxa168_eth_do_ioctl,
> > + .ndo_change_mtu = pxa168_eth_change_mtu,
> > + .ndo_tx_timeout = pxa168_eth_tx_timeout,
> > +};
> > +
> > +static int pxa168_eth_probe(struct platform_device *pdev)
> > +{
> > + struct pxa168_eth_private *pep = NULL;
> > + struct net_device *dev = NULL;
> > + struct resource *res;
> > + struct clk *clk;
> > + int err;
> > +
> > + printk(KERN_NOTICE "PXA168 10/100 Ethernet Driver\n");
> > +
> > + clk = clk_get(&pdev->dev, "MFUCLK");
> > + if (IS_ERR(clk)) {
> > + printk(KERN_ERR "%s: Fast Ethernet failed to get clock\n",
> > + DRIVER_NAME);
> > + return -ENODEV;
> > + }
> > + clk_enable(clk);
> > +
> > + dev = alloc_etherdev(sizeof(struct pxa168_eth_private));
> > + if (!dev) {
> > + err = -ENOMEM;
> > + goto out;
> > + }
> > +
> > + platform_set_drvdata(pdev, dev);
> > + pep = netdev_priv(dev);
> > + pep->dev = dev;
> > + pep->clk = clk;
> > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> > + if (res == NULL) {
> > + err = -ENODEV;
> > + goto out;
> > + }
> > + pep->base = ioremap(res->start, res->end - res->start + 1);
> > + if (pep->base == NULL) {
> > + err = -ENOMEM;
> > + goto out;
> > + }
> > + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
> > + BUG_ON(!res);
> > + dev->irq = res->start;
> > + dev->netdev_ops = &pxa168_eth_netdev_ops;
> > + dev->watchdog_timeo = 2 * HZ;
> > + dev->base_addr = 0;
> > + SET_ETHTOOL_OPS(dev, &pxa168_ethtool_ops);
> > +
> > + INIT_WORK(&pep->tx_timeout_task, pxa168_eth_tx_timeout_task);
> > +
> > + printk(KERN_INFO "%s:Using random mac address\n", DRIVER_NAME);
> > + random_ether_addr(dev->dev_addr);
> > +
> > + pep->pd = pdev->dev.platform_data;
> > + pep->rx_ring_size = NUM_RX_DESCS;
> > + if (pep->pd->rx_queue_size)
> > + pep->rx_ring_size = pep->pd->rx_queue_size;
> > +
> > + pep->tx_ring_size = NUM_TX_DESCS;
> > + if (pep->pd->tx_queue_size)
> > + pep->tx_ring_size = pep->pd->tx_queue_size;
> > +
> > + pep->port_num = pep->pd->port_number;
> > + /* Hardware supports only 3 ports */
> > + BUG_ON(pep->port_num > 2);
> > + netif_napi_add(dev, &pep->napi, pxa168_rx_poll, pep->rx_ring_size);
> > +
> > + memset(&pep->timeout, 0, sizeof(struct timer_list));
> > + init_timer(&pep->timeout);
> > + pep->timeout.function = rxq_refill_timer_wrapper;
> > + pep->timeout.data = (unsigned long)pep;
> > +
> > + pep->smi_bus = mdiobus_alloc();
> > + if (pep->smi_bus == NULL) {
> > + err = -ENOMEM;
> > + goto out;
> > + }
> > + pep->smi_bus->priv = pep;
> > + pep->smi_bus->name = "pxa168_eth smi";
> > + pep->smi_bus->read = pxa168_smi_read;
> > + pep->smi_bus->write = pxa168_smi_write;
> > + snprintf(pep->smi_bus->id, MII_BUS_ID_SIZE, "%d", pdev->id);
> > + pep->smi_bus->parent = &pdev->dev;
> > + pep->smi_bus->phy_mask = 0xffffffff;
> > + if (mdiobus_register(pep->smi_bus) < 0) {
> > + err = -ENOMEM;
> > + goto out;
> > + }
> > + pxa168_init_hw(pep);
> > + err = ethernet_phy_setup(dev);
> > + if (err)
> > + goto out;
> > + SET_NETDEV_DEV(dev, &pdev->dev);
> > + err = register_netdev(dev);
> > + if (err)
> > + goto out;
> > + return 0;
> > +out:
> > + if (pep->clk) {
> > + clk_disable(pep->clk);
> > + clk_put(pep->clk);
> > + pep->clk = NULL;
> > + }
> > + if (pep->base) {
> > + iounmap(pep->base);
> > + pep->base = NULL;
> > + }
> > + if (dev)
> > + free_netdev(dev);
> > + return err;
> > +}
> > +
> > +static int pxa168_eth_remove(struct platform_device *pdev)
> > +{
> > + struct net_device *dev = platform_get_drvdata(pdev);
> > + struct pxa168_eth_private *pep = netdev_priv(dev);
> > +
> > + if (pep->htpr) {
> > + dma_free_coherent(pep->dev->dev.parent, HASH_ADDR_TABLE_SIZE,
> > + pep->htpr, pep->htpr_dma);
> > + pep->htpr = NULL;
> > + }
> > + if (pep->clk) {
> > + clk_disable(pep->clk);
> > + clk_put(pep->clk);
> > + pep->clk = NULL;
> > + }
> > + if (pep->phy != NULL)
> > + phy_detach(pep->phy);
> > +
> > + iounmap(pep->base);
> > + pep->base = NULL;
> > + unregister_netdev(dev);
> > + flush_scheduled_work();
> > + free_netdev(dev);
> > + platform_set_drvdata(pdev, NULL);
> > + return 0;
> > +}
> > +
> > +static void pxa168_eth_shutdown(struct platform_device *pdev)
> > +{
> > + struct net_device *dev = platform_get_drvdata(pdev);
> > + eth_port_reset(dev);
> > +}
> > +
> > +#ifdef CONFIG_PM
> > +static int pxa168_eth_resume(struct platform_device *pdev)
> > +{
> > + return -ENOSYS;
> > +}
> > +
> > +static int pxa168_eth_suspend(struct platform_device *pdev, pm_message_t state)
> > +{
> > + return -ENOSYS;
> > +}
> > +
> > +#else
> > +#define pxa168_eth_resume NULL
> > +#define pxa168_eth_suspend NULL
> > +#endif
> > +
> > +static struct platform_driver pxa168_eth_driver = {
> > + .probe = pxa168_eth_probe,
> > + .remove = pxa168_eth_remove,
> > + .shutdown = pxa168_eth_shutdown,
> > + .resume = pxa168_eth_resume,
> > + .suspend = pxa168_eth_suspend,
> > + .driver = {
> > + .name = DRIVER_NAME,
> > + },
> > +};
> > +
> > +static int __init pxa168_init_module(void)
> > +{
> > + return platform_driver_register(&pxa168_eth_driver);
> > +}
> > +
> > +static void __exit pxa168_cleanup_module(void)
> > +{
> > + platform_driver_unregister(&pxa168_eth_driver);
> > +}
> > +
> > +module_init(pxa168_init_module);
> > +module_exit(pxa168_cleanup_module);
> > +
> > +MODULE_LICENSE("GPL");
> > +MODULE_DESCRIPTION("Ethernet driver for Marvell PXA168");
> > +MODULE_ALIAS("platform:pxa168_eth");
> > diff --git a/include/linux/pxa168_eth.h b/include/linux/pxa168_eth.h
> > new file mode 100644
> > index 0000000..18d75e7
> > --- /dev/null
> > +++ b/include/linux/pxa168_eth.h
> > @@ -0,0 +1,30 @@
> > +/*
> > + *pxa168 ethernet platform device data definition file.
> > + */
> > +#ifndef __LINUX_PXA168_ETH_H
> > +#define __LINUX_PXA168_ETH_H
> > +
> > +struct pxa168_eth_platform_data {
> > + int port_number;
> > + int phy_addr;
> > +
> > + /*
> > + * If speed is 0, then speed and duplex are autonegotiated.
> > + */
> > + int speed; /* 0, SPEED_10, SPEED_100 */
> > + int duplex; /* DUPLEX_HALF or DUPLEX_FULL */
> > +
> > + /*
> > + * Override default RX/TX queue sizes if nonzero.
> > + */
> > + int rx_queue_size;
> > + int tx_queue_size;
> > +
> > + /*
> > + * init callback is used for board specific initialization
> > + * e.g on Aspenite its used to initialize the PHY transceiver.
> > + */
> > + int (*init)(void);
> > +};
> > +
> > +#endif /* __LINUX_PXA168_ETH_H */
> > --
> > 1.5.3.3
> >
> >
> >
>
^ permalink raw reply
* Re: [GIT] Networking
From: Jarek Poplawski @ 2010-08-16 9:53 UTC (permalink / raw)
To: Eric Dumazet
Cc: David Miller, torvalds, akpm, netdev, linux-kernel,
Patrick McHardy
In-Reply-To: <1281883637.2942.42.camel@edumazet-laptop>
Eric Dumazet wrote:
> Le dimanche 15 aou^t 2010 a` 12:55 +0200, Eric Dumazet a écrit :
...
> [PATCH] netfilter: {ip,ip6,arp}_tables: avoid lockdep false positive
>
> After commit 24b36f019 (netfilter: {ip,ip6,arp}_tables: dont block
> bottom half more than necessary), lockdep can raise a warning
> because we attempt to lock a spinlock with BH enabled, while
> the same lock is usually locked by another cpu in a softirq context.
Btw, could you remind us how get_counters() are serialized (I guess
you can't have them on 2 cpus at the same time)?
Thanks,
Jarek P.
^ 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