diff options
author | Frank Li <Frank.Li@freescale.com> | 2012-10-30 14:25:31 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2012-11-01 12:28:44 -0400 |
commit | 6605b730c061f67c44113391e5af5125d0672e99 (patch) | |
tree | f590a2528231b7ca20a9ca32457eb0128e1e107c /drivers/net/ethernet/freescale | |
parent | d6e0d9fcbb01edc7a342b15f077a217d46f3b608 (diff) |
FEC: Add time stamping code and a PTP hardware clock
This patch adds a driver for the FEC(MX6) that offers time
stamping and a PTP haderware clock. Because FEC\ENET(MX6)
hardware frequency adjustment is complex, we have implemented
this in software by changing the multiplication factor of the
timecounter.
Signed-off-by: Frank Li <Frank.Li@freescale.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/net/ethernet/freescale')
-rw-r--r-- | drivers/net/ethernet/freescale/Kconfig | 9 | ||||
-rw-r--r-- | drivers/net/ethernet/freescale/Makefile | 1 | ||||
-rw-r--r-- | drivers/net/ethernet/freescale/fec.c | 88 | ||||
-rw-r--r-- | drivers/net/ethernet/freescale/fec.h | 38 | ||||
-rw-r--r-- | drivers/net/ethernet/freescale/fec_ptp.c | 385 |
5 files changed, 520 insertions, 1 deletions
diff --git a/drivers/net/ethernet/freescale/Kconfig b/drivers/net/ethernet/freescale/Kconfig index feff51664dcf..ff3be53d0169 100644 --- a/drivers/net/ethernet/freescale/Kconfig +++ b/drivers/net/ethernet/freescale/Kconfig | |||
@@ -92,4 +92,13 @@ config GIANFAR | |||
92 | This driver supports the Gigabit TSEC on the MPC83xx, MPC85xx, | 92 | This driver supports the Gigabit TSEC on the MPC83xx, MPC85xx, |
93 | and MPC86xx family of chips, and the FEC on the 8540. | 93 | and MPC86xx family of chips, and the FEC on the 8540. |
94 | 94 | ||
95 | config FEC_PTP | ||
96 | bool "PTP Hardware Clock (PHC)" | ||
97 | depends on FEC | ||
98 | select PPS | ||
99 | select PTP_1588_CLOCK | ||
100 | --help--- | ||
101 | Say Y here if you want to use PTP Hardware Clock (PHC) in the | ||
102 | driver. Only the basic clock operations have been implemented. | ||
103 | |||
95 | endif # NET_VENDOR_FREESCALE | 104 | endif # NET_VENDOR_FREESCALE |
diff --git a/drivers/net/ethernet/freescale/Makefile b/drivers/net/ethernet/freescale/Makefile index 3d1839afff65..d4d19b3d00ae 100644 --- a/drivers/net/ethernet/freescale/Makefile +++ b/drivers/net/ethernet/freescale/Makefile | |||
@@ -3,6 +3,7 @@ | |||
3 | # | 3 | # |
4 | 4 | ||
5 | obj-$(CONFIG_FEC) += fec.o | 5 | obj-$(CONFIG_FEC) += fec.o |
6 | obj-$(CONFIG_FEC_PTP) += fec_ptp.o | ||
6 | obj-$(CONFIG_FEC_MPC52xx) += fec_mpc52xx.o | 7 | obj-$(CONFIG_FEC_MPC52xx) += fec_mpc52xx.o |
7 | ifeq ($(CONFIG_FEC_MPC52xx_MDIO),y) | 8 | ifeq ($(CONFIG_FEC_MPC52xx_MDIO),y) |
8 | obj-$(CONFIG_FEC_MPC52xx) += fec_mpc52xx_phy.o | 9 | obj-$(CONFIG_FEC_MPC52xx) += fec_mpc52xx_phy.o |
diff --git a/drivers/net/ethernet/freescale/fec.c b/drivers/net/ethernet/freescale/fec.c index d0e1b331e8e6..2665162ff4e5 100644 --- a/drivers/net/ethernet/freescale/fec.c +++ b/drivers/net/ethernet/freescale/fec.c | |||
@@ -280,6 +280,17 @@ fec_enet_start_xmit(struct sk_buff *skb, struct net_device *ndev) | |||
280 | | BD_ENET_TX_LAST | BD_ENET_TX_TC); | 280 | | BD_ENET_TX_LAST | BD_ENET_TX_TC); |
281 | bdp->cbd_sc = status; | 281 | bdp->cbd_sc = status; |
282 | 282 | ||
283 | #ifdef CONFIG_FEC_PTP | ||
284 | bdp->cbd_bdu = 0; | ||
285 | if (unlikely(skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP && | ||
286 | fep->hwts_tx_en)) { | ||
287 | bdp->cbd_esc = (BD_ENET_TX_TS | BD_ENET_TX_INT); | ||
288 | skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS; | ||
289 | } else { | ||
290 | |||
291 | bdp->cbd_esc = BD_ENET_TX_INT; | ||
292 | } | ||
293 | #endif | ||
283 | /* Trigger transmission start */ | 294 | /* Trigger transmission start */ |
284 | writel(0, fep->hwp + FEC_X_DES_ACTIVE); | 295 | writel(0, fep->hwp + FEC_X_DES_ACTIVE); |
285 | 296 | ||
@@ -437,10 +448,17 @@ fec_restart(struct net_device *ndev, int duplex) | |||
437 | writel(1 << 8, fep->hwp + FEC_X_WMRK); | 448 | writel(1 << 8, fep->hwp + FEC_X_WMRK); |
438 | } | 449 | } |
439 | 450 | ||
451 | #ifdef CONFIG_FEC_PTP | ||
452 | ecntl |= (1 << 4); | ||
453 | #endif | ||
454 | |||
440 | /* And last, enable the transmit and receive processing */ | 455 | /* And last, enable the transmit and receive processing */ |
441 | writel(ecntl, fep->hwp + FEC_ECNTRL); | 456 | writel(ecntl, fep->hwp + FEC_ECNTRL); |
442 | writel(0, fep->hwp + FEC_R_DES_ACTIVE); | 457 | writel(0, fep->hwp + FEC_R_DES_ACTIVE); |
443 | 458 | ||
459 | #ifdef CONFIG_FEC_PTP | ||
460 | fec_ptp_start_cyclecounter(ndev); | ||
461 | #endif | ||
444 | /* Enable interrupts we wish to service */ | 462 | /* Enable interrupts we wish to service */ |
445 | writel(FEC_DEFAULT_IMASK, fep->hwp + FEC_IMASK); | 463 | writel(FEC_DEFAULT_IMASK, fep->hwp + FEC_IMASK); |
446 | } | 464 | } |
@@ -526,6 +544,19 @@ fec_enet_tx(struct net_device *ndev) | |||
526 | ndev->stats.tx_packets++; | 544 | ndev->stats.tx_packets++; |
527 | } | 545 | } |
528 | 546 | ||
547 | #ifdef CONFIG_FEC_PTP | ||
548 | if (unlikely(skb_shinfo(skb)->tx_flags & SKBTX_IN_PROGRESS)) { | ||
549 | struct skb_shared_hwtstamps shhwtstamps; | ||
550 | unsigned long flags; | ||
551 | |||
552 | memset(&shhwtstamps, 0, sizeof(shhwtstamps)); | ||
553 | spin_lock_irqsave(&fep->tmreg_lock, flags); | ||
554 | shhwtstamps.hwtstamp = ns_to_ktime( | ||
555 | timecounter_cyc2time(&fep->tc, bdp->ts)); | ||
556 | spin_unlock_irqrestore(&fep->tmreg_lock, flags); | ||
557 | skb_tstamp_tx(skb, &shhwtstamps); | ||
558 | } | ||
559 | #endif | ||
529 | if (status & BD_ENET_TX_READY) | 560 | if (status & BD_ENET_TX_READY) |
530 | printk("HEY! Enet xmit interrupt and TX_READY.\n"); | 561 | printk("HEY! Enet xmit interrupt and TX_READY.\n"); |
531 | 562 | ||
@@ -652,6 +683,21 @@ fec_enet_rx(struct net_device *ndev) | |||
652 | skb_put(skb, pkt_len - 4); /* Make room */ | 683 | skb_put(skb, pkt_len - 4); /* Make room */ |
653 | skb_copy_to_linear_data(skb, data, pkt_len - 4); | 684 | skb_copy_to_linear_data(skb, data, pkt_len - 4); |
654 | skb->protocol = eth_type_trans(skb, ndev); | 685 | skb->protocol = eth_type_trans(skb, ndev); |
686 | #ifdef CONFIG_FEC_PTP | ||
687 | /* Get receive timestamp from the skb */ | ||
688 | if (fep->hwts_rx_en) { | ||
689 | struct skb_shared_hwtstamps *shhwtstamps = | ||
690 | skb_hwtstamps(skb); | ||
691 | unsigned long flags; | ||
692 | |||
693 | memset(shhwtstamps, 0, sizeof(*shhwtstamps)); | ||
694 | |||
695 | spin_lock_irqsave(&fep->tmreg_lock, flags); | ||
696 | shhwtstamps->hwtstamp = ns_to_ktime( | ||
697 | timecounter_cyc2time(&fep->tc, bdp->ts)); | ||
698 | spin_unlock_irqrestore(&fep->tmreg_lock, flags); | ||
699 | } | ||
700 | #endif | ||
655 | if (!skb_defer_rx_timestamp(skb)) | 701 | if (!skb_defer_rx_timestamp(skb)) |
656 | netif_rx(skb); | 702 | netif_rx(skb); |
657 | } | 703 | } |
@@ -666,6 +712,12 @@ rx_processing_done: | |||
666 | status |= BD_ENET_RX_EMPTY; | 712 | status |= BD_ENET_RX_EMPTY; |
667 | bdp->cbd_sc = status; | 713 | bdp->cbd_sc = status; |
668 | 714 | ||
715 | #ifdef CONFIG_FEC_PTP | ||
716 | bdp->cbd_esc = BD_ENET_RX_INT; | ||
717 | bdp->cbd_prot = 0; | ||
718 | bdp->cbd_bdu = 0; | ||
719 | #endif | ||
720 | |||
669 | /* Update BD pointer to next entry */ | 721 | /* Update BD pointer to next entry */ |
670 | if (status & BD_ENET_RX_WRAP) | 722 | if (status & BD_ENET_RX_WRAP) |
671 | bdp = fep->rx_bd_base; | 723 | bdp = fep->rx_bd_base; |
@@ -1105,6 +1157,10 @@ static int fec_enet_ioctl(struct net_device *ndev, struct ifreq *rq, int cmd) | |||
1105 | if (!phydev) | 1157 | if (!phydev) |
1106 | return -ENODEV; | 1158 | return -ENODEV; |
1107 | 1159 | ||
1160 | #ifdef CONFIG_FEC_PTP | ||
1161 | if (cmd == SIOCSHWTSTAMP) | ||
1162 | return fec_ptp_ioctl(ndev, rq, cmd); | ||
1163 | #endif | ||
1108 | return phy_mii_ioctl(phydev, rq, cmd); | 1164 | return phy_mii_ioctl(phydev, rq, cmd); |
1109 | } | 1165 | } |
1110 | 1166 | ||
@@ -1151,6 +1207,9 @@ static int fec_enet_alloc_buffers(struct net_device *ndev) | |||
1151 | bdp->cbd_bufaddr = dma_map_single(&fep->pdev->dev, skb->data, | 1207 | bdp->cbd_bufaddr = dma_map_single(&fep->pdev->dev, skb->data, |
1152 | FEC_ENET_RX_FRSIZE, DMA_FROM_DEVICE); | 1208 | FEC_ENET_RX_FRSIZE, DMA_FROM_DEVICE); |
1153 | bdp->cbd_sc = BD_ENET_RX_EMPTY; | 1209 | bdp->cbd_sc = BD_ENET_RX_EMPTY; |
1210 | #ifdef CONFIG_FEC_PTP | ||
1211 | bdp->cbd_esc = BD_ENET_RX_INT; | ||
1212 | #endif | ||
1154 | bdp++; | 1213 | bdp++; |
1155 | } | 1214 | } |
1156 | 1215 | ||
@@ -1164,6 +1223,10 @@ static int fec_enet_alloc_buffers(struct net_device *ndev) | |||
1164 | 1223 | ||
1165 | bdp->cbd_sc = 0; | 1224 | bdp->cbd_sc = 0; |
1166 | bdp->cbd_bufaddr = 0; | 1225 | bdp->cbd_bufaddr = 0; |
1226 | |||
1227 | #ifdef CONFIG_FEC_PTP | ||
1228 | bdp->cbd_esc = BD_ENET_RX_INT; | ||
1229 | #endif | ||
1167 | bdp++; | 1230 | bdp++; |
1168 | } | 1231 | } |
1169 | 1232 | ||
@@ -1565,9 +1628,19 @@ fec_probe(struct platform_device *pdev) | |||
1565 | goto failed_clk; | 1628 | goto failed_clk; |
1566 | } | 1629 | } |
1567 | 1630 | ||
1631 | #ifdef CONFIG_FEC_PTP | ||
1632 | fep->clk_ptp = devm_clk_get(&pdev->dev, "ptp"); | ||
1633 | if (IS_ERR(fep->clk_ptp)) { | ||
1634 | ret = PTR_ERR(fep->clk_ptp); | ||
1635 | goto failed_clk; | ||
1636 | } | ||
1637 | #endif | ||
1638 | |||
1568 | clk_prepare_enable(fep->clk_ahb); | 1639 | clk_prepare_enable(fep->clk_ahb); |
1569 | clk_prepare_enable(fep->clk_ipg); | 1640 | clk_prepare_enable(fep->clk_ipg); |
1570 | 1641 | #ifdef CONFIG_FEC_PTP | |
1642 | clk_prepare_enable(fep->clk_ptp); | ||
1643 | #endif | ||
1571 | reg_phy = devm_regulator_get(&pdev->dev, "phy"); | 1644 | reg_phy = devm_regulator_get(&pdev->dev, "phy"); |
1572 | if (!IS_ERR(reg_phy)) { | 1645 | if (!IS_ERR(reg_phy)) { |
1573 | ret = regulator_enable(reg_phy); | 1646 | ret = regulator_enable(reg_phy); |
@@ -1595,6 +1668,10 @@ fec_probe(struct platform_device *pdev) | |||
1595 | if (ret) | 1668 | if (ret) |
1596 | goto failed_register; | 1669 | goto failed_register; |
1597 | 1670 | ||
1671 | #ifdef CONFIG_FEC_PTP | ||
1672 | fec_ptp_init(ndev, pdev); | ||
1673 | #endif | ||
1674 | |||
1598 | return 0; | 1675 | return 0; |
1599 | 1676 | ||
1600 | failed_register: | 1677 | failed_register: |
@@ -1604,6 +1681,9 @@ failed_init: | |||
1604 | failed_regulator: | 1681 | failed_regulator: |
1605 | clk_disable_unprepare(fep->clk_ahb); | 1682 | clk_disable_unprepare(fep->clk_ahb); |
1606 | clk_disable_unprepare(fep->clk_ipg); | 1683 | clk_disable_unprepare(fep->clk_ipg); |
1684 | #ifdef CONFIG_FEC_PTP | ||
1685 | clk_disable_unprepare(fep->clk_ptp); | ||
1686 | #endif | ||
1607 | failed_pin: | 1687 | failed_pin: |
1608 | failed_clk: | 1688 | failed_clk: |
1609 | for (i = 0; i < FEC_IRQ_NUM; i++) { | 1689 | for (i = 0; i < FEC_IRQ_NUM; i++) { |
@@ -1636,6 +1716,12 @@ fec_drv_remove(struct platform_device *pdev) | |||
1636 | if (irq > 0) | 1716 | if (irq > 0) |
1637 | free_irq(irq, ndev); | 1717 | free_irq(irq, ndev); |
1638 | } | 1718 | } |
1719 | #ifdef CONFIG_FEC_PTP | ||
1720 | del_timer_sync(&fep->time_keep); | ||
1721 | clk_disable_unprepare(fep->clk_ptp); | ||
1722 | if (fep->ptp_clock) | ||
1723 | ptp_clock_unregister(fep->ptp_clock); | ||
1724 | #endif | ||
1639 | clk_disable_unprepare(fep->clk_ahb); | 1725 | clk_disable_unprepare(fep->clk_ahb); |
1640 | clk_disable_unprepare(fep->clk_ipg); | 1726 | clk_disable_unprepare(fep->clk_ipg); |
1641 | iounmap(fep->hwp); | 1727 | iounmap(fep->hwp); |
diff --git a/drivers/net/ethernet/freescale/fec.h b/drivers/net/ethernet/freescale/fec.h index e803812975df..c5a3bc1475c7 100644 --- a/drivers/net/ethernet/freescale/fec.h +++ b/drivers/net/ethernet/freescale/fec.h | |||
@@ -13,6 +13,12 @@ | |||
13 | #define FEC_H | 13 | #define FEC_H |
14 | /****************************************************************************/ | 14 | /****************************************************************************/ |
15 | 15 | ||
16 | #ifdef CONFIG_FEC_PTP | ||
17 | #include <linux/clocksource.h> | ||
18 | #include <linux/net_tstamp.h> | ||
19 | #include <linux/ptp_clock_kernel.h> | ||
20 | #endif | ||
21 | |||
16 | #if defined(CONFIG_M523x) || defined(CONFIG_M527x) || defined(CONFIG_M528x) || \ | 22 | #if defined(CONFIG_M523x) || defined(CONFIG_M527x) || defined(CONFIG_M528x) || \ |
17 | defined(CONFIG_M520x) || defined(CONFIG_M532x) || \ | 23 | defined(CONFIG_M520x) || defined(CONFIG_M532x) || \ |
18 | defined(CONFIG_ARCH_MXC) || defined(CONFIG_SOC_IMX28) | 24 | defined(CONFIG_ARCH_MXC) || defined(CONFIG_SOC_IMX28) |
@@ -88,6 +94,13 @@ struct bufdesc { | |||
88 | unsigned short cbd_datlen; /* Data length */ | 94 | unsigned short cbd_datlen; /* Data length */ |
89 | unsigned short cbd_sc; /* Control and status info */ | 95 | unsigned short cbd_sc; /* Control and status info */ |
90 | unsigned long cbd_bufaddr; /* Buffer address */ | 96 | unsigned long cbd_bufaddr; /* Buffer address */ |
97 | #ifdef CONFIG_FEC_PTP | ||
98 | unsigned long cbd_esc; | ||
99 | unsigned long cbd_prot; | ||
100 | unsigned long cbd_bdu; | ||
101 | unsigned long ts; | ||
102 | unsigned short res0[4]; | ||
103 | #endif | ||
91 | }; | 104 | }; |
92 | #else | 105 | #else |
93 | struct bufdesc { | 106 | struct bufdesc { |
@@ -190,6 +203,9 @@ struct fec_enet_private { | |||
190 | 203 | ||
191 | struct clk *clk_ipg; | 204 | struct clk *clk_ipg; |
192 | struct clk *clk_ahb; | 205 | struct clk *clk_ahb; |
206 | #ifdef CONFIG_FEC_PTP | ||
207 | struct clk *clk_ptp; | ||
208 | #endif | ||
193 | 209 | ||
194 | /* The saved address of a sent-in-place packet/buffer, for skfree(). */ | 210 | /* The saved address of a sent-in-place packet/buffer, for skfree(). */ |
195 | unsigned char *tx_bounce[TX_RING_SIZE]; | 211 | unsigned char *tx_bounce[TX_RING_SIZE]; |
@@ -227,7 +243,29 @@ struct fec_enet_private { | |||
227 | int full_duplex; | 243 | int full_duplex; |
228 | struct completion mdio_done; | 244 | struct completion mdio_done; |
229 | int irq[FEC_IRQ_NUM]; | 245 | int irq[FEC_IRQ_NUM]; |
246 | |||
247 | #ifdef CONFIG_FEC_PTP | ||
248 | struct ptp_clock *ptp_clock; | ||
249 | struct ptp_clock_info ptp_caps; | ||
250 | unsigned long last_overflow_check; | ||
251 | spinlock_t tmreg_lock; | ||
252 | struct cyclecounter cc; | ||
253 | struct timecounter tc; | ||
254 | int rx_hwtstamp_filter; | ||
255 | u32 base_incval; | ||
256 | u32 cycle_speed; | ||
257 | int hwts_rx_en; | ||
258 | int hwts_tx_en; | ||
259 | struct timer_list time_keep; | ||
260 | #endif | ||
261 | |||
230 | }; | 262 | }; |
231 | 263 | ||
264 | #ifdef CONFIG_FEC_PTP | ||
265 | void fec_ptp_init(struct net_device *ndev, struct platform_device *pdev); | ||
266 | void fec_ptp_start_cyclecounter(struct net_device *ndev); | ||
267 | int fec_ptp_ioctl(struct net_device *ndev, struct ifreq *ifr, int cmd); | ||
268 | #endif | ||
269 | |||
232 | /****************************************************************************/ | 270 | /****************************************************************************/ |
233 | #endif /* FEC_H */ | 271 | #endif /* FEC_H */ |
diff --git a/drivers/net/ethernet/freescale/fec_ptp.c b/drivers/net/ethernet/freescale/fec_ptp.c new file mode 100644 index 000000000000..5352140468ce --- /dev/null +++ b/drivers/net/ethernet/freescale/fec_ptp.c | |||
@@ -0,0 +1,385 @@ | |||
1 | /* | ||
2 | * Fast Ethernet Controller (ENET) PTP driver for MX6x. | ||
3 | * | ||
4 | * Copyright (C) 2012 Freescale Semiconductor, Inc. | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify it | ||
7 | * under the terms and conditions of the GNU General Public License, | ||
8 | * version 2, as published by the Free Software Foundation. | ||
9 | * | ||
10 | * This program is distributed in the hope it will be useful, but WITHOUT | ||
11 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
12 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
13 | * more details. | ||
14 | * | ||
15 | * You should have received a copy of the GNU General Public License along with | ||
16 | * this program; if not, write to the Free Software Foundation, Inc., | ||
17 | * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. | ||
18 | */ | ||
19 | |||
20 | #include <linux/module.h> | ||
21 | #include <linux/kernel.h> | ||
22 | #include <linux/string.h> | ||
23 | #include <linux/ptrace.h> | ||
24 | #include <linux/errno.h> | ||
25 | #include <linux/ioport.h> | ||
26 | #include <linux/slab.h> | ||
27 | #include <linux/interrupt.h> | ||
28 | #include <linux/pci.h> | ||
29 | #include <linux/init.h> | ||
30 | #include <linux/delay.h> | ||
31 | #include <linux/netdevice.h> | ||
32 | #include <linux/etherdevice.h> | ||
33 | #include <linux/skbuff.h> | ||
34 | #include <linux/spinlock.h> | ||
35 | #include <linux/workqueue.h> | ||
36 | #include <linux/bitops.h> | ||
37 | #include <linux/io.h> | ||
38 | #include <linux/irq.h> | ||
39 | #include <linux/clk.h> | ||
40 | #include <linux/platform_device.h> | ||
41 | #include <linux/phy.h> | ||
42 | #include <linux/fec.h> | ||
43 | #include <linux/of.h> | ||
44 | #include <linux/of_device.h> | ||
45 | #include <linux/of_gpio.h> | ||
46 | #include <linux/of_net.h> | ||
47 | |||
48 | #include "fec.h" | ||
49 | |||
50 | /* FEC 1588 register bits */ | ||
51 | #define FEC_T_CTRL_SLAVE 0x00002000 | ||
52 | #define FEC_T_CTRL_CAPTURE 0x00000800 | ||
53 | #define FEC_T_CTRL_RESTART 0x00000200 | ||
54 | #define FEC_T_CTRL_PERIOD_RST 0x00000030 | ||
55 | #define FEC_T_CTRL_PERIOD_EN 0x00000010 | ||
56 | #define FEC_T_CTRL_ENABLE 0x00000001 | ||
57 | |||
58 | #define FEC_T_INC_MASK 0x0000007f | ||
59 | #define FEC_T_INC_OFFSET 0 | ||
60 | #define FEC_T_INC_CORR_MASK 0x00007f00 | ||
61 | #define FEC_T_INC_CORR_OFFSET 8 | ||
62 | |||
63 | #define FEC_ATIME_CTRL 0x400 | ||
64 | #define FEC_ATIME 0x404 | ||
65 | #define FEC_ATIME_EVT_OFFSET 0x408 | ||
66 | #define FEC_ATIME_EVT_PERIOD 0x40c | ||
67 | #define FEC_ATIME_CORR 0x410 | ||
68 | #define FEC_ATIME_INC 0x414 | ||
69 | #define FEC_TS_TIMESTAMP 0x418 | ||
70 | |||
71 | #define FEC_CC_MULT (1 << 31) | ||
72 | /** | ||
73 | * fec_ptp_read - read raw cycle counter (to be used by time counter) | ||
74 | * @cc: the cyclecounter structure | ||
75 | * | ||
76 | * this function reads the cyclecounter registers and is called by the | ||
77 | * cyclecounter structure used to construct a ns counter from the | ||
78 | * arbitrary fixed point registers | ||
79 | */ | ||
80 | static cycle_t fec_ptp_read(const struct cyclecounter *cc) | ||
81 | { | ||
82 | struct fec_enet_private *fep = | ||
83 | container_of(cc, struct fec_enet_private, cc); | ||
84 | u32 tempval; | ||
85 | |||
86 | tempval = readl(fep->hwp + FEC_ATIME_CTRL); | ||
87 | tempval |= FEC_T_CTRL_CAPTURE; | ||
88 | writel(tempval, fep->hwp + FEC_ATIME_CTRL); | ||
89 | |||
90 | return readl(fep->hwp + FEC_ATIME); | ||
91 | } | ||
92 | |||
93 | /** | ||
94 | * fec_ptp_start_cyclecounter - create the cycle counter from hw | ||
95 | * @ndev: network device | ||
96 | * | ||
97 | * this function initializes the timecounter and cyclecounter | ||
98 | * structures for use in generated a ns counter from the arbitrary | ||
99 | * fixed point cycles registers in the hardware. | ||
100 | */ | ||
101 | void fec_ptp_start_cyclecounter(struct net_device *ndev) | ||
102 | { | ||
103 | struct fec_enet_private *fep = netdev_priv(ndev); | ||
104 | unsigned long flags; | ||
105 | int inc; | ||
106 | |||
107 | inc = 1000000000 / clk_get_rate(fep->clk_ptp); | ||
108 | |||
109 | /* grab the ptp lock */ | ||
110 | spin_lock_irqsave(&fep->tmreg_lock, flags); | ||
111 | |||
112 | /* 1ns counter */ | ||
113 | writel(inc << FEC_T_INC_OFFSET, fep->hwp + FEC_ATIME_INC); | ||
114 | |||
115 | /* use free running count */ | ||
116 | writel(0, fep->hwp + FEC_ATIME_EVT_PERIOD); | ||
117 | |||
118 | writel(FEC_T_CTRL_ENABLE, fep->hwp + FEC_ATIME_CTRL); | ||
119 | |||
120 | memset(&fep->cc, 0, sizeof(fep->cc)); | ||
121 | fep->cc.read = fec_ptp_read; | ||
122 | fep->cc.mask = CLOCKSOURCE_MASK(32); | ||
123 | fep->cc.shift = 31; | ||
124 | fep->cc.mult = FEC_CC_MULT; | ||
125 | |||
126 | /* reset the ns time counter */ | ||
127 | timecounter_init(&fep->tc, &fep->cc, ktime_to_ns(ktime_get_real())); | ||
128 | |||
129 | spin_unlock_irqrestore(&fep->tmreg_lock, flags); | ||
130 | } | ||
131 | |||
132 | /** | ||
133 | * fec_ptp_adjfreq - adjust ptp cycle frequency | ||
134 | * @ptp: the ptp clock structure | ||
135 | * @ppb: parts per billion adjustment from base | ||
136 | * | ||
137 | * Adjust the frequency of the ptp cycle counter by the | ||
138 | * indicated ppb from the base frequency. | ||
139 | * | ||
140 | * Because ENET hardware frequency adjust is complex, | ||
141 | * using software method to do that. | ||
142 | */ | ||
143 | static int fec_ptp_adjfreq(struct ptp_clock_info *ptp, s32 ppb) | ||
144 | { | ||
145 | u64 diff; | ||
146 | unsigned long flags; | ||
147 | int neg_adj = 0; | ||
148 | |||
149 | struct fec_enet_private *fep = | ||
150 | container_of(ptp, struct fec_enet_private, ptp_caps); | ||
151 | |||
152 | if (ppb < 0) { | ||
153 | ppb = -ppb; | ||
154 | neg_adj = 1; | ||
155 | } | ||
156 | |||
157 | spin_lock_irqsave(&fep->tmreg_lock, flags); | ||
158 | /* | ||
159 | * dummy read to set cycle_last in tc to now. | ||
160 | * So use adjusted mult to calculate when next call | ||
161 | * timercounter_read. | ||
162 | */ | ||
163 | timecounter_read(&fep->tc); | ||
164 | fep->cc.mult = FEC_CC_MULT; | ||
165 | diff = fep->cc.mult; | ||
166 | diff *= ppb; | ||
167 | diff = div_u64(diff, 1000000000ULL); | ||
168 | |||
169 | if (neg_adj) | ||
170 | fep->cc.mult -= diff; | ||
171 | else | ||
172 | fep->cc.mult += diff; | ||
173 | |||
174 | spin_unlock_irqrestore(&fep->tmreg_lock, flags); | ||
175 | |||
176 | return 0; | ||
177 | } | ||
178 | |||
179 | /** | ||
180 | * fec_ptp_adjtime | ||
181 | * @ptp: the ptp clock structure | ||
182 | * @delta: offset to adjust the cycle counter by | ||
183 | * | ||
184 | * adjust the timer by resetting the timecounter structure. | ||
185 | */ | ||
186 | static int fec_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta) | ||
187 | { | ||
188 | struct fec_enet_private *fep = | ||
189 | container_of(ptp, struct fec_enet_private, ptp_caps); | ||
190 | unsigned long flags; | ||
191 | u64 now; | ||
192 | |||
193 | spin_lock_irqsave(&fep->tmreg_lock, flags); | ||
194 | |||
195 | now = timecounter_read(&fep->tc); | ||
196 | now += delta; | ||
197 | |||
198 | /* reset the timecounter */ | ||
199 | timecounter_init(&fep->tc, &fep->cc, now); | ||
200 | |||
201 | spin_unlock_irqrestore(&fep->tmreg_lock, flags); | ||
202 | |||
203 | return 0; | ||
204 | } | ||
205 | |||
206 | /** | ||
207 | * fec_ptp_gettime | ||
208 | * @ptp: the ptp clock structure | ||
209 | * @ts: timespec structure to hold the current time value | ||
210 | * | ||
211 | * read the timecounter and return the correct value on ns, | ||
212 | * after converting it into a struct timespec. | ||
213 | */ | ||
214 | static int fec_ptp_gettime(struct ptp_clock_info *ptp, struct timespec *ts) | ||
215 | { | ||
216 | struct fec_enet_private *adapter = | ||
217 | container_of(ptp, struct fec_enet_private, ptp_caps); | ||
218 | u64 ns; | ||
219 | u32 remainder; | ||
220 | unsigned long flags; | ||
221 | |||
222 | spin_lock_irqsave(&adapter->tmreg_lock, flags); | ||
223 | ns = timecounter_read(&adapter->tc); | ||
224 | spin_unlock_irqrestore(&adapter->tmreg_lock, flags); | ||
225 | |||
226 | ts->tv_sec = div_u64_rem(ns, 1000000000ULL, &remainder); | ||
227 | ts->tv_nsec = remainder; | ||
228 | |||
229 | return 0; | ||
230 | } | ||
231 | |||
232 | /** | ||
233 | * fec_ptp_settime | ||
234 | * @ptp: the ptp clock structure | ||
235 | * @ts: the timespec containing the new time for the cycle counter | ||
236 | * | ||
237 | * reset the timecounter to use a new base value instead of the kernel | ||
238 | * wall timer value. | ||
239 | */ | ||
240 | static int fec_ptp_settime(struct ptp_clock_info *ptp, | ||
241 | const struct timespec *ts) | ||
242 | { | ||
243 | struct fec_enet_private *fep = | ||
244 | container_of(ptp, struct fec_enet_private, ptp_caps); | ||
245 | |||
246 | u64 ns; | ||
247 | unsigned long flags; | ||
248 | |||
249 | ns = ts->tv_sec * 1000000000ULL; | ||
250 | ns += ts->tv_nsec; | ||
251 | |||
252 | spin_lock_irqsave(&fep->tmreg_lock, flags); | ||
253 | timecounter_init(&fep->tc, &fep->cc, ns); | ||
254 | spin_unlock_irqrestore(&fep->tmreg_lock, flags); | ||
255 | return 0; | ||
256 | } | ||
257 | |||
258 | /** | ||
259 | * fec_ptp_enable | ||
260 | * @ptp: the ptp clock structure | ||
261 | * @rq: the requested feature to change | ||
262 | * @on: whether to enable or disable the feature | ||
263 | * | ||
264 | */ | ||
265 | static int fec_ptp_enable(struct ptp_clock_info *ptp, | ||
266 | struct ptp_clock_request *rq, int on) | ||
267 | { | ||
268 | return -EOPNOTSUPP; | ||
269 | } | ||
270 | |||
271 | /** | ||
272 | * fec_ptp_hwtstamp_ioctl - control hardware time stamping | ||
273 | * @ndev: pointer to net_device | ||
274 | * @ifreq: ioctl data | ||
275 | * @cmd: particular ioctl requested | ||
276 | */ | ||
277 | int fec_ptp_ioctl(struct net_device *ndev, struct ifreq *ifr, int cmd) | ||
278 | { | ||
279 | struct fec_enet_private *fep = netdev_priv(ndev); | ||
280 | |||
281 | struct hwtstamp_config config; | ||
282 | |||
283 | if (copy_from_user(&config, ifr->ifr_data, sizeof(config))) | ||
284 | return -EFAULT; | ||
285 | |||
286 | /* reserved for future extensions */ | ||
287 | if (config.flags) | ||
288 | return -EINVAL; | ||
289 | |||
290 | switch (config.tx_type) { | ||
291 | case HWTSTAMP_TX_OFF: | ||
292 | fep->hwts_tx_en = 0; | ||
293 | break; | ||
294 | case HWTSTAMP_TX_ON: | ||
295 | fep->hwts_tx_en = 1; | ||
296 | break; | ||
297 | default: | ||
298 | return -ERANGE; | ||
299 | } | ||
300 | |||
301 | switch (config.rx_filter) { | ||
302 | case HWTSTAMP_FILTER_NONE: | ||
303 | if (fep->hwts_rx_en) | ||
304 | fep->hwts_rx_en = 0; | ||
305 | config.rx_filter = HWTSTAMP_FILTER_NONE; | ||
306 | break; | ||
307 | |||
308 | default: | ||
309 | /* | ||
310 | * register RXMTRL must be set in order to do V1 packets, | ||
311 | * therefore it is not possible to time stamp both V1 Sync and | ||
312 | * Delay_Req messages and hardware does not support | ||
313 | * timestamping all packets => return error | ||
314 | */ | ||
315 | fep->hwts_rx_en = 1; | ||
316 | config.rx_filter = HWTSTAMP_FILTER_ALL; | ||
317 | break; | ||
318 | } | ||
319 | |||
320 | return copy_to_user(ifr->ifr_data, &config, sizeof(config)) ? | ||
321 | -EFAULT : 0; | ||
322 | } | ||
323 | |||
324 | /** | ||
325 | * fec_time_keep - call timecounter_read every second to avoid timer overrun | ||
326 | * because ENET just support 32bit counter, will timeout in 4s | ||
327 | */ | ||
328 | static void fec_time_keep(unsigned long _data) | ||
329 | { | ||
330 | struct fec_enet_private *fep = (struct fec_enet_private *)_data; | ||
331 | u64 ns; | ||
332 | unsigned long flags; | ||
333 | |||
334 | spin_lock_irqsave(&fep->tmreg_lock, flags); | ||
335 | ns = timecounter_read(&fep->tc); | ||
336 | spin_unlock_irqrestore(&fep->tmreg_lock, flags); | ||
337 | |||
338 | mod_timer(&fep->time_keep, jiffies + HZ); | ||
339 | } | ||
340 | |||
341 | /** | ||
342 | * fec_ptp_init | ||
343 | * @ndev: The FEC network adapter | ||
344 | * | ||
345 | * This function performs the required steps for enabling ptp | ||
346 | * support. If ptp support has already been loaded it simply calls the | ||
347 | * cyclecounter init routine and exits. | ||
348 | */ | ||
349 | |||
350 | void fec_ptp_init(struct net_device *ndev, struct platform_device *pdev) | ||
351 | { | ||
352 | struct fec_enet_private *fep = netdev_priv(ndev); | ||
353 | |||
354 | fep->ptp_caps.owner = THIS_MODULE; | ||
355 | snprintf(fep->ptp_caps.name, 16, "fec ptp"); | ||
356 | |||
357 | fep->ptp_caps.max_adj = 250000000; | ||
358 | fep->ptp_caps.n_alarm = 0; | ||
359 | fep->ptp_caps.n_ext_ts = 0; | ||
360 | fep->ptp_caps.n_per_out = 0; | ||
361 | fep->ptp_caps.pps = 0; | ||
362 | fep->ptp_caps.adjfreq = fec_ptp_adjfreq; | ||
363 | fep->ptp_caps.adjtime = fec_ptp_adjtime; | ||
364 | fep->ptp_caps.gettime = fec_ptp_gettime; | ||
365 | fep->ptp_caps.settime = fec_ptp_settime; | ||
366 | fep->ptp_caps.enable = fec_ptp_enable; | ||
367 | |||
368 | spin_lock_init(&fep->tmreg_lock); | ||
369 | |||
370 | fec_ptp_start_cyclecounter(ndev); | ||
371 | |||
372 | init_timer(&fep->time_keep); | ||
373 | fep->time_keep.data = (unsigned long)fep; | ||
374 | fep->time_keep.function = fec_time_keep; | ||
375 | fep->time_keep.expires = jiffies + HZ; | ||
376 | add_timer(&fep->time_keep); | ||
377 | |||
378 | fep->ptp_clock = ptp_clock_register(&fep->ptp_caps, &pdev->dev); | ||
379 | if (IS_ERR(fep->ptp_clock)) { | ||
380 | fep->ptp_clock = NULL; | ||
381 | pr_err("ptp_clock_register failed\n"); | ||
382 | } else { | ||
383 | pr_info("registered PHC device on %s\n", ndev->name); | ||
384 | } | ||
385 | } | ||