/* 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 #include #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); }