| /* |
| * Copyright (c) 2018, The Linux Foundation. All rights reserved. |
| * Permission to use, copy, modify, and/or distribute this software for |
| * any purpose with or without fee is hereby granted, provided that the |
| * above copyright notice and this permission notice appear in all copies. |
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
| * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
| * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT |
| * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| */ |
| |
| #include <linux/if_vlan.h> |
| #include <linux/net_tstamp.h> |
| #include <linux/ptp_classify.h> |
| |
| #include "qca808x.h" |
| |
| #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4,4,0)) |
| #include <linux/time64.h> |
| #else |
| #include <linux/time.h> |
| #define ns_to_timespec64 ns_to_timespec |
| #define timespec64_to_ns timespec_to_ns |
| #define timespec64 timespec |
| #endif |
| |
| #include "qca808x_ptp.h" |
| #include "qca808x_ptp_reg.h" |
| #include "qca808x_ptp_api.h" |
| |
| #define QCA808X_PTP_EMBEDDED_MODE 0xa |
| |
| #define QCA808X_PTP_INCVAL_SYNC_MODE 0x8 |
| #define QCA808X_PTP_TICK_RATE_125M 8 |
| #define QCA808X_PTP_TICK_RATE_200M 5 |
| |
| #define PTP_HDR_RESERVED0_OFFSET 1 |
| #define PTP_HDR_RESERVED1_OFFSET 5 |
| #define PTP_HDR_CORRECTIONFIELD_OFFSET 8 |
| #define PTP_HDR_RESERVED2_OFFSET 16 |
| |
| #define SKB_TIMESTAMP_TIMEOUT 1 /* jiffies */ |
| #define GPS_WORK_TIMEOUT HZ |
| |
| #define HWTSTAMP_TX_ONESTEP_P2P (HWTSTAMP_TX_ONESTEP_SYNC + 1) |
| |
| extern struct list_head g_qca808x_phy_list; |
| void qca808x_ptp_gm_gps_seconds_sync_enable(a_uint32_t dev_id, |
| a_uint32_t phy_addr, a_bool_t en) |
| { |
| struct qca808x_phy_info *pdata; |
| #if defined(IN_PHY_I2C_MODE) |
| a_uint32_t port_id; |
| port_id = qca_ssdk_phy_addr_to_port(dev_id, phy_addr); |
| if (hsl_port_phy_access_type_get(dev_id, port_id) == PHY_I2C_ACCESS) { |
| phy_addr = qca_ssdk_port_to_phy_mdio_fake_addr(dev_id, port_id); |
| } |
| #endif |
| |
| pdata = qca808x_phy_info_get(phy_addr); |
| if (pdata) { |
| pdata->gps_seconds_sync_en = en; |
| } |
| |
| if (en == A_TRUE) { |
| schedule_delayed_work(&pdata->ts_schedule_work, GPS_WORK_TIMEOUT); |
| } |
| |
| return; |
| } |
| |
| a_bool_t qca808x_ptp_gm_gps_seconds_sync_status_get(a_uint32_t dev_id, |
| a_uint32_t phy_addr) |
| { |
| struct qca808x_phy_info *pdata; |
| #if defined(IN_PHY_I2C_MODE) |
| a_uint32_t port_id; |
| port_id = qca_ssdk_phy_addr_to_port(dev_id, phy_addr); |
| if (hsl_port_phy_access_type_get(dev_id, port_id) == PHY_I2C_ACCESS) { |
| phy_addr = qca_ssdk_port_to_phy_mdio_fake_addr(dev_id, port_id); |
| } |
| #endif |
| |
| pdata = qca808x_phy_info_get(phy_addr); |
| if (pdata) { |
| return pdata->gps_seconds_sync_en; |
| } |
| |
| return A_FALSE; |
| } |
| |
| void qca808x_ptp_clock_mode_config(a_uint32_t dev_id, |
| a_uint32_t phy_addr, a_uint16_t clock_mode, a_uint16_t step_mode) |
| { |
| struct qca808x_phy_info *pdata; |
| #if defined(IN_PHY_I2C_MODE) |
| a_uint32_t port_id; |
| port_id = qca_ssdk_phy_addr_to_port(dev_id, phy_addr); |
| if (hsl_port_phy_access_type_get(dev_id, port_id) == PHY_I2C_ACCESS) { |
| phy_addr = qca_ssdk_port_to_phy_mdio_fake_addr(dev_id, port_id); |
| } |
| #endif |
| |
| pdata = qca808x_phy_info_get(phy_addr); |
| |
| if (pdata) { |
| pdata->clock_mode = clock_mode; |
| pdata->step_mode = step_mode; |
| } |
| |
| return; |
| } |
| |
| void qca808x_ptp_stat_update(struct qca808x_phy_info *pdata, fal_ptp_direction_t direction, |
| a_int32_t msg_type, a_int32_t seqid_matched) |
| { |
| ptp_packet_stat *pkt_stat = NULL; |
| |
| if (((direction != FAL_RX_DIRECTION) && (direction != FAL_TX_DIRECTION)) || |
| (seqid_matched < PTP_PKT_SEQID_UNMATCHED) || |
| (seqid_matched >= PTP_PKT_SEQID_MATCH_MAX)) { |
| return; |
| } |
| |
| pkt_stat = &pdata->pkt_stat[direction]; |
| switch (msg_type) { |
| case QCA808X_PTP_MSG_SYNC: |
| pkt_stat->sync_cnt[seqid_matched]++; |
| break; |
| case QCA808X_PTP_MSG_DREQ: |
| pkt_stat->delay_req_cnt[seqid_matched]++; |
| break; |
| case QCA808X_PTP_MSG_PREQ: |
| pkt_stat->pdelay_req_cnt[seqid_matched]++; |
| break; |
| case QCA808X_PTP_MSG_PRESP: |
| pkt_stat->pdelay_resp_cnt[seqid_matched]++; |
| break; |
| case QCA808X_PTP_MSG_MAX: |
| pkt_stat->event_pkt_cnt++; |
| break; |
| default: |
| SSDK_DEBUG("%s: msg %x is not event frame\n", |
| __func__, msg_type); |
| } |
| } |
| |
| void qca808x_ptp_stat_get(void) |
| { |
| int i = 0; |
| struct qca808x_phy_info *pdata = NULL; |
| ptp_packet_stat *pkt_stat; |
| |
| list_for_each_entry(pdata, &g_qca808x_phy_list, list) { |
| pkt_stat = pdata->pkt_stat; |
| SSDK_INFO("PHY [%#x] PTP event packet statistics:\n", pdata->phy_addr); |
| for (i=0; i <= FAL_TX_DIRECTION; i++) |
| { |
| if (i == FAL_TX_DIRECTION) { |
| SSDK_INFO("----------TX direction----------\n"); |
| } else { |
| SSDK_INFO("----------RX direction----------\n"); |
| } |
| |
| SSDK_INFO("even sum: %lld\n", |
| pkt_stat[i].event_pkt_cnt); |
| SSDK_INFO("seq id matched stat:\n"); |
| SSDK_INFO("sync: %lld\n", |
| pkt_stat[i].sync_cnt[PTP_PKT_SEQID_MATCHED]); |
| SSDK_INFO("delay_req: %lld\n", |
| pkt_stat[i].delay_req_cnt[PTP_PKT_SEQID_MATCHED]); |
| SSDK_INFO("pdelay_req: %lld\n", |
| pkt_stat[i].pdelay_req_cnt[PTP_PKT_SEQID_MATCHED]); |
| SSDK_INFO("pdelay_resp: %lld\n\n", |
| pkt_stat[i].pdelay_resp_cnt[PTP_PKT_SEQID_MATCHED]); |
| |
| SSDK_INFO("seq id unmatched stat:\n"); |
| SSDK_INFO("sync: %lld\n", |
| pkt_stat[i].sync_cnt[PTP_PKT_SEQID_UNMATCHED]); |
| SSDK_INFO("delay_req: %lld\n", |
| pkt_stat[i].delay_req_cnt[PTP_PKT_SEQID_UNMATCHED]); |
| SSDK_INFO("pdelay_req: %lld\n", |
| pkt_stat[i].pdelay_req_cnt[PTP_PKT_SEQID_UNMATCHED]); |
| SSDK_INFO("pdelay_resp: %lld\n\n", |
| pkt_stat[i].pdelay_resp_cnt[PTP_PKT_SEQID_UNMATCHED]); |
| } |
| } |
| } |
| |
| void qca808x_ptp_stat_set(void) |
| { |
| struct qca808x_phy_info *pdata; |
| |
| list_for_each_entry(pdata, &g_qca808x_phy_list, list) { |
| memset(pdata->pkt_stat, 0, sizeof(pdata->pkt_stat)); |
| } |
| } |
| |
| static sw_error_t qca808x_ptp_clock_synce_clock_enable(a_uint32_t dev_id, |
| a_uint32_t phy_id, a_bool_t enable) |
| { |
| a_uint16_t phy_data, phy_data1; |
| sw_error_t ret = SW_OK; |
| |
| phy_data = qca808x_phy_debug_read(dev_id, phy_id, |
| QCA808X_DEBUG_ANA_CLOCK_CTRL_REG); |
| |
| phy_data1 = qca808x_phy_mmd_read(dev_id, phy_id, QCA808X_PHY_MMD7_NUM, |
| QCA808X_MMD7_CLOCK_CTRL_REG); |
| |
| if (enable == A_TRUE) { |
| /* enable analog synce clock output */ |
| phy_data |= QCA808X_ANALOG_PHY_SYNCE_CLOCK_EN; |
| /* enable digital synce clock output */ |
| phy_data1 |= QCA808X_DIGITAL_PHY_SYNCE_CLOCK_EN; |
| } else { |
| phy_data &= ~QCA808X_ANALOG_PHY_SYNCE_CLOCK_EN; |
| phy_data1 &= ~QCA808X_DIGITAL_PHY_SYNCE_CLOCK_EN; |
| } |
| |
| ret = qca808x_phy_debug_write(dev_id, phy_id, |
| QCA808X_DEBUG_ANA_CLOCK_CTRL_REG, phy_data); |
| |
| ret |= qca808x_phy_mmd_write(dev_id, phy_id, QCA808X_PHY_MMD7_NUM, |
| QCA808X_MMD7_CLOCK_CTRL_REG, phy_data1); |
| return ret; |
| } |
| |
| static sw_error_t qca808x_ptp_clock_incval_mode_set(a_uint32_t dev_id, |
| a_uint32_t phy_id, a_bool_t enable) |
| { |
| a_uint16_t phy_data; |
| sw_error_t ret = SW_OK; |
| |
| phy_data = qca808x_phy_mmd_read(dev_id, phy_id, QCA808X_PHY_MMD3_NUM, |
| PTP_RTC_EXT_CONF_REG_ADDRESS); |
| |
| if (enable == A_TRUE) { |
| phy_data |= QCA808X_PTP_INCVAL_SYNC_MODE; |
| } else { |
| phy_data &= ~QCA808X_PTP_INCVAL_SYNC_MODE; |
| } |
| |
| ret = qca808x_phy_mmd_write(dev_id, phy_id, QCA808X_PHY_MMD3_NUM, |
| PTP_RTC_EXT_CONF_REG_ADDRESS, phy_data); |
| |
| return ret; |
| } |
| |
| sw_error_t qca808x_ptp_config_init(struct phy_device *phydev) |
| { |
| fal_ptp_config_t ptp_config; |
| fal_ptp_reference_clock_t ptp_ref_clock; |
| fal_ptp_rx_timestamp_mode_t rx_ts_mode; |
| fal_ptp_time_t ptp_time = {0}; |
| sw_error_t ret = SW_OK; |
| a_uint32_t dev_id = 0, phy_id = 0; |
| qca808x_priv *priv = phydev->priv; |
| const struct qca808x_phy_info *pdata = priv->phy_info; |
| struct qca808x_ptp_info *ptp_info = &priv->ptp_info; |
| |
| if (!pdata) { |
| return SW_FAIL; |
| } |
| |
| dev_id = pdata->dev_id; |
| phy_id = pdata->phy_addr; |
| |
| /* disable ptp function by default */ |
| ptp_info->hwts_tx_type = HWTSTAMP_TX_OFF; |
| ptp_info->hwts_rx_type = PTP_CLASS_NONE; |
| |
| ptp_config.ptp_en = A_FALSE; |
| ptp_config.clock_mode = FAL_OC_CLOCK_MODE; |
| ptp_config.step_mode = FAL_TWO_STEP_MODE; |
| ret = qca808x_phy_ptp_config_set(dev_id, phy_id, &ptp_config); |
| |
| qca808x_ptp_clock_mode_config(dev_id, phy_id, ptp_config.clock_mode, |
| ptp_config.step_mode); |
| |
| /* set rtc clock to asynchronization mode*/ |
| ret |= qca808x_ptp_clock_incval_mode_set(dev_id, phy_id, A_FALSE); |
| |
| /* adjust frequency to 8ns(125MHz) */ |
| ptp_time.nanoseconds = QCA808X_PTP_TICK_RATE_125M; |
| ret |= qca808x_phy_ptp_rtc_adjfreq_set(dev_id, phy_id, &ptp_time); |
| |
| /* use SyncE reference clock */ |
| ptp_ref_clock = FAL_REF_CLOCK_SYNCE; |
| ret |= qca808x_phy_ptp_reference_clock_set(dev_id, phy_id, ptp_ref_clock); |
| |
| /* use Embed mode to get RX timestamp */ |
| rx_ts_mode = FAL_RX_TS_EMBED; |
| ret |= qca808x_phy_ptp_rx_timestamp_mode_set(dev_id, phy_id, rx_ts_mode); |
| |
| /* disable SYNCE clock output */ |
| ret |= qca808x_ptp_clock_synce_clock_enable(dev_id, phy_id, A_FALSE); |
| |
| if (ret != SW_OK) { |
| SSDK_ERROR("%s failed\n", __func__); |
| } |
| |
| return ret; |
| } |
| |
| static a_uint8_t* skb_ptp_header(struct sk_buff *skb, int type) |
| { |
| a_uint8_t *data = skb_mac_header(skb); |
| a_uint32_t offset = 0; |
| |
| if (type & PTP_CLASS_VLAN) { |
| offset += VLAN_HLEN; |
| } |
| |
| switch (type & PTP_CLASS_PMASK) { |
| case PTP_CLASS_IPV4: |
| offset += ETH_HLEN + IPV4_HLEN(data + offset) + UDP_HLEN; |
| break; |
| case PTP_CLASS_IPV6: |
| offset += ETH_HLEN + IP6_HLEN + UDP_HLEN; |
| break; |
| case PTP_CLASS_L2: |
| offset += ETH_HLEN; |
| break; |
| default: |
| return NULL; |
| } |
| |
| if (skb->len + ETH_HLEN < offset + |
| OFF_PTP_SEQUENCE_ID + sizeof(a_uint16_t)) { |
| return NULL; |
| } |
| |
| return data + offset; |
| } |
| |
| void qca808x_pkt_info_get(struct sk_buff *skb, |
| unsigned int type, fal_ptp_pkt_info_t *pkt_info) |
| { |
| a_uint16_t *seqid, seqid_pkt; |
| a_uint32_t *clockid; |
| a_uint32_t clockid_lo; |
| a_uint64_t clockid_pkt; |
| a_uint16_t *portid, portid_pkt; |
| a_uint8_t msgtype_pkt; |
| a_uint8_t *ptp_header = NULL; |
| |
| ptp_header = skb_ptp_header(skb, type); |
| if (!ptp_header) { |
| return; |
| } |
| |
| #define OFF_PTP_CLOCK_ID 20 |
| #define OFF_PTP_PORT_ID 28 |
| |
| seqid = (a_uint16_t *)(ptp_header + OFF_PTP_SEQUENCE_ID); |
| seqid_pkt = ntohs(*seqid); |
| clockid = (a_uint32_t *)(ptp_header + OFF_PTP_CLOCK_ID); |
| clockid_pkt = ntohl(*clockid); |
| |
| clockid = (a_uint32_t *)(ptp_header + OFF_PTP_CLOCK_ID + 4); |
| clockid_lo = ntohl(*clockid); |
| clockid_pkt = (clockid_pkt << 32) | clockid_lo; |
| portid = (a_uint16_t *)(ptp_header + OFF_PTP_PORT_ID); |
| portid_pkt = ntohs(*portid); |
| msgtype_pkt = (*ptp_header) & 0xf; |
| |
| pkt_info->sequence_id = seqid_pkt; |
| pkt_info->clock_identify = clockid_pkt; |
| pkt_info->port_number = portid_pkt; |
| pkt_info->msg_type = msgtype_pkt; |
| return ; |
| } |
| |
| static void tx_timestamp_work(struct work_struct *work) |
| { |
| struct sk_buff *skb; |
| struct skb_shared_hwtstamps shhwtstamps; |
| struct timespec64 ts; |
| a_uint64_t ns; |
| a_uint32_t dev_id, phy_id; |
| qca808x_ptp_cb *ptp_cb; |
| struct qca808x_phy_info *pdata; |
| fal_ptp_pkt_info_t pkt_info; |
| fal_ptp_time_t tx_time = {0}; |
| sw_error_t ret = SW_OK; |
| a_uint16_t times = 0; |
| a_uint16_t seqid = 0; |
| struct qca808x_ptp_info *ptp_data = |
| container_of(work, struct qca808x_ptp_info, tx_ts_work.work); |
| |
| qca808x_priv *priv = |
| container_of(ptp_data, qca808x_priv, ptp_info); |
| |
| pdata = priv->phy_info; |
| |
| if (!pdata) { |
| return; |
| } |
| |
| dev_id = pdata->dev_id; |
| phy_id = pdata->phy_addr; |
| |
| memset(&shhwtstamps, 0, sizeof(shhwtstamps)); |
| |
| while ((skb = skb_dequeue(&ptp_data->tx_queue))) { |
| ptp_cb = (qca808x_ptp_cb *)skb->cb; |
| qca808x_pkt_info_get(skb, ptp_cb->ptp_type, &pkt_info); |
| |
| times = 0; |
| do { |
| /* poll the seqid of the transmitted ptp packet to |
| * acquire the correspoding tx time stamp. |
| */ |
| seqid = qca808x_phy_mmd_read(dev_id, phy_id, QCA808X_PHY_MMD3_NUM, |
| PTP_TX_SEQID_REG_ADDRESS); |
| udelay(1); |
| times++; |
| } while (seqid != pkt_info.sequence_id && times < 100); |
| |
| ret = qca808x_phy_ptp_timestamp_get(dev_id, phy_id, |
| FAL_TX_DIRECTION, &pkt_info, &tx_time); |
| |
| if (ret == SW_NOT_FOUND) { |
| qca808x_ptp_stat_update(pdata, FAL_TX_DIRECTION, |
| pkt_info.msg_type, PTP_PKT_SEQID_UNMATCHED); |
| SSDK_DEBUG("Fail to get tx_ts: sequence_id:%x, clock_identify:%llx," |
| " port_number:%x, msg_type:%x\n", |
| pkt_info.sequence_id, pkt_info.clock_identify, |
| pkt_info.port_number, pkt_info.msg_type); |
| } else { |
| qca808x_ptp_stat_update(pdata, FAL_TX_DIRECTION, |
| pkt_info.msg_type, PTP_PKT_SEQID_MATCHED); |
| } |
| ts.tv_sec = tx_time.seconds; |
| ts.tv_nsec = tx_time.nanoseconds; |
| |
| ns = timespec64_to_ns(&ts); |
| |
| shhwtstamps.hwtstamp = ns_to_ktime(ns); |
| skb_complete_tx_timestamp(skb, &shhwtstamps); |
| } |
| |
| if (!skb_queue_empty(&ptp_data->tx_queue)) |
| schedule_delayed_work(&ptp_data->tx_ts_work, SKB_TIMESTAMP_TIMEOUT); |
| } |
| |
| static void ptp_ingress_time_sync(a_uint32_t phy_addr, a_uint32_t ingress_time, |
| a_bool_t forward) |
| { |
| fal_ptp_time_t ingress_trig_time = {0}; |
| struct qca808x_phy_info *pdata = NULL; |
| |
| ingress_trig_time.nanoseconds = ingress_time; |
| |
| if (forward == A_FALSE) { |
| list_for_each_entry(pdata, &g_qca808x_phy_list, list) { |
| if (pdata->phydev_addr == phy_addr) { |
| qca808x_phy_ptp_pkt_timestamp_set(pdata->dev_id, |
| pdata->phy_addr, &ingress_trig_time); |
| break; |
| } |
| } |
| } else { |
| list_for_each_entry(pdata, &g_qca808x_phy_list, list) { |
| if (pdata->phydev_addr != phy_addr) { |
| qca808x_phy_ptp_pkt_timestamp_set(pdata->dev_id, |
| pdata->phy_addr, &ingress_trig_time); |
| } |
| } |
| } |
| } |
| |
| static void rx_timestamp_work(struct work_struct *work) |
| { |
| struct sk_buff *skb; |
| struct skb_shared_hwtstamps *shhwtstamps = NULL; |
| struct timespec64 ts; |
| a_uint64_t ns; |
| a_uint32_t dev_id, phy_id; |
| qca808x_ptp_cb *ptp_cb; |
| struct qca808x_phy_info *pdata; |
| fal_ptp_pkt_info_t pkt_info; |
| fal_ptp_time_t rx_time = {0}; |
| sw_error_t ret = SW_OK; |
| |
| struct qca808x_ptp_info *ptp_data = |
| container_of(work, struct qca808x_ptp_info, rx_ts_work.work); |
| |
| qca808x_priv *priv = |
| container_of(ptp_data, qca808x_priv, ptp_info); |
| |
| pdata = priv->phy_info; |
| |
| if (!pdata) { |
| return; |
| } |
| dev_id = pdata->dev_id; |
| phy_id = pdata->phy_addr; |
| |
| /* Deliver packets */ |
| while ((skb = skb_dequeue(&ptp_data->rx_queue))) { |
| ptp_cb = (qca808x_ptp_cb *)skb->cb; |
| qca808x_pkt_info_get(skb, ptp_cb->ptp_type, &pkt_info); |
| |
| ret = qca808x_phy_ptp_timestamp_get(dev_id, phy_id, |
| FAL_RX_DIRECTION, &pkt_info, &rx_time); |
| if (ret == SW_NOT_FOUND) { |
| qca808x_ptp_stat_update(pdata, FAL_RX_DIRECTION, |
| pkt_info.msg_type, PTP_PKT_SEQID_UNMATCHED); |
| SSDK_DEBUG("Fail to get rx_ts: sequence_id:%x, clock_identify:%llx, " |
| "port_number:%x, msg_type:%x\n", |
| pkt_info.sequence_id, pkt_info.clock_identify, |
| pkt_info.port_number, pkt_info.msg_type); |
| } else { |
| qca808x_ptp_stat_update(pdata, FAL_RX_DIRECTION, |
| pkt_info.msg_type, PTP_PKT_SEQID_MATCHED); |
| } |
| |
| ts.tv_sec = rx_time.seconds; |
| ts.tv_nsec = rx_time.nanoseconds; |
| ns = timespec64_to_ns(&ts); |
| shhwtstamps = skb_hwtstamps(skb); |
| memset(shhwtstamps, 0, sizeof(*shhwtstamps)); |
| shhwtstamps->hwtstamp = ns_to_ktime(ns); |
| |
| /* OC/BC needs record ingress time stamp on receiving |
| * peer delay request message under one-step mode. |
| * TC one step mode should use the embeded mode for offloading |
| * function. |
| */ |
| if (pdata->step_mode == FAL_ONE_STEP_MODE) { |
| switch (pdata->clock_mode) { |
| case FAL_OC_CLOCK_MODE: |
| case FAL_BC_CLOCK_MODE: |
| if (pkt_info.msg_type == QCA808X_PTP_MSG_PREQ) { |
| ptp_ingress_time_sync(phy_id, |
| ts.tv_nsec, A_FALSE); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| netif_rx_ni(skb); |
| } |
| |
| if (!skb_queue_empty(&ptp_data->rx_queue)) |
| schedule_delayed_work(&ptp_data->rx_ts_work, SKB_TIMESTAMP_TIMEOUT); |
| } |
| |
| static void qca808x_gps_second_sync(struct qca808x_phy_info *pdata, a_int32_t *buf) |
| { |
| fal_ptp_time_t time, old_time; |
| a_uint32_t dev_id, phy_id; |
| |
| if (!pdata) { |
| return; |
| } |
| dev_id = pdata->dev_id; |
| phy_id = pdata->phy_addr; |
| /* 0-3: time of week; 4-5: week number; 6-7: UTC offset |
| * Time(UTC) = Time(GPS) - UTC offset */ |
| #define WEEK_TIME 604800 |
| time.seconds = ((a_int64_t)buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3]) + |
| ((buf[4] << 8 | buf[5]) * WEEK_TIME) - (buf[6] << 8 | buf[7]); |
| |
| time.nanoseconds = 0; |
| time.fracnanoseconds = 0; |
| |
| qca808x_phy_ptp_rtc_time_get(dev_id, phy_id, &old_time); |
| time.seconds -= old_time.seconds; |
| qca808x_phy_ptp_rtc_adjtime_set(dev_id, phy_id, &time); |
| |
| qca808x_ptp_gm_gps_seconds_sync_enable(dev_id, phy_id, A_FALSE); |
| return; |
| } |
| |
| static void gps_seconds_sync_thread(struct qca808x_phy_info *pdata) |
| { |
| struct file *filp; |
| mm_segment_t fs; |
| a_uint32_t nread; |
| char buff[128]; |
| char *dev ="/dev/ttyMSM0"; |
| a_int32_t data[32]; |
| a_uint32_t i = 0, j = 0; |
| a_bool_t is_time_pkt = A_FALSE; |
| a_int32_t try_cycle = 32; |
| |
| filp = filp_open(dev, O_RDONLY, 0); |
| if (IS_ERR(filp)) |
| { |
| SSDK_ERROR("Open %s error\n", dev); |
| return; |
| } |
| |
| fs=get_fs(); |
| set_fs(KERNEL_DS); |
| while(1) |
| { |
| memset(data, 0, sizeof(data)); |
| memset(buff, 0, sizeof(buff)); |
| is_time_pkt = A_FALSE; |
| filp->f_pos = 0; |
| nread = filp->f_op->read(filp, buff, sizeof(buff), &filp->f_pos); |
| if (nread > 0) |
| { |
| /* the packet format: <PKT_DLE><PKT_ID><DATA STRING><PKT_DLE><PKT_ETX> */ |
| #define PKT_DLE 0x10 |
| #define PKT_ETX 0x3 |
| #define PKT_PTIME_ID 0x8f |
| #define PKT_PTIME_SID 0xab |
| #define PKT_PTIME_LEN 0x10 |
| buff[nread+1]='\0'; |
| for(i = 0; i < nread; i++) |
| { |
| if (is_time_pkt == A_FALSE) |
| { |
| if (buff[i] == PKT_DLE && i+2 < nread && |
| buff[i+1] == PKT_PTIME_ID && |
| buff[i+2] == PKT_PTIME_SID) { |
| is_time_pkt = A_TRUE; |
| i = i + 2; |
| j = 0; |
| } |
| } else { |
| data[j++] = buff[i]; |
| if (j >= PKT_PTIME_LEN && data[j-2] == PKT_DLE && |
| data[j-1] == PKT_ETX) |
| { |
| qca808x_gps_second_sync(pdata, data); |
| goto gps_time_sync_exit; |
| } |
| |
| if (j > PKT_PTIME_LEN+2) { |
| is_time_pkt = A_FALSE; |
| } |
| } |
| } |
| } |
| |
| if (--try_cycle <= 0) { |
| break; |
| } |
| } |
| |
| gps_time_sync_exit: |
| set_fs(fs); |
| filp_close(filp, NULL); |
| } |
| |
| static void qca808x_ptp_schedule_work(struct work_struct *work) |
| { |
| struct qca808x_phy_info *pdata = |
| container_of(work, struct qca808x_phy_info, ts_schedule_work.work); |
| |
| if (!pdata) { |
| return; |
| } |
| gps_seconds_sync_thread(pdata); |
| |
| if (pdata->gps_seconds_sync_en == A_TRUE) { |
| schedule_delayed_work(&pdata->ts_schedule_work, GPS_WORK_TIMEOUT); |
| } |
| } |
| |
| static void ingress_trig_time_work(struct work_struct *work) |
| { |
| const struct qca808x_phy_info *pdata; |
| struct qca808x_ptp_info *ptp_data = |
| container_of(work, struct qca808x_ptp_info, ingress_trig_work.work); |
| |
| qca808x_priv *priv = |
| container_of(ptp_data, qca808x_priv, ptp_info); |
| |
| pdata = priv->phy_info; |
| |
| if (!pdata) { |
| return; |
| } |
| |
| switch (pdata->clock_mode) { |
| case FAL_P2PTC_CLOCK_MODE: |
| /* p2p tc one step just use ingress trig time for pdelay resp, |
| * the ingress timestamp should be recorded in the local phy. |
| */ |
| ptp_ingress_time_sync(pdata->phy_addr, ptp_data->ingress_time, A_FALSE); |
| break; |
| case FAL_E2ETC_CLOCK_MODE: |
| ptp_ingress_time_sync(pdata->phy_addr, ptp_data->ingress_time, A_TRUE); |
| break; |
| case FAL_OC_CLOCK_MODE: |
| case FAL_BC_CLOCK_MODE: |
| ptp_ingress_time_sync(pdata->phy_addr, ptp_data->ingress_time, A_FALSE); |
| break; |
| default: |
| break; |
| } |
| |
| return; |
| } |
| |
| /****************************************************************************** |
| * |
| * qca808x_ptp_settime - reset the rtc timecounter |
| * |
| * ptp: the ptp clock info structure |
| * ts: the new rtc timecounter |
| * |
| */ |
| static int qca808x_ptp_settime(struct ptp_clock_info *ptp, |
| const struct timespec64 *ts) |
| { |
| const struct qca808x_phy_info *pdata; |
| struct qca808x_ptp_clock *clock = |
| container_of(ptp, struct qca808x_ptp_clock, caps); |
| |
| fal_ptp_time_t ptp_time = {0}; |
| |
| qca808x_priv *priv = clock->priv; |
| pdata = priv->phy_info; |
| |
| if (!pdata) { |
| return SW_FAIL; |
| } |
| ptp_time.seconds = ts->tv_sec; |
| ptp_time.nanoseconds = ts->tv_nsec; |
| |
| mutex_lock(&clock->tsreg_lock); |
| qca808x_phy_ptp_rtc_time_set(pdata->dev_id, pdata->phy_addr, &ptp_time); |
| mutex_unlock(&clock->tsreg_lock); |
| return 0; |
| } |
| |
| /****************************************************************************** |
| * |
| * qca808x_ptp_gettime - read the rtc timecounter |
| * |
| * ptp: the ptp clock info structure |
| * ts: the timespace to hold the current rtc time |
| * |
| */ |
| static int qca808x_ptp_gettime(struct ptp_clock_info *ptp, |
| struct timespec64 *ts) |
| { |
| const struct qca808x_phy_info *pdata; |
| struct qca808x_ptp_clock *clock = |
| container_of(ptp, struct qca808x_ptp_clock, caps); |
| |
| fal_ptp_time_t ptp_time = {0}; |
| |
| qca808x_priv *priv = clock->priv; |
| pdata = priv->phy_info; |
| |
| if (!pdata) { |
| return SW_FAIL; |
| } |
| mutex_lock(&clock->tsreg_lock); |
| qca808x_phy_ptp_rtc_time_get(pdata->dev_id, pdata->phy_addr, &ptp_time); |
| mutex_unlock(&clock->tsreg_lock); |
| |
| ts->tv_sec = ptp_time.seconds; |
| ts->tv_nsec = ptp_time.nanoseconds; |
| return 0; |
| } |
| |
| /****************************************************************************** |
| * |
| * qca808x_ptp_adjtime - adjust the rtc timecounter offset |
| * |
| * ptp: the ptp clock info structure |
| * delta: offset to be adjusted per cycle counter |
| * |
| */ |
| static int qca808x_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta) |
| { |
| struct timespec64 ts; |
| const struct qca808x_phy_info *pdata; |
| struct qca808x_ptp_clock *clock = |
| container_of(ptp, struct qca808x_ptp_clock, caps); |
| |
| fal_ptp_time_t ptp_time = {0}; |
| |
| qca808x_priv *priv = clock->priv; |
| pdata = priv->phy_info; |
| |
| if (!pdata) { |
| return SW_FAIL; |
| } |
| ts = ns_to_timespec64(delta); |
| ptp_time.seconds = ts.tv_sec; |
| ptp_time.nanoseconds = ts.tv_nsec; |
| |
| mutex_lock(&clock->tsreg_lock); |
| qca808x_phy_ptp_rtc_adjtime_set(pdata->dev_id, pdata->phy_addr, &ptp_time); |
| mutex_unlock(&clock->tsreg_lock); |
| return 0; |
| } |
| |
| static void qca808x_ppb_to_freq (a_int32_t speed, a_int32_t ppb, fal_ptp_time_t *ptp_time) |
| { |
| a_uint64_t rate; |
| a_uint16_t ns, ns_tmp; |
| a_int32_t neg_adj = 0, tmp = 0; |
| |
| if (ppb < 0) { |
| neg_adj = 1; |
| ppb = -ppb; |
| } |
| |
| rate = ppb; |
| rate <<= 20; |
| /* divided by (200Mhz-ppb/8)/64 */ |
| if (speed == FAL_SPEED_2500) { |
| tmp = (ppb/5)/64; |
| ns_tmp = QCA808X_PTP_TICK_RATE_200M; |
| rate = div_u64(rate, 3125000 + tmp); |
| } else { |
| tmp = (ppb/8)/64; |
| ns_tmp = QCA808X_PTP_TICK_RATE_125M; |
| rate = div_u64(rate, 1953125 + tmp); |
| } |
| |
| if(neg_adj && rate != 0) { |
| ns = ns_tmp - 1; |
| rate = (2<<26)-rate; |
| } else { |
| ns = ns_tmp; |
| } |
| |
| /* remove the redundant bits, only 26 bits for fracnanoseconds */ |
| while (rate & 0xfc000000) { |
| rate >>= 1; |
| } |
| |
| ptp_time->seconds = 0; |
| ptp_time->nanoseconds = ns; |
| ptp_time->fracnanoseconds = rate; |
| |
| return; |
| } |
| |
| static void qca808x_ptp_adjfreq_sync(a_uint32_t phy_addr, |
| a_int32_t ppb, fal_ptp_time_t ptp_time_org) |
| { |
| fal_ptp_reference_clock_t ref_clock = FAL_REF_CLOCK_LOCAL; |
| fal_ptp_time_t ptp_time = {0}; |
| struct qca808x_phy_info *pdata = NULL; |
| sw_error_t ret = SW_OK; |
| a_uint32_t gm_mode = 0; |
| |
| /* |
| * In BC mode, the SYNC clock, PPS and Toduart PINs are connected, |
| * the adjust frequency should be same among the ports to guaranteeing |
| * the RTC consistent. |
| */ |
| list_for_each_entry(pdata, &g_qca808x_phy_list, list) { |
| if (pdata->phydev_addr != phy_addr) { |
| ret = qca808x_ptp_gm_conf0_reg_grandmaster_mode_get(pdata->dev_id, |
| pdata->phy_addr, &gm_mode); |
| /* The grandmaster mode should be configured to sync RTC */ |
| if (ret == SW_OK && gm_mode == PTP_REG_BIT_TRUE) { |
| ptp_time = ptp_time_org; |
| ret = qca808x_phy_ptp_reference_clock_get(pdata->dev_id, |
| pdata->phy_addr, &ref_clock); |
| /* |
| * BC ports share the same RTC clock in the external mode |
| * so the adjust frequency should be also same, otherwise |
| * the ppb should be converted to the corresponding value |
| * of the adjust frequency. |
| */ |
| if (ret == SW_OK && ref_clock != FAL_REF_CLOCK_EXTERNAL) { |
| qca808x_ppb_to_freq(pdata->speed, ppb, &ptp_time); |
| } |
| qca808x_phy_ptp_rtc_adjfreq_set(pdata->dev_id, |
| pdata->phy_addr, &ptp_time); |
| } |
| } |
| } |
| |
| return; |
| } |
| |
| /****************************************************************************** |
| * |
| * qca808x_ptp_adjfreq - adjust the frequency of cycle counter |
| * |
| * ptp: the ptp clock info structure |
| * ppb: parts per billion adjustment from master |
| * |
| */ |
| static int qca808x_ptp_adjfreq(struct ptp_clock_info *ptp, s32 ppb) |
| { |
| const struct qca808x_phy_info *pdata; |
| struct qca808x_ptp_clock *clock = |
| container_of(ptp, struct qca808x_ptp_clock, caps); |
| |
| fal_ptp_time_t ptp_time = {0}; |
| |
| qca808x_priv *priv = clock->priv; |
| struct phy_device *phydev = priv->phydev; |
| pdata = priv->phy_info; |
| |
| if (!pdata || !phydev) { |
| return SW_FAIL; |
| } |
| |
| qca808x_ppb_to_freq(pdata->speed, ppb, &ptp_time); |
| |
| mutex_lock(&clock->tsreg_lock); |
| qca808x_phy_ptp_rtc_adjfreq_set(pdata->dev_id, pdata->phy_addr, &ptp_time); |
| if (pdata->clock_mode == FAL_BC_CLOCK_MODE) { |
| /* Keep RTC time consistent among BC ports */ |
| qca808x_ptp_adjfreq_sync(pdata->phy_addr, ppb, ptp_time); |
| } |
| mutex_unlock(&clock->tsreg_lock); |
| return 0; |
| } |
| |
| static int qca808x_ptp_enable(struct ptp_clock_info *ptp, |
| struct ptp_clock_request *rq, int on) |
| { |
| return -EOPNOTSUPP; |
| } |
| |
| #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4,4,0)) |
| static int qca808x_ptp_verify(struct ptp_clock_info *ptp, unsigned int pin, |
| enum ptp_pin_function func, unsigned int chan) |
| { |
| return 1; |
| } |
| #endif |
| |
| void qca808x_ptp_change_notify(struct phy_device *phydev) |
| { |
| fal_ptp_reference_clock_t ptp_ref_clock = FAL_REF_CLOCK_EXTERNAL; |
| fal_ptp_time_t ptp_cycle_time = {0}; |
| a_uint32_t nanoseconds = 0; |
| qca808x_priv *priv = phydev->priv; |
| struct qca808x_phy_info *pdata = priv->phy_info; |
| |
| if (!pdata) { |
| return; |
| } |
| |
| if (pdata->speed != phydev->speed) { |
| if (phydev->speed == SPEED_2500 && |
| pdata->speed < SPEED_2500) { |
| /* adjust frequency to 5ns(200MHz) */ |
| nanoseconds = QCA808X_PTP_TICK_RATE_200M; |
| } else if (pdata->speed == SPEED_2500 && |
| phydev->speed < SPEED_2500) { |
| /* adjust frequency to 8ns(125MHz) */ |
| nanoseconds = QCA808X_PTP_TICK_RATE_125M; |
| } |
| |
| if (phydev->speed == SPEED_10) { |
| /* local free running clock for 10M */ |
| ptp_ref_clock = FAL_REF_CLOCK_LOCAL; |
| } else if (pdata->speed == SPEED_10) { |
| ptp_ref_clock = FAL_REF_CLOCK_SYNCE; |
| } |
| |
| if (ptp_ref_clock != FAL_REF_CLOCK_EXTERNAL) { |
| qca808x_phy_ptp_reference_clock_set(pdata->dev_id, |
| pdata->phy_addr, ptp_ref_clock); |
| } |
| |
| pdata->speed = phydev->speed; |
| if (nanoseconds != 0) { |
| ptp_cycle_time.nanoseconds = nanoseconds; |
| qca808x_phy_ptp_rtc_adjfreq_set(pdata->dev_id, |
| pdata->phy_addr, &ptp_cycle_time); |
| } |
| } |
| } |
| |
| int qca808x_hwtstamp(struct phy_device *phydev, struct ifreq *ifr) |
| { |
| struct hwtstamp_config cfg; |
| a_uint32_t gm_mode = 0; |
| fal_ptp_reference_clock_t ref_clock = FAL_REF_CLOCK_LOCAL; |
| sw_error_t ret = SW_OK; |
| fal_ptp_config_t ptp_config = {0}; |
| qca808x_priv *priv = phydev->priv; |
| struct qca808x_phy_info *pdata = priv->phy_info; |
| struct qca808x_ptp_info *ptp_info = &priv->ptp_info; |
| struct qca808x_ptp_clock *clock = ptp_info->clock; |
| |
| if (!pdata || !clock) { |
| return -EFAULT; |
| } |
| |
| if (copy_from_user(&cfg, ifr->ifr_data, sizeof(cfg))) |
| return -EFAULT; |
| if (cfg.flags) /* reserved for future extensions */ |
| return -EINVAL; |
| |
| if (cfg.tx_type < 0 || cfg.tx_type > HWTSTAMP_TX_ONESTEP_P2P) |
| return -ERANGE; |
| |
| ptp_info->hwts_tx_type = cfg.tx_type; |
| |
| switch (cfg.rx_filter) { |
| case HWTSTAMP_FILTER_NONE: |
| ptp_info->hwts_rx_type = PTP_CLASS_NONE; |
| break; |
| case HWTSTAMP_FILTER_PTP_V2_EVENT: |
| case HWTSTAMP_FILTER_PTP_V2_SYNC: |
| case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ: |
| ptp_info->hwts_rx_type = PTP_CLASS_L4 | PTP_CLASS_L2; |
| break; |
| case HWTSTAMP_FILTER_PTP_V2_L4_EVENT: |
| case HWTSTAMP_FILTER_PTP_V2_L4_SYNC: |
| case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ: |
| ptp_info->hwts_rx_type = PTP_CLASS_L4; |
| break; |
| case HWTSTAMP_FILTER_PTP_V2_L2_EVENT: |
| case HWTSTAMP_FILTER_PTP_V2_L2_SYNC: |
| case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ: |
| ptp_info->hwts_rx_type = PTP_CLASS_L2; |
| break; |
| default: |
| return -ERANGE; |
| } |
| |
| mutex_lock(&clock->tsreg_lock); |
| ptp_config.clock_mode = pdata->clock_mode; |
| switch (ptp_info->hwts_tx_type) { |
| case HWTSTAMP_TX_ON: |
| ptp_config.ptp_en = A_TRUE; |
| ptp_config.step_mode = FAL_TWO_STEP_MODE; |
| break; |
| case HWTSTAMP_TX_ONESTEP_SYNC: |
| case HWTSTAMP_TX_ONESTEP_P2P: |
| ptp_config.ptp_en = A_TRUE; |
| ptp_config.step_mode = FAL_ONE_STEP_MODE; |
| break; |
| case HWTSTAMP_TX_OFF: |
| default: |
| ptp_config.ptp_en = A_FALSE; |
| ptp_config.step_mode = FAL_TWO_STEP_MODE; |
| break; |
| } |
| ret = qca808x_phy_ptp_config_set(pdata->dev_id, pdata->phy_addr, &ptp_config); |
| mutex_unlock(&clock->tsreg_lock); |
| if (ret != SW_OK) { |
| return -EFAULT; |
| } |
| |
| /* |
| * disable SYNCE clock output by default, |
| * only enabling the clock output under the |
| * BC mode && not in external reference mode |
| */ |
| qca808x_ptp_clock_synce_clock_enable(pdata->dev_id, pdata->phy_addr, A_FALSE); |
| |
| if (pdata->clock_mode == FAL_BC_CLOCK_MODE) { |
| ret = qca808x_ptp_gm_conf0_reg_grandmaster_mode_get(pdata->dev_id, |
| pdata->phy_addr, &gm_mode); |
| /* The grandmaster mode should be configured to sync RTC */ |
| if (ret == SW_OK && gm_mode != PTP_REG_BIT_TRUE) { |
| ret = qca808x_phy_ptp_reference_clock_get(pdata->dev_id, |
| pdata->phy_addr, &ref_clock); |
| /* |
| * The PHC should be with below PINs connected for clock synchronized |
| * so NAPA1 should be configured as sync or local reference clock, |
| * and NAPA2 is configured as FAL_REF_CLOCK_EXTERNAL & grandmaster mode. |
| * |
| * Napa1 ToD out --- > Napa2 ToD in. |
| * Napa1 PPS out --- > Napa2 PPS in. |
| * Napa1 sync clock out --- > Napa2 reference clock in. |
| */ |
| if (ret == SW_OK && ref_clock != FAL_REF_CLOCK_EXTERNAL) { |
| /* enable SYNCE clock output */ |
| qca808x_ptp_clock_synce_clock_enable(pdata->dev_id, |
| pdata->phy_addr, A_TRUE); |
| } |
| } |
| } |
| |
| pdata->step_mode = ptp_config.step_mode; |
| |
| return copy_to_user(ifr->ifr_data, &cfg, sizeof(cfg)) ? -EFAULT : 0; |
| } |
| |
| bool qca808x_rxtstamp(struct phy_device *phydev, |
| struct sk_buff *nskb, int type) |
| { |
| struct skb_shared_hwtstamps *shhwtstamps = NULL; |
| struct timespec64 ts = {0}; |
| a_bool_t ingress_trig_flag = A_FALSE; |
| a_uint64_t ns; |
| a_int64_t *correction; |
| a_uint16_t *seqid; |
| a_int32_t ptp_class; |
| a_uint32_t *reserved2; |
| a_uint8_t *reserved0, *reserved1; |
| a_uint32_t *cf1; |
| a_uint8_t *ptp_header; |
| a_uint8_t embed_val, pkt_type; |
| qca808x_ptp_cb *ptp_cb = (qca808x_ptp_cb *)nskb->cb; |
| qca808x_priv *priv = phydev->priv; |
| struct qca808x_phy_info *pdata = priv->phy_info; |
| struct qca808x_ptp_info *ptp_info = &priv->ptp_info; |
| |
| if (ptp_info->hwts_rx_type == PTP_CLASS_NONE || !pdata) { |
| return false; |
| } |
| |
| /* The PTP_CLASS_NONE is passed, which indicates that the |
| * PTP class is not determined, calling ptp_classify_raw to |
| * classfy the packet. |
| */ |
| if (type == PTP_CLASS_NONE) { |
| __skb_push(nskb, ETH_HLEN); |
| /* dissecting the packet content to get ptp class */ |
| ptp_class = ptp_classify_raw(nskb); |
| __skb_pull(nskb, ETH_HLEN); |
| if (ptp_class == PTP_CLASS_NONE) { |
| /* this case should not happen, only ptp event packet passed */ |
| SSDK_ERROR("%s: No PTP event packet received\n", __func__); |
| return false; |
| } |
| type = ptp_class; |
| } |
| |
| if ((ptp_info->hwts_rx_type & type) == PTP_CLASS_NONE) { |
| return false; |
| } |
| |
| ptp_header = skb_ptp_header(nskb, type); |
| if (!ptp_header) { |
| return false; |
| } |
| shhwtstamps = skb_hwtstamps(nskb); |
| memset(shhwtstamps, 0, sizeof(*shhwtstamps)); |
| |
| #define PTP_HDR_CORRECTIONFIELD_CPY_SRC 11 |
| #define PTP_HDR_CORRECTIONFIELD_CPY_DST 9 |
| #define PTP_HDR_CORRECTIONFIELD_CPY_LEN 5 |
| |
| reserved0 = ptp_header + PTP_HDR_RESERVED0_OFFSET; |
| reserved1 = ptp_header + PTP_HDR_RESERVED1_OFFSET; |
| cf1 = (a_uint32_t *)(ptp_header + PTP_HDR_CORRECTIONFIELD_OFFSET); |
| correction = (a_uint64_t *)(ptp_header + PTP_HDR_CORRECTIONFIELD_OFFSET); |
| seqid = (a_uint16_t *)(ptp_header + OFF_PTP_SEQUENCE_ID); |
| reserved2 = (a_uint32_t *)(ptp_header + PTP_HDR_RESERVED2_OFFSET); |
| |
| embed_val = (*reserved0 & 0xf0) >> 4; |
| pkt_type = *ptp_header & 0xf; |
| |
| qca808x_ptp_stat_update(pdata, FAL_RX_DIRECTION, |
| QCA808X_PTP_MSG_MAX, PTP_PKT_SEQID_UNMATCHED); |
| |
| if (embed_val == QCA808X_PTP_EMBEDDED_MODE) { |
| ts.tv_sec = ntohl(*reserved2); |
| ts.tv_nsec = ((a_uint32_t)*reserved1 << 24) | (ntohl(*cf1) >> 8); |
| |
| if (pdata->step_mode == FAL_ONE_STEP_MODE) { |
| switch (pdata->clock_mode) { |
| case FAL_OC_CLOCK_MODE: |
| case FAL_BC_CLOCK_MODE: |
| case FAL_P2PTC_CLOCK_MODE: |
| /* message sync with the timestamp inserted into the ptp |
| * header, do not use the ingress_trig_time register, the |
| * ingress time will be acquired from ptp header. |
| * the ingress time of pdealy request msg should be |
| * recorded and will be copied the corresponding pdealy |
| * response msg. |
| */ |
| if (pkt_type == QCA808X_PTP_MSG_PREQ) { |
| ptp_info->embeded_ts.reserved0 = *reserved0; |
| ptp_info->embeded_ts.reserved1 = *reserved1; |
| ptp_info->embeded_ts.reserved2 = *reserved2; |
| ptp_info->embeded_ts.correction = *correction; |
| ptp_info->embeded_ts.seqid = *seqid; |
| ptp_info->embeded_ts.msg_type = pkt_type; |
| } |
| break; |
| case FAL_E2ETC_CLOCK_MODE: |
| ingress_trig_flag = A_TRUE; |
| break; |
| default: |
| break; |
| } |
| if (ingress_trig_flag == A_TRUE) { |
| ptp_info->ingress_time = ts.tv_nsec; |
| schedule_delayed_work(&ptp_info->ingress_trig_work, 0); |
| } |
| } |
| |
| /* restore the original correctionfield value except for |
| * the TC one-step mode offloading*/ |
| if (!(((pdata->clock_mode == FAL_P2PTC_CLOCK_MODE && |
| pkt_type == QCA808X_PTP_MSG_SYNC) || |
| pdata->clock_mode == FAL_E2ETC_CLOCK_MODE) && |
| pdata->step_mode == FAL_ONE_STEP_MODE)) |
| { |
| /* in embeded mode for the rx time stamp, the correction field |
| * is modfied to keep the low 50 bit of nanosecond and the |
| * fractional nanoseconds should be dropped |
| */ |
| *reserved0 = *reserved0 & 0xf; |
| memmove(ptp_header + PTP_HDR_CORRECTIONFIELD_CPY_DST, |
| ptp_header + PTP_HDR_CORRECTIONFIELD_CPY_SRC, |
| PTP_HDR_CORRECTIONFIELD_CPY_LEN); |
| memset(ptp_header + PTP_HDR_CORRECTIONFIELD_OFFSET, 0, 1); |
| memset(ptp_header + PTP_HDR_RESERVED2_OFFSET - 2, 0, 6); |
| } |
| } else { |
| ptp_cb->ptp_type = type; |
| ptp_cb->pkt_type = pkt_type; |
| skb_queue_tail(&ptp_info->rx_queue, nskb); |
| schedule_delayed_work(&ptp_info->rx_ts_work, 0); |
| return true; |
| } |
| ns = timespec64_to_ns(&ts); |
| |
| qca808x_ptp_stat_update(pdata, FAL_RX_DIRECTION, |
| pkt_type, PTP_PKT_SEQID_MATCHED); |
| |
| shhwtstamps->hwtstamp = ns_to_ktime(ns); |
| netif_rx_ni(nskb); |
| |
| return true; |
| } |
| |
| void qca808x_txtstamp(struct phy_device *phydev, |
| struct sk_buff *org_skb, int type) |
| { |
| a_uint8_t msg_type; |
| struct sk_buff *skb; |
| qca808x_ptp_cb *ptp_cb; |
| a_uint8_t *ptp_header; |
| a_int64_t *correction; |
| a_uint32_t *reserved2; |
| a_uint8_t *reserved0, *reserved1; |
| a_uint16_t *seqid; |
| a_int32_t ptp_class; |
| qca808x_priv *priv = phydev->priv; |
| struct qca808x_ptp_info *ptp_info = &priv->ptp_info; |
| |
| /* The PTP_CLASS_NONE is passed, which indicates that the |
| * PTP class is not determined, calling ptp_classify_raw to |
| * classfy the packet. |
| */ |
| if (type == PTP_CLASS_NONE) { |
| ptp_class = ptp_classify_raw(org_skb); |
| if (ptp_class == PTP_CLASS_NONE) { |
| return; |
| } |
| skb = skb_clone_sk(org_skb); |
| if (!skb) { |
| SSDK_ERROR("%s: skb_clone_sk failed\n", __func__); |
| return; |
| } |
| type = ptp_class; |
| } else { |
| skb = org_skb; |
| } |
| |
| ptp_header = skb_ptp_header(skb, type); |
| if (!ptp_header) { |
| kfree_skb(skb); |
| return; |
| } |
| |
| ptp_cb = (qca808x_ptp_cb *)skb->cb; |
| seqid = (a_uint16_t *)(ptp_header + OFF_PTP_SEQUENCE_ID); |
| reserved0 = ptp_header + PTP_HDR_RESERVED0_OFFSET; |
| reserved1 = ptp_header + PTP_HDR_RESERVED1_OFFSET; |
| reserved2 = (a_uint32_t *)(ptp_header + PTP_HDR_RESERVED2_OFFSET); |
| correction = (a_uint64_t *)(ptp_header + PTP_HDR_CORRECTIONFIELD_OFFSET); |
| msg_type = *ptp_header & 0xf; |
| switch (ptp_info->hwts_tx_type) { |
| case HWTSTAMP_TX_ONESTEP_SYNC: |
| if (msg_type == QCA808X_PTP_MSG_SYNC) { |
| skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS; |
| kfree_skb(skb); |
| return; |
| } |
| break; |
| case HWTSTAMP_TX_ONESTEP_P2P: |
| switch (msg_type) { |
| case QCA808X_PTP_MSG_PRESP: |
| if (ptp_info->embeded_ts.seqid == *seqid && |
| ptp_info->embeded_ts.msg_type == |
| QCA808X_PTP_MSG_PREQ) { |
| *reserved0 = ptp_info->embeded_ts.reserved0; |
| *reserved1 = ptp_info->embeded_ts.reserved1; |
| *reserved2 = ptp_info->embeded_ts.reserved2; |
| *correction = ptp_info->embeded_ts.correction; |
| } |
| /* fall down */ |
| case QCA808X_PTP_MSG_SYNC: |
| skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS; |
| kfree_skb(skb); |
| return; |
| default: |
| break; |
| } |
| break; |
| case HWTSTAMP_TX_ON: |
| break; |
| /* enqueue skb to get tx timestamp */ |
| case HWTSTAMP_TX_OFF: |
| default: |
| kfree_skb(skb); |
| return; |
| } |
| |
| skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS; |
| skb_queue_tail(&ptp_info->tx_queue, skb); |
| ptp_cb->ptp_type = type; |
| qca808x_ptp_stat_update(priv->phy_info, FAL_TX_DIRECTION, |
| QCA808X_PTP_MSG_MAX, PTP_PKT_SEQID_UNMATCHED); |
| schedule_delayed_work(&ptp_info->tx_ts_work, 0); |
| } |
| |
| #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4,4,0)) |
| int qca808x_ts_info(struct phy_device *phydev, |
| struct ethtool_ts_info *info) |
| { |
| qca808x_priv *priv = phydev->priv; |
| struct qca808x_ptp_info *ptp_info = &priv->ptp_info; |
| struct qca808x_ptp_clock *clock = ptp_info->clock; |
| |
| if (clock) { |
| info->phc_index = ptp_clock_index(clock->ptp_clock); |
| } |
| |
| info->so_timestamping = |
| SOF_TIMESTAMPING_TX_HARDWARE | |
| SOF_TIMESTAMPING_RX_HARDWARE | |
| SOF_TIMESTAMPING_RAW_HARDWARE; |
| info->tx_types = |
| (1 << HWTSTAMP_TX_OFF) | |
| (1 << HWTSTAMP_TX_ON) | |
| (1 << HWTSTAMP_TX_ONESTEP_SYNC) | |
| (1 << HWTSTAMP_TX_ONESTEP_P2P); |
| |
| info->rx_filters = |
| (1 << HWTSTAMP_FILTER_NONE) | |
| (1 << HWTSTAMP_FILTER_PTP_V2_L4_EVENT) | |
| (1 << HWTSTAMP_FILTER_PTP_V2_L2_EVENT) | |
| (1 << HWTSTAMP_FILTER_PTP_V2_EVENT); |
| |
| return 0; |
| } |
| #endif |
| |
| static int qca808x_ptp_register(struct phy_device *phydev) |
| { |
| int err; |
| struct qca808x_ptp_clock *clock; |
| qca808x_priv *priv = phydev->priv; |
| struct qca808x_ptp_info *ptp_info = &priv->ptp_info; |
| |
| clock = kzalloc(sizeof(struct qca808x_ptp_clock), GFP_KERNEL); |
| if (!clock) { |
| return -ENOMEM; |
| } |
| |
| mutex_init(&clock->tsreg_lock); |
| clock->caps.owner = THIS_MODULE; |
| #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0)) |
| snprintf(clock->caps.name, sizeof(clock->caps.name), "qca808x timer %x", phydev->mdio.addr); |
| #else |
| snprintf(clock->caps.name, sizeof(clock->caps.name), "qca808x timer %x", phydev->addr); |
| #endif |
| clock->caps.max_adj = 3124999; |
| clock->caps.n_alarm = 0; |
| clock->caps.n_ext_ts = 6; |
| clock->caps.n_per_out = 7; |
| clock->caps.pps = 0; |
| #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4,4,0)) |
| clock->caps.n_pins = 0; |
| clock->caps.verify = qca808x_ptp_verify; |
| clock->caps.gettime64 = qca808x_ptp_gettime; |
| clock->caps.settime64 = qca808x_ptp_settime; |
| #else |
| clock->caps.gettime = qca808x_ptp_gettime; |
| clock->caps.settime = qca808x_ptp_settime; |
| #endif |
| clock->caps.adjfreq = qca808x_ptp_adjfreq; |
| clock->caps.adjtime = qca808x_ptp_adjtime; |
| clock->caps.enable = qca808x_ptp_enable; |
| |
| #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0)) |
| clock->ptp_clock = ptp_clock_register(&clock->caps, &phydev->mdio.dev); |
| #else |
| clock->ptp_clock = ptp_clock_register(&clock->caps, &phydev->dev); |
| #endif |
| if (IS_ERR(clock->ptp_clock)) { |
| err = PTR_ERR(clock->ptp_clock); |
| kfree(clock); |
| return err; |
| } |
| ptp_info->clock = clock; |
| clock->priv = priv; |
| |
| SSDK_INFO("qca808x ptp clock registered\n"); |
| return 0; |
| } |
| |
| static void qca808x_ptp_unregister(struct phy_device *phydev) |
| { |
| qca808x_priv *priv = phydev->priv; |
| struct qca808x_ptp_info *ptp_info = &priv->ptp_info; |
| struct qca808x_ptp_clock *clock = ptp_info->clock; |
| |
| if (clock) { |
| ptp_clock_unregister(clock->ptp_clock); |
| mutex_destroy(&clock->tsreg_lock); |
| kfree(clock); |
| } |
| } |
| |
| int qca808x_ptp_init(qca808x_priv *priv) |
| { |
| int err; |
| struct qca808x_ptp_info *ptp_info; |
| struct qca808x_phy_info *pdata; |
| |
| if (!priv) { |
| return -1; |
| } |
| |
| ptp_info = &priv->ptp_info; |
| pdata = priv->phy_info; |
| INIT_DELAYED_WORK(&ptp_info->tx_ts_work, tx_timestamp_work); |
| INIT_DELAYED_WORK(&ptp_info->rx_ts_work, rx_timestamp_work); |
| skb_queue_head_init(&ptp_info->tx_queue); |
| skb_queue_head_init(&ptp_info->rx_queue); |
| |
| INIT_DELAYED_WORK(&ptp_info->ingress_trig_work, ingress_trig_time_work); |
| INIT_DELAYED_WORK(&pdata->ts_schedule_work, qca808x_ptp_schedule_work); |
| |
| err = qca808x_ptp_register(priv->phydev); |
| if (err <0) { |
| SSDK_ERROR("qca808x ptp clock register failed\n"); |
| kfree(ptp_info); |
| return err; |
| } |
| |
| return err; |
| } |
| |
| void qca808x_ptp_deinit(qca808x_priv *priv) |
| { |
| struct qca808x_ptp_info *ptp_info; |
| struct qca808x_phy_info *pdata; |
| if (!priv) { |
| return; |
| } |
| |
| ptp_info = &priv->ptp_info; |
| pdata = priv->phy_info; |
| cancel_delayed_work_sync(&ptp_info->tx_ts_work); |
| cancel_delayed_work_sync(&ptp_info->rx_ts_work); |
| cancel_delayed_work_sync(&ptp_info->ingress_trig_work); |
| cancel_delayed_work_sync(&pdata->ts_schedule_work); |
| skb_queue_purge(&ptp_info->tx_queue); |
| skb_queue_purge(&ptp_info->rx_queue); |
| |
| qca808x_ptp_unregister(priv->phydev); |
| } |