diff options
-rw-r--r-- | arch/arm/mach-ixp4xx/include/mach/ixp46x_ts.h | 78 | ||||
-rw-r--r-- | drivers/net/arm/ixp4xx_eth.c | 195 | ||||
-rw-r--r-- | drivers/ptp/Kconfig | 13 | ||||
-rw-r--r-- | drivers/ptp/Makefile | 1 | ||||
-rw-r--r-- | drivers/ptp/ptp_ixp46x.c | 332 |
5 files changed, 616 insertions, 3 deletions
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 000000000000..292d55ed2113 --- /dev/null +++ b/arch/arm/mach-ixp4xx/include/mach/ixp46x_ts.h | |||
@@ -0,0 +1,78 @@ | |||
1 | /* | ||
2 | * PTP 1588 clock using the IXP46X | ||
3 | * | ||
4 | * Copyright (C) 2010 OMICRON electronics GmbH | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation; either version 2 of the License, or | ||
9 | * (at your option) any later version. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License | ||
17 | * along with this program; if not, write to the Free Software | ||
18 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
19 | */ | ||
20 | |||
21 | #ifndef _IXP46X_TS_H_ | ||
22 | #define _IXP46X_TS_H_ | ||
23 | |||
24 | #define DEFAULT_ADDEND 0xF0000029 | ||
25 | #define TICKS_NS_SHIFT 4 | ||
26 | |||
27 | struct ixp46x_channel_ctl { | ||
28 | u32 ch_control; /* 0x40 Time Synchronization Channel Control */ | ||
29 | u32 ch_event; /* 0x44 Time Synchronization Channel Event */ | ||
30 | u32 tx_snap_lo; /* 0x48 Transmit Snapshot Low Register */ | ||
31 | u32 tx_snap_hi; /* 0x4C Transmit Snapshot High Register */ | ||
32 | u32 rx_snap_lo; /* 0x50 Receive Snapshot Low Register */ | ||
33 | u32 rx_snap_hi; /* 0x54 Receive Snapshot High Register */ | ||
34 | u32 src_uuid_lo; /* 0x58 Source UUID0 Low Register */ | ||
35 | u32 src_uuid_hi; /* 0x5C Sequence Identifier/Source UUID0 High */ | ||
36 | }; | ||
37 | |||
38 | struct ixp46x_ts_regs { | ||
39 | u32 control; /* 0x00 Time Sync Control Register */ | ||
40 | u32 event; /* 0x04 Time Sync Event Register */ | ||
41 | u32 addend; /* 0x08 Time Sync Addend Register */ | ||
42 | u32 accum; /* 0x0C Time Sync Accumulator Register */ | ||
43 | u32 test; /* 0x10 Time Sync Test Register */ | ||
44 | u32 unused; /* 0x14 */ | ||
45 | u32 rsystime_lo; /* 0x18 RawSystemTime_Low Register */ | ||
46 | u32 rsystime_hi; /* 0x1C RawSystemTime_High Register */ | ||
47 | u32 systime_lo; /* 0x20 SystemTime_Low Register */ | ||
48 | u32 systime_hi; /* 0x24 SystemTime_High Register */ | ||
49 | u32 trgt_lo; /* 0x28 TargetTime_Low Register */ | ||
50 | u32 trgt_hi; /* 0x2C TargetTime_High Register */ | ||
51 | u32 asms_lo; /* 0x30 Auxiliary Slave Mode Snapshot Low */ | ||
52 | u32 asms_hi; /* 0x34 Auxiliary Slave Mode Snapshot High */ | ||
53 | u32 amms_lo; /* 0x38 Auxiliary Master Mode Snapshot Low */ | ||
54 | u32 amms_hi; /* 0x3C Auxiliary Master Mode Snapshot High */ | ||
55 | |||
56 | struct ixp46x_channel_ctl channel[3]; | ||
57 | }; | ||
58 | |||
59 | /* 0x00 Time Sync Control Register Bits */ | ||
60 | #define TSCR_AMM (1<<3) | ||
61 | #define TSCR_ASM (1<<2) | ||
62 | #define TSCR_TTM (1<<1) | ||
63 | #define TSCR_RST (1<<0) | ||
64 | |||
65 | /* 0x04 Time Sync Event Register Bits */ | ||
66 | #define TSER_SNM (1<<3) | ||
67 | #define TSER_SNS (1<<2) | ||
68 | #define TTIPEND (1<<1) | ||
69 | |||
70 | /* 0x40 Time Synchronization Channel Control Register Bits */ | ||
71 | #define MASTER_MODE (1<<0) | ||
72 | #define TIMESTAMP_ALL (1<<1) | ||
73 | |||
74 | /* 0x44 Time Synchronization Channel Event Register Bits */ | ||
75 | #define TX_SNAPSHOT_LOCKED (1<<0) | ||
76 | #define RX_SNAPSHOT_LOCKED (1<<1) | ||
77 | |||
78 | #endif | ||
diff --git a/drivers/net/arm/ixp4xx_eth.c b/drivers/net/arm/ixp4xx_eth.c index 9eb9b98a7ae3..de51e8453c13 100644 --- a/drivers/net/arm/ixp4xx_eth.c +++ b/drivers/net/arm/ixp4xx_eth.c | |||
@@ -30,9 +30,12 @@ | |||
30 | #include <linux/etherdevice.h> | 30 | #include <linux/etherdevice.h> |
31 | #include <linux/io.h> | 31 | #include <linux/io.h> |
32 | #include <linux/kernel.h> | 32 | #include <linux/kernel.h> |
33 | #include <linux/net_tstamp.h> | ||
33 | #include <linux/phy.h> | 34 | #include <linux/phy.h> |
34 | #include <linux/platform_device.h> | 35 | #include <linux/platform_device.h> |
36 | #include <linux/ptp_classify.h> | ||
35 | #include <linux/slab.h> | 37 | #include <linux/slab.h> |
38 | #include <mach/ixp46x_ts.h> | ||
36 | #include <mach/npe.h> | 39 | #include <mach/npe.h> |
37 | #include <mach/qmgr.h> | 40 | #include <mach/qmgr.h> |
38 | 41 | ||
@@ -67,6 +70,10 @@ | |||
67 | #define RXFREE_QUEUE(port_id) (NPE_ID(port_id) + 26) | 70 | #define RXFREE_QUEUE(port_id) (NPE_ID(port_id) + 26) |
68 | #define TXDONE_QUEUE 31 | 71 | #define TXDONE_QUEUE 31 |
69 | 72 | ||
73 | #define PTP_SLAVE_MODE 1 | ||
74 | #define PTP_MASTER_MODE 2 | ||
75 | #define PORT2CHANNEL(p) NPE_ID(p->id) | ||
76 | |||
70 | /* TX Control Registers */ | 77 | /* TX Control Registers */ |
71 | #define TX_CNTRL0_TX_EN 0x01 | 78 | #define TX_CNTRL0_TX_EN 0x01 |
72 | #define TX_CNTRL0_HALFDUPLEX 0x02 | 79 | #define TX_CNTRL0_HALFDUPLEX 0x02 |
@@ -171,6 +178,8 @@ struct port { | |||
171 | int id; /* logical port ID */ | 178 | int id; /* logical port ID */ |
172 | int speed, duplex; | 179 | int speed, duplex; |
173 | u8 firmware[4]; | 180 | u8 firmware[4]; |
181 | int hwts_tx_en; | ||
182 | int hwts_rx_en; | ||
174 | }; | 183 | }; |
175 | 184 | ||
176 | /* NPE message structure */ | 185 | /* NPE message structure */ |
@@ -246,6 +255,172 @@ static int ports_open; | |||
246 | static struct port *npe_port_tab[MAX_NPES]; | 255 | static struct port *npe_port_tab[MAX_NPES]; |
247 | static struct dma_pool *dma_pool; | 256 | static struct dma_pool *dma_pool; |
248 | 257 | ||
258 | static struct sock_filter ptp_filter[] = { | ||
259 | PTP_FILTER | ||
260 | }; | ||
261 | |||
262 | static int ixp_ptp_match(struct sk_buff *skb, u16 uid_hi, u32 uid_lo, u16 seqid) | ||
263 | { | ||
264 | u8 *data = skb->data; | ||
265 | unsigned int offset; | ||
266 | u16 *hi, *id; | ||
267 | u32 lo; | ||
268 | |||
269 | if (sk_run_filter(skb, ptp_filter) != PTP_CLASS_V1_IPV4) | ||
270 | return 0; | ||
271 | |||
272 | offset = ETH_HLEN + IPV4_HLEN(data) + UDP_HLEN; | ||
273 | |||
274 | if (skb->len < offset + OFF_PTP_SEQUENCE_ID + sizeof(seqid)) | ||
275 | return 0; | ||
276 | |||
277 | hi = (u16 *)(data + offset + OFF_PTP_SOURCE_UUID); | ||
278 | id = (u16 *)(data + offset + OFF_PTP_SEQUENCE_ID); | ||
279 | |||
280 | memcpy(&lo, &hi[1], sizeof(lo)); | ||
281 | |||
282 | return (uid_hi == ntohs(*hi) && | ||
283 | uid_lo == ntohl(lo) && | ||
284 | seqid == ntohs(*id)); | ||
285 | } | ||
286 | |||
287 | static void ixp_rx_timestamp(struct port *port, struct sk_buff *skb) | ||
288 | { | ||
289 | struct skb_shared_hwtstamps *shhwtstamps; | ||
290 | struct ixp46x_ts_regs *regs; | ||
291 | u64 ns; | ||
292 | u32 ch, hi, lo, val; | ||
293 | u16 uid, seq; | ||
294 | |||
295 | if (!port->hwts_rx_en) | ||
296 | return; | ||
297 | |||
298 | ch = PORT2CHANNEL(port); | ||
299 | |||
300 | regs = (struct ixp46x_ts_regs __iomem *) IXP4XX_TIMESYNC_BASE_VIRT; | ||
301 | |||
302 | val = __raw_readl(®s->channel[ch].ch_event); | ||
303 | |||
304 | if (!(val & RX_SNAPSHOT_LOCKED)) | ||
305 | return; | ||
306 | |||
307 | lo = __raw_readl(®s->channel[ch].src_uuid_lo); | ||
308 | hi = __raw_readl(®s->channel[ch].src_uuid_hi); | ||
309 | |||
310 | uid = hi & 0xffff; | ||
311 | seq = (hi >> 16) & 0xffff; | ||
312 | |||
313 | if (!ixp_ptp_match(skb, htons(uid), htonl(lo), htons(seq))) | ||
314 | goto out; | ||
315 | |||
316 | lo = __raw_readl(®s->channel[ch].rx_snap_lo); | ||
317 | hi = __raw_readl(®s->channel[ch].rx_snap_hi); | ||
318 | ns = ((u64) hi) << 32; | ||
319 | ns |= lo; | ||
320 | ns <<= TICKS_NS_SHIFT; | ||
321 | |||
322 | shhwtstamps = skb_hwtstamps(skb); | ||
323 | memset(shhwtstamps, 0, sizeof(*shhwtstamps)); | ||
324 | shhwtstamps->hwtstamp = ns_to_ktime(ns); | ||
325 | out: | ||
326 | __raw_writel(RX_SNAPSHOT_LOCKED, ®s->channel[ch].ch_event); | ||
327 | } | ||
328 | |||
329 | static void ixp_tx_timestamp(struct port *port, struct sk_buff *skb) | ||
330 | { | ||
331 | struct skb_shared_hwtstamps shhwtstamps; | ||
332 | struct ixp46x_ts_regs *regs; | ||
333 | struct skb_shared_info *shtx; | ||
334 | u64 ns; | ||
335 | u32 ch, cnt, hi, lo, val; | ||
336 | |||
337 | shtx = skb_shinfo(skb); | ||
338 | if (unlikely(shtx->tx_flags & SKBTX_HW_TSTAMP && port->hwts_tx_en)) | ||
339 | shtx->tx_flags |= SKBTX_IN_PROGRESS; | ||
340 | else | ||
341 | return; | ||
342 | |||
343 | ch = PORT2CHANNEL(port); | ||
344 | |||
345 | regs = (struct ixp46x_ts_regs __iomem *) IXP4XX_TIMESYNC_BASE_VIRT; | ||
346 | |||
347 | /* | ||
348 | * This really stinks, but we have to poll for the Tx time stamp. | ||
349 | * Usually, the time stamp is ready after 4 to 6 microseconds. | ||
350 | */ | ||
351 | for (cnt = 0; cnt < 100; cnt++) { | ||
352 | val = __raw_readl(®s->channel[ch].ch_event); | ||
353 | if (val & TX_SNAPSHOT_LOCKED) | ||
354 | break; | ||
355 | udelay(1); | ||
356 | } | ||
357 | if (!(val & TX_SNAPSHOT_LOCKED)) { | ||
358 | shtx->tx_flags &= ~SKBTX_IN_PROGRESS; | ||
359 | return; | ||
360 | } | ||
361 | |||
362 | lo = __raw_readl(®s->channel[ch].tx_snap_lo); | ||
363 | hi = __raw_readl(®s->channel[ch].tx_snap_hi); | ||
364 | ns = ((u64) hi) << 32; | ||
365 | ns |= lo; | ||
366 | ns <<= TICKS_NS_SHIFT; | ||
367 | |||
368 | memset(&shhwtstamps, 0, sizeof(shhwtstamps)); | ||
369 | shhwtstamps.hwtstamp = ns_to_ktime(ns); | ||
370 | skb_tstamp_tx(skb, &shhwtstamps); | ||
371 | |||
372 | __raw_writel(TX_SNAPSHOT_LOCKED, ®s->channel[ch].ch_event); | ||
373 | } | ||
374 | |||
375 | static int hwtstamp_ioctl(struct net_device *netdev, struct ifreq *ifr, int cmd) | ||
376 | { | ||
377 | struct hwtstamp_config cfg; | ||
378 | struct ixp46x_ts_regs *regs; | ||
379 | struct port *port = netdev_priv(netdev); | ||
380 | int ch; | ||
381 | |||
382 | if (copy_from_user(&cfg, ifr->ifr_data, sizeof(cfg))) | ||
383 | return -EFAULT; | ||
384 | |||
385 | if (cfg.flags) /* reserved for future extensions */ | ||
386 | return -EINVAL; | ||
387 | |||
388 | ch = PORT2CHANNEL(port); | ||
389 | regs = (struct ixp46x_ts_regs __iomem *) IXP4XX_TIMESYNC_BASE_VIRT; | ||
390 | |||
391 | switch (cfg.tx_type) { | ||
392 | case HWTSTAMP_TX_OFF: | ||
393 | port->hwts_tx_en = 0; | ||
394 | break; | ||
395 | case HWTSTAMP_TX_ON: | ||
396 | port->hwts_tx_en = 1; | ||
397 | break; | ||
398 | default: | ||
399 | return -ERANGE; | ||
400 | } | ||
401 | |||
402 | switch (cfg.rx_filter) { | ||
403 | case HWTSTAMP_FILTER_NONE: | ||
404 | port->hwts_rx_en = 0; | ||
405 | break; | ||
406 | case HWTSTAMP_FILTER_PTP_V1_L4_SYNC: | ||
407 | port->hwts_rx_en = PTP_SLAVE_MODE; | ||
408 | __raw_writel(0, ®s->channel[ch].ch_control); | ||
409 | break; | ||
410 | case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ: | ||
411 | port->hwts_rx_en = PTP_MASTER_MODE; | ||
412 | __raw_writel(MASTER_MODE, ®s->channel[ch].ch_control); | ||
413 | break; | ||
414 | default: | ||
415 | return -ERANGE; | ||
416 | } | ||
417 | |||
418 | /* Clear out any old time stamps. */ | ||
419 | __raw_writel(TX_SNAPSHOT_LOCKED | RX_SNAPSHOT_LOCKED, | ||
420 | ®s->channel[ch].ch_event); | ||
421 | |||
422 | return copy_to_user(ifr->ifr_data, &cfg, sizeof(cfg)) ? -EFAULT : 0; | ||
423 | } | ||
249 | 424 | ||
250 | static int ixp4xx_mdio_cmd(struct mii_bus *bus, int phy_id, int location, | 425 | static int ixp4xx_mdio_cmd(struct mii_bus *bus, int phy_id, int location, |
251 | int write, u16 cmd) | 426 | int write, u16 cmd) |
@@ -573,6 +748,7 @@ static int eth_poll(struct napi_struct *napi, int budget) | |||
573 | 748 | ||
574 | debug_pkt(dev, "eth_poll", skb->data, skb->len); | 749 | debug_pkt(dev, "eth_poll", skb->data, skb->len); |
575 | 750 | ||
751 | ixp_rx_timestamp(port, skb); | ||
576 | skb->protocol = eth_type_trans(skb, dev); | 752 | skb->protocol = eth_type_trans(skb, dev); |
577 | dev->stats.rx_packets++; | 753 | dev->stats.rx_packets++; |
578 | dev->stats.rx_bytes += skb->len; | 754 | dev->stats.rx_bytes += skb->len; |
@@ -679,14 +855,12 @@ static int eth_xmit(struct sk_buff *skb, struct net_device *dev) | |||
679 | return NETDEV_TX_OK; | 855 | return NETDEV_TX_OK; |
680 | } | 856 | } |
681 | memcpy_swab32(mem, (u32 *)((int)skb->data & ~3), bytes / 4); | 857 | memcpy_swab32(mem, (u32 *)((int)skb->data & ~3), bytes / 4); |
682 | dev_kfree_skb(skb); | ||
683 | #endif | 858 | #endif |
684 | 859 | ||
685 | phys = dma_map_single(&dev->dev, mem, bytes, DMA_TO_DEVICE); | 860 | phys = dma_map_single(&dev->dev, mem, bytes, DMA_TO_DEVICE); |
686 | if (dma_mapping_error(&dev->dev, phys)) { | 861 | if (dma_mapping_error(&dev->dev, phys)) { |
687 | #ifdef __ARMEB__ | ||
688 | dev_kfree_skb(skb); | 862 | dev_kfree_skb(skb); |
689 | #else | 863 | #ifndef __ARMEB__ |
690 | kfree(mem); | 864 | kfree(mem); |
691 | #endif | 865 | #endif |
692 | dev->stats.tx_dropped++; | 866 | dev->stats.tx_dropped++; |
@@ -728,6 +902,13 @@ static int eth_xmit(struct sk_buff *skb, struct net_device *dev) | |||
728 | #if DEBUG_TX | 902 | #if DEBUG_TX |
729 | printk(KERN_DEBUG "%s: eth_xmit end\n", dev->name); | 903 | printk(KERN_DEBUG "%s: eth_xmit end\n", dev->name); |
730 | #endif | 904 | #endif |
905 | |||
906 | ixp_tx_timestamp(port, skb); | ||
907 | skb_tx_timestamp(skb); | ||
908 | |||
909 | #ifndef __ARMEB__ | ||
910 | dev_kfree_skb(skb); | ||
911 | #endif | ||
731 | return NETDEV_TX_OK; | 912 | return NETDEV_TX_OK; |
732 | } | 913 | } |
733 | 914 | ||
@@ -783,6 +964,9 @@ static int eth_ioctl(struct net_device *dev, struct ifreq *req, int cmd) | |||
783 | if (!netif_running(dev)) | 964 | if (!netif_running(dev)) |
784 | return -EINVAL; | 965 | return -EINVAL; |
785 | 966 | ||
967 | if (cpu_is_ixp46x() && cmd == SIOCSHWTSTAMP) | ||
968 | return hwtstamp_ioctl(dev, req, cmd); | ||
969 | |||
786 | return phy_mii_ioctl(port->phydev, req, cmd); | 970 | return phy_mii_ioctl(port->phydev, req, cmd); |
787 | } | 971 | } |
788 | 972 | ||
@@ -1171,6 +1355,11 @@ static int __devinit eth_init_one(struct platform_device *pdev) | |||
1171 | char phy_id[MII_BUS_ID_SIZE + 3]; | 1355 | char phy_id[MII_BUS_ID_SIZE + 3]; |
1172 | int err; | 1356 | int err; |
1173 | 1357 | ||
1358 | if (ptp_filter_init(ptp_filter, ARRAY_SIZE(ptp_filter))) { | ||
1359 | pr_err("ixp4xx_eth: bad ptp filter\n"); | ||
1360 | return -EINVAL; | ||
1361 | } | ||
1362 | |||
1174 | if (!(dev = alloc_etherdev(sizeof(struct port)))) | 1363 | if (!(dev = alloc_etherdev(sizeof(struct port)))) |
1175 | return -ENOMEM; | 1364 | return -ENOMEM; |
1176 | 1365 | ||
diff --git a/drivers/ptp/Kconfig b/drivers/ptp/Kconfig index 12eb8447c26b..fcfafd091fdd 100644 --- a/drivers/ptp/Kconfig +++ b/drivers/ptp/Kconfig | |||
@@ -40,4 +40,17 @@ config PTP_1588_CLOCK_GIANFAR | |||
40 | To compile this driver as a module, choose M here: the module | 40 | To compile this driver as a module, choose M here: the module |
41 | will be called gianfar_ptp. | 41 | will be called gianfar_ptp. |
42 | 42 | ||
43 | config PTP_1588_CLOCK_IXP46X | ||
44 | tristate "Intel IXP46x as PTP clock" | ||
45 | depends on PTP_1588_CLOCK | ||
46 | depends on IXP4XX_ETH | ||
47 | help | ||
48 | This driver adds support for using the IXP46X as a PTP | ||
49 | clock. This clock is only useful if your PTP programs are | ||
50 | getting hardware time stamps on the PTP Ethernet packets | ||
51 | using the SO_TIMESTAMPING API. | ||
52 | |||
53 | To compile this driver as a module, choose M here: the module | ||
54 | will be called ptp_ixp46x. | ||
55 | |||
43 | endmenu | 56 | endmenu |
diff --git a/drivers/ptp/Makefile b/drivers/ptp/Makefile index 480e2afdc999..f6933e83de72 100644 --- a/drivers/ptp/Makefile +++ b/drivers/ptp/Makefile | |||
@@ -4,3 +4,4 @@ | |||
4 | 4 | ||
5 | ptp-y := ptp_clock.o ptp_chardev.o ptp_sysfs.o | 5 | ptp-y := ptp_clock.o ptp_chardev.o ptp_sysfs.o |
6 | obj-$(CONFIG_PTP_1588_CLOCK) += ptp.o | 6 | obj-$(CONFIG_PTP_1588_CLOCK) += ptp.o |
7 | 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 000000000000..803d665b15ef --- /dev/null +++ b/drivers/ptp/ptp_ixp46x.c | |||
@@ -0,0 +1,332 @@ | |||
1 | /* | ||
2 | * PTP 1588 clock using the IXP46X | ||
3 | * | ||
4 | * Copyright (C) 2010 OMICRON electronics GmbH | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation; either version 2 of the License, or | ||
9 | * (at your option) any later version. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License | ||
17 | * along with this program; if not, write to the Free Software | ||
18 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
19 | */ | ||
20 | #include <linux/device.h> | ||
21 | #include <linux/err.h> | ||
22 | #include <linux/gpio.h> | ||
23 | #include <linux/init.h> | ||
24 | #include <linux/interrupt.h> | ||
25 | #include <linux/io.h> | ||
26 | #include <linux/irq.h> | ||
27 | #include <linux/kernel.h> | ||
28 | #include <linux/module.h> | ||
29 | |||
30 | #include <linux/ptp_clock_kernel.h> | ||
31 | #include <mach/ixp46x_ts.h> | ||
32 | |||
33 | #define DRIVER "ptp_ixp46x" | ||
34 | #define N_EXT_TS 2 | ||
35 | #define MASTER_GPIO 8 | ||
36 | #define MASTER_IRQ 25 | ||
37 | #define SLAVE_GPIO 7 | ||
38 | #define SLAVE_IRQ 24 | ||
39 | |||
40 | struct ixp_clock { | ||
41 | struct ixp46x_ts_regs *regs; | ||
42 | struct ptp_clock *ptp_clock; | ||
43 | struct ptp_clock_info caps; | ||
44 | int exts0_enabled; | ||
45 | int exts1_enabled; | ||
46 | }; | ||
47 | |||
48 | DEFINE_SPINLOCK(register_lock); | ||
49 | |||
50 | /* | ||
51 | * Register access functions | ||
52 | */ | ||
53 | |||
54 | static u64 ixp_systime_read(struct ixp46x_ts_regs *regs) | ||
55 | { | ||
56 | u64 ns; | ||
57 | u32 lo, hi; | ||
58 | |||
59 | lo = __raw_readl(®s->systime_lo); | ||
60 | hi = __raw_readl(®s->systime_hi); | ||
61 | |||
62 | ns = ((u64) hi) << 32; | ||
63 | ns |= lo; | ||
64 | ns <<= TICKS_NS_SHIFT; | ||
65 | |||
66 | return ns; | ||
67 | } | ||
68 | |||
69 | static void ixp_systime_write(struct ixp46x_ts_regs *regs, u64 ns) | ||
70 | { | ||
71 | u32 hi, lo; | ||
72 | |||
73 | ns >>= TICKS_NS_SHIFT; | ||
74 | hi = ns >> 32; | ||
75 | lo = ns & 0xffffffff; | ||
76 | |||
77 | __raw_writel(lo, ®s->systime_lo); | ||
78 | __raw_writel(hi, ®s->systime_hi); | ||
79 | } | ||
80 | |||
81 | /* | ||
82 | * Interrupt service routine | ||
83 | */ | ||
84 | |||
85 | static irqreturn_t isr(int irq, void *priv) | ||
86 | { | ||
87 | struct ixp_clock *ixp_clock = priv; | ||
88 | struct ixp46x_ts_regs *regs = ixp_clock->regs; | ||
89 | struct ptp_clock_event event; | ||
90 | u32 ack = 0, lo, hi, val; | ||
91 | |||
92 | val = __raw_readl(®s->event); | ||
93 | |||
94 | if (val & TSER_SNS) { | ||
95 | ack |= TSER_SNS; | ||
96 | if (ixp_clock->exts0_enabled) { | ||
97 | hi = __raw_readl(®s->asms_hi); | ||
98 | lo = __raw_readl(®s->asms_lo); | ||
99 | event.type = PTP_CLOCK_EXTTS; | ||
100 | event.index = 0; | ||
101 | event.timestamp = ((u64) hi) << 32; | ||
102 | event.timestamp |= lo; | ||
103 | event.timestamp <<= TICKS_NS_SHIFT; | ||
104 | ptp_clock_event(ixp_clock->ptp_clock, &event); | ||
105 | } | ||
106 | } | ||
107 | |||
108 | if (val & TSER_SNM) { | ||
109 | ack |= TSER_SNM; | ||
110 | if (ixp_clock->exts1_enabled) { | ||
111 | hi = __raw_readl(®s->amms_hi); | ||
112 | lo = __raw_readl(®s->amms_lo); | ||
113 | event.type = PTP_CLOCK_EXTTS; | ||
114 | event.index = 1; | ||
115 | event.timestamp = ((u64) hi) << 32; | ||
116 | event.timestamp |= lo; | ||
117 | event.timestamp <<= TICKS_NS_SHIFT; | ||
118 | ptp_clock_event(ixp_clock->ptp_clock, &event); | ||
119 | } | ||
120 | } | ||
121 | |||
122 | if (val & TTIPEND) | ||
123 | ack |= TTIPEND; /* this bit seems to be always set */ | ||
124 | |||
125 | if (ack) { | ||
126 | __raw_writel(ack, ®s->event); | ||
127 | return IRQ_HANDLED; | ||
128 | } else | ||
129 | return IRQ_NONE; | ||
130 | } | ||
131 | |||
132 | /* | ||
133 | * PTP clock operations | ||
134 | */ | ||
135 | |||
136 | static int ptp_ixp_adjfreq(struct ptp_clock_info *ptp, s32 ppb) | ||
137 | { | ||
138 | u64 adj; | ||
139 | u32 diff, addend; | ||
140 | int neg_adj = 0; | ||
141 | struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps); | ||
142 | struct ixp46x_ts_regs *regs = ixp_clock->regs; | ||
143 | |||
144 | if (ppb < 0) { | ||
145 | neg_adj = 1; | ||
146 | ppb = -ppb; | ||
147 | } | ||
148 | addend = DEFAULT_ADDEND; | ||
149 | adj = addend; | ||
150 | adj *= ppb; | ||
151 | diff = div_u64(adj, 1000000000ULL); | ||
152 | |||
153 | addend = neg_adj ? addend - diff : addend + diff; | ||
154 | |||
155 | __raw_writel(addend, ®s->addend); | ||
156 | |||
157 | return 0; | ||
158 | } | ||
159 | |||
160 | static int ptp_ixp_adjtime(struct ptp_clock_info *ptp, s64 delta) | ||
161 | { | ||
162 | s64 now; | ||
163 | unsigned long flags; | ||
164 | struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps); | ||
165 | struct ixp46x_ts_regs *regs = ixp_clock->regs; | ||
166 | |||
167 | spin_lock_irqsave(®ister_lock, flags); | ||
168 | |||
169 | now = ixp_systime_read(regs); | ||
170 | now += delta; | ||
171 | ixp_systime_write(regs, now); | ||
172 | |||
173 | spin_unlock_irqrestore(®ister_lock, flags); | ||
174 | |||
175 | return 0; | ||
176 | } | ||
177 | |||
178 | static int ptp_ixp_gettime(struct ptp_clock_info *ptp, struct timespec *ts) | ||
179 | { | ||
180 | u64 ns; | ||
181 | u32 remainder; | ||
182 | unsigned long flags; | ||
183 | struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps); | ||
184 | struct ixp46x_ts_regs *regs = ixp_clock->regs; | ||
185 | |||
186 | spin_lock_irqsave(®ister_lock, flags); | ||
187 | |||
188 | ns = ixp_systime_read(regs); | ||
189 | |||
190 | spin_unlock_irqrestore(®ister_lock, flags); | ||
191 | |||
192 | ts->tv_sec = div_u64_rem(ns, 1000000000, &remainder); | ||
193 | ts->tv_nsec = remainder; | ||
194 | return 0; | ||
195 | } | ||
196 | |||
197 | static int ptp_ixp_settime(struct ptp_clock_info *ptp, | ||
198 | const struct timespec *ts) | ||
199 | { | ||
200 | u64 ns; | ||
201 | unsigned long flags; | ||
202 | struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps); | ||
203 | struct ixp46x_ts_regs *regs = ixp_clock->regs; | ||
204 | |||
205 | ns = ts->tv_sec * 1000000000ULL; | ||
206 | ns += ts->tv_nsec; | ||
207 | |||
208 | spin_lock_irqsave(®ister_lock, flags); | ||
209 | |||
210 | ixp_systime_write(regs, ns); | ||
211 | |||
212 | spin_unlock_irqrestore(®ister_lock, flags); | ||
213 | |||
214 | return 0; | ||
215 | } | ||
216 | |||
217 | static int ptp_ixp_enable(struct ptp_clock_info *ptp, | ||
218 | struct ptp_clock_request *rq, int on) | ||
219 | { | ||
220 | struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps); | ||
221 | |||
222 | switch (rq->type) { | ||
223 | case PTP_CLK_REQ_EXTTS: | ||
224 | switch (rq->extts.index) { | ||
225 | case 0: | ||
226 | ixp_clock->exts0_enabled = on ? 1 : 0; | ||
227 | break; | ||
228 | case 1: | ||
229 | ixp_clock->exts1_enabled = on ? 1 : 0; | ||
230 | break; | ||
231 | default: | ||
232 | return -EINVAL; | ||
233 | } | ||
234 | return 0; | ||
235 | default: | ||
236 | break; | ||
237 | } | ||
238 | |||
239 | return -EOPNOTSUPP; | ||
240 | } | ||
241 | |||
242 | static struct ptp_clock_info ptp_ixp_caps = { | ||
243 | .owner = THIS_MODULE, | ||
244 | .name = "IXP46X timer", | ||
245 | .max_adj = 66666655, | ||
246 | .n_ext_ts = N_EXT_TS, | ||
247 | .pps = 0, | ||
248 | .adjfreq = ptp_ixp_adjfreq, | ||
249 | .adjtime = ptp_ixp_adjtime, | ||
250 | .gettime = ptp_ixp_gettime, | ||
251 | .settime = ptp_ixp_settime, | ||
252 | .enable = ptp_ixp_enable, | ||
253 | }; | ||
254 | |||
255 | /* module operations */ | ||
256 | |||
257 | static struct ixp_clock ixp_clock; | ||
258 | |||
259 | static int setup_interrupt(int gpio) | ||
260 | { | ||
261 | int irq; | ||
262 | |||
263 | gpio_line_config(gpio, IXP4XX_GPIO_IN); | ||
264 | |||
265 | irq = gpio_to_irq(gpio); | ||
266 | |||
267 | if (NO_IRQ == irq) | ||
268 | return NO_IRQ; | ||
269 | |||
270 | if (irq_set_irq_type(irq, IRQF_TRIGGER_FALLING)) { | ||
271 | pr_err("cannot set trigger type for irq %d\n", irq); | ||
272 | return NO_IRQ; | ||
273 | } | ||
274 | |||
275 | if (request_irq(irq, isr, 0, DRIVER, &ixp_clock)) { | ||
276 | pr_err("request_irq failed for irq %d\n", irq); | ||
277 | return NO_IRQ; | ||
278 | } | ||
279 | |||
280 | return irq; | ||
281 | } | ||
282 | |||
283 | static void __exit ptp_ixp_exit(void) | ||
284 | { | ||
285 | free_irq(MASTER_IRQ, &ixp_clock); | ||
286 | free_irq(SLAVE_IRQ, &ixp_clock); | ||
287 | ptp_clock_unregister(ixp_clock.ptp_clock); | ||
288 | } | ||
289 | |||
290 | static int __init ptp_ixp_init(void) | ||
291 | { | ||
292 | if (!cpu_is_ixp46x()) | ||
293 | return -ENODEV; | ||
294 | |||
295 | ixp_clock.regs = | ||
296 | (struct ixp46x_ts_regs __iomem *) IXP4XX_TIMESYNC_BASE_VIRT; | ||
297 | |||
298 | ixp_clock.caps = ptp_ixp_caps; | ||
299 | |||
300 | ixp_clock.ptp_clock = ptp_clock_register(&ixp_clock.caps); | ||
301 | |||
302 | if (IS_ERR(ixp_clock.ptp_clock)) | ||
303 | return PTR_ERR(ixp_clock.ptp_clock); | ||
304 | |||
305 | __raw_writel(DEFAULT_ADDEND, &ixp_clock.regs->addend); | ||
306 | __raw_writel(1, &ixp_clock.regs->trgt_lo); | ||
307 | __raw_writel(0, &ixp_clock.regs->trgt_hi); | ||
308 | __raw_writel(TTIPEND, &ixp_clock.regs->event); | ||
309 | |||
310 | if (MASTER_IRQ != setup_interrupt(MASTER_GPIO)) { | ||
311 | pr_err("failed to setup gpio %d as irq\n", MASTER_GPIO); | ||
312 | goto no_master; | ||
313 | } | ||
314 | if (SLAVE_IRQ != setup_interrupt(SLAVE_GPIO)) { | ||
315 | pr_err("failed to setup gpio %d as irq\n", SLAVE_GPIO); | ||
316 | goto no_slave; | ||
317 | } | ||
318 | |||
319 | return 0; | ||
320 | no_slave: | ||
321 | free_irq(MASTER_IRQ, &ixp_clock); | ||
322 | no_master: | ||
323 | ptp_clock_unregister(ixp_clock.ptp_clock); | ||
324 | return -ENODEV; | ||
325 | } | ||
326 | |||
327 | module_init(ptp_ixp_init); | ||
328 | module_exit(ptp_ixp_exit); | ||
329 | |||
330 | MODULE_AUTHOR("Richard Cochran <richard.cochran@omicron.at>"); | ||
331 | MODULE_DESCRIPTION("PTP clock using the IXP46X timer"); | ||
332 | MODULE_LICENSE("GPL"); | ||