net:qfec: Use 19.2 MHz clock for timestamping

This patch correct configuration of timestamp registers,
adds sysfs cmd and tstamp files to capture system-time
and display current timestamp.

Acked-by: Kaushik Sikdar <ksikdar@qualcomm.com>
Signed-off-by: Rohit Vaswani <rvaswani@codeaurora.org>
diff --git a/drivers/net/qfec.c b/drivers/net/qfec.c
index 90e8eff..71ddcff 100644
--- a/drivers/net/qfec.c
+++ b/drivers/net/qfec.c
@@ -33,7 +33,7 @@
 #include "qfec.h"
 
 #define QFEC_NAME       "qfec"
-#define QFEC_DRV_VER    "June 18a 2011"
+#define QFEC_DRV_VER    "July 14 2011"
 
 #define ETH_BUF_SIZE    0x600
 #define MAX_N_BD        50
@@ -638,8 +638,7 @@
 
 	{ 1, TS_HI_UPDT_REG,         "TS_HI_UPDATE_REG",       0 },
 	{ 1, TS_LO_UPDT_REG,         "TS_LO_UPDATE_REG",       0 },
-	{ 0, TS_SUB_SEC_INCR_REG,    "TS_SUB_SEC_INCR_REG",    86 },
-
+	{ 0, TS_SUB_SEC_INCR_REG,    "TS_SUB_SEC_INCR_REG",    1 },
 	{ 0, TS_CTL_REG,             "TS_CTL_REG",        TS_CTL_TSENALL
 							| TS_CTL_TSCTRLSSR
 							| TS_CTL_TSINIT
@@ -963,14 +962,11 @@
  */
 
 static struct qfec_pll_cfg qfec_pll_ptp = {
-	/* 25 MHz */
-	0,      ETH_MD_M(1) | ETH_MD_2D_N(10),    ETH_NS_NM(10-1)
+	/* 19.2 MHz  tcxo */
+	0,      0,                                ETH_NS_PRE_DIV(0)
 						| EMAC_PTP_NS_ROOT_EN
 						| EMAC_PTP_NS_CLK_EN
-						| ETH_NS_MCNTR_EN
-						| ETH_NS_MCNTR_MODE_DUAL
-						| ETH_NS_PRE_DIV(0)
-						| CLK_SRC_PLL_EMAC
+						| CLK_SRC_TCXO
 };
 
 #define PLLTEST_PAD_CFG     0x01E0
@@ -1302,27 +1298,173 @@
 }
 
 /*
- * read timestamp from buffer descriptor
- *    the pbuf and next fields of the buffer descriptors are overwritten
- *    with the timestamp high and low register values.   The high register
- *    counts seconds, but the sub-second increment register is programmed
- *    with the appropriate value to increment the timestamp low register
- *    such that it overflows at 0x8000 0000.  The low register value
- *    (next) must be converted to units of nano secs, * 10^9 / 2^31.
+ * process timestamp values
+ *    The pbuf and next fields of the buffer descriptors are overwritten
+ *    with the timestamp high and low register values.
+ *
+ *    The low register is incremented by the value in the subsec_increment
+ *    register and overflows at 0x8000 0000 causing the high register to
+ *    increment.
+ *
+ *    The subsec_increment register is recommended to be set to the number
+ *    of nanosec corresponding to each clock tic, scaled by 2^31 / 10^9
+ *    (e.g. 40 * 2^32 / 10^9 = 85.9, or 86 for 25 MHz).  However, the
+ *    rounding error in this case will result in a 1 sec error / ~14 mins.
+ *
+ *    An alternate approach is used.  The subsec_increment is set to 1,
+ *    and the concatenation of the 2 timestamp registers used to count
+ *    clock tics.  The 63-bit result is manipulated to determine the number
+ *    of sec and ns.
+ */
+
+/*
+ * convert 19.2 MHz clock tics into sec/ns
+ */
+#define TS_LOW_REG_BITS    31
+
+#define MILLION            1000000UL
+#define BILLION            1000000000UL
+
+#define F_CLK              19200000UL
+#define F_CLK_PRE_SC       24
+#define F_CLK_INV_Q        56
+#define F_CLK_INV          (((unsigned long long)1 << F_CLK_INV_Q) / F_CLK)
+#define F_CLK_TO_NS_Q      25
+#define F_CLK_TO_NS \
+	(((((unsigned long long)1<<F_CLK_TO_NS_Q)*BILLION)+(F_CLK-1))/F_CLK)
+#define US_TO_F_CLK_Q      20
+#define US_TO_F_CLK \
+	(((((unsigned long long)1<<US_TO_F_CLK_Q)*F_CLK)+(MILLION-1))/MILLION)
+
+static inline void qfec_get_sec(uint64_t *cnt,
+			uint32_t  *sec, uint32_t  *ns)
+{
+	unsigned long long  t;
+	unsigned long long  subsec;
+
+	t       = *cnt >> F_CLK_PRE_SC;
+	t      *= F_CLK_INV;
+	t     >>= F_CLK_INV_Q - F_CLK_PRE_SC;
+	*sec    = t;
+
+	t       = *cnt - (t * F_CLK);
+	subsec  = t;
+
+	if (subsec >= F_CLK)  {
+		subsec -= F_CLK;
+		*sec   += 1;
+	}
+
+	subsec  *= F_CLK_TO_NS;
+	subsec >>= F_CLK_TO_NS_Q;
+	*ns      = subsec;
+}
+
+/*
+ * read ethernet timestamp registers, pass up raw register values
+ * and values converted to sec/ns
  */
 static void qfec_read_timestamp(struct buf_desc *p_bd,
 	struct skb_shared_hwtstamps *ts)
 {
-	unsigned long  sec = (unsigned long)qfec_bd_next_get(p_bd);
-	long long      ns  = (unsigned long)qfec_bd_pbuf_get(p_bd);
+	unsigned long long  cnt;
+	unsigned int        sec;
+	unsigned int        subsec;
 
-#define BILLION		1000000000
-#define LOW_REG_BITS    31
-	ns  *= BILLION;
-	ns >>= LOW_REG_BITS;
+	cnt    = (unsigned long)qfec_bd_next_get(p_bd);
+	cnt  <<= TS_LOW_REG_BITS;
+	cnt   |= (unsigned long)qfec_bd_pbuf_get(p_bd);
 
-	ts->hwtstamp  = ktime_set(sec, ns);
-	ts->syststamp = ktime_set(sec, ns);
+	/* report raw counts as concatenated 63 bits */
+	sec    = cnt >> 32;
+	subsec = cnt & 0xffffffff;
+
+	ts->hwtstamp  = ktime_set(sec, subsec);
+
+	/* translate counts to sec and ns */
+	qfec_get_sec(&cnt, &sec, &subsec);
+
+	ts->syststamp = ktime_set(sec, subsec);
+}
+
+/*
+ * capture the current system time in the timestamp registers
+ */
+static int qfec_cmd(struct device *dev, struct device_attribute *attr,
+				const char *buf, size_t count)
+{
+	struct qfec_priv  *priv = netdev_priv(to_net_dev(dev));
+	struct timeval     tv;
+
+	if (!strncmp(buf, "setTs", 5))  {
+		unsigned long long  cnt;
+		uint32_t            ts_hi;
+		uint32_t            ts_lo;
+		unsigned long long  subsec;
+
+		do_gettimeofday(&tv);
+
+		/* convert raw sec/usec to ns */
+		subsec   = tv.tv_usec;
+		subsec  *= US_TO_F_CLK;
+		subsec >>= US_TO_F_CLK_Q;
+
+		cnt     = tv.tv_sec;
+		cnt    *= F_CLK;
+		cnt    += subsec;
+
+		ts_hi   = cnt >> 31;
+		ts_lo   = cnt & 0x7FFFFFFF;
+
+		qfec_reg_write(priv, TS_HI_UPDT_REG, ts_hi);
+		qfec_reg_write(priv, TS_LO_UPDT_REG, ts_lo);
+
+		qfec_reg_write(priv, TS_CTL_REG,
+			qfec_reg_read(priv, TS_CTL_REG) | TS_CTL_TSINIT);
+	} else
+		pr_err("%s: unknown cmd, %s.\n", __func__, buf);
+
+	return strnlen(buf, count);
+}
+
+/*
+ * display ethernet tstamp and system time
+ */
+static int qfec_tstamp_show(struct device *dev, struct device_attribute *attr,
+				char *buf)
+{
+	struct qfec_priv   *priv = netdev_priv(to_net_dev(dev));
+	int                 count = PAGE_SIZE;
+	int                 l;
+	struct timeval      tv;
+	unsigned long long  cnt;
+	uint32_t            sec;
+	uint32_t            ns;
+	uint32_t            ts_hi;
+	uint32_t            ts_lo;
+
+	/* insure that ts_hi didn't increment during read */
+	do {
+		ts_hi = qfec_reg_read(priv, TS_HIGH_REG);
+		ts_lo = qfec_reg_read(priv, TS_LOW_REG);
+	} while (ts_hi != qfec_reg_read(priv, TS_HIGH_REG));
+
+	cnt    = ts_hi;
+	cnt  <<= TS_LOW_REG_BITS;
+	cnt   |= ts_lo;
+
+	do_gettimeofday(&tv);
+
+	ts_hi  = cnt >> 32;
+	ts_lo  = cnt & 0xffffffff;
+
+	qfec_get_sec(&cnt, &sec, &ns);
+
+	l = snprintf(buf, count,
+		"%12u.%09u sec 0x%08x 0x%08x tstamp  %12u.%06u time-of-day\n",
+		sec, ns, ts_hi, ts_lo, (int)tv.tv_sec, (int)tv.tv_usec);
+
+	return l;
 }
 
 /*
@@ -2278,9 +2420,11 @@
 static DEVICE_ATTR(bd_rx,   0444, qfec_bd_rx_show,   NULL);
 static DEVICE_ATTR(cfg,     0444, qfec_config_show,  NULL);
 static DEVICE_ATTR(clk_reg, 0444, qfec_clk_reg_show, NULL);
+static DEVICE_ATTR(cmd,     0222, NULL,              qfec_cmd);
 static DEVICE_ATTR(cntrs,   0444, qfec_cntrs_show,   NULL);
-static DEVICE_ATTR(stats,   0444, qfec_stats_show,   NULL);
 static DEVICE_ATTR(reg,     0444, qfec_reg_show,     NULL);
+static DEVICE_ATTR(stats,   0444, qfec_stats_show,   NULL);
+static DEVICE_ATTR(tstamp,  0444, qfec_tstamp_show,  NULL);
 
 static void qfec_sysfs_create(struct net_device *dev)
 {
@@ -2288,9 +2432,11 @@
 		device_create_file(&(dev->dev), &dev_attr_bd_rx) ||
 		device_create_file(&(dev->dev), &dev_attr_cfg) ||
 		device_create_file(&(dev->dev), &dev_attr_clk_reg) ||
+		device_create_file(&(dev->dev), &dev_attr_cmd) ||
 		device_create_file(&(dev->dev), &dev_attr_cntrs) ||
 		device_create_file(&(dev->dev), &dev_attr_reg) ||
-		device_create_file(&(dev->dev), &dev_attr_stats))
+		device_create_file(&(dev->dev), &dev_attr_stats) ||
+		device_create_file(&(dev->dev), &dev_attr_tstamp))
 		pr_err("qfec_sysfs_create failed to create sysfs files\n");
 }