#include <asm/byteorder.h>
#include <linux/uaccess.h>
+#include <uapi/linux/net_tstamp.h>
+#include <linux/ptp_clock_kernel.h>
+
#ifdef CONFIG_SPARC
#include <asm/idprom.h>
#include <asm/prom.h>
#define DRV_MODULE_NAME "tg3"
#define TG3_MAJ_NUM 3
-#define TG3_MIN_NUM 127
+#define TG3_MIN_NUM 128
#define DRV_MODULE_VERSION \
__stringify(TG3_MAJ_NUM) "." __stringify(TG3_MIN_NUM)
-#define DRV_MODULE_RELDATE "November 14, 2012"
+#define DRV_MODULE_RELDATE "December 03, 2012"
#define RESET_KIND_SHUTDOWN 0
#define RESET_KIND_INIT 1
return err;
}
+/* tp->lock must be held */
+static u64 tg3_refclk_read(struct tg3 *tp)
+{
+ u64 stamp = tr32(TG3_EAV_REF_CLCK_LSB);
+ return stamp | (u64)tr32(TG3_EAV_REF_CLCK_MSB) << 32;
+}
+
+/* tp->lock must be held */
+static void tg3_refclk_write(struct tg3 *tp, u64 newval)
+{
+ tw32(TG3_EAV_REF_CLCK_CTL, TG3_EAV_REF_CLCK_CTL_STOP);
+ tw32(TG3_EAV_REF_CLCK_LSB, newval & 0xffffffff);
+ tw32(TG3_EAV_REF_CLCK_MSB, newval >> 32);
+ tw32_f(TG3_EAV_REF_CLCK_CTL, TG3_EAV_REF_CLCK_CTL_RESUME);
+}
+
+static inline void tg3_full_lock(struct tg3 *tp, int irq_sync);
+static inline void tg3_full_unlock(struct tg3 *tp);
+static int tg3_get_ts_info(struct net_device *dev, struct ethtool_ts_info *info)
+{
+ struct tg3 *tp = netdev_priv(dev);
+
+ info->so_timestamping = SOF_TIMESTAMPING_TX_SOFTWARE |
+ SOF_TIMESTAMPING_RX_SOFTWARE |
+ SOF_TIMESTAMPING_SOFTWARE |
+ SOF_TIMESTAMPING_TX_HARDWARE |
+ SOF_TIMESTAMPING_RX_HARDWARE |
+ SOF_TIMESTAMPING_RAW_HARDWARE;
+
+ if (tp->ptp_clock)
+ info->phc_index = ptp_clock_index(tp->ptp_clock);
+ else
+ info->phc_index = -1;
+
+ info->tx_types = (1 << HWTSTAMP_TX_OFF) | (1 << HWTSTAMP_TX_ON);
+
+ info->rx_filters = (1 << HWTSTAMP_FILTER_NONE) |
+ (1 << HWTSTAMP_FILTER_PTP_V1_L4_EVENT) |
+ (1 << HWTSTAMP_FILTER_PTP_V2_L2_EVENT) |
+ (1 << HWTSTAMP_FILTER_PTP_V2_L4_EVENT);
+ return 0;
+}
+
+static int tg3_ptp_adjfreq(struct ptp_clock_info *ptp, s32 ppb)
+{
+ struct tg3 *tp = container_of(ptp, struct tg3, ptp_info);
+ bool neg_adj = false;
+ u32 correction = 0;
+
+ if (ppb < 0) {
+ neg_adj = true;
+ ppb = -ppb;
+ }
+
+ /* Frequency adjustment is performed using hardware with a 24 bit
+ * accumulator and a programmable correction value. On each clk, the
+ * correction value gets added to the accumulator and when it
+ * overflows, the time counter is incremented/decremented.
+ *
+ * So conversion from ppb to correction value is
+ * ppb * (1 << 24) / 1000000000
+ */
+ correction = div_u64((u64)ppb * (1 << 24), 1000000000ULL) &
+ TG3_EAV_REF_CLK_CORRECT_MASK;
+
+ tg3_full_lock(tp, 0);
+
+ if (correction)
+ tw32(TG3_EAV_REF_CLK_CORRECT_CTL,
+ TG3_EAV_REF_CLK_CORRECT_EN |
+ (neg_adj ? TG3_EAV_REF_CLK_CORRECT_NEG : 0) | correction);
+ else
+ tw32(TG3_EAV_REF_CLK_CORRECT_CTL, 0);
+
+ tg3_full_unlock(tp);
+
+ return 0;
+}
+
+static int tg3_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
+{
+ struct tg3 *tp = container_of(ptp, struct tg3, ptp_info);
+
+ tg3_full_lock(tp, 0);
+ tp->ptp_adjust += delta;
+ tg3_full_unlock(tp);
+
+ return 0;
+}
+
+static int tg3_ptp_gettime(struct ptp_clock_info *ptp, struct timespec *ts)
+{
+ u64 ns;
+ u32 remainder;
+ struct tg3 *tp = container_of(ptp, struct tg3, ptp_info);
+
+ tg3_full_lock(tp, 0);
+ ns = tg3_refclk_read(tp);
+ ns += tp->ptp_adjust;
+ tg3_full_unlock(tp);
+
+ ts->tv_sec = div_u64_rem(ns, 1000000000, &remainder);
+ ts->tv_nsec = remainder;
+
+ return 0;
+}
+
+static int tg3_ptp_settime(struct ptp_clock_info *ptp,
+ const struct timespec *ts)
+{
+ u64 ns;
+ struct tg3 *tp = container_of(ptp, struct tg3, ptp_info);
+
+ ns = timespec_to_ns(ts);
+
+ tg3_full_lock(tp, 0);
+ tg3_refclk_write(tp, ns);
+ tp->ptp_adjust = 0;
+ tg3_full_unlock(tp);
+
+ return 0;
+}
+
+static int tg3_ptp_enable(struct ptp_clock_info *ptp,
+ struct ptp_clock_request *rq, int on)
+{
+ return -EOPNOTSUPP;
+}
+
+static const struct ptp_clock_info tg3_ptp_caps = {
+ .owner = THIS_MODULE,
+ .name = "tg3 clock",
+ .max_adj = 250000000,
+ .n_alarm = 0,
+ .n_ext_ts = 0,
+ .n_per_out = 0,
+ .pps = 0,
+ .adjfreq = tg3_ptp_adjfreq,
+ .adjtime = tg3_ptp_adjtime,
+ .gettime = tg3_ptp_gettime,
+ .settime = tg3_ptp_settime,
+ .enable = tg3_ptp_enable,
+};
+
+static void tg3_hwclock_to_timestamp(struct tg3 *tp, u64 hwclock,
+ struct skb_shared_hwtstamps *timestamp)
+{
+ memset(timestamp, 0, sizeof(struct skb_shared_hwtstamps));
+ timestamp->hwtstamp = ns_to_ktime((hwclock & TG3_TSTAMP_MASK) +
+ tp->ptp_adjust);
+}
+
+/* tp->lock must be held */
+static void tg3_ptp_init(struct tg3 *tp)
+{
+ if (!tg3_flag(tp, PTP_CAPABLE))
+ return;
+
+ /* Initialize the hardware clock to the system time. */
+ tg3_refclk_write(tp, ktime_to_ns(ktime_get_real()));
+ tp->ptp_adjust = 0;
+ tp->ptp_info = tg3_ptp_caps;
+}
+
+/* tp->lock must be held */
+static void tg3_ptp_resume(struct tg3 *tp)
+{
+ if (!tg3_flag(tp, PTP_CAPABLE))
+ return;
+
+ tg3_refclk_write(tp, ktime_to_ns(ktime_get_real()) + tp->ptp_adjust);
+ tp->ptp_adjust = 0;
+}
+
+static void tg3_ptp_fini(struct tg3 *tp)
+{
+ if (!tg3_flag(tp, PTP_CAPABLE) || !tp->ptp_clock)
+ return;
+
+ ptp_clock_unregister(tp->ptp_clock);
+ tp->ptp_clock = NULL;
+ tp->ptp_adjust = 0;
+}
+
static inline int tg3_irq_sync(struct tg3 *tp)
{
return tp->irq_sync;
return;
}
+ if (tnapi->tx_ring[sw_idx].len_flags & TXD_FLAG_HWTSTAMP) {
+ struct skb_shared_hwtstamps timestamp;
+ u64 hwclock = tr32(TG3_TX_TSTAMP_LSB);
+ hwclock |= (u64)tr32(TG3_TX_TSTAMP_MSB) << 32;
+
+ tg3_hwclock_to_timestamp(tp, hwclock, ×tamp);
+
+ skb_tstamp_tx(skb, ×tamp);
+ }
+
pci_unmap_single(tp->pdev,
dma_unmap_addr(ri, mapping),
skb_headlen(skb),
dma_addr_t dma_addr;
u32 opaque_key, desc_idx, *post_ptr;
u8 *data;
+ u64 tstamp = 0;
desc_idx = desc->opaque & RXD_OPAQUE_INDEX_MASK;
opaque_key = desc->opaque & RXD_OPAQUE_RING_MASK;
len = ((desc->idx_len & RXD_LEN_MASK) >> RXD_LEN_SHIFT) -
ETH_FCS_LEN;
+ if ((desc->type_flags & RXD_FLAG_PTPSTAT_MASK) ==
+ RXD_FLAG_PTPSTAT_PTPV1 ||
+ (desc->type_flags & RXD_FLAG_PTPSTAT_MASK) ==
+ RXD_FLAG_PTPSTAT_PTPV2) {
+ tstamp = tr32(TG3_RX_TSTAMP_LSB);
+ tstamp |= (u64)tr32(TG3_RX_TSTAMP_MSB) << 32;
+ }
+
if (len > TG3_RX_COPY_THRESH(tp)) {
int skb_size;
unsigned int frag_size;
}
skb_put(skb, len);
+ if (tstamp)
+ tg3_hwclock_to_timestamp(tp, tstamp,
+ skb_hwtstamps(skb));
+
if ((tp->dev->features & NETIF_F_RXCSUM) &&
(desc->type_flags & RXD_FLAG_TCPUDP_CSUM) &&
(((desc->ip_tcp_csum & RXD_TCPCSUM_MASK)
netif_tx_disable(tp->dev);
}
+/* tp->lock must be held */
static inline void tg3_netif_start(struct tg3 *tp)
{
+ tg3_ptp_resume(tp);
+
/* NOTE: unconditional netif_tx_wake_all_queues is only
* appropriate so long as all callers are assured to
* have free tx slots (such as after tg3_init_hw)
vlan = vlan_tx_tag_get(skb);
}
+ if ((unlikely(skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP)) &&
+ tg3_flag(tp, TX_TSTAMP_EN)) {
+ skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
+ base_flags |= TXD_FLAG_HWTSTAMP;
+ }
+
len = skb_headlen(skb);
mapping = pci_map_single(tp->pdev, skb->data, len, PCI_DMA_TODEVICE);
*/
tp->grc_mode |= GRC_MODE_NO_TX_PHDR_CSUM;
- tw32(GRC_MODE,
- tp->grc_mode |
- (GRC_MODE_IRQ_ON_MAC_ATTN | GRC_MODE_HOST_STACKUP));
+ val = GRC_MODE_IRQ_ON_MAC_ATTN | GRC_MODE_HOST_STACKUP;
+ if (tp->rxptpctl)
+ tw32(TG3_RX_PTP_CTL,
+ tp->rxptpctl | TG3_RX_PTP_CTL_HWTS_INTERLOCK);
+
+ if (tg3_flag(tp, PTP_CAPABLE))
+ val |= GRC_MODE_TIME_SYNC_ENABLE;
+
+ tw32(GRC_MODE, tp->grc_mode | val);
/* Setup the timer prescalar register. Clock is always 66Mhz. */
val = tr32(GRC_MISC_CFG);
tg3_flag_clear(tp, ENABLE_TSS);
}
-static int tg3_start(struct tg3 *tp, bool reset_phy, bool test_irq)
+static int tg3_start(struct tg3 *tp, bool reset_phy, bool test_irq,
+ bool init)
{
struct net_device *dev = tp->dev;
int i, err;
tg3_flag_set(tp, INIT_COMPLETE);
tg3_enable_ints(tp);
+ if (init)
+ tg3_ptp_init(tp);
+ else
+ tg3_ptp_resume(tp);
+
+
tg3_full_unlock(tp);
netif_tx_start_all_queues(dev);
tg3_full_unlock(tp);
- err = tg3_start(tp, true, true);
+ err = tg3_start(tp, true, true, true);
if (err) {
tg3_frob_aux_power(tp, false);
pci_set_power_state(tp->pdev, PCI_D3hot);
}
+
+ if (tg3_flag(tp, PTP_CAPABLE)) {
+ tp->ptp_clock = ptp_clock_register(&tp->ptp_info,
+ &tp->pdev->dev);
+ if (IS_ERR(tp->ptp_clock))
+ tp->ptp_clock = NULL;
+ }
+
return err;
}
{
struct tg3 *tp = netdev_priv(dev);
+ tg3_ptp_fini(tp);
+
tg3_stop(tp);
/* Clear stats across close / open calls */
tg3_carrier_off(tp);
- tg3_start(tp, true, false);
+ tg3_start(tp, true, false, false);
return 0;
}
}
tg3_full_lock(tp, irq_sync);
-
tg3_halt(tp, RESET_KIND_SUSPEND, 1);
err = tg3_nvram_lock(tp);
tg3_halt_cpu(tp, RX_CPU_BASE);
}
+static int tg3_hwtstamp_ioctl(struct net_device *dev,
+ struct ifreq *ifr, int cmd)
+{
+ struct tg3 *tp = netdev_priv(dev);
+ struct hwtstamp_config stmpconf;
+
+ if (!tg3_flag(tp, PTP_CAPABLE))
+ return -EINVAL;
+
+ if (copy_from_user(&stmpconf, ifr->ifr_data, sizeof(stmpconf)))
+ return -EFAULT;
+
+ if (stmpconf.flags)
+ return -EINVAL;
+
+ switch (stmpconf.tx_type) {
+ case HWTSTAMP_TX_ON:
+ tg3_flag_set(tp, TX_TSTAMP_EN);
+ break;
+ case HWTSTAMP_TX_OFF:
+ tg3_flag_clear(tp, TX_TSTAMP_EN);
+ break;
+ default:
+ return -ERANGE;
+ }
+
+ switch (stmpconf.rx_filter) {
+ case HWTSTAMP_FILTER_NONE:
+ tp->rxptpctl = 0;
+ break;
+ case HWTSTAMP_FILTER_PTP_V1_L4_EVENT:
+ tp->rxptpctl = TG3_RX_PTP_CTL_RX_PTP_V1_EN |
+ TG3_RX_PTP_CTL_ALL_V1_EVENTS;
+ break;
+ case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
+ tp->rxptpctl = TG3_RX_PTP_CTL_RX_PTP_V1_EN |
+ TG3_RX_PTP_CTL_SYNC_EVNT;
+ break;
+ case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
+ tp->rxptpctl = TG3_RX_PTP_CTL_RX_PTP_V1_EN |
+ TG3_RX_PTP_CTL_DELAY_REQ;
+ break;
+ case HWTSTAMP_FILTER_PTP_V2_EVENT:
+ tp->rxptpctl = TG3_RX_PTP_CTL_RX_PTP_V2_EN |
+ TG3_RX_PTP_CTL_ALL_V2_EVENTS;
+ break;
+ case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
+ tp->rxptpctl = TG3_RX_PTP_CTL_RX_PTP_V2_L2_EN |
+ TG3_RX_PTP_CTL_ALL_V2_EVENTS;
+ break;
+ case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
+ tp->rxptpctl = TG3_RX_PTP_CTL_RX_PTP_V2_L4_EN |
+ TG3_RX_PTP_CTL_ALL_V2_EVENTS;
+ break;
+ case HWTSTAMP_FILTER_PTP_V2_SYNC:
+ tp->rxptpctl = TG3_RX_PTP_CTL_RX_PTP_V2_EN |
+ TG3_RX_PTP_CTL_SYNC_EVNT;
+ break;
+ case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
+ tp->rxptpctl = TG3_RX_PTP_CTL_RX_PTP_V2_L2_EN |
+ TG3_RX_PTP_CTL_SYNC_EVNT;
+ break;
+ case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
+ tp->rxptpctl = TG3_RX_PTP_CTL_RX_PTP_V2_L4_EN |
+ TG3_RX_PTP_CTL_SYNC_EVNT;
+ break;
+ case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
+ tp->rxptpctl = TG3_RX_PTP_CTL_RX_PTP_V2_EN |
+ TG3_RX_PTP_CTL_DELAY_REQ;
+ break;
+ case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
+ tp->rxptpctl = TG3_RX_PTP_CTL_RX_PTP_V2_L2_EN |
+ TG3_RX_PTP_CTL_DELAY_REQ;
+ break;
+ case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
+ tp->rxptpctl = TG3_RX_PTP_CTL_RX_PTP_V2_L4_EN |
+ TG3_RX_PTP_CTL_DELAY_REQ;
+ break;
+ default:
+ return -ERANGE;
+ }
+
+ if (netif_running(dev) && tp->rxptpctl)
+ tw32(TG3_RX_PTP_CTL,
+ tp->rxptpctl | TG3_RX_PTP_CTL_HWTS_INTERLOCK);
+
+ return copy_to_user(ifr->ifr_data, &stmpconf, sizeof(stmpconf)) ?
+ -EFAULT : 0;
+}
+
static int tg3_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
{
struct mii_ioctl_data *data = if_mii(ifr);
return err;
+ case SIOCSHWTSTAMP:
+ return tg3_hwtstamp_ioctl(dev, ifr, cmd);
+
default:
/* do nothing */
break;
.set_rxfh_indir = tg3_set_rxfh_indir,
.get_channels = tg3_get_channels,
.set_channels = tg3_set_channels,
- .get_ts_info = ethtool_op_get_ts_info,
+ .get_ts_info = tg3_get_ts_info,
};
static struct rtnl_link_stats64 *tg3_get_stats64(struct net_device *dev,
return false;
}
-static int tg3_get_invariants(struct tg3 *tp,
- const struct pci_device_id *ent)
+static int tg3_get_invariants(struct tg3 *tp, const struct pci_device_id *ent)
{
u32 misc_ctrl_reg;
u32 pci_state_reg, grc_misc_cfg;
pci_set_drvdata(pdev, dev);
+ if (GET_ASIC_REV(tp->pci_chip_rev_id) == ASIC_REV_5719 ||
+ GET_ASIC_REV(tp->pci_chip_rev_id) == ASIC_REV_5720)
+ tg3_flag_set(tp, PTP_CAPABLE);
+
if (tg3_flag(tp, 5717_PLUS)) {
/* Resume a low-power mode */
tg3_frob_aux_power(tp, false);
tg3_full_lock(tp, 0);
tg3_flag_set(tp, INIT_COMPLETE);
err = tg3_restart_hw(tp, 1);
- tg3_full_unlock(tp);
if (err) {
+ tg3_full_unlock(tp);
netdev_err(netdev, "Cannot restart hardware after reset.\n");
goto done;
}
tg3_netif_start(tp);
+ tg3_full_unlock(tp);
+
tg3_phy_start(tp);
done: