920 lines
23 KiB
C
920 lines
23 KiB
C
|
/* Copyright (c) 2013-2015, The Linux Foundation. All rights reserved.
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU General Public License version 2 and
|
||
|
* only version 2 as published by the Free Software Foundation.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
* GNU General Public License for more details.
|
||
|
*/
|
||
|
|
||
|
/* Qualcomm Technologies, Inc. EMAC Ethernet Controller driver.
|
||
|
*/
|
||
|
|
||
|
#include <linux/phy.h>
|
||
|
#include <linux/net_tstamp.h>
|
||
|
#include "emac.h"
|
||
|
#include "emac_hw.h"
|
||
|
#include "emac_ptp.h"
|
||
|
|
||
|
#define RTC_INC_FRAC_NS_BMSK 0x03ffffff
|
||
|
#define RTC_INC_FRAC_NS_SHFT 0
|
||
|
#define RTC_INC_NS_BMSK 0xfc000000
|
||
|
#define RTC_INC_NS_SHFT 26
|
||
|
#define RTC_NUM_FRAC_NS_PER_NS (1 << RTC_INC_NS_SHFT)
|
||
|
|
||
|
#define TS_TX_FIFO_SYNC_RST (TX_INDX_FIFO_SYNC_RST | TX_TS_FIFO_SYNC_RST)
|
||
|
#define TS_RX_FIFO_SYNC_RST (RX_TS_FIFO1_SYNC_RST | RX_TS_FIFO2_SYNC_RST)
|
||
|
#define TS_FIFO_SYNC_RST (TS_TX_FIFO_SYNC_RST | TS_RX_FIFO_SYNC_RST)
|
||
|
|
||
|
struct emac_tstamp_hw_delay {
|
||
|
int phy_mode;
|
||
|
u32 speed;
|
||
|
u32 tx;
|
||
|
u32 rx;
|
||
|
};
|
||
|
|
||
|
struct emac_ptp_frac_ns_adj {
|
||
|
u32 ref_clk_rate;
|
||
|
s32 adj_val;
|
||
|
};
|
||
|
|
||
|
static const struct emac_tstamp_hw_delay emac_ptp_hw_delay[] = {
|
||
|
{ PHY_INTERFACE_MODE_SGMII, 1000, 16, 60 },
|
||
|
{ PHY_INTERFACE_MODE_SGMII, 100, 280, 100 },
|
||
|
{ PHY_INTERFACE_MODE_SGMII, 10, 2400, 400 },
|
||
|
{ 0 }
|
||
|
};
|
||
|
|
||
|
static inline u32 get_rtc_ref_clkrate(struct emac_hw *hw)
|
||
|
{
|
||
|
struct emac_adapter *adpt = emac_hw_get_adap(hw);
|
||
|
|
||
|
return clk_get_rate(adpt->clk[EMAC_CLK_125M].clk);
|
||
|
}
|
||
|
|
||
|
static inline bool is_valid_frac_ns_adj(s32 val)
|
||
|
{
|
||
|
if ((val >= RTC_NUM_FRAC_NS_PER_NS) || (val <= -RTC_NUM_FRAC_NS_PER_NS))
|
||
|
return false;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
static s32 get_frac_ns_adj_from_tbl(struct emac_hw *hw)
|
||
|
{
|
||
|
const struct emac_ptp_frac_ns_adj *tbl = hw->frac_ns_adj_tbl;
|
||
|
u32 clk = get_rtc_ref_clkrate(hw);
|
||
|
s32 val = 0;
|
||
|
int i;
|
||
|
|
||
|
for (i = 0; tbl && i < hw->frac_ns_adj_tbl_sz; i++) {
|
||
|
if (tbl[i].ref_clk_rate == clk) {
|
||
|
if (is_valid_frac_ns_adj(tbl[i].adj_val))
|
||
|
val = tbl[i].adj_val;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return val;
|
||
|
}
|
||
|
|
||
|
static int emac_hw_set_rtc_inc_value(struct emac_hw *hw, s32 adj)
|
||
|
{
|
||
|
u32 clk = get_rtc_ref_clkrate(hw);
|
||
|
u32 ns, frac, rem, inc;
|
||
|
u64 v;
|
||
|
|
||
|
ns = div_u64_rem(1000000000LL, clk, &rem);
|
||
|
v = (u64)rem << RTC_INC_NS_SHFT;
|
||
|
frac = div_u64(v, clk);
|
||
|
|
||
|
if (adj) {
|
||
|
s32 res;
|
||
|
|
||
|
res = (s32)frac + adj;
|
||
|
if (res < 0) {
|
||
|
ns--;
|
||
|
res += RTC_NUM_FRAC_NS_PER_NS;
|
||
|
} else if (res >= RTC_NUM_FRAC_NS_PER_NS) {
|
||
|
ns++;
|
||
|
res -= RTC_NUM_FRAC_NS_PER_NS;
|
||
|
}
|
||
|
frac = (u32)res;
|
||
|
}
|
||
|
|
||
|
inc = (ns << RTC_INC_NS_SHFT) | frac;
|
||
|
emac_reg_w32(hw, EMAC_1588, EMAC_P1588_INC_VALUE_2,
|
||
|
(inc >> 16) & INC_VALUE_2_BMSK);
|
||
|
emac_reg_w32(hw, EMAC_1588, EMAC_P1588_INC_VALUE_1,
|
||
|
inc & INC_VALUE_1_BMSK);
|
||
|
wmb(); /* ensure P1588_INC_VALUE is set before we proceed */
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static const struct emac_tstamp_hw_delay *emac_get_ptp_hw_delay(u32 link_speed,
|
||
|
int phy_mode)
|
||
|
{
|
||
|
const struct emac_tstamp_hw_delay *info = emac_ptp_hw_delay;
|
||
|
u32 speed;
|
||
|
|
||
|
switch (link_speed) {
|
||
|
case EMAC_LINK_SPEED_1GB_FULL:
|
||
|
speed = 1000;
|
||
|
break;
|
||
|
case EMAC_LINK_SPEED_100_FULL:
|
||
|
case EMAC_LINK_SPEED_100_HALF:
|
||
|
speed = 100;
|
||
|
break;
|
||
|
case EMAC_LINK_SPEED_10_FULL:
|
||
|
case EMAC_LINK_SPEED_10_HALF:
|
||
|
speed = 10;
|
||
|
break;
|
||
|
default:
|
||
|
speed = 0;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
for (info = emac_ptp_hw_delay; info->phy_mode; info++) {
|
||
|
if (info->phy_mode == phy_mode && info->speed == speed)
|
||
|
return info;
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
static int emac_hw_adjust_tstamp_offset(struct emac_hw *hw,
|
||
|
enum emac_ptp_clk_mode clk_mode,
|
||
|
u32 link_speed)
|
||
|
{
|
||
|
const struct emac_tstamp_hw_delay *delay_info;
|
||
|
struct emac_phy *phy = &emac_hw_get_adap(hw)->phy;
|
||
|
|
||
|
delay_info = emac_get_ptp_hw_delay(link_speed, phy->phy_mode);
|
||
|
|
||
|
if (clk_mode == emac_ptp_clk_mode_oc_one_step) {
|
||
|
u32 latency = (delay_info) ? delay_info->tx : 0;
|
||
|
|
||
|
emac_reg_update32(hw, EMAC_1588, EMAC_P1588_TX_LATENCY,
|
||
|
TX_LATENCY_BMSK, latency << TX_LATENCY_SHFT);
|
||
|
wmb(); /* ensure that the latency time is flushed to HW */
|
||
|
}
|
||
|
|
||
|
if (delay_info) {
|
||
|
hw->tstamp_rx_offset = delay_info->rx;
|
||
|
hw->tstamp_tx_offset = delay_info->tx;
|
||
|
} else {
|
||
|
hw->tstamp_rx_offset = 0;
|
||
|
hw->tstamp_tx_offset = 0;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int emac_hw_config_tx_tstamp(struct emac_hw *hw, bool enable)
|
||
|
{
|
||
|
if (enable) {
|
||
|
/* Reset the TX timestamp FIFO */
|
||
|
emac_reg_update32(hw, EMAC_CSR, EMAC_EMAC_WRAPPER_CSR1,
|
||
|
TS_TX_FIFO_SYNC_RST, TS_TX_FIFO_SYNC_RST);
|
||
|
wmb(); /* ensure that the Tx timestamp reset is flushed to HW */
|
||
|
emac_reg_update32(hw, EMAC_CSR, EMAC_EMAC_WRAPPER_CSR1,
|
||
|
TS_TX_FIFO_SYNC_RST, 0);
|
||
|
wmb(); /* ensure that the Tx timestamp is out of reset */
|
||
|
|
||
|
emac_reg_update32(hw, EMAC_CSR, EMAC_EMAC_WRAPPER_CSR1,
|
||
|
TX_TS_ENABLE, TX_TS_ENABLE);
|
||
|
wmb(); /* ensure enabling the Tx timestamp is flushed to HW */
|
||
|
SET_FLAG(hw, HW_TS_TX_EN);
|
||
|
} else {
|
||
|
emac_reg_update32(hw, EMAC_CSR, EMAC_EMAC_WRAPPER_CSR1,
|
||
|
TX_TS_ENABLE, 0);
|
||
|
wmb(); /* ensure disabling the Tx timestamp is flushed to HW */
|
||
|
CLR_FLAG(hw, HW_TS_TX_EN);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int emac_hw_config_rx_tstamp(struct emac_hw *hw, bool enable)
|
||
|
{
|
||
|
if (enable) {
|
||
|
/* Reset the RX timestamp FIFO */
|
||
|
emac_reg_update32(hw, EMAC_CSR, EMAC_EMAC_WRAPPER_CSR1,
|
||
|
TS_RX_FIFO_SYNC_RST, TS_RX_FIFO_SYNC_RST);
|
||
|
wmb(); /* ensure that the Rx timestamp reset is flushed to HW */
|
||
|
emac_reg_update32(hw, EMAC_CSR, EMAC_EMAC_WRAPPER_CSR1,
|
||
|
TS_RX_FIFO_SYNC_RST, 0);
|
||
|
wmb(); /* ensure that the Rx timestamp is out of reset */
|
||
|
|
||
|
SET_FLAG(hw, HW_TS_RX_EN);
|
||
|
} else {
|
||
|
CLR_FLAG(hw, HW_TS_RX_EN);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int emac_hw_1588_core_disable(struct emac_hw *hw)
|
||
|
{
|
||
|
if (TEST_FLAG(hw, HW_TS_RX_EN))
|
||
|
emac_hw_config_rx_tstamp(hw, false);
|
||
|
if (TEST_FLAG(hw, HW_TS_TX_EN))
|
||
|
emac_hw_config_tx_tstamp(hw, false);
|
||
|
|
||
|
emac_reg_update32(hw, EMAC_CSR, EMAC_EMAC_WRAPPER_CSR1,
|
||
|
DIS_1588_CLKS, DIS_1588_CLKS);
|
||
|
emac_reg_update32(hw, EMAC_CSR, EMAC_EMAC_WRAPPER_CSR10,
|
||
|
DIS_1588, DIS_1588);
|
||
|
emac_reg_update32(hw, EMAC_1588, EMAC_P1588_CTRL_REG,
|
||
|
BYPASS_O, BYPASS_O);
|
||
|
emac_reg_w32(hw, EMAC_1588, EMAC_P1588_PTP_EXPANDED_INT_MASK, 0);
|
||
|
wmb(); /* ensure that disabling PTP is flushed to HW */
|
||
|
|
||
|
CLR_FLAG(hw, HW_PTP_EN);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int emac_hw_1588_core_enable(struct emac_hw *hw,
|
||
|
enum emac_ptp_mode mode,
|
||
|
enum emac_ptp_clk_mode clk_mode,
|
||
|
u32 link_speed,
|
||
|
s32 frac_ns_adj)
|
||
|
{
|
||
|
if ((clk_mode != emac_ptp_clk_mode_oc_one_step) &&
|
||
|
(clk_mode != emac_ptp_clk_mode_oc_two_step)) {
|
||
|
emac_dbg(emac_hw_get_adap(hw), hw, "invalid ptp clk mode %d\n",
|
||
|
clk_mode);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
emac_reg_update32(hw, EMAC_CSR, EMAC_EMAC_WRAPPER_CSR1,
|
||
|
DIS_1588_CLKS, 0);
|
||
|
emac_reg_update32(hw, EMAC_CSR, EMAC_EMAC_WRAPPER_CSR10, DIS_1588, 0);
|
||
|
emac_reg_update32(hw, EMAC_1588, EMAC_P1588_CTRL_REG, BYPASS_O, 0);
|
||
|
emac_reg_w32(hw, EMAC_1588, EMAC_P1588_PTP_EXPANDED_INT_MASK, 0);
|
||
|
emac_reg_update32(hw, EMAC_1588, EMAC_P1588_RTC_EXPANDED_CONFIG,
|
||
|
RTC_READ_MODE, RTC_READ_MODE);
|
||
|
emac_reg_update32(hw, EMAC_1588, EMAC_P1588_CTRL_REG, ATTACH_EN, 0);
|
||
|
wmb(); /* ensure P1588_CTRL_REG is set before we proceed */
|
||
|
|
||
|
emac_hw_adjust_tstamp_offset(hw, clk_mode, link_speed);
|
||
|
|
||
|
emac_reg_update32(hw, EMAC_1588, EMAC_P1588_CTRL_REG, CLOCK_MODE_BMSK,
|
||
|
(clk_mode << CLOCK_MODE_SHFT));
|
||
|
emac_reg_update32(hw, EMAC_1588, EMAC_P1588_CTRL_REG, ETH_MODE_SW,
|
||
|
(link_speed == EMAC_LINK_SPEED_1GB_FULL) ?
|
||
|
0 : ETH_MODE_SW);
|
||
|
|
||
|
/* set RTC increment every 8ns to fit 125MHZ clock */
|
||
|
emac_hw_set_rtc_inc_value(hw, frac_ns_adj);
|
||
|
|
||
|
emac_reg_update32(hw, EMAC_CSR, EMAC_EMAC_WRAPPER_CSR10,
|
||
|
RD_CLR_1588, RD_CLR_1588);
|
||
|
wmb(); /* ensure clear-on-read is enabled on PTP config registers */
|
||
|
|
||
|
emac_reg_r32(hw, EMAC_1588, EMAC_P1588_PTP_EXPANDED_INT_STATUS);
|
||
|
|
||
|
/* Reset the timestamp FIFO */
|
||
|
emac_reg_update32(hw, EMAC_CSR, EMAC_EMAC_WRAPPER_CSR1,
|
||
|
TS_FIFO_SYNC_RST, TS_FIFO_SYNC_RST);
|
||
|
wmb(); /* ensure timestamp reset is complete */
|
||
|
emac_reg_update32(hw, EMAC_CSR, EMAC_EMAC_WRAPPER_CSR1,
|
||
|
TS_FIFO_SYNC_RST, 0);
|
||
|
wmb(); /* ensure timestamp is out of reset */
|
||
|
|
||
|
if (mode == emac_ptp_mode_master)
|
||
|
emac_reg_update32(hw, EMAC_1588,
|
||
|
EMAC_P1588_GRAND_MASTER_CONFIG_0,
|
||
|
GRANDMASTER_MODE | GM_PPS_SYNC,
|
||
|
GRANDMASTER_MODE);
|
||
|
else
|
||
|
emac_reg_update32(hw, EMAC_1588,
|
||
|
EMAC_P1588_GRAND_MASTER_CONFIG_0,
|
||
|
GRANDMASTER_MODE | GM_PPS_SYNC, 0);
|
||
|
wmb(); /* ensure gradmaster mode setting is flushed to HW */
|
||
|
|
||
|
SET_FLAG(hw, HW_PTP_EN);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void rtc_settime(struct emac_hw *hw, const struct timespec *ts)
|
||
|
{
|
||
|
emac_reg_w32(hw, EMAC_1588, EMAC_P1588_RTC_PRELOADED_5, 0);
|
||
|
|
||
|
emac_reg_w32(hw, EMAC_1588, EMAC_P1588_RTC_PRELOADED_4,
|
||
|
(ts->tv_sec >> 16) & RTC_PRELOADED_4_BMSK);
|
||
|
emac_reg_w32(hw, EMAC_1588, EMAC_P1588_RTC_PRELOADED_3,
|
||
|
ts->tv_sec & RTC_PRELOADED_3_BMSK);
|
||
|
emac_reg_w32(hw, EMAC_1588, EMAC_P1588_RTC_PRELOADED_2,
|
||
|
(ts->tv_nsec >> 16) & RTC_PRELOADED_2_BMSK);
|
||
|
emac_reg_w32(hw, EMAC_1588, EMAC_P1588_RTC_PRELOADED_1,
|
||
|
ts->tv_nsec & RTC_PRELOADED_1_BMSK);
|
||
|
|
||
|
emac_reg_update32(hw, EMAC_1588, EMAC_P1588_RTC_EXPANDED_CONFIG,
|
||
|
LOAD_RTC, LOAD_RTC);
|
||
|
wmb(); /* ensure RTC setting is flushed to HW */
|
||
|
}
|
||
|
|
||
|
static void rtc_gettime(struct emac_hw *hw, struct timespec *ts)
|
||
|
{
|
||
|
emac_reg_update32(hw, EMAC_1588, EMAC_P1588_RTC_EXPANDED_CONFIG,
|
||
|
RTC_SNAPSHOT, RTC_SNAPSHOT);
|
||
|
wmb(); /* ensure snapshot is saved before reading it back */
|
||
|
|
||
|
ts->tv_sec = emac_reg_field_r32(hw, EMAC_1588, EMAC_P1588_REAL_TIME_5,
|
||
|
REAL_TIME_5_BMSK, REAL_TIME_5_SHFT);
|
||
|
ts->tv_sec = (u64)ts->tv_sec << 32;
|
||
|
ts->tv_sec |= emac_reg_field_r32(hw, EMAC_1588, EMAC_P1588_REAL_TIME_4,
|
||
|
REAL_TIME_4_BMSK, REAL_TIME_4_SHFT);
|
||
|
ts->tv_sec <<= 16;
|
||
|
ts->tv_sec |= emac_reg_field_r32(hw, EMAC_1588, EMAC_P1588_REAL_TIME_3,
|
||
|
REAL_TIME_3_BMSK, REAL_TIME_3_SHFT);
|
||
|
|
||
|
ts->tv_nsec = emac_reg_field_r32(hw, EMAC_1588, EMAC_P1588_REAL_TIME_2,
|
||
|
REAL_TIME_2_BMSK, REAL_TIME_2_SHFT);
|
||
|
ts->tv_nsec <<= 16;
|
||
|
ts->tv_nsec |= emac_reg_field_r32(hw, EMAC_1588, EMAC_P1588_REAL_TIME_1,
|
||
|
REAL_TIME_1_BMSK, REAL_TIME_1_SHFT);
|
||
|
}
|
||
|
|
||
|
static void rtc_adjtime(struct emac_hw *hw, s64 delta)
|
||
|
{
|
||
|
s32 delta_ns;
|
||
|
s32 delta_sec;
|
||
|
|
||
|
delta_sec = div_s64_rem(delta, 1000000000LL, &delta_ns);
|
||
|
|
||
|
emac_reg_w32(hw, EMAC_1588, EMAC_P1588_SEC_OFFSET_3, 0);
|
||
|
emac_reg_w32(hw, EMAC_1588, EMAC_P1588_SEC_OFFSET_2,
|
||
|
(delta_sec >> 16) & SEC_OFFSET_2_BMSK);
|
||
|
emac_reg_w32(hw, EMAC_1588, EMAC_P1588_SEC_OFFSET_1,
|
||
|
delta_sec & SEC_OFFSET_1_BMSK);
|
||
|
emac_reg_w32(hw, EMAC_1588, EMAC_P1588_NANO_OFFSET_2,
|
||
|
(delta_ns >> 16) & NANO_OFFSET_2_BMSK);
|
||
|
emac_reg_w32(hw, EMAC_1588, EMAC_P1588_NANO_OFFSET_1,
|
||
|
(delta_ns & NANO_OFFSET_1_BMSK));
|
||
|
emac_reg_w32(hw, EMAC_1588, EMAC_P1588_ADJUST_RTC, 1);
|
||
|
wmb(); /* ensure that RTC adjustment is flushed to HW */
|
||
|
}
|
||
|
|
||
|
static void rtc_ns_sync_pps_in(struct emac_hw *hw)
|
||
|
{
|
||
|
u32 ts;
|
||
|
s64 delta = 0;
|
||
|
|
||
|
ts = emac_reg_r32(hw, EMAC_1588, EMAC_P1588_GM_PPS_TIMESTAMP_2);
|
||
|
ts <<= 16;
|
||
|
|
||
|
ts |= emac_reg_r32(hw, EMAC_1588, EMAC_P1588_GM_PPS_TIMESTAMP_1);
|
||
|
|
||
|
if (ts < 500000000)
|
||
|
delta = 0LL - (s64)ts;
|
||
|
else
|
||
|
delta = 1000000000LL - (s64)ts;
|
||
|
|
||
|
if (delta) {
|
||
|
rtc_adjtime(hw, delta);
|
||
|
emac_dbg(emac_hw_get_adap(hw), intr,
|
||
|
"RTC_SYNC: gm_pps_tstamp_ns 0x%08x, adjust %lldns\n",
|
||
|
ts, delta);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void emac_ptp_rtc_ns_sync(struct emac_hw *hw)
|
||
|
{
|
||
|
unsigned long flag;
|
||
|
|
||
|
spin_lock_irqsave(&hw->ptp_lock, flag);
|
||
|
rtc_ns_sync_pps_in(hw);
|
||
|
spin_unlock_irqrestore(&hw->ptp_lock, flag);
|
||
|
}
|
||
|
|
||
|
int emac_ptp_config(struct emac_hw *hw)
|
||
|
{
|
||
|
struct timespec ts;
|
||
|
int ret = 0;
|
||
|
unsigned long flag;
|
||
|
|
||
|
spin_lock_irqsave(&hw->ptp_lock, flag);
|
||
|
|
||
|
if (TEST_FLAG(hw, HW_PTP_EN))
|
||
|
goto unlock_out;
|
||
|
|
||
|
hw->frac_ns_adj = get_frac_ns_adj_from_tbl(hw);
|
||
|
ret = emac_hw_1588_core_enable(hw,
|
||
|
hw->ptp_mode,
|
||
|
hw->ptp_clk_mode,
|
||
|
EMAC_LINK_SPEED_1GB_FULL,
|
||
|
hw->frac_ns_adj);
|
||
|
if (ret)
|
||
|
goto unlock_out;
|
||
|
|
||
|
getnstimeofday(&ts);
|
||
|
rtc_settime(hw, &ts);
|
||
|
|
||
|
emac_hw_get_adap(hw)->irq[0].mask |= PTP_INT;
|
||
|
hw->ptp_intr_mask = PPS_IN;
|
||
|
|
||
|
unlock_out:
|
||
|
spin_unlock_irqrestore(&hw->ptp_lock, flag);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
int emac_ptp_stop(struct emac_hw *hw)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
unsigned long flag;
|
||
|
|
||
|
spin_lock_irqsave(&hw->ptp_lock, flag);
|
||
|
|
||
|
if (TEST_FLAG(hw, HW_PTP_EN))
|
||
|
ret = emac_hw_1588_core_disable(hw);
|
||
|
|
||
|
hw->ptp_intr_mask = 0;
|
||
|
emac_hw_get_adap(hw)->irq[0].mask &= ~PTP_INT;
|
||
|
|
||
|
spin_unlock_irqrestore(&hw->ptp_lock, flag);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
int emac_ptp_set_linkspeed(struct emac_hw *hw, u32 link_speed)
|
||
|
{
|
||
|
unsigned long flag;
|
||
|
|
||
|
spin_lock_irqsave(&hw->ptp_lock, flag);
|
||
|
emac_reg_update32(hw, EMAC_1588, EMAC_P1588_CTRL_REG, ETH_MODE_SW,
|
||
|
(link_speed == EMAC_LINK_SPEED_1GB_FULL) ? 0 :
|
||
|
ETH_MODE_SW);
|
||
|
wmb(); /* ensure ETH_MODE_SW is set before we proceed */
|
||
|
emac_hw_adjust_tstamp_offset(hw, hw->ptp_clk_mode, link_speed);
|
||
|
spin_unlock_irqrestore(&hw->ptp_lock, flag);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void emac_ptp_intr(struct emac_hw *hw)
|
||
|
{
|
||
|
u32 isr, status;
|
||
|
|
||
|
isr = emac_reg_r32(hw, EMAC_1588, EMAC_P1588_PTP_EXPANDED_INT_STATUS);
|
||
|
status = isr & hw->ptp_intr_mask;
|
||
|
|
||
|
emac_dbg(emac_hw_get_adap(hw), intr,
|
||
|
"receive ptp interrupt: isr 0x%x\n", isr);
|
||
|
|
||
|
if (status & PPS_IN)
|
||
|
emac_ptp_rtc_ns_sync(hw);
|
||
|
}
|
||
|
|
||
|
static int emac_ptp_settime(struct emac_hw *hw, const struct timespec *ts)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
unsigned long flag;
|
||
|
|
||
|
spin_lock_irqsave(&hw->ptp_lock, flag);
|
||
|
if (!TEST_FLAG(hw, HW_PTP_EN))
|
||
|
ret = -EPERM;
|
||
|
else
|
||
|
rtc_settime(hw, ts);
|
||
|
spin_unlock_irqrestore(&hw->ptp_lock, flag);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int emac_ptp_gettime(struct emac_hw *hw, struct timespec *ts)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
unsigned long flag;
|
||
|
|
||
|
spin_lock_irqsave(&hw->ptp_lock, flag);
|
||
|
if (!TEST_FLAG(hw, HW_PTP_EN))
|
||
|
ret = -EPERM;
|
||
|
else
|
||
|
rtc_gettime(hw, ts);
|
||
|
spin_unlock_irqrestore(&hw->ptp_lock, flag);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
int emac_ptp_adjtime(struct emac_hw *hw, s64 delta)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
unsigned long flag;
|
||
|
|
||
|
spin_lock_irqsave(&hw->ptp_lock, flag);
|
||
|
if (!TEST_FLAG(hw, HW_PTP_EN))
|
||
|
ret = -EPERM;
|
||
|
else
|
||
|
rtc_adjtime(hw, delta);
|
||
|
spin_unlock_irqrestore(&hw->ptp_lock, flag);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
int emac_tstamp_ioctl(struct net_device *netdev, struct ifreq *ifr, int cmd)
|
||
|
{
|
||
|
struct emac_adapter *adpt = netdev_priv(netdev);
|
||
|
struct emac_hw *hw = &adpt->hw;
|
||
|
struct hwtstamp_config cfg;
|
||
|
|
||
|
if (!TEST_FLAG(hw, HW_PTP_EN))
|
||
|
return -EPERM;
|
||
|
|
||
|
if (copy_from_user(&cfg, ifr->ifr_data, sizeof(cfg)))
|
||
|
return -EFAULT;
|
||
|
|
||
|
switch (cfg.tx_type) {
|
||
|
case HWTSTAMP_TX_OFF:
|
||
|
emac_hw_config_tx_tstamp(hw, false);
|
||
|
break;
|
||
|
case HWTSTAMP_TX_ON:
|
||
|
if (TEST_FLAG(hw, HW_TS_TX_EN))
|
||
|
break;
|
||
|
|
||
|
emac_hw_config_tx_tstamp(hw, true);
|
||
|
break;
|
||
|
default:
|
||
|
return -ERANGE;
|
||
|
}
|
||
|
|
||
|
switch (cfg.rx_filter) {
|
||
|
case HWTSTAMP_FILTER_NONE:
|
||
|
emac_hw_config_rx_tstamp(hw, false);
|
||
|
break;
|
||
|
default:
|
||
|
cfg.rx_filter = HWTSTAMP_FILTER_ALL;
|
||
|
if (TEST_FLAG(hw, HW_TS_RX_EN))
|
||
|
break;
|
||
|
|
||
|
emac_hw_config_rx_tstamp(hw, true);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return copy_to_user(ifr->ifr_data, &cfg, sizeof(cfg)) ?
|
||
|
-EFAULT : 0;
|
||
|
}
|
||
|
|
||
|
static ssize_t emac_ptp_sysfs_tstamp_set(struct device *dev,
|
||
|
struct device_attribute *attr,
|
||
|
const char *buf, size_t count)
|
||
|
{
|
||
|
struct emac_adapter *adpt = netdev_priv(to_net_dev(dev));
|
||
|
struct timespec ts;
|
||
|
int ret;
|
||
|
|
||
|
getnstimeofday(&ts);
|
||
|
ret = emac_ptp_settime(&adpt->hw, &ts);
|
||
|
if (!ret)
|
||
|
ret = count;
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static ssize_t emac_ptp_sysfs_tstamp_show(struct device *dev,
|
||
|
struct device_attribute *attr,
|
||
|
char *buf)
|
||
|
{
|
||
|
struct emac_adapter *adpt = netdev_priv(to_net_dev(dev));
|
||
|
struct timespec ts = { 0 };
|
||
|
struct timespec ts_now = { 0 };
|
||
|
int count = PAGE_SIZE;
|
||
|
ssize_t retval;
|
||
|
|
||
|
retval = emac_ptp_gettime(&adpt->hw, &ts);
|
||
|
if (retval)
|
||
|
return retval;
|
||
|
|
||
|
getnstimeofday(&ts_now);
|
||
|
retval = scnprintf(buf, count,
|
||
|
"%12u.%09u tstamp %12u.%08u time-of-day\n",
|
||
|
(int)ts.tv_sec, (int)ts.tv_nsec,
|
||
|
(int)ts_now.tv_sec, (int)ts_now.tv_nsec);
|
||
|
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
/* display ethernet mac time as well as the time of the next mac pps pulse */
|
||
|
static ssize_t emac_ptp_sysfs_mtnp_show(struct device *dev,
|
||
|
struct device_attribute *attr,
|
||
|
char *buf)
|
||
|
{
|
||
|
struct emac_adapter *adpt = netdev_priv(to_net_dev(dev));
|
||
|
int count = PAGE_SIZE;
|
||
|
struct timespec ts;
|
||
|
ssize_t ret;
|
||
|
|
||
|
ret = emac_ptp_gettime(&adpt->hw, &ts);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
return scnprintf(buf, count, "%ld %ld %d %ld\n",
|
||
|
ts.tv_sec,
|
||
|
ts.tv_nsec,
|
||
|
(ts.tv_nsec == 0) ? 1 : 0,
|
||
|
(ts.tv_nsec == 0) ? 0 : (NSEC_PER_SEC - ts.tv_nsec));
|
||
|
}
|
||
|
|
||
|
/* Do a "slam" of a very particular time into the time registers... */
|
||
|
static ssize_t emac_ptp_sysfs_slam(struct device *dev,
|
||
|
struct device_attribute *attr,
|
||
|
const char *buf, size_t count)
|
||
|
{
|
||
|
struct emac_adapter *adpt = netdev_priv(to_net_dev(dev));
|
||
|
uint32_t sec = 0;
|
||
|
uint32_t nsec = 0;
|
||
|
ssize_t ret = -EINVAL;
|
||
|
|
||
|
if (sscanf(buf, "%u %u", &sec, &nsec) == 2) {
|
||
|
struct timespec ts = {sec, nsec};
|
||
|
|
||
|
ret = emac_ptp_settime(&adpt->hw, &ts);
|
||
|
if (ret) {
|
||
|
pr_err("%s: emac_ptp_settime failed.\n", __func__);
|
||
|
return ret;
|
||
|
}
|
||
|
ret = count;
|
||
|
} else {
|
||
|
pr_err("%s: sscanf failed.\n", __func__);
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/* Do a coarse time ajustment (ie. coarsely adjust (+/-) the time
|
||
|
* registers by the passed offset)
|
||
|
*/
|
||
|
static ssize_t emac_ptp_sysfs_cadj(struct device *dev,
|
||
|
struct device_attribute *attr,
|
||
|
const char *buf, size_t count)
|
||
|
{
|
||
|
struct emac_adapter *adpt = netdev_priv(to_net_dev(dev));
|
||
|
int64_t offset = 0;
|
||
|
ssize_t ret = -EINVAL;
|
||
|
|
||
|
if (!kstrtos64(buf, 10, &offset)) {
|
||
|
struct timespec ts;
|
||
|
uint64_t new_offset;
|
||
|
uint32_t sec;
|
||
|
uint32_t nsec;
|
||
|
|
||
|
ret = emac_ptp_gettime(&adpt->hw, &ts);
|
||
|
if (ret) {
|
||
|
pr_err("%s: emac_ptp_gettime failed.\n", __func__);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
sec = ts.tv_sec;
|
||
|
nsec = ts.tv_nsec;
|
||
|
|
||
|
new_offset = (((uint64_t)sec * NSEC_PER_SEC) +
|
||
|
(uint64_t)nsec) + offset;
|
||
|
|
||
|
nsec = do_div(new_offset, NSEC_PER_SEC);
|
||
|
sec = new_offset;
|
||
|
|
||
|
ts.tv_sec = sec;
|
||
|
ts.tv_nsec = nsec;
|
||
|
|
||
|
ret = emac_ptp_settime(&adpt->hw, &ts);
|
||
|
if (ret) {
|
||
|
pr_err("%s: emac_ptp_settime failed.\n", __func__);
|
||
|
return ret;
|
||
|
}
|
||
|
ret = count;
|
||
|
} else {
|
||
|
pr_err("%s: sscanf failed.\n", __func__);
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/* Do a fine time ajustment (ie. have the timestamp registers adjust
|
||
|
* themselves by the passed amount).
|
||
|
*/
|
||
|
static ssize_t emac_ptp_sysfs_fadj(struct device *dev,
|
||
|
struct device_attribute *attr,
|
||
|
const char *buf, size_t count)
|
||
|
{
|
||
|
struct emac_adapter *adpt = netdev_priv(to_net_dev(dev));
|
||
|
int64_t offset = 0;
|
||
|
ssize_t ret = -EINVAL;
|
||
|
|
||
|
if (!kstrtos64(buf, 10, &offset)) {
|
||
|
ret = emac_ptp_adjtime(&adpt->hw, offset);
|
||
|
if (ret) {
|
||
|
pr_err("%s: emac_ptp_adjtime failed.\n", __func__);
|
||
|
return ret;
|
||
|
}
|
||
|
ret = count;
|
||
|
} else {
|
||
|
pr_err("%s: sscanf failed.\n", __func__);
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static ssize_t emac_ptp_sysfs_mode_show(struct device *dev,
|
||
|
struct device_attribute *attr,
|
||
|
char *buf)
|
||
|
{
|
||
|
struct emac_adapter *adpt = netdev_priv(to_net_dev(dev));
|
||
|
int count = PAGE_SIZE;
|
||
|
ssize_t ret;
|
||
|
|
||
|
ret = scnprintf(buf, count, "%s\n",
|
||
|
(adpt->hw.ptp_mode == emac_ptp_mode_master) ?
|
||
|
"master" : "slave");
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static ssize_t emac_ptp_sysfs_mode_set(struct device *dev,
|
||
|
struct device_attribute *attr,
|
||
|
const char *buf, size_t count)
|
||
|
{
|
||
|
struct emac_adapter *adpt = netdev_priv(to_net_dev(dev));
|
||
|
struct emac_phy *phy = &adpt->phy;
|
||
|
struct emac_hw *hw = &adpt->hw;
|
||
|
enum emac_ptp_mode mode;
|
||
|
|
||
|
if (!strcmp(buf, "master"))
|
||
|
mode = emac_ptp_mode_master;
|
||
|
else if (!strcmp(buf, "slave"))
|
||
|
mode = emac_ptp_mode_slave;
|
||
|
else
|
||
|
return -EINVAL;
|
||
|
|
||
|
if (mode == hw->ptp_mode)
|
||
|
goto out;
|
||
|
|
||
|
if (TEST_FLAG(hw, HW_PTP_EN)) {
|
||
|
bool rx_tstamp_enable = TEST_FLAG(hw, HW_TS_RX_EN);
|
||
|
bool tx_tstamp_enable = TEST_FLAG(hw, HW_TS_TX_EN);
|
||
|
|
||
|
emac_hw_1588_core_disable(hw);
|
||
|
emac_hw_1588_core_enable(hw, mode, hw->ptp_clk_mode,
|
||
|
phy->link_speed, hw->frac_ns_adj);
|
||
|
if (rx_tstamp_enable)
|
||
|
emac_hw_config_rx_tstamp(hw, true);
|
||
|
if (tx_tstamp_enable)
|
||
|
emac_hw_config_tx_tstamp(hw, true);
|
||
|
|
||
|
emac_reg_w32(hw, EMAC_1588, EMAC_P1588_PTP_EXPANDED_INT_MASK,
|
||
|
hw->ptp_intr_mask);
|
||
|
wmb(); /* ensure PTP_EXPANDED_INT_MASK is set */
|
||
|
}
|
||
|
|
||
|
hw->ptp_mode = mode;
|
||
|
|
||
|
out:
|
||
|
return count;
|
||
|
}
|
||
|
|
||
|
static ssize_t emac_ptp_sysfs_frac_ns_adj_show(struct device *dev,
|
||
|
struct device_attribute *attr,
|
||
|
char *buf)
|
||
|
{
|
||
|
struct emac_adapter *adpt = netdev_priv(to_net_dev(dev));
|
||
|
struct emac_hw *hw = &adpt->hw;
|
||
|
int count = PAGE_SIZE;
|
||
|
ssize_t ret;
|
||
|
|
||
|
if (!TEST_FLAG(hw, HW_PTP_EN))
|
||
|
return -EPERM;
|
||
|
|
||
|
ret = scnprintf(buf, count, "%d\n", adpt->hw.frac_ns_adj);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static ssize_t emac_ptp_sysfs_frac_ns_adj_set(struct device *dev,
|
||
|
struct device_attribute *attr,
|
||
|
const char *buf, size_t count)
|
||
|
{
|
||
|
struct emac_adapter *adpt = netdev_priv(to_net_dev(dev));
|
||
|
struct emac_hw *hw = &adpt->hw;
|
||
|
s32 adj;
|
||
|
|
||
|
if (!TEST_FLAG(hw, HW_PTP_EN))
|
||
|
return -EPERM;
|
||
|
|
||
|
if (kstrtos32(buf, 0, &adj))
|
||
|
return -EINVAL;
|
||
|
|
||
|
if (!is_valid_frac_ns_adj(adj))
|
||
|
return -EINVAL;
|
||
|
|
||
|
emac_hw_set_rtc_inc_value(hw, adj);
|
||
|
hw->frac_ns_adj = adj;
|
||
|
|
||
|
return count;
|
||
|
}
|
||
|
|
||
|
static struct device_attribute ptp_sysfs_devattr[] = {
|
||
|
__ATTR(tstamp, S_IRUSR|S_IRGRP|S_IWUSR|S_IWGRP,
|
||
|
emac_ptp_sysfs_tstamp_show, emac_ptp_sysfs_tstamp_set),
|
||
|
__ATTR(mtnp, S_IRUSR|S_IRGRP, emac_ptp_sysfs_mtnp_show, NULL),
|
||
|
__ATTR(slam, S_IWUSR|S_IWGRP, NULL, emac_ptp_sysfs_slam),
|
||
|
__ATTR(cadj, S_IWUSR|S_IWGRP, NULL, emac_ptp_sysfs_cadj),
|
||
|
__ATTR(fadj, S_IWUSR|S_IWGRP, NULL, emac_ptp_sysfs_fadj),
|
||
|
__ATTR(frac_ns_adj, S_IRUSR|S_IRGRP|S_IWUSR|S_IWGRP,
|
||
|
emac_ptp_sysfs_frac_ns_adj_show, emac_ptp_sysfs_frac_ns_adj_set),
|
||
|
__ATTR(ptp_mode, S_IRUSR|S_IRGRP|S_IWUSR|S_IWGRP,
|
||
|
emac_ptp_sysfs_mode_show, emac_ptp_sysfs_mode_set),
|
||
|
__ATTR_NULL
|
||
|
};
|
||
|
|
||
|
static void emac_ptp_sysfs_create(struct net_device *netdev)
|
||
|
{
|
||
|
struct emac_adapter *adpt = netdev_priv(netdev);
|
||
|
struct device_attribute *devattr;
|
||
|
|
||
|
for (devattr = ptp_sysfs_devattr; devattr->attr.name; devattr++) {
|
||
|
if (device_create_file(&netdev->dev, devattr)) {
|
||
|
emac_err(adpt,
|
||
|
"emac_ptp: failed to create sysfs files\n");
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void emac_ptp_sysfs_remove(struct net_device *netdev)
|
||
|
{
|
||
|
struct device_attribute *devattr;
|
||
|
|
||
|
for (devattr = ptp_sysfs_devattr; devattr->attr.name; devattr++)
|
||
|
device_remove_file(&netdev->dev, devattr);
|
||
|
}
|
||
|
|
||
|
static void emac_ptp_of_get_property(struct emac_adapter *adpt)
|
||
|
{
|
||
|
struct emac_hw *hw = &adpt->hw;
|
||
|
struct device *parent = adpt->netdev->dev.parent;
|
||
|
struct device_node *node = parent->of_node;
|
||
|
const int *tbl;
|
||
|
struct emac_ptp_frac_ns_adj *adj_tbl = NULL;
|
||
|
int size, tbl_size;
|
||
|
|
||
|
if (of_property_read_bool(node, "qcom,emac-ptp-grandmaster"))
|
||
|
hw->ptp_mode = emac_ptp_mode_master;
|
||
|
else
|
||
|
hw->ptp_mode = emac_ptp_mode_slave;
|
||
|
|
||
|
hw->frac_ns_adj_tbl = NULL;
|
||
|
hw->frac_ns_adj_tbl_sz = 0;
|
||
|
|
||
|
tbl = of_get_property(node, "qcom,emac-ptp-frac-ns-adj", &size);
|
||
|
if (!tbl)
|
||
|
return;
|
||
|
|
||
|
if ((size % sizeof(struct emac_ptp_frac_ns_adj))) {
|
||
|
emac_err(adpt, "emac_ptp: invalid frac-ns-adj tbl size(%d)\n",
|
||
|
size);
|
||
|
return;
|
||
|
}
|
||
|
tbl_size = size / sizeof(struct emac_ptp_frac_ns_adj);
|
||
|
|
||
|
adj_tbl = kzalloc(size, GFP_KERNEL);
|
||
|
if (!adj_tbl)
|
||
|
return;
|
||
|
|
||
|
if (of_property_read_u32_array(node, "qcom,emac-ptp-frac-ns-adj",
|
||
|
(u32 *)adj_tbl, size/sizeof(u32))) {
|
||
|
emac_err(adpt, "emac_ptp: failed to read frac-ns-adj tbl\n");
|
||
|
kfree(adj_tbl);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
hw->frac_ns_adj_tbl = adj_tbl;
|
||
|
hw->frac_ns_adj_tbl_sz = tbl_size;
|
||
|
}
|
||
|
|
||
|
int emac_ptp_init(struct net_device *netdev)
|
||
|
{
|
||
|
struct emac_adapter *adpt = netdev_priv(netdev);
|
||
|
struct emac_hw *hw = &adpt->hw;
|
||
|
int ret = 0;
|
||
|
|
||
|
emac_ptp_of_get_property(adpt);
|
||
|
spin_lock_init(&hw->ptp_lock);
|
||
|
emac_ptp_sysfs_create(netdev);
|
||
|
ret = emac_hw_1588_core_disable(hw);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
void emac_ptp_remove(struct net_device *netdev)
|
||
|
{
|
||
|
struct emac_adapter *adpt = netdev_priv(netdev);
|
||
|
struct emac_hw *hw = &adpt->hw;
|
||
|
|
||
|
emac_ptp_sysfs_remove(netdev);
|
||
|
kfree(hw->frac_ns_adj_tbl);
|
||
|
}
|