diff options
author | Barry Song <barry.song@analog.com> | 2010-05-17 17:19:40 -0700 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2010-05-17 17:19:40 -0700 |
commit | fe92afedee23e1d91f0133360a24d2bf48270739 (patch) | |
tree | 20564c377f57af2a94644abcd18b953a6aba8c7c /drivers/net/bfin_mac.c | |
parent | ab6e3feba1f1bc3b9418b854da6f481408d243de (diff) | |
download | op-kernel-dev-fe92afedee23e1d91f0133360a24d2bf48270739.zip op-kernel-dev-fe92afedee23e1d91f0133360a24d2bf48270739.tar.gz |
netdev: bfin_mac: add support for IEEE 1588 PTP
Newer on-chip MAC peripherals support IEEE 1588 PTP in the hardware, so
extend the driver to support this functionality.
Signed-off-by: Barry Song <barry.song@analog.com>
Signed-off-by: Mike Frysinger <vapier@gentoo.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/net/bfin_mac.c')
-rw-r--r-- | drivers/net/bfin_mac.c | 341 |
1 files changed, 340 insertions, 1 deletions
diff --git a/drivers/net/bfin_mac.c b/drivers/net/bfin_mac.c index 3554870..a173d8a 100644 --- a/drivers/net/bfin_mac.c +++ b/drivers/net/bfin_mac.c @@ -33,6 +33,7 @@ #include <asm/dma.h> #include <linux/dma-mapping.h> +#include <asm/div64.h> #include <asm/dpmc.h> #include <asm/blackfin.h> #include <asm/cacheflush.h> @@ -551,6 +552,309 @@ static int bfin_mac_set_mac_address(struct net_device *dev, void *p) return 0; } +#ifdef CONFIG_BFIN_MAC_USE_HWSTAMP +#define bfin_mac_hwtstamp_is_none(cfg) ((cfg) == HWTSTAMP_FILTER_NONE) + +static int bfin_mac_hwtstamp_ioctl(struct net_device *netdev, + struct ifreq *ifr, int cmd) +{ + struct hwtstamp_config config; + struct bfin_mac_local *lp = netdev_priv(netdev); + u16 ptpctl; + u32 ptpfv1, ptpfv2, ptpfv3, ptpfoff; + + if (copy_from_user(&config, ifr->ifr_data, sizeof(config))) + return -EFAULT; + + pr_debug("%s config flag:0x%x, tx_type:0x%x, rx_filter:0x%x\n", + __func__, config.flags, config.tx_type, config.rx_filter); + + /* reserved for future extensions */ + if (config.flags) + return -EINVAL; + + if ((config.tx_type != HWTSTAMP_TX_OFF) && + (config.tx_type != HWTSTAMP_TX_ON)) + return -ERANGE; + + ptpctl = bfin_read_EMAC_PTP_CTL(); + + switch (config.rx_filter) { + case HWTSTAMP_FILTER_NONE: + /* + * Dont allow any timestamping + */ + ptpfv3 = 0xFFFFFFFF; + bfin_write_EMAC_PTP_FV3(ptpfv3); + break; + case HWTSTAMP_FILTER_PTP_V1_L4_EVENT: + case HWTSTAMP_FILTER_PTP_V1_L4_SYNC: + case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ: + /* + * Clear the five comparison mask bits (bits[12:8]) in EMAC_PTP_CTL) + * to enable all the field matches. + */ + ptpctl &= ~0x1F00; + bfin_write_EMAC_PTP_CTL(ptpctl); + /* + * Keep the default values of the EMAC_PTP_FOFF register. + */ + ptpfoff = 0x4A24170C; + bfin_write_EMAC_PTP_FOFF(ptpfoff); + /* + * Keep the default values of the EMAC_PTP_FV1 and EMAC_PTP_FV2 + * registers. + */ + ptpfv1 = 0x11040800; + bfin_write_EMAC_PTP_FV1(ptpfv1); + ptpfv2 = 0x0140013F; + bfin_write_EMAC_PTP_FV2(ptpfv2); + /* + * The default value (0xFFFC) allows the timestamping of both + * received Sync messages and Delay_Req messages. + */ + ptpfv3 = 0xFFFFFFFC; + bfin_write_EMAC_PTP_FV3(ptpfv3); + + config.rx_filter = HWTSTAMP_FILTER_PTP_V1_L4_EVENT; + break; + case HWTSTAMP_FILTER_PTP_V2_L4_EVENT: + case HWTSTAMP_FILTER_PTP_V2_L4_SYNC: + case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ: + /* Clear all five comparison mask bits (bits[12:8]) in the + * EMAC_PTP_CTL register to enable all the field matches. + */ + ptpctl &= ~0x1F00; + bfin_write_EMAC_PTP_CTL(ptpctl); + /* + * Keep the default values of the EMAC_PTP_FOFF register, except set + * the PTPCOF field to 0x2A. + */ + ptpfoff = 0x2A24170C; + bfin_write_EMAC_PTP_FOFF(ptpfoff); + /* + * Keep the default values of the EMAC_PTP_FV1 and EMAC_PTP_FV2 + * registers. + */ + ptpfv1 = 0x11040800; + bfin_write_EMAC_PTP_FV1(ptpfv1); + ptpfv2 = 0x0140013F; + bfin_write_EMAC_PTP_FV2(ptpfv2); + /* + * To allow the timestamping of Pdelay_Req and Pdelay_Resp, set + * the value to 0xFFF0. + */ + ptpfv3 = 0xFFFFFFF0; + bfin_write_EMAC_PTP_FV3(ptpfv3); + + config.rx_filter = HWTSTAMP_FILTER_PTP_V2_L4_EVENT; + break; + case HWTSTAMP_FILTER_PTP_V2_L2_EVENT: + case HWTSTAMP_FILTER_PTP_V2_L2_SYNC: + case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ: + /* + * Clear bits 8 and 12 of the EMAC_PTP_CTL register to enable only the + * EFTM and PTPCM field comparison. + */ + ptpctl &= ~0x1100; + bfin_write_EMAC_PTP_CTL(ptpctl); + /* + * Keep the default values of all the fields of the EMAC_PTP_FOFF + * register, except set the PTPCOF field to 0x0E. + */ + ptpfoff = 0x0E24170C; + bfin_write_EMAC_PTP_FOFF(ptpfoff); + /* + * Program bits [15:0] of the EMAC_PTP_FV1 register to 0x88F7, which + * corresponds to PTP messages on the MAC layer. + */ + ptpfv1 = 0x110488F7; + bfin_write_EMAC_PTP_FV1(ptpfv1); + ptpfv2 = 0x0140013F; + bfin_write_EMAC_PTP_FV2(ptpfv2); + /* + * To allow the timestamping of Pdelay_Req and Pdelay_Resp + * messages, set the value to 0xFFF0. + */ + ptpfv3 = 0xFFFFFFF0; + bfin_write_EMAC_PTP_FV3(ptpfv3); + + config.rx_filter = HWTSTAMP_FILTER_PTP_V2_L2_EVENT; + break; + default: + return -ERANGE; + } + + if (config.tx_type == HWTSTAMP_TX_OFF && + bfin_mac_hwtstamp_is_none(config.rx_filter)) { + ptpctl &= ~PTP_EN; + bfin_write_EMAC_PTP_CTL(ptpctl); + + SSYNC(); + } else { + ptpctl |= PTP_EN; + bfin_write_EMAC_PTP_CTL(ptpctl); + + /* + * clear any existing timestamp + */ + bfin_read_EMAC_PTP_RXSNAPLO(); + bfin_read_EMAC_PTP_RXSNAPHI(); + + bfin_read_EMAC_PTP_TXSNAPLO(); + bfin_read_EMAC_PTP_TXSNAPHI(); + + /* + * Set registers so that rollover occurs soon to test this. + */ + bfin_write_EMAC_PTP_TIMELO(0x00000000); + bfin_write_EMAC_PTP_TIMEHI(0xFF800000); + + SSYNC(); + + lp->compare.last_update = 0; + timecounter_init(&lp->clock, + &lp->cycles, + ktime_to_ns(ktime_get_real())); + timecompare_update(&lp->compare, 0); + } + + lp->stamp_cfg = config; + return copy_to_user(ifr->ifr_data, &config, sizeof(config)) ? + -EFAULT : 0; +} + +static void bfin_dump_hwtamp(char *s, ktime_t *hw, ktime_t *ts, struct timecompare *cmp) +{ + ktime_t sys = ktime_get_real(); + + pr_debug("%s %s hardware:%d,%d transform system:%d,%d system:%d,%d, cmp:%lld, %lld\n", + __func__, s, hw->tv.sec, hw->tv.nsec, ts->tv.sec, ts->tv.nsec, sys.tv.sec, + sys.tv.nsec, cmp->offset, cmp->skew); +} + +static void bfin_tx_hwtstamp(struct net_device *netdev, struct sk_buff *skb) +{ + struct bfin_mac_local *lp = netdev_priv(netdev); + union skb_shared_tx *shtx = skb_tx(skb); + + if (shtx->hardware) { + int timeout_cnt = MAX_TIMEOUT_CNT; + + /* When doing time stamping, keep the connection to the socket + * a while longer + */ + shtx->in_progress = 1; + + /* + * The timestamping is done at the EMAC module's MII/RMII interface + * when the module sees the Start of Frame of an event message packet. This + * interface is the closest possible place to the physical Ethernet transmission + * medium, providing the best timing accuracy. + */ + while ((!(bfin_read_EMAC_PTP_ISTAT() & TXTL)) && (--timeout_cnt)) + udelay(1); + if (timeout_cnt == 0) + printk(KERN_ERR DRV_NAME + ": fails to timestamp the TX packet\n"); + else { + struct skb_shared_hwtstamps shhwtstamps; + u64 ns; + u64 regval; + + regval = bfin_read_EMAC_PTP_TXSNAPLO(); + regval |= (u64)bfin_read_EMAC_PTP_TXSNAPHI() << 32; + memset(&shhwtstamps, 0, sizeof(shhwtstamps)); + ns = timecounter_cyc2time(&lp->clock, + regval); + timecompare_update(&lp->compare, ns); + shhwtstamps.hwtstamp = ns_to_ktime(ns); + shhwtstamps.syststamp = + timecompare_transform(&lp->compare, ns); + skb_tstamp_tx(skb, &shhwtstamps); + + bfin_dump_hwtamp("TX", &shhwtstamps.hwtstamp, &shhwtstamps.syststamp, &lp->compare); + } + } +} + +static void bfin_rx_hwtstamp(struct net_device *netdev, struct sk_buff *skb) +{ + struct bfin_mac_local *lp = netdev_priv(netdev); + u32 valid; + u64 regval, ns; + struct skb_shared_hwtstamps *shhwtstamps; + + if (bfin_mac_hwtstamp_is_none(lp->stamp_cfg.rx_filter)) + return; + + valid = bfin_read_EMAC_PTP_ISTAT() & RXEL; + if (!valid) + return; + + shhwtstamps = skb_hwtstamps(skb); + + regval = bfin_read_EMAC_PTP_RXSNAPLO(); + regval |= (u64)bfin_read_EMAC_PTP_RXSNAPHI() << 32; + ns = timecounter_cyc2time(&lp->clock, regval); + timecompare_update(&lp->compare, ns); + memset(shhwtstamps, 0, sizeof(*shhwtstamps)); + shhwtstamps->hwtstamp = ns_to_ktime(ns); + shhwtstamps->syststamp = timecompare_transform(&lp->compare, ns); + + bfin_dump_hwtamp("RX", &shhwtstamps->hwtstamp, &shhwtstamps->syststamp, &lp->compare); +} + +/* + * bfin_read_clock - read raw cycle counter (to be used by time counter) + */ +static cycle_t bfin_read_clock(const struct cyclecounter *tc) +{ + u64 stamp; + + stamp = bfin_read_EMAC_PTP_TIMELO(); + stamp |= (u64)bfin_read_EMAC_PTP_TIMEHI() << 32ULL; + + return stamp; +} + +#define PTP_CLK 25000000 + +static void bfin_mac_hwtstamp_init(struct net_device *netdev) +{ + struct bfin_mac_local *lp = netdev_priv(netdev); + u64 append; + + /* Initialize hardware timer */ + append = PTP_CLK * (1ULL << 32); + do_div(append, get_sclk()); + bfin_write_EMAC_PTP_ADDEND((u32)append); + + memset(&lp->cycles, 0, sizeof(lp->cycles)); + lp->cycles.read = bfin_read_clock; + lp->cycles.mask = CLOCKSOURCE_MASK(64); + lp->cycles.mult = 1000000000 / PTP_CLK; + lp->cycles.shift = 0; + + /* Synchronize our NIC clock against system wall clock */ + memset(&lp->compare, 0, sizeof(lp->compare)); + lp->compare.source = &lp->clock; + lp->compare.target = ktime_get_real; + lp->compare.num_samples = 10; + + /* Initialize hwstamp config */ + lp->stamp_cfg.rx_filter = HWTSTAMP_FILTER_NONE; + lp->stamp_cfg.tx_type = HWTSTAMP_TX_OFF; +} + +#else +# define bfin_mac_hwtstamp_is_none(cfg) 0 +# define bfin_mac_hwtstamp_init(dev) +# define bfin_mac_hwtstamp_ioctl(dev, ifr, cmd) (-EOPNOTSUPP) +# define bfin_rx_hwtstamp(dev, skb) +# define bfin_tx_hwtstamp(dev, skb) +#endif + static void adjust_tx_list(void) { int timeout_cnt = MAX_TIMEOUT_CNT; @@ -608,18 +912,32 @@ static int bfin_mac_hard_start_xmit(struct sk_buff *skb, { u16 *data; u32 data_align = (unsigned long)(skb->data) & 0x3; + union skb_shared_tx *shtx = skb_tx(skb); + current_tx_ptr->skb = skb; if (data_align == 0x2) { /* move skb->data to current_tx_ptr payload */ data = (u16 *)(skb->data) - 1; - *data = (u16)(skb->len); + *data = (u16)(skb->len); + /* + * When transmitting an Ethernet packet, the PTP_TSYNC module requires + * a DMA_Length_Word field associated with the packet. The lower 12 bits + * of this field are the length of the packet payload in bytes and the higher + * 4 bits are the timestamping enable field. + */ + if (shtx->hardware) + *data |= 0x1000; + current_tx_ptr->desc_a.start_addr = (u32)data; /* this is important! */ blackfin_dcache_flush_range((u32)data, (u32)((u8 *)data + skb->len + 4)); } else { *((u16 *)(current_tx_ptr->packet)) = (u16)(skb->len); + /* enable timestamping for the sent packet */ + if (shtx->hardware) + *((u16 *)(current_tx_ptr->packet)) |= 0x1000; memcpy((u8 *)(current_tx_ptr->packet + 2), skb->data, skb->len); current_tx_ptr->desc_a.start_addr = @@ -653,6 +971,9 @@ static int bfin_mac_hard_start_xmit(struct sk_buff *skb, out: adjust_tx_list(); + + bfin_tx_hwtstamp(dev, skb); + current_tx_ptr = current_tx_ptr->next; dev->stats.tx_packets++; dev->stats.tx_bytes += (skb->len); @@ -663,9 +984,11 @@ static void bfin_mac_rx(struct net_device *dev) { struct sk_buff *skb, *new_skb; unsigned short len; + struct bfin_mac_local *lp __maybe_unused = netdev_priv(dev); /* allocate a new skb for next time receive */ skb = current_rx_ptr->skb; + new_skb = dev_alloc_skb(PKT_BUF_SZ + NET_IP_ALIGN); if (!new_skb) { printk(KERN_NOTICE DRV_NAME @@ -690,6 +1013,9 @@ static void bfin_mac_rx(struct net_device *dev) (unsigned long)skb->tail); skb->protocol = eth_type_trans(skb, dev); + + bfin_rx_hwtstamp(dev, skb); + #if defined(BFIN_MAC_CSUM_OFFLOAD) skb->csum = current_rx_ptr->status.ip_payload_csum; skb->ip_summed = CHECKSUM_COMPLETE; @@ -871,6 +1197,16 @@ static void bfin_mac_set_multicast_list(struct net_device *dev) } } +static int bfin_mac_ioctl(struct net_device *netdev, struct ifreq *ifr, int cmd) +{ + switch (cmd) { + case SIOCSHWTSTAMP: + return bfin_mac_hwtstamp_ioctl(netdev, ifr, cmd); + default: + return -EOPNOTSUPP; + } +} + /* * this puts the device in an inactive state */ @@ -955,6 +1291,7 @@ static const struct net_device_ops bfin_mac_netdev_ops = { .ndo_set_mac_address = bfin_mac_set_mac_address, .ndo_tx_timeout = bfin_mac_timeout, .ndo_set_multicast_list = bfin_mac_set_multicast_list, + .ndo_do_ioctl = bfin_mac_ioctl, .ndo_validate_addr = eth_validate_addr, .ndo_change_mtu = eth_change_mtu, #ifdef CONFIG_NET_POLL_CONTROLLER @@ -1046,6 +1383,8 @@ static int __devinit bfin_mac_probe(struct platform_device *pdev) goto out_err_reg_ndev; } + bfin_mac_hwtstamp_init(ndev); + /* now, print out the card info, in a short format.. */ dev_info(&pdev->dev, "%s, Version %s\n", DRV_DESC, DRV_VERSION); |