diff options
author | Chad Reese <kreese@caviumnetworks.com> | 2012-08-21 14:45:07 -0400 |
---|---|---|
committer | David Daney <david.daney@cavium.com> | 2012-08-31 14:49:18 -0400 |
commit | 3d305850261dfbf815eb7a0f0b768d4e1a11485a (patch) | |
tree | 450d288a21c6a50e68de38f5818c2badfa71cb2c /drivers/net/ethernet/octeon | |
parent | eeae05aa21695703e1979999a9a4a861447045c9 (diff) |
netdev: octeon_mgmt: Add hardware timestamp support.
Octeon cn6XXX models have timestamp support on the mgmt ports, so hook
it up.
Signed-off-by: Chad Reese <kreese@caviumnetworks.com>
Signed-off-by: David Daney <david.daney@cavium.com>
Acked-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/net/ethernet/octeon')
-rw-r--r-- | drivers/net/ethernet/octeon/octeon_mgmt.c | 157 |
1 files changed, 152 insertions, 5 deletions
diff --git a/drivers/net/ethernet/octeon/octeon_mgmt.c b/drivers/net/ethernet/octeon/octeon_mgmt.c index c4df1ab13b6..687a6a0c714 100644 --- a/drivers/net/ethernet/octeon/octeon_mgmt.c +++ b/drivers/net/ethernet/octeon/octeon_mgmt.c | |||
@@ -10,6 +10,7 @@ | |||
10 | #include <linux/dma-mapping.h> | 10 | #include <linux/dma-mapping.h> |
11 | #include <linux/etherdevice.h> | 11 | #include <linux/etherdevice.h> |
12 | #include <linux/capability.h> | 12 | #include <linux/capability.h> |
13 | #include <linux/net_tstamp.h> | ||
13 | #include <linux/interrupt.h> | 14 | #include <linux/interrupt.h> |
14 | #include <linux/netdevice.h> | 15 | #include <linux/netdevice.h> |
15 | #include <linux/spinlock.h> | 16 | #include <linux/spinlock.h> |
@@ -114,6 +115,7 @@ struct octeon_mgmt { | |||
114 | u64 agl_prt_ctl; | 115 | u64 agl_prt_ctl; |
115 | int port; | 116 | int port; |
116 | int irq; | 117 | int irq; |
118 | bool has_rx_tstamp; | ||
117 | u64 *tx_ring; | 119 | u64 *tx_ring; |
118 | dma_addr_t tx_ring_handle; | 120 | dma_addr_t tx_ring_handle; |
119 | unsigned int tx_next; | 121 | unsigned int tx_next; |
@@ -238,6 +240,28 @@ static void octeon_mgmt_rx_fill_ring(struct net_device *netdev) | |||
238 | } | 240 | } |
239 | } | 241 | } |
240 | 242 | ||
243 | static ktime_t ptp_to_ktime(u64 ptptime) | ||
244 | { | ||
245 | ktime_t ktimebase; | ||
246 | u64 ptpbase; | ||
247 | unsigned long flags; | ||
248 | |||
249 | local_irq_save(flags); | ||
250 | /* Fill the icache with the code */ | ||
251 | ktime_get_real(); | ||
252 | /* Flush all pending operations */ | ||
253 | mb(); | ||
254 | /* Read the time and PTP clock as close together as | ||
255 | * possible. It is important that this sequence take the same | ||
256 | * amount of time to reduce jitter | ||
257 | */ | ||
258 | ktimebase = ktime_get_real(); | ||
259 | ptpbase = cvmx_read_csr(CVMX_MIO_PTP_CLOCK_HI); | ||
260 | local_irq_restore(flags); | ||
261 | |||
262 | return ktime_sub_ns(ktimebase, ptpbase - ptptime); | ||
263 | } | ||
264 | |||
241 | static void octeon_mgmt_clean_tx_buffers(struct octeon_mgmt *p) | 265 | static void octeon_mgmt_clean_tx_buffers(struct octeon_mgmt *p) |
242 | { | 266 | { |
243 | union cvmx_mixx_orcnt mix_orcnt; | 267 | union cvmx_mixx_orcnt mix_orcnt; |
@@ -277,6 +301,20 @@ static void octeon_mgmt_clean_tx_buffers(struct octeon_mgmt *p) | |||
277 | 301 | ||
278 | dma_unmap_single(p->dev, re.s.addr, re.s.len, | 302 | dma_unmap_single(p->dev, re.s.addr, re.s.len, |
279 | DMA_TO_DEVICE); | 303 | DMA_TO_DEVICE); |
304 | |||
305 | /* Read the hardware TX timestamp if one was recorded */ | ||
306 | if (unlikely(re.s.tstamp)) { | ||
307 | struct skb_shared_hwtstamps ts; | ||
308 | /* Read the timestamp */ | ||
309 | u64 ns = cvmx_read_csr(CVMX_MIXX_TSTAMP(p->port)); | ||
310 | /* Remove the timestamp from the FIFO */ | ||
311 | cvmx_write_csr(CVMX_MIXX_TSCTL(p->port), 0); | ||
312 | /* Tell the kernel about the timestamp */ | ||
313 | ts.syststamp = ptp_to_ktime(ns); | ||
314 | ts.hwtstamp = ns_to_ktime(ns); | ||
315 | skb_tstamp_tx(skb, &ts); | ||
316 | } | ||
317 | |||
280 | dev_kfree_skb_any(skb); | 318 | dev_kfree_skb_any(skb); |
281 | cleaned++; | 319 | cleaned++; |
282 | 320 | ||
@@ -377,6 +415,16 @@ static int octeon_mgmt_receive_one(struct octeon_mgmt *p) | |||
377 | /* A good packet, send it up. */ | 415 | /* A good packet, send it up. */ |
378 | skb_put(skb, re.s.len); | 416 | skb_put(skb, re.s.len); |
379 | good: | 417 | good: |
418 | /* Process the RX timestamp if it was recorded */ | ||
419 | if (p->has_rx_tstamp) { | ||
420 | /* The first 8 bytes are the timestamp */ | ||
421 | u64 ns = *(u64 *)skb->data; | ||
422 | struct skb_shared_hwtstamps *ts; | ||
423 | ts = skb_hwtstamps(skb); | ||
424 | ts->hwtstamp = ns_to_ktime(ns); | ||
425 | ts->syststamp = ptp_to_ktime(ns); | ||
426 | __skb_pull(skb, 8); | ||
427 | } | ||
380 | skb->protocol = eth_type_trans(skb, netdev); | 428 | skb->protocol = eth_type_trans(skb, netdev); |
381 | netdev->stats.rx_packets++; | 429 | netdev->stats.rx_packets++; |
382 | netdev->stats.rx_bytes += skb->len; | 430 | netdev->stats.rx_bytes += skb->len; |
@@ -661,18 +709,114 @@ static irqreturn_t octeon_mgmt_interrupt(int cpl, void *dev_id) | |||
661 | return IRQ_HANDLED; | 709 | return IRQ_HANDLED; |
662 | } | 710 | } |
663 | 711 | ||
664 | static int octeon_mgmt_ioctl(struct net_device *netdev, | 712 | static int octeon_mgmt_ioctl_hwtstamp(struct net_device *netdev, |
665 | struct ifreq *rq, int cmd) | 713 | struct ifreq *rq, int cmd) |
666 | { | 714 | { |
667 | struct octeon_mgmt *p = netdev_priv(netdev); | 715 | struct octeon_mgmt *p = netdev_priv(netdev); |
716 | struct hwtstamp_config config; | ||
717 | union cvmx_mio_ptp_clock_cfg ptp; | ||
718 | union cvmx_agl_gmx_rxx_frm_ctl rxx_frm_ctl; | ||
719 | bool have_hw_timestamps = false; | ||
720 | |||
721 | if (copy_from_user(&config, rq->ifr_data, sizeof(config))) | ||
722 | return -EFAULT; | ||
668 | 723 | ||
669 | if (!netif_running(netdev)) | 724 | if (config.flags) /* reserved for future extensions */ |
670 | return -EINVAL; | 725 | return -EINVAL; |
671 | 726 | ||
672 | if (!p->phydev) | 727 | /* Check the status of hardware for tiemstamps */ |
728 | if (OCTEON_IS_MODEL(OCTEON_CN6XXX)) { | ||
729 | /* Get the current state of the PTP clock */ | ||
730 | ptp.u64 = cvmx_read_csr(CVMX_MIO_PTP_CLOCK_CFG); | ||
731 | if (!ptp.s.ext_clk_en) { | ||
732 | /* The clock has not been configured to use an | ||
733 | * external source. Program it to use the main clock | ||
734 | * reference. | ||
735 | */ | ||
736 | u64 clock_comp = (NSEC_PER_SEC << 32) / octeon_get_io_clock_rate(); | ||
737 | if (!ptp.s.ptp_en) | ||
738 | cvmx_write_csr(CVMX_MIO_PTP_CLOCK_COMP, clock_comp); | ||
739 | pr_info("PTP Clock: Using sclk reference at %lld Hz\n", | ||
740 | (NSEC_PER_SEC << 32) / clock_comp); | ||
741 | } else { | ||
742 | /* The clock is already programmed to use a GPIO */ | ||
743 | u64 clock_comp = cvmx_read_csr(CVMX_MIO_PTP_CLOCK_COMP); | ||
744 | pr_info("PTP Clock: Using GPIO %d at %lld Hz\n", | ||
745 | ptp.s.ext_clk_in, | ||
746 | (NSEC_PER_SEC << 32) / clock_comp); | ||
747 | } | ||
748 | |||
749 | /* Enable the clock if it wasn't done already */ | ||
750 | if (!ptp.s.ptp_en) { | ||
751 | ptp.s.ptp_en = 1; | ||
752 | cvmx_write_csr(CVMX_MIO_PTP_CLOCK_CFG, ptp.u64); | ||
753 | } | ||
754 | have_hw_timestamps = true; | ||
755 | } | ||
756 | |||
757 | if (!have_hw_timestamps) | ||
673 | return -EINVAL; | 758 | return -EINVAL; |
674 | 759 | ||
675 | return phy_mii_ioctl(p->phydev, rq, cmd); | 760 | switch (config.tx_type) { |
761 | case HWTSTAMP_TX_OFF: | ||
762 | case HWTSTAMP_TX_ON: | ||
763 | break; | ||
764 | default: | ||
765 | return -ERANGE; | ||
766 | } | ||
767 | |||
768 | switch (config.rx_filter) { | ||
769 | case HWTSTAMP_FILTER_NONE: | ||
770 | p->has_rx_tstamp = false; | ||
771 | rxx_frm_ctl.u64 = cvmx_read_csr(p->agl + AGL_GMX_RX_FRM_CTL); | ||
772 | rxx_frm_ctl.s.ptp_mode = 0; | ||
773 | cvmx_write_csr(p->agl + AGL_GMX_RX_FRM_CTL, rxx_frm_ctl.u64); | ||
774 | break; | ||
775 | case HWTSTAMP_FILTER_ALL: | ||
776 | case HWTSTAMP_FILTER_SOME: | ||
777 | case HWTSTAMP_FILTER_PTP_V1_L4_EVENT: | ||
778 | case HWTSTAMP_FILTER_PTP_V1_L4_SYNC: | ||
779 | case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ: | ||
780 | case HWTSTAMP_FILTER_PTP_V2_L4_EVENT: | ||
781 | case HWTSTAMP_FILTER_PTP_V2_L4_SYNC: | ||
782 | case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ: | ||
783 | case HWTSTAMP_FILTER_PTP_V2_L2_EVENT: | ||
784 | case HWTSTAMP_FILTER_PTP_V2_L2_SYNC: | ||
785 | case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ: | ||
786 | case HWTSTAMP_FILTER_PTP_V2_EVENT: | ||
787 | case HWTSTAMP_FILTER_PTP_V2_SYNC: | ||
788 | case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ: | ||
789 | p->has_rx_tstamp = have_hw_timestamps; | ||
790 | config.rx_filter = HWTSTAMP_FILTER_ALL; | ||
791 | if (p->has_rx_tstamp) { | ||
792 | rxx_frm_ctl.u64 = cvmx_read_csr(p->agl + AGL_GMX_RX_FRM_CTL); | ||
793 | rxx_frm_ctl.s.ptp_mode = 1; | ||
794 | cvmx_write_csr(p->agl + AGL_GMX_RX_FRM_CTL, rxx_frm_ctl.u64); | ||
795 | } | ||
796 | break; | ||
797 | default: | ||
798 | return -ERANGE; | ||
799 | } | ||
800 | |||
801 | if (copy_to_user(rq->ifr_data, &config, sizeof(config))) | ||
802 | return -EFAULT; | ||
803 | |||
804 | return 0; | ||
805 | } | ||
806 | |||
807 | static int octeon_mgmt_ioctl(struct net_device *netdev, | ||
808 | struct ifreq *rq, int cmd) | ||
809 | { | ||
810 | struct octeon_mgmt *p = netdev_priv(netdev); | ||
811 | |||
812 | switch (cmd) { | ||
813 | case SIOCSHWTSTAMP: | ||
814 | return octeon_mgmt_ioctl_hwtstamp(netdev, rq, cmd); | ||
815 | default: | ||
816 | if (p->phydev) | ||
817 | return phy_mii_ioctl(p->phydev, rq, cmd); | ||
818 | return -EINVAL; | ||
819 | } | ||
676 | } | 820 | } |
677 | 821 | ||
678 | static void octeon_mgmt_disable_link(struct octeon_mgmt *p) | 822 | static void octeon_mgmt_disable_link(struct octeon_mgmt *p) |
@@ -1052,6 +1196,7 @@ static int octeon_mgmt_open(struct net_device *netdev) | |||
1052 | /* Enable packet I/O. */ | 1196 | /* Enable packet I/O. */ |
1053 | 1197 | ||
1054 | rxx_frm_ctl.u64 = 0; | 1198 | rxx_frm_ctl.u64 = 0; |
1199 | rxx_frm_ctl.s.ptp_mode = p->has_rx_tstamp ? 1 : 0; | ||
1055 | rxx_frm_ctl.s.pre_align = 1; | 1200 | rxx_frm_ctl.s.pre_align = 1; |
1056 | /* | 1201 | /* |
1057 | * When set, disables the length check for non-min sized pkts | 1202 | * When set, disables the length check for non-min sized pkts |
@@ -1155,6 +1300,7 @@ static int octeon_mgmt_xmit(struct sk_buff *skb, struct net_device *netdev) | |||
1155 | int rv = NETDEV_TX_BUSY; | 1300 | int rv = NETDEV_TX_BUSY; |
1156 | 1301 | ||
1157 | re.d64 = 0; | 1302 | re.d64 = 0; |
1303 | re.s.tstamp = ((skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) != 0); | ||
1158 | re.s.len = skb->len; | 1304 | re.s.len = skb->len; |
1159 | re.s.addr = dma_map_single(p->dev, skb->data, | 1305 | re.s.addr = dma_map_single(p->dev, skb->data, |
1160 | skb->len, | 1306 | skb->len, |
@@ -1293,6 +1439,7 @@ static int __devinit octeon_mgmt_probe(struct platform_device *pdev) | |||
1293 | 1439 | ||
1294 | p->netdev = netdev; | 1440 | p->netdev = netdev; |
1295 | p->dev = &pdev->dev; | 1441 | p->dev = &pdev->dev; |
1442 | p->has_rx_tstamp = false; | ||
1296 | 1443 | ||
1297 | data = of_get_property(pdev->dev.of_node, "cell-index", &len); | 1444 | data = of_get_property(pdev->dev.of_node, "cell-index", &len); |
1298 | if (data && len == sizeof(*data)) { | 1445 | if (data && len == sizeof(*data)) { |