/*
 * Copyright (c) 2018, The Linux Foundation. All rights reserved.
 *
 * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. 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 = NULL;

	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 = NULL;

	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 = NULL;

	pdata = qca808x_phy_info_get(phy_addr);

	if (pdata) {
		pdata->clock_mode = clock_mode;
		pdata->step_mode = step_mode;
	}

	return;
}

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 = 0, phy_data1 = 0;
	sw_error_t ret = SW_OK;

	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;
	}

	ret = hsl_phy_modify_debug(dev_id, phy_id, QCA808X_DEBUG_ANA_CLOCK_CTRL_REG,
		QCA808X_ANALOG_PHY_SYNCE_CLOCK_EN, phy_data);

	ret |= hsl_phy_modify_mmd(dev_id, phy_id, A_TRUE, QCA808X_PHY_MMD7_NUM,
		QCA808X_MMD7_CLOCK_CTRL_REG, QCA808X_DIGITAL_PHY_SYNCE_CLOCK_EN,
		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 = 0;

	if (enable == A_TRUE) {
		phy_data |= QCA808X_PTP_INCVAL_SYNC_MODE;
	}

	return hsl_phy_modify_mmd(dev_id, phy_id, A_TRUE, QCA808X_PHY_MMD3_NUM,
		PTP_RTC_EXT_CONF_REG_ADDRESS, QCA808X_PTP_INCVAL_SYNC_MODE, phy_data);
}

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);

	hsl_ptp_event_pkt_stat_t *pkt_stat = priv->ptp_event_stat;

	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 = hsl_phy_mmd_reg_read(dev_id, phy_id, A_TRUE,
				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) {
			hsl_ptp_event_stat_update(&pkt_stat[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 {
			hsl_ptp_event_stat_update(&pkt_stat[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);

	hsl_ptp_event_pkt_stat_t *pkt_stat = priv->ptp_event_stat;

	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) {
			hsl_ptp_event_stat_update(&pkt_stat[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 {
			hsl_ptp_event_stat_update(&pkt_stat[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 == PTP_MSG_PREQ) {
						ptp_ingress_time_sync(phy_id,
								ts.tv_nsec, A_FALSE);
					}
					break;
				default:
					break;
			}
		}
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6,1,0))
		netif_rx(skb);
#else
		netif_rx_ni(skb);
#endif
	}

	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;
#if (LINUX_VERSION_CODE < KERNEL_VERSION(6,1,0))
	mm_segment_t fs;
#endif
	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;
	}

#if (LINUX_VERSION_CODE < KERNEL_VERSION(6,1,0))
	fs=get_fs();
	set_fs(KERNEL_DS);
#endif
	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:
#if (LINUX_VERSION_CODE < KERNEL_VERSION(6,1,0))
	set_fs(fs);
#endif
	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
*
*/
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6,6,0))
static int qca808x_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
#else
static int qca808x_ptp_adjfreq(struct ptp_clock_info *ptp, s32 ppb)
#endif
{
	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;

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6,6,0))
	s32 ppb = scaled_ppm_to_ppb(scaled_ppm);
#endif
	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

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6,1,0))
void qca808x_ptp_change_notify(struct mii_timestamper *mii_ts, struct phy_device *phydev)
{
	qca808x_priv *priv = container_of(mii_ts, qca808x_priv, mii_ts);
#else
void qca808x_ptp_change_notify(struct phy_device *phydev)
{
	qca808x_priv *priv = phydev->priv;
#endif
	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;
	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);
		}
	}
}

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6,1,0))
int qca808x_hwtstamp(struct mii_timestamper *mii_ts, struct ifreq *ifr)
{
	qca808x_priv *priv = container_of(mii_ts, qca808x_priv, mii_ts);
#else
int qca808x_hwtstamp(struct phy_device *phydev, struct ifreq *ifr)
{
	qca808x_priv *priv = phydev->priv;
#endif
	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};
	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;
}

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6,1,0))
bool qca808x_rxtstamp(struct mii_timestamper *mii_ts, struct sk_buff *nskb, int type)
{
	qca808x_priv *priv = container_of(mii_ts, qca808x_priv, mii_ts);
#else
bool qca808x_rxtstamp(struct phy_device *phydev, struct sk_buff *nskb, int type)
{
	qca808x_priv *priv = phydev->priv;
#endif
	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;
	struct qca808x_phy_info *pdata = priv->phy_info;
	struct qca808x_ptp_info *ptp_info = &priv->ptp_info;
	hsl_ptp_event_pkt_stat_t *pkt_stat = priv->ptp_event_stat;

	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;

	hsl_ptp_event_stat_update(&pkt_stat[FAL_RX_DIRECTION],
			pkt_type, PTP_PKT_SEQID_CHECKED);

	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 == 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 == 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);

	hsl_ptp_event_stat_update(&pkt_stat[FAL_RX_DIRECTION],
			pkt_type, PTP_PKT_SEQID_MATCHED);

	shhwtstamps->hwtstamp = ns_to_ktime(ns);
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6,1,0))
	netif_rx(nskb);
