diff options
Diffstat (limited to 'drivers/net/ethernet/oki-semi/pch_gbe/pch_gbe_main.c')
-rw-r--r-- | drivers/net/ethernet/oki-semi/pch_gbe/pch_gbe_main.c | 217 |
1 files changed, 214 insertions, 3 deletions
diff --git a/drivers/net/ethernet/oki-semi/pch_gbe/pch_gbe_main.c b/drivers/net/ethernet/oki-semi/pch_gbe/pch_gbe_main.c index 69a66545c8a..8035e5ff6e0 100644 --- a/drivers/net/ethernet/oki-semi/pch_gbe/pch_gbe_main.c +++ b/drivers/net/ethernet/oki-semi/pch_gbe/pch_gbe_main.c @@ -1,6 +1,6 @@ /* * Copyright (C) 1999 - 2010 Intel Corporation. - * Copyright (C) 2010 OKI SEMICONDUCTOR CO., LTD. + * Copyright (C) 2010 - 2012 LAPIS SEMICONDUCTOR CO., LTD. * * This code was derived from the Intel e1000e Linux driver. * @@ -21,6 +21,10 @@ #include "pch_gbe.h" #include "pch_gbe_api.h" #include <linux/module.h> +#ifdef CONFIG_PCH_PTP +#include <linux/net_tstamp.h> +#include <linux/ptp_classify.h> +#endif #define DRV_VERSION "1.00" const char pch_driver_version[] = DRV_VERSION; @@ -95,12 +99,195 @@ const char pch_driver_version[] = DRV_VERSION; #define PCH_GBE_INT_DISABLE_ALL 0 +#ifdef CONFIG_PCH_PTP +/* Macros for ieee1588 */ +#define TICKS_NS_SHIFT 5 + +/* 0x40 Time Synchronization Channel Control Register Bits */ +#define MASTER_MODE (1<<0) +#define SLAVE_MODE (0<<0) +#define V2_MODE (1<<31) +#define CAP_MODE0 (0<<16) +#define CAP_MODE2 (1<<17) + +/* 0x44 Time Synchronization Channel Event Register Bits */ +#define TX_SNAPSHOT_LOCKED (1<<0) +#define RX_SNAPSHOT_LOCKED (1<<1) +#endif + static unsigned int copybreak __read_mostly = PCH_GBE_COPYBREAK_DEFAULT; static int pch_gbe_mdio_read(struct net_device *netdev, int addr, int reg); static void pch_gbe_mdio_write(struct net_device *netdev, int addr, int reg, int data); +#ifdef CONFIG_PCH_PTP +static struct sock_filter ptp_filter[] = { + PTP_FILTER +}; + +static int pch_ptp_match(struct sk_buff *skb, u16 uid_hi, u32 uid_lo, u16 seqid) +{ + u8 *data = skb->data; + unsigned int offset; + u16 *hi, *id; + u32 lo; + + if ((sk_run_filter(skb, ptp_filter) != PTP_CLASS_V2_IPV4) && + (sk_run_filter(skb, ptp_filter) != PTP_CLASS_V1_IPV4)) { + return 0; + } + + offset = ETH_HLEN + IPV4_HLEN(data) + UDP_HLEN; + + if (skb->len < offset + OFF_PTP_SEQUENCE_ID + sizeof(seqid)) + return 0; + + hi = (u16 *)(data + offset + OFF_PTP_SOURCE_UUID); + id = (u16 *)(data + offset + OFF_PTP_SEQUENCE_ID); + + memcpy(&lo, &hi[1], sizeof(lo)); + + return (uid_hi == *hi && + uid_lo == lo && + seqid == *id); +} + +static void pch_rx_timestamp( + struct pch_gbe_adapter *adapter, struct sk_buff *skb) +{ + struct skb_shared_hwtstamps *shhwtstamps; + struct pci_dev *pdev; + u64 ns; + u32 hi, lo, val; + u16 uid, seq; + + if (!adapter->hwts_rx_en) + return; + + /* Get ieee1588's dev information */ + pdev = adapter->ptp_pdev; + + val = pch_ch_event_read(pdev); + + if (!(val & RX_SNAPSHOT_LOCKED)) + return; + + lo = pch_src_uuid_lo_read(pdev); + hi = pch_src_uuid_hi_read(pdev); + + uid = hi & 0xffff; + seq = (hi >> 16) & 0xffff; + + if (!pch_ptp_match(skb, htons(uid), htonl(lo), htons(seq))) + goto out; + + ns = pch_rx_snap_read(pdev); + ns <<= TICKS_NS_SHIFT; + + shhwtstamps = skb_hwtstamps(skb); + memset(shhwtstamps, 0, sizeof(*shhwtstamps)); + shhwtstamps->hwtstamp = ns_to_ktime(ns); +out: + pch_ch_event_write(pdev, RX_SNAPSHOT_LOCKED); +} + +static void pch_tx_timestamp( + struct pch_gbe_adapter *adapter, struct sk_buff *skb) +{ + struct skb_shared_hwtstamps shhwtstamps; + struct pci_dev *pdev; + struct skb_shared_info *shtx; + u64 ns; + u32 cnt, val; + + shtx = skb_shinfo(skb); + if (unlikely(shtx->tx_flags & SKBTX_HW_TSTAMP && adapter->hwts_tx_en)) + shtx->tx_flags |= SKBTX_IN_PROGRESS; + else + return; + + /* Get ieee1588's dev information */ + pdev = adapter->ptp_pdev; + + /* + * 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 = pch_ch_event_read(pdev); + if (val & TX_SNAPSHOT_LOCKED) + break; + udelay(1); + } + if (!(val & TX_SNAPSHOT_LOCKED)) { + shtx->tx_flags &= ~SKBTX_IN_PROGRESS; + return; + } + + ns = pch_tx_snap_read(pdev); + ns <<= TICKS_NS_SHIFT; + + memset(&shhwtstamps, 0, sizeof(shhwtstamps)); + shhwtstamps.hwtstamp = ns_to_ktime(ns); + skb_tstamp_tx(skb, &shhwtstamps); + + pch_ch_event_write(pdev, TX_SNAPSHOT_LOCKED); +} + +static int hwtstamp_ioctl(struct net_device *netdev, struct ifreq *ifr, int cmd) +{ + struct hwtstamp_config cfg; + struct pch_gbe_adapter *adapter = netdev_priv(netdev); + struct pci_dev *pdev; + + if (copy_from_user(&cfg, ifr->ifr_data, sizeof(cfg))) + return -EFAULT; + + if (cfg.flags) /* reserved for future extensions */ + return -EINVAL; + + /* Get ieee1588's dev information */ + pdev = adapter->ptp_pdev; + + switch (cfg.tx_type) { + case HWTSTAMP_TX_OFF: + adapter->hwts_tx_en = 0; + break; + case HWTSTAMP_TX_ON: + adapter->hwts_tx_en = 1; + break; + default: + return -ERANGE; + } + + switch (cfg.rx_filter) { + case HWTSTAMP_FILTER_NONE: + adapter->hwts_rx_en = 0; + break; + case HWTSTAMP_FILTER_PTP_V1_L4_SYNC: + adapter->hwts_rx_en = 0; + pch_ch_control_write(pdev, (SLAVE_MODE | CAP_MODE0)); + break; + case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ: + adapter->hwts_rx_en = 1; + pch_ch_control_write(pdev, (MASTER_MODE | CAP_MODE0)); + break; + case HWTSTAMP_FILTER_PTP_V2_EVENT: + adapter->hwts_rx_en = 1; + pch_ch_control_write(pdev, (V2_MODE | CAP_MODE2)); + break; + default: + return -ERANGE; + } + + /* Clear out any old time stamps. */ + pch_ch_event_write(pdev, TX_SNAPSHOT_LOCKED | RX_SNAPSHOT_LOCKED); + + return copy_to_user(ifr->ifr_data, &cfg, sizeof(cfg)) ? -EFAULT : 0; +} +#endif + inline void pch_gbe_mac_load_mac_addr(struct pch_gbe_hw *hw) { iowrite32(0x01, &hw->reg->MAC_ADDR_LOAD); @@ -1072,6 +1259,11 @@ static void pch_gbe_tx_queue(struct pch_gbe_adapter *adapter, iowrite32(tx_ring->dma + (int)sizeof(struct pch_gbe_tx_desc) * ring_num, &hw->reg->TX_DSC_SW_P); + +#ifdef CONFIG_PCH_PTP + pch_tx_timestamp(adapter, skb); +#endif + dev_kfree_skb_any(skb); } @@ -1543,6 +1735,11 @@ pch_gbe_clean_rx(struct pch_gbe_adapter *adapter, adapter->stats.multicast++; /* Write meta date of skb */ skb_put(skb, length); + +#ifdef CONFIG_PCH_PTP + pch_rx_timestamp(adapter, skb); +#endif + skb->protocol = eth_type_trans(skb, netdev); if (tcp_ip_status & PCH_GBE_RXD_ACC_STAT_TCPIPOK) skb->ip_summed = CHECKSUM_NONE; @@ -2144,6 +2341,11 @@ static int pch_gbe_ioctl(struct net_device *netdev, struct ifreq *ifr, int cmd) pr_debug("cmd : 0x%04x\n", cmd); +#ifdef CONFIG_PCH_PTP + if (cmd == SIOCSHWTSTAMP) + return hwtstamp_ioctl(netdev, ifr, cmd); +#endif + return generic_mii_ioctl(&adapter->mii, if_mii(ifr), cmd, NULL); } @@ -2435,6 +2637,15 @@ static int pch_gbe_probe(struct pci_dev *pdev, goto err_free_netdev; } +#ifdef CONFIG_PCH_PTP + adapter->ptp_pdev = pci_get_bus_and_slot(adapter->pdev->bus->number, + PCI_DEVFN(12, 4)); + if (ptp_filter_init(ptp_filter, ARRAY_SIZE(ptp_filter))) { + pr_err("Bad ptp filter\n"); + return -EINVAL; + } +#endif + netdev->netdev_ops = &pch_gbe_netdev_ops; netdev->watchdog_timeo = PCH_GBE_WATCHDOG_PERIOD; netif_napi_add(netdev, &adapter->napi, @@ -2499,7 +2710,7 @@ static int pch_gbe_probe(struct pci_dev *pdev, netif_carrier_off(netdev); netif_stop_queue(netdev); - dev_dbg(&pdev->dev, "OKIsemi(R) PCH Network Connection\n"); + dev_dbg(&pdev->dev, "PCH Network Connection\n"); device_set_wakeup_enable(&pdev->dev, 1); return 0; @@ -2600,7 +2811,7 @@ module_init(pch_gbe_init_module); module_exit(pch_gbe_exit_module); MODULE_DESCRIPTION("EG20T PCH Gigabit ethernet Driver"); -MODULE_AUTHOR("OKI SEMICONDUCTOR, <toshiharu-linux@dsn.okisemi.com>"); +MODULE_AUTHOR("LAPIS SEMICONDUCTOR, <tshimizu818@gmail.com>"); MODULE_LICENSE("GPL"); MODULE_VERSION(DRV_VERSION); MODULE_DEVICE_TABLE(pci, pch_gbe_pcidev_id); |