mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
433 lines
12 KiB
433 lines
12 KiB
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) |
|
/* QLogic qed NIC Driver |
|
* Copyright (c) 2015-2017 QLogic Corporation |
|
* Copyright (c) 2019-2020 Marvell International Ltd. |
|
*/ |
|
|
|
#include <linux/types.h> |
|
#include "qed.h" |
|
#include "qed_dev_api.h" |
|
#include "qed_hw.h" |
|
#include "qed_l2.h" |
|
#include "qed_mcp.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) |
|
/* Param mask for Hardware to detect/timestamp the L2/L4 unicast PTP packets */ |
|
#define QED_PTP_UCAST_PARAM_MASK 0x70F |
|
|
|
static enum qed_resc_lock qed_ptcdev_to_resc(struct qed_hwfn *p_hwfn) |
|
{ |
|
switch (MFW_PORT(p_hwfn)) { |
|
case 0: |
|
return QED_RESC_LOCK_PTP_PORT0; |
|
case 1: |
|
return QED_RESC_LOCK_PTP_PORT1; |
|
case 2: |
|
return QED_RESC_LOCK_PTP_PORT2; |
|
case 3: |
|
return QED_RESC_LOCK_PTP_PORT3; |
|
default: |
|
return QED_RESC_LOCK_RESC_INVALID; |
|
} |
|
} |
|
|
|
static int qed_ptp_res_lock(struct qed_hwfn *p_hwfn, struct qed_ptt *p_ptt) |
|
{ |
|
struct qed_resc_lock_params params; |
|
enum qed_resc_lock resource; |
|
int rc; |
|
|
|
resource = qed_ptcdev_to_resc(p_hwfn); |
|
if (resource == QED_RESC_LOCK_RESC_INVALID) |
|
return -EINVAL; |
|
|
|
qed_mcp_resc_lock_default_init(¶ms, NULL, resource, true); |
|
|
|
rc = qed_mcp_resc_lock(p_hwfn, p_ptt, ¶ms); |
|
if (rc && rc != -EINVAL) { |
|
return rc; |
|
} else if (rc == -EINVAL) { |
|
/* MFW doesn't support resource locking, first PF on the port |
|
* has lock ownership. |
|
*/ |
|
if (p_hwfn->abs_pf_id < p_hwfn->cdev->num_ports_in_engine) |
|
return 0; |
|
|
|
DP_INFO(p_hwfn, "PF doesn't have lock ownership\n"); |
|
return -EBUSY; |
|
} else if (!rc && !params.b_granted) { |
|
DP_INFO(p_hwfn, "Failed to acquire ptp resource lock\n"); |
|
return -EBUSY; |
|
} |
|
|
|
return rc; |
|
} |
|
|
|
static int qed_ptp_res_unlock(struct qed_hwfn *p_hwfn, struct qed_ptt *p_ptt) |
|
{ |
|
struct qed_resc_unlock_params params; |
|
enum qed_resc_lock resource; |
|
int rc; |
|
|
|
resource = qed_ptcdev_to_resc(p_hwfn); |
|
if (resource == QED_RESC_LOCK_RESC_INVALID) |
|
return -EINVAL; |
|
|
|
qed_mcp_resc_lock_default_init(NULL, ¶ms, resource, true); |
|
|
|
rc = qed_mcp_resc_unlock(p_hwfn, p_ptt, ¶ms); |
|
if (rc == -EINVAL) { |
|
/* MFW doesn't support locking, first PF has lock ownership */ |
|
if (p_hwfn->abs_pf_id < p_hwfn->cdev->num_ports_in_engine) { |
|
rc = 0; |
|
} else { |
|
DP_INFO(p_hwfn, "PF doesn't have lock ownership\n"); |
|
return -EINVAL; |
|
} |
|
} else if (rc) { |
|
DP_INFO(p_hwfn, "Failed to release the ptp resource lock\n"); |
|
} |
|
|
|
return rc; |
|
} |
|
|
|
/* 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_VERBOSE(p_hwfn, QED_MSG_DEBUG, |
|
"Invalid Tx timestamp, buf_seqid = %08x\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_filters(struct qed_dev *cdev, |
|
enum qed_ptp_filter_type rx_type, |
|
enum qed_ptp_hwtstamp_tx_type tx_type) |
|
{ |
|
struct qed_hwfn *p_hwfn = QED_LEADING_HWFN(cdev); |
|
struct qed_ptt *p_ptt = p_hwfn->p_ptp_ptt; |
|
u32 rule_mask, enable_cfg = 0x0; |
|
|
|
switch (rx_type) { |
|
case QED_PTP_FILTER_NONE: |
|
enable_cfg = 0x0; |
|
rule_mask = 0x3FFF; |
|
break; |
|
case QED_PTP_FILTER_ALL: |
|
enable_cfg = 0x7; |
|
rule_mask = 0x3CAA; |
|
break; |
|
case QED_PTP_FILTER_V1_L4_EVENT: |
|
enable_cfg = 0x3; |
|
rule_mask = 0x3FFA; |
|
break; |
|
case QED_PTP_FILTER_V1_L4_GEN: |
|
enable_cfg = 0x3; |
|
rule_mask = 0x3FFE; |
|
break; |
|
case QED_PTP_FILTER_V2_L4_EVENT: |
|
enable_cfg = 0x5; |
|
rule_mask = 0x3FAA; |
|
break; |
|
case QED_PTP_FILTER_V2_L4_GEN: |
|
enable_cfg = 0x5; |
|
rule_mask = 0x3FEE; |
|
break; |
|
case QED_PTP_FILTER_V2_L2_EVENT: |
|
enable_cfg = 0x5; |
|
rule_mask = 0x3CFF; |
|
break; |
|
case QED_PTP_FILTER_V2_L2_GEN: |
|
enable_cfg = 0x5; |
|
rule_mask = 0x3EFF; |
|
break; |
|
case QED_PTP_FILTER_V2_EVENT: |
|
enable_cfg = 0x5; |
|
rule_mask = 0x3CAA; |
|
break; |
|
case QED_PTP_FILTER_V2_GEN: |
|
enable_cfg = 0x5; |
|
rule_mask = 0x3EEE; |
|
break; |
|
default: |
|
DP_INFO(p_hwfn, "Invalid PTP filter type %d\n", rx_type); |
|
return -EINVAL; |
|
} |
|
|
|
qed_wr(p_hwfn, p_ptt, NIG_REG_LLH_PTP_PARAM_MASK, |
|
QED_PTP_UCAST_PARAM_MASK); |
|
qed_wr(p_hwfn, p_ptt, NIG_REG_LLH_PTP_RULE_MASK, rule_mask); |
|
qed_wr(p_hwfn, p_ptt, NIG_REG_RX_PTP_EN, enable_cfg); |
|
|
|
if (tx_type == QED_PTP_HWTSTAMP_TX_OFF) { |
|
qed_wr(p_hwfn, p_ptt, NIG_REG_TX_PTP_EN, 0x0); |
|
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); |
|
} else { |
|
qed_wr(p_hwfn, p_ptt, NIG_REG_TX_PTP_EN, enable_cfg); |
|
qed_wr(p_hwfn, p_ptt, NIG_REG_TX_LLH_PTP_PARAM_MASK, |
|
QED_PTP_UCAST_PARAM_MASK); |
|
qed_wr(p_hwfn, p_ptt, NIG_REG_TX_LLH_PTP_RULE_MASK, rule_mask); |
|
} |
|
|
|
/* 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; |
|
int rc; |
|
|
|
p_ptt = qed_ptt_acquire(p_hwfn); |
|
if (!p_ptt) { |
|
DP_NOTICE(p_hwfn, "Failed to acquire PTT for PTP\n"); |
|
return -EBUSY; |
|
} |
|
|
|
p_hwfn->p_ptp_ptt = p_ptt; |
|
|
|
rc = qed_ptp_res_lock(p_hwfn, p_ptt); |
|
if (rc) { |
|
DP_INFO(p_hwfn, |
|
"Couldn't acquire the resource lock, skip ptp enable for this PF\n"); |
|
qed_ptt_release(p_hwfn, p_ptt); |
|
p_hwfn->p_ptp_ptt = NULL; |
|
return rc; |
|
} |
|
|
|
/* 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 */ |
|
if (QED_IS_BB_B0(p_hwfn->cdev)) |
|
qed_wr(p_hwfn, p_ptt, NIG_REG_TIMESYNC_GEN_REG_BB, 2); |
|
if (QED_IS_AH(p_hwfn->cdev)) |
|
qed_wr(p_hwfn, p_ptt, NIG_REG_TSGEN_FREECNT_UPDATE_K2, 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 */ |
|
if (QED_IS_BB_B0(p_hwfn->cdev)) |
|
qed_wr(p_hwfn, p_ptt, NIG_REG_TIMESYNC_GEN_REG_BB, 4); |
|
if (QED_IS_AH(p_hwfn->cdev)) { |
|
qed_wr(p_hwfn, p_ptt, NIG_REG_TSGEN_FREECNT_UPDATE_K2, 4); |
|
qed_wr(p_hwfn, p_ptt, NIG_REG_PTP_LATCH_OSTS_PKT_TIME, 1); |
|
} |
|
|
|
/* 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_disable(struct qed_dev *cdev) |
|
{ |
|
struct qed_hwfn *p_hwfn = QED_LEADING_HWFN(cdev); |
|
struct qed_ptt *p_ptt = p_hwfn->p_ptp_ptt; |
|
|
|
qed_ptp_res_unlock(p_hwfn, p_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); |
|
|
|
qed_ptt_release(p_hwfn, p_ptt); |
|
p_hwfn->p_ptp_ptt = NULL; |
|
|
|
return 0; |
|
} |
|
|
|
const struct qed_eth_ptp_ops qed_ptp_ops_pass = { |
|
.cfg_filters = qed_ptp_hw_cfg_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, |
|
};
|
|
|