#else
	netif_rx_ni(nskb);
#endif

	return true;
}

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6,1,0))
void qca808x_txtstamp(struct mii_timestamper *mii_ts, struct sk_buff *org_skb, int type)
{
	qca808x_priv *priv = container_of(mii_ts, qca808x_priv, mii_ts);
#else
void qca808x_txtstamp(struct phy_device *phydev, struct sk_buff *org_skb, int type)
{
	qca808x_priv *priv = phydev->priv;
#endif
	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;
	struct qca808x_ptp_info *ptp_info = &priv->ptp_info;
	hsl_ptp_event_pkt_stat_t *pkt_stat = priv->ptp_event_stat;

	/* 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 == 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 PTP_MSG_PRESP:
					if (ptp_info->embeded_ts.seqid == *seqid &&
							ptp_info->embeded_ts.msg_type ==
							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;
					}
#if defined(FALLTHROUGH)
					fallthrough;
#else
					/* fall through */
#endif
				case 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;
	hsl_ptp_event_stat_update(&pkt_stat[FAL_TX_DIRECTION],
			msg_type, PTP_PKT_SEQID_CHECKED);
	schedule_delayed_work(&ptp_info->tx_ts_work, 0);
}

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4,4,0))
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6,1,0))
int qca808x_ts_info(struct mii_timestamper *mii_ts,
		struct ethtool_ts_info *info)
{
	qca808x_priv *priv = container_of(mii_ts, qca808x_priv, mii_ts);
#else
int qca808x_ts_info(struct phy_device *phydev,
		struct ethtool_ts_info *info)
{
	qca808x_priv *priv = phydev->priv;
#endif
	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
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6,6,0))
	clock->caps.adjfine =	qca808x_ptp_adjfine;
#else
	clock->caps.adjfreq	= qca808x_ptp_adjfreq;
#endif
	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 phydev %s ptp clock registered\n", phydev_name(phydev));
	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;
	}

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6,1,0))
	priv->mii_ts.rxtstamp = qca808x_rxtstamp;
	priv->mii_ts.txtstamp = qca808x_txtstamp;
	priv->mii_ts.hwtstamp = qca808x_hwtstamp;
	priv->mii_ts.ts_info  = qca808x_ts_info;
	priv->mii_ts.link_state = qca808x_ptp_change_notify;

	priv->phydev->mii_ts = &priv->mii_ts;
#endif
	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);
}

static int qca808x_ptp_callback_init(struct device *dev, void *ptp_instance)
{
	int rv = 0;
	struct phy_device *phydev;

	phydev = to_phy_device(dev);

	/* phydev is null or phydev has been initialzed wiht ptp */
	if (!phydev || phydev->priv)
		return rv;

	rv = qca808x_phy_probe(phydev);
	if (rv)
		return rv;

	rv = qca808x_ptp_config_init(phydev);
	(*(int *)ptp_instance)++;

	return rv;
}

int qca808x_ptp_hook_init(void)
{
	int rv = 0;
	int ptp_instance = 0;
	struct device_driver *drv = NULL;
	struct phy_driver *phydrv = NULL;

	drv = driver_find(QCA808X_PHY_DRIVER_NAME, &mdio_bus_type);
	if (drv) {
		phydrv = to_phy_driver(drv);
#if (LINUX_VERSION_CODE < KERNEL_VERSION(6,1,0))
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4,4,0))
		phydrv->link_change_notify = qca808x_ptp_change_notify;
		phydrv->ts_info = qca808x_ts_info;
#endif
		phydrv->hwtstamp = qca808x_hwtstamp;
		phydrv->rxtstamp = qca808x_rxtstamp;
		phydrv->txtstamp = qca808x_txtstamp;
#endif
		rv = driver_for_each_device(drv, NULL, &ptp_instance, qca808x_ptp_callback_init);
	}

	if (ptp_instance)
		SSDK_INFO("qca808x ptp driver hooked on %d instance\n", ptp_instance);

	return rv;
}

static int qca808x_ptp_callback_cleanup(struct device *dev, void *p)
{
	struct phy_device *phydev;

	phydev = to_phy_device(dev);

	/* phydev is null or phydev has been removed wiht ptp */
	if (!phydev || !phydev->priv)
		return 0;

	qca808x_phy_remove(phydev);
	return 0;
}

void qca808x_ptp_hook_cleanup(void)
{
	int rv = 0;
	struct device_driver *drv = NULL;
	struct phy_driver *phydrv = NULL;

	drv = driver_find(QCA808X_PHY_DRIVER_NAME, &mdio_bus_type);
	if (drv) {
		rv = driver_for_each_device(drv, NULL, NULL, qca808x_ptp_callback_cleanup);
		phydrv = to_phy_driver(drv);
#if (LINUX_VERSION_CODE < KERNEL_VERSION(6,1,0))
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4,4,0))
		phydrv->link_change_notify = NULL;
		phydrv->ts_info = NULL;
#endif
		phydrv->hwtstamp = NULL;
		phydrv->rxtstamp = NULL;
		phydrv->txtstamp = NULL;
#endif
	}

	return;
}
