|
@@ -0,0 +1,323 @@
|
|
|
+/* QLogic qed NIC Driver
|
|
|
+ * Copyright (c) 2015-2017 QLogic Corporation
|
|
|
+ *
|
|
|
+ * This software is available to you under a choice of one of two
|
|
|
+ * licenses. You may choose to be licensed under the terms of the GNU
|
|
|
+ * General Public License (GPL) Version 2, available from the file
|
|
|
+ * COPYING in the main directory of this source tree, or the
|
|
|
+ * OpenIB.org BSD license below:
|
|
|
+ *
|
|
|
+ * Redistribution and use in source and binary forms, with or
|
|
|
+ * without modification, are permitted provided that the following
|
|
|
+ * conditions are met:
|
|
|
+ *
|
|
|
+ * - Redistributions of source code must retain the above
|
|
|
+ * copyright notice, this list of conditions and the following
|
|
|
+ * disclaimer.
|
|
|
+ *
|
|
|
+ * - Redistributions in binary form must reproduce the above
|
|
|
+ * copyright notice, this list of conditions and the following
|
|
|
+ * disclaimer in the documentation and /or other materials
|
|
|
+ * provided with the distribution.
|
|
|
+ *
|
|
|
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
|
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
|
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
|
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
|
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
|
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
|
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
+ * SOFTWARE.
|
|
|
+ */
|
|
|
+#include <linux/types.h>
|
|
|
+#include "qed.h"
|
|
|
+#include "qed_dev_api.h"
|
|
|
+#include "qed_hw.h"
|
|
|
+#include "qed_l2.h"
|
|
|
+#include "qed_ptp.h"
|
|
|
+#include "qed_reg_addr.h"
|
|
|
+
|
|
|
+/* 16 nano second time quantas to wait before making a Drift adjustment */
|
|
|
+#define QED_DRIFT_CNTR_TIME_QUANTA_SHIFT 0
|
|
|
+/* Nano seconds to add/subtract when making a Drift adjustment */
|
|
|
+#define QED_DRIFT_CNTR_ADJUSTMENT_SHIFT 28
|
|
|
+/* Add/subtract the Adjustment_Value when making a Drift adjustment */
|
|
|
+#define QED_DRIFT_CNTR_DIRECTION_SHIFT 31
|
|
|
+#define QED_TIMESTAMP_MASK BIT(16)
|
|
|
+
|
|
|
+/* Read Rx timestamp */
|
|
|
+static int qed_ptp_hw_read_rx_ts(struct qed_dev *cdev, u64 *timestamp)
|
|
|
+{
|
|
|
+ struct qed_hwfn *p_hwfn = QED_LEADING_HWFN(cdev);
|
|
|
+ struct qed_ptt *p_ptt = p_hwfn->p_ptp_ptt;
|
|
|
+ u32 val;
|
|
|
+
|
|
|
+ *timestamp = 0;
|
|
|
+ val = qed_rd(p_hwfn, p_ptt, NIG_REG_LLH_PTP_HOST_BUF_SEQID);
|
|
|
+ if (!(val & QED_TIMESTAMP_MASK)) {
|
|
|
+ DP_INFO(p_hwfn, "Invalid Rx timestamp, buf_seqid = %d\n", val);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ val = qed_rd(p_hwfn, p_ptt, NIG_REG_LLH_PTP_HOST_BUF_TS_LSB);
|
|
|
+ *timestamp = qed_rd(p_hwfn, p_ptt, NIG_REG_LLH_PTP_HOST_BUF_TS_MSB);
|
|
|
+ *timestamp <<= 32;
|
|
|
+ *timestamp |= val;
|
|
|
+
|
|
|
+ /* Reset timestamp register to allow new timestamp */
|
|
|
+ qed_wr(p_hwfn, p_ptt, NIG_REG_LLH_PTP_HOST_BUF_SEQID,
|
|
|
+ QED_TIMESTAMP_MASK);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* Read Tx timestamp */
|
|
|
+static int qed_ptp_hw_read_tx_ts(struct qed_dev *cdev, u64 *timestamp)
|
|
|
+{
|
|
|
+ struct qed_hwfn *p_hwfn = QED_LEADING_HWFN(cdev);
|
|
|
+ struct qed_ptt *p_ptt = p_hwfn->p_ptp_ptt;
|
|
|
+ u32 val;
|
|
|
+
|
|
|
+ *timestamp = 0;
|
|
|
+ val = qed_rd(p_hwfn, p_ptt, NIG_REG_TX_LLH_PTP_BUF_SEQID);
|
|
|
+ if (!(val & QED_TIMESTAMP_MASK)) {
|
|
|
+ DP_INFO(p_hwfn, "Invalid Tx timestamp, buf_seqid = %d\n", val);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ val = qed_rd(p_hwfn, p_ptt, NIG_REG_TX_LLH_PTP_BUF_TS_LSB);
|
|
|
+ *timestamp = qed_rd(p_hwfn, p_ptt, NIG_REG_TX_LLH_PTP_BUF_TS_MSB);
|
|
|
+ *timestamp <<= 32;
|
|
|
+ *timestamp |= val;
|
|
|
+
|
|
|
+ /* Reset timestamp register to allow new timestamp */
|
|
|
+ qed_wr(p_hwfn, p_ptt, NIG_REG_TX_LLH_PTP_BUF_SEQID, QED_TIMESTAMP_MASK);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* Read Phy Hardware Clock */
|
|
|
+static int qed_ptp_hw_read_cc(struct qed_dev *cdev, u64 *phc_cycles)
|
|
|
+{
|
|
|
+ struct qed_hwfn *p_hwfn = QED_LEADING_HWFN(cdev);
|
|
|
+ struct qed_ptt *p_ptt = p_hwfn->p_ptp_ptt;
|
|
|
+ u32 temp = 0;
|
|
|
+
|
|
|
+ temp = qed_rd(p_hwfn, p_ptt, NIG_REG_TSGEN_SYNC_TIME_LSB);
|
|
|
+ *phc_cycles = qed_rd(p_hwfn, p_ptt, NIG_REG_TSGEN_SYNC_TIME_MSB);
|
|
|
+ *phc_cycles <<= 32;
|
|
|
+ *phc_cycles |= temp;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* Filter PTP protocol packets that need to be timestamped */
|
|
|
+static int qed_ptp_hw_cfg_rx_filters(struct qed_dev *cdev,
|
|
|
+ enum qed_ptp_filter_type type)
|
|
|
+{
|
|
|
+ struct qed_hwfn *p_hwfn = QED_LEADING_HWFN(cdev);
|
|
|
+ struct qed_ptt *p_ptt = p_hwfn->p_ptp_ptt;
|
|
|
+ u32 rule_mask, parm_mask;
|
|
|
+
|
|
|
+ switch (type) {
|
|
|
+ case QED_PTP_FILTER_L2_IPV4_IPV6:
|
|
|
+ parm_mask = 0x6AA;
|
|
|
+ rule_mask = 0x3EEE;
|
|
|
+ break;
|
|
|
+ case QED_PTP_FILTER_L2:
|
|
|
+ parm_mask = 0x6BF;
|
|
|
+ rule_mask = 0x3EFF;
|
|
|
+ break;
|
|
|
+ case QED_PTP_FILTER_IPV4_IPV6:
|
|
|
+ parm_mask = 0x7EA;
|
|
|
+ rule_mask = 0x3FFE;
|
|
|
+ break;
|
|
|
+ case QED_PTP_FILTER_IPV4:
|
|
|
+ parm_mask = 0x7EE;
|
|
|
+ rule_mask = 0x3FFE;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ DP_INFO(p_hwfn, "Invalid PTP filter type %d\n", type);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ qed_wr(p_hwfn, p_ptt, NIG_REG_LLH_PTP_PARAM_MASK, parm_mask);
|
|
|
+ qed_wr(p_hwfn, p_ptt, NIG_REG_LLH_PTP_RULE_MASK, rule_mask);
|
|
|
+
|
|
|
+ qed_wr(p_hwfn, p_ptt, NIG_REG_LLH_PTP_TO_HOST, 0x1);
|
|
|
+
|
|
|
+ /* Reset possibly old timestamps */
|
|
|
+ qed_wr(p_hwfn, p_ptt, NIG_REG_LLH_PTP_HOST_BUF_SEQID,
|
|
|
+ QED_TIMESTAMP_MASK);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* Adjust the HW clock by a rate given in parts-per-billion (ppb) units.
|
|
|
+ * FW/HW accepts the adjustment value in terms of 3 parameters:
|
|
|
+ * Drift period - adjustment happens once in certain number of nano seconds.
|
|
|
+ * Drift value - time is adjusted by a certain value, for example by 5 ns.
|
|
|
+ * Drift direction - add or subtract the adjustment value.
|
|
|
+ * The routine translates ppb into the adjustment triplet in an optimal manner.
|
|
|
+ */
|
|
|
+static int qed_ptp_hw_adjfreq(struct qed_dev *cdev, s32 ppb)
|
|
|
+{
|
|
|
+ s64 best_val = 0, val, best_period = 0, period, approx_dev, dif, dif2;
|
|
|
+ struct qed_hwfn *p_hwfn = QED_LEADING_HWFN(cdev);
|
|
|
+ struct qed_ptt *p_ptt = p_hwfn->p_ptp_ptt;
|
|
|
+ u32 drift_ctr_cfg = 0, drift_state;
|
|
|
+ int drift_dir = 1;
|
|
|
+
|
|
|
+ if (ppb < 0) {
|
|
|
+ ppb = -ppb;
|
|
|
+ drift_dir = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (ppb > 1) {
|
|
|
+ s64 best_dif = ppb, best_approx_dev = 1;
|
|
|
+
|
|
|
+ /* Adjustment value is up to +/-7ns, find an optimal value in
|
|
|
+ * this range.
|
|
|
+ */
|
|
|
+ for (val = 7; val > 0; val--) {
|
|
|
+ period = div_s64(val * 1000000000, ppb);
|
|
|
+ period -= 8;
|
|
|
+ period >>= 4;
|
|
|
+ if (period < 1)
|
|
|
+ period = 1;
|
|
|
+ if (period > 0xFFFFFFE)
|
|
|
+ period = 0xFFFFFFE;
|
|
|
+
|
|
|
+ /* Check both rounding ends for approximate error */
|
|
|
+ approx_dev = period * 16 + 8;
|
|
|
+ dif = ppb * approx_dev - val * 1000000000;
|
|
|
+ dif2 = dif + 16 * ppb;
|
|
|
+
|
|
|
+ if (dif < 0)
|
|
|
+ dif = -dif;
|
|
|
+ if (dif2 < 0)
|
|
|
+ dif2 = -dif2;
|
|
|
+
|
|
|
+ /* Determine which end gives better approximation */
|
|
|
+ if (dif * (approx_dev + 16) > dif2 * approx_dev) {
|
|
|
+ period++;
|
|
|
+ approx_dev += 16;
|
|
|
+ dif = dif2;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Track best approximation found so far */
|
|
|
+ if (best_dif * approx_dev > dif * best_approx_dev) {
|
|
|
+ best_dif = dif;
|
|
|
+ best_val = val;
|
|
|
+ best_period = period;
|
|
|
+ best_approx_dev = approx_dev;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else if (ppb == 1) {
|
|
|
+ /* This is a special case as its the only value which wouldn't
|
|
|
+ * fit in a s64 variable. In order to prevent castings simple
|
|
|
+ * handle it seperately.
|
|
|
+ */
|
|
|
+ best_val = 4;
|
|
|
+ best_period = 0xee6b27f;
|
|
|
+ } else {
|
|
|
+ best_val = 0;
|
|
|
+ best_period = 0xFFFFFFF;
|
|
|
+ }
|
|
|
+
|
|
|
+ drift_ctr_cfg = (best_period << QED_DRIFT_CNTR_TIME_QUANTA_SHIFT) |
|
|
|
+ (((int)best_val) << QED_DRIFT_CNTR_ADJUSTMENT_SHIFT) |
|
|
|
+ (((int)drift_dir) << QED_DRIFT_CNTR_DIRECTION_SHIFT);
|
|
|
+
|
|
|
+ qed_wr(p_hwfn, p_ptt, NIG_REG_TSGEN_RST_DRIFT_CNTR, 0x1);
|
|
|
+
|
|
|
+ drift_state = qed_rd(p_hwfn, p_ptt, NIG_REG_TSGEN_RST_DRIFT_CNTR);
|
|
|
+ if (drift_state & 1) {
|
|
|
+ qed_wr(p_hwfn, p_ptt, NIG_REG_TSGEN_DRIFT_CNTR_CONF,
|
|
|
+ drift_ctr_cfg);
|
|
|
+ } else {
|
|
|
+ DP_INFO(p_hwfn, "Drift counter is not reset\n");
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ qed_wr(p_hwfn, p_ptt, NIG_REG_TSGEN_RST_DRIFT_CNTR, 0x0);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int qed_ptp_hw_enable(struct qed_dev *cdev)
|
|
|
+{
|
|
|
+ struct qed_hwfn *p_hwfn = QED_LEADING_HWFN(cdev);
|
|
|
+ struct qed_ptt *p_ptt = p_hwfn->p_ptp_ptt;
|
|
|
+
|
|
|
+ /* Reset PTP event detection rules - will be configured in the IOCTL */
|
|
|
+ qed_wr(p_hwfn, p_ptt, NIG_REG_LLH_PTP_PARAM_MASK, 0x7FF);
|
|
|
+ qed_wr(p_hwfn, p_ptt, NIG_REG_LLH_PTP_RULE_MASK, 0x3FFF);
|
|
|
+ qed_wr(p_hwfn, p_ptt, NIG_REG_TX_LLH_PTP_PARAM_MASK, 0x7FF);
|
|
|
+ qed_wr(p_hwfn, p_ptt, NIG_REG_TX_LLH_PTP_RULE_MASK, 0x3FFF);
|
|
|
+
|
|
|
+ qed_wr(p_hwfn, p_ptt, NIG_REG_TX_PTP_EN, 7);
|
|
|
+ qed_wr(p_hwfn, p_ptt, NIG_REG_RX_PTP_EN, 7);
|
|
|
+
|
|
|
+ qed_wr(p_hwfn, p_ptt, NIG_REG_TS_OUTPUT_ENABLE_PDA, 0x1);
|
|
|
+
|
|
|
+ /* Pause free running counter */
|
|
|
+ qed_wr(p_hwfn, p_ptt, NIG_REG_TIMESYNC_GEN_REG_BB, 2);
|
|
|
+
|
|
|
+ qed_wr(p_hwfn, p_ptt, NIG_REG_TSGEN_FREE_CNT_VALUE_LSB, 0);
|
|
|
+ qed_wr(p_hwfn, p_ptt, NIG_REG_TSGEN_FREE_CNT_VALUE_MSB, 0);
|
|
|
+ /* Resume free running counter */
|
|
|
+ qed_wr(p_hwfn, p_ptt, NIG_REG_TIMESYNC_GEN_REG_BB, 4);
|
|
|
+
|
|
|
+ /* Disable drift register */
|
|
|
+ qed_wr(p_hwfn, p_ptt, NIG_REG_TSGEN_DRIFT_CNTR_CONF, 0x0);
|
|
|
+ qed_wr(p_hwfn, p_ptt, NIG_REG_TSGEN_RST_DRIFT_CNTR, 0x0);
|
|
|
+
|
|
|
+ /* Reset possibly old timestamps */
|
|
|
+ qed_wr(p_hwfn, p_ptt, NIG_REG_LLH_PTP_HOST_BUF_SEQID,
|
|
|
+ QED_TIMESTAMP_MASK);
|
|
|
+ qed_wr(p_hwfn, p_ptt, NIG_REG_TX_LLH_PTP_BUF_SEQID, QED_TIMESTAMP_MASK);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int qed_ptp_hw_hwtstamp_tx_on(struct qed_dev *cdev)
|
|
|
+{
|
|
|
+ struct qed_hwfn *p_hwfn = QED_LEADING_HWFN(cdev);
|
|
|
+ struct qed_ptt *p_ptt = p_hwfn->p_ptp_ptt;
|
|
|
+
|
|
|
+ qed_wr(p_hwfn, p_ptt, NIG_REG_TX_LLH_PTP_PARAM_MASK, 0x6AA);
|
|
|
+ qed_wr(p_hwfn, p_ptt, NIG_REG_TX_LLH_PTP_RULE_MASK, 0x3EEE);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int qed_ptp_hw_disable(struct qed_dev *cdev)
|
|
|
+{
|
|
|
+ struct qed_hwfn *p_hwfn = QED_LEADING_HWFN(cdev);
|
|
|
+ struct qed_ptt *p_ptt = p_hwfn->p_ptp_ptt;
|
|
|
+
|
|
|
+ /* Reset PTP event detection rules */
|
|
|
+ qed_wr(p_hwfn, p_ptt, NIG_REG_LLH_PTP_PARAM_MASK, 0x7FF);
|
|
|
+ qed_wr(p_hwfn, p_ptt, NIG_REG_LLH_PTP_RULE_MASK, 0x3FFF);
|
|
|
+
|
|
|
+ qed_wr(p_hwfn, p_ptt, NIG_REG_TX_LLH_PTP_PARAM_MASK, 0x7FF);
|
|
|
+ qed_wr(p_hwfn, p_ptt, NIG_REG_TX_LLH_PTP_RULE_MASK, 0x3FFF);
|
|
|
+
|
|
|
+ /* Disable the PTP feature */
|
|
|
+ qed_wr(p_hwfn, p_ptt, NIG_REG_RX_PTP_EN, 0x0);
|
|
|
+ qed_wr(p_hwfn, p_ptt, NIG_REG_TX_PTP_EN, 0x0);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+const struct qed_eth_ptp_ops qed_ptp_ops_pass = {
|
|
|
+ .hwtstamp_tx_on = qed_ptp_hw_hwtstamp_tx_on,
|
|
|
+ .cfg_rx_filters = qed_ptp_hw_cfg_rx_filters,
|
|
|
+ .read_rx_ts = qed_ptp_hw_read_rx_ts,
|
|
|
+ .read_tx_ts = qed_ptp_hw_read_tx_ts,
|
|
|
+ .read_cc = qed_ptp_hw_read_cc,
|
|
|
+ .adjfreq = qed_ptp_hw_adjfreq,
|
|
|
+ .disable = qed_ptp_hw_disable,
|
|
|
+ .enable = qed_ptp_hw_enable,
|
|
|
+};
|