Hi experts,
now i am verifing ptp phy driver for motorcomm 1000M phy which supports 1588v2 & gptp(802.1as),
and facing some issues. pls help to give some suggestion. thank u a lot.
env description:
1. TI am335x disable TI CPU CPTS feature
2. linux kernel version 4.19.94-rt39-ga242ccf3f1
3. ptp4l application and cmd
 (master: ./ptp4l -f automotive-master.cfg -i eth0 -m -l 7)
 (slave: ./ptp4l -f automotive-slave.cfg -i eth0 -m -l 7)
ptp phy driver debug detail:
1. ptp4l sk_receive() 中 poll() timeout,
console log:
increasing tx_timestamp_timeout may correct this issue, but it is likely caused by a driver bug.
and check errno val: errno: -6
ptp4l source code snipper below:
int sk_receive(int fd, void *buf, int buflen,
 struct address *addr, struct hw_timestamp *hwts, int flags)
{
      ...
if (flags == MSG_ERRQUEUE) {
 struct pollfd pfd = { fd, sk_events, 0 };
 res = poll(&pfd, 1, sk_tx_timeout);
 if (res < 1) {
 pr_err(res ? "poll for tx timestamp failed: %m" :
 "timed out while polling for tx timestamp");
 pr_err("increasing tx_timestamp_timeout may correct "
 "this issue, but it is likely caused by a driver bug");
 return -errno;
 } else if (!(pfd.revents & sk_revents)) {
 pr_err("poll for tx timestamp woke up on non ERR event");
 return -1;
 }
 }
cnt = recvmsg(fd, &msg, flags);
...
2. linux kernel: sock_queue_err_skb() log :sk->sk_flags: 0x800100 => socket status is not SOCK_DEAD;
3. linux kernel: sock_queue_err_skb(), before invoking skb_queue_tail()
   check skb->sk->sk_error_queue.qlen: 0x0, after invoking skb_queue_tail(), 
 check skb->sk->sk_error_queue.qlen: 0x1, => skb attached hardware tx timestamp enqueued into err queue.
4. linux kernel: sk->sk_error_report(sk) callback sock_def_error_report() is called, 
   => err report is triggered to wake up the poll in ptp4l application.
5. master application(ptp4l) socket fd: 14
6. linux kernel: skb->tstamp assigned to ktime_get_real()
7. linux kernel: skb->ip_summed assigned to CHECKSUM_UNNECESSARY
8. linux kernel: before skb enqueued, skb->cb is cleared and
 before skb_complete_tx_timestamp() called, skb->cb is cleared again.
9. strace -e ioctl ./ptp4l -i eth0 -m
   ioctl(SIOCSHWTSTAMP, ...) = 0
motorcomm phy ptp driver source code attached.
// SPDX-License-Identifier: GPL-2.0+
/*
 * drivers/net/phy/motorcomm.c
 *
 * Driver for Motorcomm PHYs
 *
 * Author: Jie Han<jie.han@motor-comm.com>
 *
 * Copyright (c) 2025 Motorcomm, Inc.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.
 *
 * Support Motorcomm Automotive Phys:
 * 1000M Phys: YT8011
 */
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/phy.h>
#ifndef LINUX_VERSION_CODE
#include <linux/version.h>
#else
#define KERNEL_VERSION(a, b, c) (((a) << 16) + ((b) << 8) + (c))
#endif
#include <linux/netdevice.h>
#include <linux/debugfs.h>
#include <linux/uaccess.h>
#include <linux/string.h>
#include <linux/ethtool.h>
#include <linux/list.h>
#include <linux/mii.h>
#include <linux/net_tstamp.h>
#include <linux/ptp_classify.h>
#include <linux/ptp_clock_kernel.h>
#include <linux/math64.h>
//#include <linux/skbuff.h>
//#include <linux/net.h>      // sock_put()
//#include <linux/socket.h>   // SCM_TSTAMP_SND
//#include <linux/skbuff.h>   // __skb_complete_tx_timestamp()
#include <net/sock.h>
//#include <linux/compiler-attributes.h>	//fallthrough
#include <linux/rtc.h>
#include <linux/time64.h>
//#include <linux/sock.h>
//debug (wrapper to unify log format)
#define DEBUG_PTP
#if defined DEBUG_PTP
#define dbg_ptp(fmt, ...) pr_info(fmt, ##__VA_ARGS__)
#else
#define dbg_ptp(fmt, ...)
#endif
	
#define YTPHY_LINUX_VERSION "2.2.45591"
#define MODULE_NAME "yt"
#define MOTORCOMM_PHY_ID_MASK		0xffffffff
#define PHY_ID_YT8011			0x4f51eb01
#define REG_PHY_SPEC_STATUS		0x11
#define REG_DEBUG_ADDR_OFFSET		0x1e
#define REG_DEBUG_DATA			0x1f
#define REG_MII_MMD_CTRL		0x0D
#define REG_MII_MMD_DATA		0x0E
#define YTXXXX_SPEED_MODE		0xc000
#define YTXXXX_SPEED_MODE_BIT		14
#define YTXXXX_DUPLEX_BIT		13
#define YTXXXX_LINK_STATUS_BIT		10
#define YTPHY_UTP_INTR_REG		0x12
#define YTPHY_UTP_INTR_STATUS_REG	0x13
#define YTPHY_INTR_LINK_STATUS		(BIT(11) | BIT(10))
#define YTPHY_REG_SPACE_UTP		0
#define YT801X_REG_SMI_MUX		0x9000
enum GPTP_ROLE {
	SLAVE = 0,
	MASTER
};
struct yt8xxx_priv {
	u8 chip_mode;
	u8 role;
	struct yt_ptp_private *ptp_priv;
};
enum yt8011_reg_space_type {
	YT8011_REG_SPACE_UTP,
	YT8011_REG_SPACE_SDS,
};
/* ext reg 0x8000 bit1:0 PTP_CLK_TYPE
 * 2'b00 Ordinary/boundary two-step clock;
 * 2'b01 Ordinary/boundary one-step clock;
 * 2'b10 Transparent two-step clock;
 * 2'b11 Transparent one-step clock;
 */
enum GPTP_CLK_MODE {
	OC_2_STEPS = 0,
	OC_1_STEPS,
	TC_2_STEPS,
	TC_1_STEPS
};
/* ext reg 0x8000 bit3:2 RTC_CLK_SEL
 * 2'b00: ADC clock, after rotator;
 * 2'b01: DAC clock;
 * 2'b10: ADC clock, before rotator;
 * 2'b11: SYNC_IN;
 */
enum CLK_SOURCE {
	ADC_CLK_AFTER_ROTATOR = 0,
	DAC_CLK,
	ADC_CLK_BEFORE_ROTATOR,
	SYNC_IN
};
#define VLAN_HLEN 4
#define SYNC                  0x0
#define DELAY_REQ             0x1
#define PDELAY_REQ            0x2
#define PDELAY_RESP           0x3
#define FOLLOW_UP             0x8
#define DELAY_RESP            0x9
#define PDELAY_RESP_FOLLOW_UP 0xA
#define ANNOUNCE              0xB
#define SIGNALING             0xC
#define MANAGEMENT            0xD
static int yt8011_select_page(struct phy_device *phydev, int page);
static int yt8011_restore_page(struct phy_device *phydev, int oldpage, int ret);
static long yt8011_ptp_do_aux_work(struct ptp_clock_info *clock_info);
#if (KERNEL_VERSION(5, 5, 0) > LINUX_VERSION_CODE)
static inline void phy_lock_mdio_bus(struct phy_device *phydev)
{
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
	mutex_lock(&phydev->bus->mdio_lock);
#else
	mutex_lock(&phydev->mdio.bus->mdio_lock);
#endif
}
static inline void phy_unlock_mdio_bus(struct phy_device *phydev)
{
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
	mutex_unlock(&phydev->bus->mdio_lock);
#else
	mutex_unlock(&phydev->mdio.bus->mdio_lock);
#endif
}
#endif
#if (KERNEL_VERSION(4, 16, 0) > LINUX_VERSION_CODE)
static inline int __phy_read(struct phy_device *phydev, u32 regnum)
{
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
	struct mii_bus *bus = phydev->bus;
	int addr = phydev->addr;
	return bus->read(bus, phydev->addr, regnum);
#else
	struct mii_bus *bus = phydev->mdio.bus;
	int addr = phydev->mdio.addr;
#endif
	return bus->read(bus, addr, regnum);
}
static inline int __phy_write(struct phy_device *phydev, u32 regnum, u16 val)
{
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
	struct mii_bus *bus = phydev->bus;
	int addr = phydev->addr;
#else
	struct mii_bus *bus = phydev->mdio.bus;
	int addr = phydev->mdio.addr;
#endif
	return bus->write(bus, addr, regnum, val);
}
#endif
static int __ytphy_read_ext(struct phy_device *phydev,
						    u32 regnum)
{
	int ret;
	ret = __phy_write(phydev, REG_DEBUG_ADDR_OFFSET, regnum);
	if (ret < 0)
		return ret;
	return __phy_read(phydev, REG_DEBUG_DATA);
}
static int ytphy_read_ext(struct phy_device *phydev, u32 regnum)
{
	int ret;
	phy_lock_mdio_bus(phydev);
	ret = __phy_write(phydev, REG_DEBUG_ADDR_OFFSET, regnum);
	if (ret < 0)
		goto err_handle;
	ret = __phy_read(phydev, REG_DEBUG_DATA);
	if (ret < 0)
		goto err_handle;
err_handle:
	phy_unlock_mdio_bus(phydev);
	return ret;
}
static int ytphy_write_ext(struct phy_device *phydev, u32 regnum, u16 val)
{
	int ret;
	phy_lock_mdio_bus(phydev);
	ret = __phy_write(phydev, REG_DEBUG_ADDR_OFFSET, regnum);
	if (ret < 0)
		goto err_handle;
	ret = __phy_write(phydev, REG_DEBUG_DATA, val);
	if (ret < 0)
		goto err_handle;
err_handle:
	phy_unlock_mdio_bus(phydev);
	return ret;
}
static int __ytphy_write_ext(struct phy_device *phydev,
						     u32 regnum, u16 val)
{
	int ret;
	ret = __phy_write(phydev, REG_DEBUG_ADDR_OFFSET, regnum);
	if (ret < 0)
		return ret;
	ret = __phy_write(phydev, REG_DEBUG_DATA, val);
	if (ret < 0)
		return ret;
	return 0;
}
static int ytphy_read_mmd(struct phy_device* phydev,
						  u16 device, u16 reg)
{
	int val;
	phy_lock_mdio_bus(phydev);
	__phy_write(phydev, REG_MII_MMD_CTRL, device);
	__phy_write(phydev, REG_MII_MMD_DATA, reg);
	__phy_write(phydev, REG_MII_MMD_CTRL, device | 0x4000);
	val = __phy_read(phydev, REG_MII_MMD_DATA);
	if (val < 0) {
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
		dev_err(&phydev->dev, "error read mmd device(%u) reg (%u)\n",
			device, reg);
#else
		dev_err(&phydev->mdio.dev,
			"error read mmd device(%u) reg (%u)\n", device, reg);
#endif
		goto err_handle;
	}
err_handle:
	phy_unlock_mdio_bus(phydev);
	return val;
}
__attribute__((unused)) static int ytphy_write_mmd(struct phy_device* phydev,
						   u16 device, u16 reg,
						   u16 value)
{
	int ret = 0;
	phy_lock_mdio_bus(phydev);
	__phy_write(phydev, REG_MII_MMD_CTRL, device);
	__phy_write(phydev, REG_MII_MMD_DATA, reg);
	__phy_write(phydev, REG_MII_MMD_CTRL, device | 0x4000);
	__phy_write(phydev, REG_MII_MMD_DATA, value);
	phy_unlock_mdio_bus(phydev);
	return ret;
}
__attribute__((unused)) static int __ytphy_read_mmd(struct phy_device* phydev,
						    u16 device, u16 reg)
{
	int val;
	__phy_write(phydev, REG_MII_MMD_CTRL, device);
	__phy_write(phydev, REG_MII_MMD_DATA, reg);
	__phy_write(phydev, REG_MII_MMD_CTRL, device | 0x4000);
	val = __phy_read(phydev, REG_MII_MMD_DATA);
	return val;
}
__attribute__((unused)) static int __ytphy_write_mmd(struct phy_device* phydev,
						     u16 device, u16 reg,
						     u16 value)
{
	__phy_write(phydev, REG_MII_MMD_CTRL, device);
	__phy_write(phydev, REG_MII_MMD_DATA, reg);
	__phy_write(phydev, REG_MII_MMD_CTRL, device | 0x4000);
	__phy_write(phydev, REG_MII_MMD_DATA, value);
	return 0;
}
static int __ytphy_soft_reset(struct phy_device *phydev)
{
	int ret = 0, val = 0;
	val = __phy_read(phydev, MII_BMCR);
	if (val < 0)
		return val;
	ret = __phy_write(phydev, MII_BMCR, val | BMCR_RESET);
	if (ret < 0)
		return ret;
	return ret;
}
struct yt_ptp_private {
	struct phy_device *phydev;
#if (KERNEL_VERSION(5, 5, 19) < LINUX_VERSION_CODE)
	struct mii_timestamper mii_ts;
#endif
	struct ptp_clock *clock;
	struct ptp_clock_info clock_info;
	struct mutex mutex;
	struct sk_buff_head tx_queue;
	int tx_type;	//ioctl()
	bool hwts_rx;	//ioctl()
	int layer;
	int version;
};
struct yt_ptp_skb_cb {
	unsigned long timeout;
	u16 seq_id;
	u8 msgtype;
};
struct yt_ptp_capture {
	ktime_t hwtstamp;
	u16 seq_id;
	u8 msgtype;
};
#if (KERNEL_VERSION(5, 5, 19) < LINUX_VERSION_CODE)
static struct yt_ptp_private *miits2ptppriv(struct mii_timestamper *mii_ts)
{
	return container_of(mii_ts, struct yt_ptp_private, mii_ts);
}
#endif
static struct yt_ptp_private *clkinfo2ptppriv(struct ptp_clock_info *clock_info)
{
	return container_of(clock_info, struct yt_ptp_private, clock_info);
}
static int yt8011_ptp_get_ts_paged(struct phy_device *phydev,
				   struct timespec64 *ts, int page)
{
	int ret = 0, oldpage;
	oldpage = yt8011_select_page(phydev, page);
	if (oldpage < 0)
		goto err_restore_page;
	/* ext reg 0x8049 rtc_real_time_i bit63:48(s_h) */
	ret = __ytphy_read_ext(phydev, 0x8049);
	if (ret < 0)
		goto err_restore_page;
	ts->tv_sec = (ret << 16);
	/* ext reg 0x804a rtc_real_time_i bit47:32(s_l) */
	ret = __ytphy_read_ext(phydev, 0x804a);
	if (ret < 0)
		goto err_restore_page;
	ts->tv_sec |= ret;
	/* ext reg 0x804b rtc_real_time_i bit31:16(ns_h) */
	ret = __ytphy_read_ext(phydev, 0x804b);
	if (ret < 0)
		goto err_restore_page;
	ts->tv_nsec = (ret << 16);
	/* ext reg 0x804c rtc_real_time_i bit15:0(ns_l) */
	ret = __ytphy_read_ext(phydev, 0x804c);
	if (ret < 0)
		goto err_restore_page;
	ts->tv_nsec |= ret;
err_restore_page:
	return yt8011_restore_page(phydev, oldpage, ret);
}
static int yt8011_ptp_set_ts_paged(struct phy_device *phydev,
				     const struct timespec64 *ts, int page)
{
	int ret = 0, oldpage;
	oldpage = yt8011_select_page(phydev, page);
	if (oldpage < 0)
		goto err_restore_page;
	/* ext reg 0x8062(sec) RTC_PRELOAD_VAL bit63:48 */
	ret = __ytphy_write_ext(phydev, 0x8062, (ts->tv_sec >> 16) & 0xffff);
	if (ret < 0)
		goto err_restore_page;
	/* ext reg 0x8063(sec) RTC_PRELOAD_VAL bit47:32 */
	ret = __ytphy_write_ext(phydev, 0x8063, ts->tv_sec & 0xffff);
	if (ret < 0)
		goto err_restore_page;
	/* ext reg 0x8064(nsec) RTC_PRELOAD_VAL bit31:16 */
	ret = __ytphy_write_ext(phydev, 0x8064, (ts->tv_nsec >> 16) & 0xffff);
	if (ret < 0)
		goto err_restore_page;
	/* ext reg 0x8065(nsec) RTC_PRELOAD_VAL bit15:0 */
	ret = __ytphy_write_ext(phydev, 0x8065, ts->tv_nsec & 0xffff);
	if (ret < 0)
		goto err_restore_page;
err_restore_page:
	return yt8011_restore_page(phydev, oldpage, ret);
}
static int yt8011_ptp_rtc_load_paged(struct phy_device *phydev, int page)
{
	int ret = 0, oldpage;
	oldpage = yt8011_select_page(phydev, page);
	if (oldpage < 0)
		goto err_restore_page;
	/* 
	 * YT8011 load RTC val, ext reg 0x8060 bit0 1'b1
	 * Setting it to 1 loads value 8061~8065 RTC_PRELOAD to RTC.
	 * This bit is self-clearing and always read back as 0.
	 */
	ret = __ytphy_read_ext(phydev, 0x8060);
	if (ret < 0)
		goto err_restore_page;
	ret = __ytphy_write_ext(phydev, 0x8060, ret | BIT(0));
	if (ret < 0)
		goto err_restore_page;
err_restore_page:
	return yt8011_restore_page(phydev, oldpage, ret);
}
#if (KERNEL_VERSION(4, 20, 17) < LINUX_VERSION_CODE)
static int yt8011_ptp_gettimex(struct ptp_clock_info *clock_info,
			       struct timespec64 *ts,
			       struct ptp_system_timestamp *sts)
{
	struct yt_ptp_private *ptp_priv = clkinfo2ptppriv(clock_info);
	int err;
	mutex_lock(&ptp_priv->mutex);
	err = yt8011_ptp_get_ts_paged(ptp_priv->phydev, ts,
				      YT8011_REG_SPACE_UTP);
	mutex_unlock(&ptp_priv->mutex);
	return err < 0 ? err : 0;
}
#else
static int yt8011_ptp_gettime(struct ptp_clock_info *clock_info,
			      struct timespec64 *ts)
{
	struct yt_ptp_private *ptp_priv = clkinfo2ptppriv(clock_info);
	int err;
	mutex_lock(&ptp_priv->mutex);
	err = yt8011_ptp_get_ts_paged(ptp_priv->phydev, ts,
				      YT8011_REG_SPACE_UTP);
	mutex_unlock(&ptp_priv->mutex);
	return err < 0 ? err : 0;
}
#endif
static int yt8011_ptp_settime(struct ptp_clock_info *clock_info,
			      const struct timespec64 *ts)
{
	struct yt_ptp_private *ptp_priv = clkinfo2ptppriv(clock_info);
	int err;
	mutex_lock(&ptp_priv->mutex);
	err = yt8011_ptp_set_ts_paged(ptp_priv->phydev, ts,
				      YT8011_REG_SPACE_UTP);
	if (err < 0)
		goto out;
	err = yt8011_ptp_rtc_load_paged(ptp_priv->phydev,
					YT8011_REG_SPACE_UTP);
out:
	mutex_unlock(&ptp_priv->mutex);
	return err < 0 ? err : 0;
}
static int yt8011_ptp_adjtime_paged(struct phy_device *phydev,
				    struct timespec64* ts, int page)
{
	int ret = 0, oldpage;
	oldpage = yt8011_select_page(phydev, page);
	if (oldpage < 0)
		goto err_restore_page;
	/* ext reg 0x8045 OFFSET_SEC bit31:16 */
	ret = __ytphy_write_ext(phydev, 0x8045, (ts->tv_sec >> 16) & 0xffff);
	if (ret < 0)
		goto err_restore_page;
	/* ext reg 0x8046 OFFSET_SEC bit15:0 */
	ret = __ytphy_write_ext(phydev, 0x8046, ts->tv_sec & 0xffff);
	if (ret < 0)
		goto err_restore_page;
	/* ext reg 0x8042 OFFSET_NANO bit31:16 */
	ret = __ytphy_write_ext(phydev, 0x8042, (ts->tv_nsec >> 16) & 0xffff);
	if (ret < 0)
		goto err_restore_page;
	/* ext reg 0x8043 OFFSET_NANO bit15:0 */
	ret = __ytphy_write_ext(phydev, 0x8043, ts->tv_nsec & 0xffff);
	if (ret < 0)
		goto err_restore_page;
	/* ext reg 0x8047 EN_OFFSET bit0 
	 * 1'b1 adjust the RTC with the value OFFSET_SEC and OFFSET_NANO.
	 */
	ret = __ytphy_read_ext(phydev, 0x8047);
	if (ret < 0)
		goto err_restore_page;
	ret = __ytphy_write_ext(phydev, 0x8047, ret | BIT(0));
	if (ret < 0)
		goto err_restore_page;
err_restore_page:
	return yt8011_restore_page(phydev, oldpage, ret);
}
static int yt8011_ptp_adjtime(struct ptp_clock_info *clock_info, s64 delta_ns)
{
	struct yt_ptp_private *ptp_priv = clkinfo2ptppriv(clock_info);
	struct phy_device *phydev = ptp_priv->phydev;
	struct timespec64 ts;
	int err;
	ts = ns_to_timespec64(delta_ns);
	mutex_lock(&ptp_priv->mutex);
	err = yt8011_ptp_adjtime_paged(phydev, &ts, YT8011_REG_SPACE_UTP);
	mutex_unlock(&ptp_priv->mutex);
	return err < 0 ? err : 0;
}
static int yt8011_ptp_adjfine_paged(struct phy_device *phydev,
				    unsigned long adj_step, int page)
{
	int ret = 0, oldpage;
	oldpage = yt8011_select_page(phydev, page);
	if (oldpage < 0)
		goto err_restore_page;
	/*
	 * ext reg 0x8040 RTC_STEP bit31:16
	 * ext reg 0x8041 RTC_STEP bit15:0
	 */
	ret = __ytphy_write_ext(phydev, 0x8040, (adj_step >> 16) & 0xffff);
	if (ret < 0)
		goto err_restore_page;
	ret = __ytphy_write_ext(phydev, 0x8041, adj_step & 0xffff);
	if (ret < 0)
		goto err_restore_page;
err_restore_page:
	return yt8011_restore_page(phydev, oldpage, ret);
}
/* rtc clk 375MHz, rtc step = 1 000 000 000 / 375 000 000 = 2.66ns
 * fractional part(0.66 << 26 = 0x2AA AAAA), integer part(2 << 26 = 0x800 0000)
 * so, the frequency adjustment base is 0xAAA AAAA(0x2AA AAAA | 0x800 0000),
 * or 2.6666667 * (2^26) = (2.6666667 << 26)
 *
 * Frequency adjustment is
 * adj = {scaled_ppm * 0xAAAAAAA} / (10^6 * 2^16)
 * adj = {scaled_ppm * [2.6666667 * (2^26)]} / (10^6 * 2^16)
 * adj = {scaled_ppm * [2.6666667 * (2^26)]} / ((2 * 5)^6 * 2^16)
 * adj = {scaled_ppm * [2.6666667 * (2^26)]} / (5^6 * 2^22)
 * adj = {scaled_ppm * [2.6666667 * (2^4)]} / (5^6)
 * adj = {scaled_ppm * [2.6666667 * (2^4)]} / 15625
 * scaled_ppm: long(unit: ppm) and Q16.16 format
 * #define RTC_DEFAULT_CYCLE_RATE 0xAAAAAAA(2.6666667*(2^26))
 * double rate = (1000000 + scaled_ppm) / 10^6;
 * double rate = (1000000 + scaled_ppm) / 1000000;
 * double step = ((double)RTC_DEFAULT_CYCLE_RATE / 2^26) * rate;
 * double step = ((double)RTC_DEFAULT_CYCLE_RATE / 0x4000000) * rate;
 * unsigned long adj_step = (unsigned long)(step << 26 = step * 2^26);
 * unsigned long adj_step = (unsigned long)(step * 0x4000000);
 */
static int yt8011_ptp_adjfine(struct ptp_clock_info *clock_info,
			      long scaled_ppm)
{
#define RTC_DEFAULT_CYCLE_RATE 0xAAAAAAA
	struct yt_ptp_private *ptp_priv = clkinfo2ptppriv(clock_info);
	unsigned long adj_step;
	int neg_adj = 0;
	u32 diff;
	u64 adj;
	dbg_ptp("@jie.han %s, %s, %d scaled_ppm: %ld\n",
		__FILE__, __func__, __LINE__, scaled_ppm);
	if (scaled_ppm < 0) {
		neg_adj = 1;
		scaled_ppm = -scaled_ppm;
	}
	//adj = (scaled_ppm * 266667LL) / 100000LL;
	adj = (u64)scaled_ppm * 266667LL;
	adj = div64_u64(adj, 100000LL);
	adj <<= 4;
	diff = div_u64(adj, 15625);
	//diff = div64_u64(adj, 15625);
	adj_step = RTC_DEFAULT_CYCLE_RATE + (neg_adj ? -diff : diff);
	mutex_lock(&ptp_priv->mutex);
	yt8011_ptp_adjfine_paged(ptp_priv->phydev, adj_step,
				 YT8011_REG_SPACE_UTP);
	mutex_unlock(&ptp_priv->mutex);
	return 0;
}
#define YT_SKB_CB(skb)		((struct yt_ptp_skb_cb *)(skb)->cb)
static const struct ptp_clock_info yt8011_ptp_clock_info = {
	.owner		= THIS_MODULE,
	.name		= KBUILD_MODNAME,
	.max_adj	= 100000000,
#if (KERNEL_VERSION(4, 20, 17) < LINUX_VERSION_CODE)
	.gettimex64	= yt8011_ptp_gettimex,
#else
	.gettime64	= yt8011_ptp_gettime,
#endif
	.settime64	= yt8011_ptp_settime,
	.adjtime	= yt8011_ptp_adjtime,
	.adjfine	= yt8011_ptp_adjfine,
	.do_aux_work	= yt8011_ptp_do_aux_work,
};
static int yt_ptp_get_txtstamp_paged(struct phy_device *phydev,
				     struct yt_ptp_capture *capts, u8 page)
{
	int ret = 0, oldpage;
	uint32_t sec, nsec;
	oldpage = yt8011_select_page(phydev, page);
	if (oldpage < 0)
		goto err_restore_page;
	ret = __ytphy_read_ext(phydev, 0x8020);
	if (ret < 0)
		goto err_restore_page;
	capts->seq_id = ret;
	ret = (__ytphy_read_ext(phydev, 0x802b) & 0xf000) >> 12;
	if (ret < 0)
		goto err_restore_page;
	capts->msgtype = ret;
	ret = __ytphy_read_ext(phydev, 0x8027);
	if (ret < 0)
		goto err_restore_page;
	sec = ret << 16;
	ret = __ytphy_read_ext(phydev, 0x8028);
	if (ret < 0)
		goto err_restore_page;
	sec |= ret;
	ret = __ytphy_read_ext(phydev, 0x8029);
	if (ret < 0)
		goto err_restore_page;
	nsec = ret << 16;
	ret = __ytphy_read_ext(phydev, 0x802a);
	if (ret < 0)
		goto err_restore_page;
	nsec |= ret;
	capts->hwtstamp = ktime_set(sec, nsec);
err_restore_page:
	return yt8011_restore_page(phydev, oldpage, ret < 0 ? ret : 0);
}
static bool yt8011_ptp_get_txtstamp(struct yt_ptp_private *ptp_priv,
				    struct yt_ptp_capture *capts)
{
	struct phy_device *phydev = ptp_priv->phydev;
	int ret;
	//dbg_ptp("@jie.han %s, %s, %d\n", __FILE__, __func__, __LINE__);
	mutex_lock(&ptp_priv->mutex);
	ret = yt_ptp_get_txtstamp_paged(phydev, capts, YT8011_REG_SPACE_UTP);
	mutex_unlock(&ptp_priv->mutex);
	dbg_ptp("@jie.han %s, %s, %d yt_ptp_get_txtstamp_paged() ret: %d, "
		"capts->seq_id: %d, capts->msgtype: %s\n",
		__FILE__, __func__, __LINE__, ret,
		capts->seq_id,
		capts->msgtype == SYNC ? "SYNC" :
		capts->msgtype == DELAY_REQ ? "DELAY_REQ" :
		capts->msgtype == PDELAY_REQ ? "PDELAY_REQ" :
		capts->msgtype == PDELAY_RESP ? "PDELAY_RESP" :
		capts->msgtype == FOLLOW_UP ? "FOLLOW_UP" :
		capts->msgtype == DELAY_RESP ? "DELAY_RESP" :
		capts->msgtype == PDELAY_RESP_FOLLOW_UP ? "PDELAY_RESP_FOLLOW_UP" :
		capts->msgtype == ANNOUNCE ? "ANNOUNCE" :
		capts->msgtype == SIGNALING ? "SIGNALING" :
		capts->msgtype == MANAGEMENT ? "MANAGEMENT" : "other");
	return (ret < 0 ? false : true);
}
static bool yt8011_ptp_match_txtstamp(struct yt_ptp_private *ptp_priv,
				      struct yt_ptp_capture *capts)
{
	//struct skb_shared_hwtstamps hwts;
	struct skb_shared_hwtstamps *shhwtstamps;
	//struct sk_buff *skb, *ts_skb;
	unsigned long now = jiffies;
	struct sk_buff *skb, *skb_tmp;
	bool matched = false;
	unsigned long flags;
	//bool first = false;
	u8 msgtype;
	u16 seqid;
	//dbg_ptp("@jie.han %s, %s, %d\n", __FILE__, __func__, __LINE__);
	//ts_skb = NULL;
	spin_lock_irqsave(&ptp_priv->tx_queue.lock, flags);
	skb_queue_walk_safe(&ptp_priv->tx_queue, skb, skb_tmp) {
		dbg_ptp("@jie.han %s, %s, %d skb->sk->sk_flags: %#lx\n",
			__FILE__, __func__, __LINE__, skb->sk->sk_flags);
		//dbg_ptp("@jie.han %s, %s, %d skb->sk->sk_refcnt: %d\n",
			//__FILE__, __func__, __LINE__, atomic_read(&skb->sk->sk_refcnt));	//compile err
			//__FILE__, __func__, __LINE__, refcount_read(&skb->sk->sk_refcnt));//compile err
			//__FILE__, __func__, __LINE__, skb->sk->sk_refcnt.refs.counter);
		dbg_ptp("@jie.han %s, %s, %d skb->sk->sk_refcnt: %d\n",
			__FILE__, __func__, __LINE__, skb->sk->sk_refcnt.refs.counter);
		if (time_after(now, YT_SKB_CB(skb)->timeout)) {
			dbg_ptp("@jie.han %s, %s, %d time_after(skb) timeout, then free it.\n",
				__FILE__, __func__, __LINE__);
			__skb_unlink(skb, &ptp_priv->tx_queue);
			if (skb->sk)
				sock_put(skb->sk);
			kfree_skb(skb); 		
			continue;
		}
		seqid = YT_SKB_CB(skb)->seq_id;
		msgtype = YT_SKB_CB(skb)->msgtype;
		if (seqid == capts->seq_id && msgtype == capts->msgtype) {
			__skb_unlink(skb, &ptp_priv->tx_queue);
			dbg_ptp("@jie.han %s, %s, %d skb->sk->sk_flags: %#lx\n",
				__FILE__, __func__, __LINE__, skb->sk->sk_flags);
			shhwtstamps = skb_hwtstamps(skb);
			memset(shhwtstamps, 0, sizeof(*shhwtstamps));
			shhwtstamps->hwtstamp = capts->hwtstamp;
			//skb->tstamp = ktime_to_ns(capts->hwtstamp);
			skb->tstamp = ktime_get_real();
			skb->ip_summed = CHECKSUM_UNNECESSARY;
			
			dbg_ptp("@jie.han %s, %s, %d matched, seqid: %d, msgtype: %s\n",
				__FILE__, __func__, __LINE__,
				seqid,
				msgtype == SYNC ? "SYNC" :
				msgtype == DELAY_REQ ? "DELAY_REQ" :
				msgtype == PDELAY_REQ ? "PDELAY_REQ" :
				msgtype == PDELAY_RESP ? "PDELAY_RESP" :
				msgtype == FOLLOW_UP ? "FOLLOW_UP" :
				msgtype == DELAY_RESP ? "DELAY_RESP" :
				msgtype == PDELAY_RESP_FOLLOW_UP ? "PDELAY_RESP_FOLLOW_UP" :
				msgtype == ANNOUNCE ? "ANNOUNCE" :
				msgtype == SIGNALING ? "SIGNALING" :
				msgtype == MANAGEMENT ? "MANAGEMENT" : "other");
			if (skb->sk) {
				//__skb_complete_tx_timestamp(skb, skb->sk,
				//			SCM_TSTAMP_SND, false);
				//sock_put(skb->sk);
				dbg_ptp("@jie.han %s, %s, %d skb->sk->sk_flags: %#lx\n",
					__FILE__, __func__, __LINE__, skb->sk->sk_flags);
				memset(YT_SKB_CB(skb), 0x0, sizeof(struct yt_ptp_skb_cb));
				skb_complete_tx_timestamp(skb, shhwtstamps);
			} else
				kfree_skb(skb);
			matched = true;
			break;
		}
	}
	spin_unlock_irqrestore(&ptp_priv->tx_queue.lock, flags);
	return matched;
}
void yt8011_ptp_purge_tx_queue(struct yt_ptp_private *ptp_priv)
{
	struct sk_buff *skb, *tmp;
	unsigned long flags;
	spin_lock_irqsave(&ptp_priv->tx_queue.lock, flags);
	skb_queue_walk_safe(&ptp_priv->tx_queue, skb, tmp) {
		__skb_unlink(skb, &ptp_priv->tx_queue);
		if (skb->sk)
			sock_put(skb->sk);
		kfree_skb(skb);
	}
	spin_unlock_irqrestore(&ptp_priv->tx_queue.lock, flags);
}
static long yt8011_ptp_do_aux_work(struct ptp_clock_info *clock_info)
{
	struct yt_ptp_private *ptp_priv = clkinfo2ptppriv(clock_info);
	struct yt_ptp_capture capts;
	bool reschedule = false;
	int limit = 8;
	
	memset(&capts, 0x0, sizeof(capts));
	//dbg_ptp("@jie.han %s, %s, %d\n", __FILE__, __func__, __LINE__);
	/* ref to bcm and nxp in kernel v6.9 */
	while (!skb_queue_empty_lockless(&ptp_priv->tx_queue) && limit--) {
		if (!yt8011_ptp_get_txtstamp(ptp_priv, &capts)) {
			reschedule = true;
			break;
		}
		if (!yt8011_ptp_match_txtstamp(ptp_priv, &capts)) {		
			pr_warn("@jie.han %s, %s, %d "
				"failed to match TX timestamp, msgtype = 0x%x, seqid = %d\n",
				__FILE__, __func__, __LINE__, capts.msgtype, capts.seq_id);
			//skb_queue_purge(&ptp_priv->tx_queue);
			yt8011_ptp_purge_tx_queue(ptp_priv);
			reschedule = true;
			break;
		}
	}
	/* delay of the next auxiliary work scheduling time (>=0) or
	 * negative value in case further scheduling is not required.
	 */
	dbg_ptp("@jie.han %s, %s, %d reschedule: %s\n",
		__FILE__, __func__, __LINE__, reschedule == true ? "true" : "false");
	return reschedule ? 1 : -1;
}
static int yt8011_ptp_get_rxtstamp_paged(struct phy_device *phydev,
					 u8 eventtype, u16 seqid, 
					 u32 *sec, u32 *nsec,
					 u8 page)
{
	int ret = 0, oldpage;
	uint16_t id;
	int type;
	int idx;
	oldpage = yt8011_select_page(phydev, page);
	if (oldpage < 0)
		goto err_restore_page;
	for (idx = 0; idx < 4; idx++) {
		if (idx == 0) {
			ret = __ytphy_read_ext(phydev, 0x8010);
			if (ret < 0)
				goto err_restore_page;
			id = ret;
			ret = __ytphy_read_ext(phydev, 0x801b);
			if (ret < 0)
				goto err_restore_page;
			type = (ret & 0xf000) >> 12;
			if (id == seqid && type == eventtype) {
				ret = __ytphy_read_ext(phydev, 0x8017);
				if (ret < 0)
					goto err_restore_page;
				*sec = ret << 16;
				ret = __ytphy_read_ext(phydev, 0x8018);
				if (ret < 0)
					goto err_restore_page;
				*sec |= ret;
				ret = __ytphy_read_ext(phydev, 0x8019);
				if (ret < 0)
					goto err_restore_page;
				*nsec = ret << 16;
				ret = __ytphy_read_ext(phydev, 0x801a);
				if (ret < 0)
					goto err_restore_page;
				*nsec |= ret;
				break;
			}
		} else if (idx == 1) {
			ret = __ytphy_read_ext(phydev, 0x8100);
			if (ret < 0)
				goto err_restore_page;
			id = ret;
			ret = __ytphy_read_ext(phydev, 0x810b);
			if (ret < 0)
				goto err_restore_page;
			type = (ret & 0xf000) >> 12;
			if (id == seqid && type == eventtype) {
				ret = __ytphy_read_ext(phydev, 0x8107);
				if (ret < 0)
					goto err_restore_page;
				*sec = ret << 16;
				ret = __ytphy_read_ext(phydev, 0x8108);
				if (ret < 0)
					goto err_restore_page;
				*sec |= ret;
				ret = __ytphy_read_ext(phydev, 0x8109);
				if (ret < 0)
					goto err_restore_page;
				*nsec = ret << 16;
				ret= __ytphy_read_ext(phydev, 0x810a);
				if (ret < 0)
					goto err_restore_page;
				*nsec |= ret;
				break;
			}
		} else if (idx == 2) {
			ret = __ytphy_read_ext(phydev, 0x8110);
			if (ret < 0)
				goto err_restore_page;
			ret = __ytphy_read_ext(phydev, 0x811b);
			if (ret < 0)
				goto err_restore_page;
			type = (ret & 0xf000) >> 12;
			if (id == seqid && type == eventtype) {
				ret = __ytphy_read_ext(phydev, 0x8117);
				if (ret < 0)
					goto err_restore_page;
				*sec = ret << 16;
				ret = __ytphy_read_ext(phydev, 0x8118);
				if (ret < 0)
					goto err_restore_page;
				*sec |= ret;
				ret = __ytphy_read_ext(phydev, 0x8119);
				if (ret < 0)
					goto err_restore_page;
				*nsec = ret << 16;
				ret = __ytphy_read_ext(phydev, 0x811a);
				if (ret < 0)
					goto err_restore_page;
				*nsec |= ret;
				break;
			}
		} else if (idx == 3) {
			ret = __ytphy_read_ext(phydev, 0x8120);
			if (ret < 0)
				goto err_restore_page;
			ret = __ytphy_read_ext(phydev, 0x812b);
			if (ret < 0)
				goto err_restore_page;
			type = (ret & 0xf000) >> 12;
			if (id == seqid && type == eventtype) {
				ret = __ytphy_read_ext(phydev, 0x8127);
				if (ret < 0)
					goto err_restore_page;
				*sec = ret << 16;
				ret = __ytphy_read_ext(phydev, 0x8128);
				if (ret < 0)
					goto err_restore_page;
				*sec |= ret;
				ret = __ytphy_read_ext(phydev, 0x8129);
				if (ret < 0)
					goto err_restore_page;
				*nsec = ret << 16;
				ret = __ytphy_read_ext(phydev, 0x812a);
				if (ret < 0)
					goto err_restore_page;
				*nsec |= ret;
				break;
			}
		}
	}
 
	dbg_ptp("@jie.han %s, %s, %d idx: %d\n",
		__FILE__, __func__, __LINE__, idx);
err_restore_page:
	return yt8011_restore_page(phydev, oldpage, ret);
}
#if (KERNEL_VERSION(5, 5, 19) < LINUX_VERSION_CODE)
static bool yt8011_ptp_mii_rxtstamp(struct mii_timestamper *mii_ts,
				    struct sk_buff *skb, int type)
{
	struct yt_ptp_private *ptp_priv = miits2ptppriv(mii_ts);
	struct phy_device *phydev = ptp_priv->phydev;
	struct skb_shared_hwtstamps *hwts;
	struct ptp_header *header;
	u16 sequence_id;
	u32 sec, nsec;
	u8 msg_type;
	int ret;
	dbg_ptp("@jie.han %s, %s, %d\n", __FILE__, __func__, __LINE__);
	if (!ptp_priv->hwts_rx)
		return false;
	header = ptp_parse_header(skb, type);
	if (!header)
		return false;
	msg_type = ptp_get_msgtype(header, type);
	sequence_id = header->sequence_id;
	ret = yt8011_ptp_get_rxtstamp_paged(phydev, msg_type,
					    sequence_id, &sec, &nsec,
					    YT8011_REG_SPACE_UTP);
	if (ret > 0) {
		//get the rx timestamp and assigned skb's hwts->hwtstamp
		hwts = skb_hwtstamps(skb);
		hwts->hwtstamp = ktime_set(sec, nsec);
		netif_rx(skb);
		return true;
	} else
		return false;
}
static void yt8011_ptp_mii_txtstamp(struct mii_timestamper *mii_ts,
				    struct sk_buff *skb, int type)
{
	struct yt_ptp_private *ptp_priv = miits2ptppriv(mii_ts);
	struct ptp_header *hdr;
	int msgtype;
	dbg_ptp("@jie.han %s, %s, %d\n", __FILE__, __func__, __LINE__);
	hdr = ptp_parse_header(skb, type);
	if (!hdr)
		goto out;
	msgtype = ptp_get_msgtype(hdr, type);
	dbg_ptp("@jie.han %s, %s, %d ptp_priv->tx_type: %d\n",
		__FILE__, __func__, __LINE__, ptp_priv->tx_type);
	switch (ptp_priv->tx_type) {
	case HWTSTAMP_TX_ONESTEP_P2P:
	case HWTSTAMP_TX_ONESTEP_SYNC:
	case HWTSTAMP_TX_ON:
		YT_SKB_CB(skb)->timeout = jiffies + SKB_TIMESTAMP_TIMEOUT;
		YT_SKB_CB(skb)->seq_id = be16_to_cpu(hdr->sequence_id);
		YT_SKB_CB(skb)->msgtype = msgtype;
		skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
		skb_queue_tail(&ptp_priv->tx_queue, skb);
		ptp_schedule_worker(ptp_priv->clock, 0);
		return;
	default:
		break;
	}
out:
	kfree_skb(skb);
}
static int yt8011_ptp_mii_hwtstamp(struct mii_timestamper *mii_ts,
				   struct kernel_hwtstamp_config *cfg,
				   struct netlink_ext_ack *extack)
{
	struct yt_ptp_private *ptp_priv = miits2ptppriv(mii_ts);
	struct yt8xxx_priv *priv;
	u16 cfg0 = 0, cfg1 = 0;
	dbg_ptp("@jie.han %s, %s, %d\n", __FILE__, __func__, __LINE__);
	priv = container_of(ptp_priv, struct yt8xxx_priv, ptp_priv);
	switch (cfg.rx_filter) {
	case HWTSTAMP_FILTER_NONE:
	case HWTSTAMP_FILTER_PTP_V1_L4_EVENT:
	case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
	case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
		ptp_priv->hwts_rx = 0;
		ptp_priv->layer = 0;
		ptp_priv->version = 0;
		break;
	case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
	case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
	case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
		ptp_priv->hwts_rx = 1;
		ptp_priv->layer = PTP_CLASS_L4;
		ptp_priv->version = PTP_CLASS_V2;
		break;
	case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
	case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
	case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
		ptp_priv->hwts_rx = 1;
		ptp_priv->layer = PTP_CLASS_L2;
		ptp_priv->version = PTP_CLASS_V2;
		break;
	case HWTSTAMP_FILTER_PTP_V2_EVENT:
	case HWTSTAMP_FILTER_PTP_V2_SYNC:
	case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
		ptp_priv->hwts_rx = 1;
		ptp_priv->layer = PTP_CLASS_L4 | PTP_CLASS_L2;
		ptp_priv->version = PTP_CLASS_V2;
		break;
	default:
		return -ERANGE;
	}
	ptp_priv->tx_type = cfg->tx_type;
	cfg0 |= OC_2_STEPS;
	if (priv->role == MASTER)
		cfg0 |= (DAC_CLK << 2);
	else
		cfg0 |= (ADC_CLK_AFTER_ROTATOR << 2);
	/* ext reg 0x8000 bit4 BP_1588
	 * 1'b0 = PTP timestamp engine normal operation.
	 * 1'b1 = Bypass IEEE1588v2 related functions.	  
	 */
	cfg0 &= ~BIT(4);
	
	/* ext reg 0x8000 bit7 EN_GATE_1588
	 * 1'b0 = Disable gating 1588 clock domain.
	 * 1'b1 = Enable gating 1588 clock domain.
	 */
	cfg0 &= ~BIT(7);
	/* ext reg 0x8001 bit4
	 * 1'b1: PTP mode  1'b0:gPTP mode(802.1AS)
	 */
	if (ptp_priv->layer & PTP_CLASS_L4)
		cfg1 |= BIT(4);
	else
		cfg1 &= ~BIT(4);
	mutex_lock(&ptp_priv->mutex);
	yt8011_ptp_init_paged(ptp_priv->phydev, cfg0, cfg1,
			      YT8011_REG_SPACE_UTP);
	mutex_unlock(&ptp_priv->mutex);
	/* purge existing data */
	skb_queue_purge(&priv->tx_queue);
	return 0;
}
static int yt8011_ptp_mii_ts_info(struct mii_timestamper *mii_ts,
				  struct ethtool_ts_info *ts_info)
{
	struct yt_ptp_private *ptp_priv = miits2ptppriv(mii_ts);
	dbg_ptp("@jie.han %s, %s, %d\n", __FILE__, __func__, __LINE__);
	ts_info->phc_index = ptp_clock_index(ptp_priv->clock);
	ts_info->so_timestamping =	//software and hardware ts support
		SOF_TIMESTAMPING_TX_SOFTWARE |
		SOF_TIMESTAMPING_RX_SOFTWARE |
		SOF_TIMESTAMPING_SOFTWARE |
		SOF_TIMESTAMPING_TX_HARDWARE |
		SOF_TIMESTAMPING_RX_HARDWARE |
		SOF_TIMESTAMPING_RAW_HARDWARE;
	//v5.15 support HWTSTAMP_TX_ONESTEP_P2P
	ts_info->tx_types =			//hardware ts support only
		BIT(HWTSTAMP_TX_ON) |
		BIT(HWTSTAMP_TX_OFF) |
		//BIT(HWTSTAMP_TX_ONESTEP_SYNC) |
		BIT(HWTSTAMP_TX_ONESTEP_P2P);
	ts_info->rx_filters =		//hardware ts support only
		BIT(HWTSTAMP_FILTER_NONE) |
		BIT(HWTSTAMP_FILTER_PTP_V2_EVENT);
	return 0;
}
#endif
time64_t read_rtc_time_sec(void) {
	struct rtc_device *rtc;
	struct rtc_time tm;
	int ret;
	time64_t seconds = 0;
	//u64 nanoseconds;
	// 1. �� RTC �豸(�����豸��Ϊ "rtc0")
	//https://elixir.bootlin.com/linux/v3.8.13/A/ident/rtc_class_open
	//https://elixir.bootlin.com/linux/v6.15-rc5/A/ident/rtc_class_open
	rtc = rtc_class_open("rtc0");
	if (IS_ERR(rtc)) {
		pr_err("Failed to open RTC device");
		return seconds;
	}
	// 2. ��ȡʱ�䵽 tm �ṹ��
	//https://elixir.bootlin.com/linux/v3.8.13/A/ident/rtc_read_time
	//https://elixir.bootlin.com/linux/v6.15-rc5/A/ident/rtc_read_time
	ret = rtc_read_time(rtc, &tm);
	if (ret) {
		pr_err("Failed to read RTC time");
		goto out;
	}
	// 3. �� rtc_time ת��Ϊ Unix ʱ���(��)
	//https://elixir.bootlin.com/linux/v3.19/A/ident/rtc_tm_to_time64
	//https://elixir.bootlin.com/linux/v6.15-rc5/A/ident/rtc_tm_to_time64
	seconds = rtc_tm_to_time64(&tm);
	// 4. ת��Ϊ����
	//nanoseconds = seconds * NSEC_PER_SEC;
	//pr_info("RTC Time: %lld.%09d seconds since 1970-01-01 00:00:00 UTC",
	//        (long long)seconds, (int)(nanoseconds % NSEC_PER_SEC));
out:
	rtc_class_close(rtc); // �ر��豸
	return seconds;
}
static int yt8011_ptp_init_paged(struct phy_device *phydev,
				 u16 cfg0, u16 cfg1, u8 page)
{
	int ret = 0, oldpage;
	time64_t seconds;
	oldpage = yt8011_select_page(phydev, page);
	if (oldpage < 0)
		goto err_restore_page;
	ret = __ytphy_write_ext(phydev, 0x8000, cfg0);
	if (ret < 0)
		goto err_restore_page;
	ret = __ytphy_write_ext(phydev, 0x8001, cfg1);
	if (ret < 0)
		goto err_restore_page;
	/* rtc step cfg
	 * rtc clk f = 375MHz, T = 2.6666667ns
	 * rtc step = T << 26 = 0xaaa aaaa
	 * ext reg 0x8040 RTC_STEP
	 * bit31:16 MSB 16bits of part of the RTC step.
	 * ext reg 0x8041 RTC_STEP
	 * bit15:0 LSB 16bits of part of the RTC step.
	 */
	ret = __ytphy_write_ext(phydev, 0x8040, 0x0aaa);
	if (ret < 0)
		goto err_restore_page;
	ret = __ytphy_write_ext(phydev, 0x8041, 0xaaaa);
	if (ret < 0)
		goto err_restore_page;
	/* rtc cfg
	 * eg, 2023-01-01 00h00m00 s -> 1672531200 s -> 0x63b0 cd00
	 * write ext reg 0x8061(sec) RTC_PRELOAD_VAL bit79:64
	 * write ext reg 0x8062(sec) RTC_PRELOAD_VAL bit63:48
	 * write ext reg 0x8063(sec) RTC_PRELOAD_VAL bit47:32
	 * write ext reg 0x8064(nsec) RTC_PRELOAD_VAL bit31:16
	 * write ext reg 0x8065(nsec) RTC_PRELOAD_VAL bit15:0
	 */
	seconds = read_rtc_time_sec();
	/* s_h */
	ret = __ytphy_write_ext(phydev, 0x8061, 0x0);
	if (ret < 0)
		goto err_restore_page;
	/* s_m */
	//ret = __ytphy_write_ext(phydev, 0x8062, 0x63b0);
	ret = __ytphy_write_ext(phydev, 0x8062, (seconds >> 16) & 0xffff);
	if (ret < 0)
		goto err_restore_page;
	/* s_l */
	//ret = __ytphy_write_ext(phydev, 0x8063, 0xcd00);
	ret = __ytphy_write_ext(phydev, 0x8063, seconds & 0xffff);
	if (ret < 0)
		goto err_restore_page;
	/* ns_h */
	ret = __ytphy_write_ext(phydev, 0x8064, 0x0);
	if (ret < 0)
		goto err_restore_page;
	/* ns_l */
	ret = __ytphy_write_ext(phydev, 0x8065, 0x0);
	if (ret < 0)
		goto err_restore_page;
	/* load rtc
	 * ext reg 0x8060 bit0 LOAD_RTC
	 * 1'b1 loads value 0x8061~0x8065 RTC_PRELOAD to RTC.
	 * This bit is self-clearing and always read back as 0.
	 */
	ret = __ytphy_read_ext(phydev, 0x8060);
	if (ret < 0)
		goto err_restore_page;
	ret = __ytphy_write_ext(phydev, 0x8060, ret | BIT(0));
	if (ret < 0)
		goto err_restore_page;
err_restore_page:
	return yt8011_restore_page(phydev, oldpage, ret);
}
struct yt_ptp_private *yt8011_ptp_priv(struct phy_device *phydev)
{
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
	struct device *dev = &phydev->dev;
#else
	struct device *dev = &phydev->mdio.dev;
#endif
	struct yt_ptp_private *ptp_priv;
	struct ptp_clock *clock;
	ptp_priv = devm_kzalloc(dev, sizeof(*ptp_priv), GFP_KERNEL);
	if (!ptp_priv)
		return ERR_PTR(-ENOMEM);
		//return -ENOMEM;
	ptp_priv->clock_info = yt8011_ptp_clock_info;
	clock = ptp_clock_register(&ptp_priv->clock_info, dev);
	if (IS_ERR(clock))
		return ERR_CAST(clock);
	ptp_priv->clock = clock;
	ptp_priv->phydev = phydev;
	mutex_init(&ptp_priv->mutex);
	skb_queue_head_init(&ptp_priv->tx_queue);
#if (KERNEL_VERSION(5, 6, 0) > LINUX_VERSION_CODE)
#else
	ptp_priv->mii_ts.rxtstamp = yt8011_ptp_mii_rxtstamp;
	ptp_priv->mii_ts.txtstamp = yt8011_ptp_mii_txtstamp;
	ptp_priv->mii_ts.hwtstamp = yt8011_ptp_mii_hwtstamp;
	ptp_priv->mii_ts.ts_info = yt8011_ptp_mii_ts_info;
	ptp_priv->phydev->mii_ts = &ptp_priv->mii_ts;
#endif	
	return ptp_priv;
}
static int yt8011_probe(struct phy_device *phydev)
{
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
	struct device *dev = &phydev->dev;
#else
	struct device *dev = &phydev->mdio.dev;
#endif
	struct yt8xxx_priv *priv;
	int chip_config;
	u16 role;
	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
	if (!priv)
		//return ERR_PTR(-ENOMEM);
		return -ENOMEM;
	phydev->priv = priv;
	/* ext reg 0x9030 bit0 
	 * 0 = chip works in RGMII mode; 1 = chip works in SGMII mode 
	 */
	chip_config = ytphy_read_ext(phydev, 0x9030);
	priv->chip_mode = chip_config & 0x1;
	role = ytphy_read_mmd(phydev, 0x1, 0x834);
	
	priv->role = role & BIT(14) ? MASTER : SLAVE;
	priv->ptp_priv = yt8011_ptp_priv(phydev);
	if (IS_ERR(priv->ptp_priv))
		return PTR_ERR(priv->ptp_priv);
	return 0;
}
static int yt8011_soft_reset_paged(struct phy_device *phydev, int page)
{
	int ret = 0, oldpage;
	oldpage = yt8011_select_page(phydev, page);
	if (oldpage >= 0)
		ret = __ytphy_soft_reset(phydev);
	return yt8011_restore_page(phydev, oldpage, ret);
}
static int yt8011_soft_reset(struct phy_device *phydev)
{
	struct yt8xxx_priv *priv = phydev->priv;
	/* utp */
	yt8011_soft_reset_paged(phydev, YT8011_REG_SPACE_UTP);
	if (priv->chip_mode) {	/* sgmii */
		yt8011_soft_reset_paged(phydev, YT8011_REG_SPACE_SDS);
	}
	return 0;
}
static int yt8011_config_aneg(struct phy_device *phydev)
{
	return 0;
}
#if (KERNEL_VERSION(3, 14, 79) < LINUX_VERSION_CODE)
static int yt8011_aneg_done(struct phy_device *phydev)
{
	int link_utp = 0;
	/* UTP */
	ytphy_write_ext(phydev, 0x9000, 0);
	link_utp = !!(phy_read(phydev, REG_PHY_SPEC_STATUS) &
			(BIT(YTXXXX_LINK_STATUS_BIT)));
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
	netdev_info(phydev->attached_dev, "%s, phy addr: %d, link_utp: %d\n",
		    __func__, phydev->addr, link_utp);
#else
	netdev_info(phydev->attached_dev, "%s, phy addr: %d, link_utp: %d\n",
		    __func__, phydev->mdio.addr, link_utp);
#endif
	return !!(link_utp);
}
#endif
static int yt8011_config_paged(struct phy_device *phydev, int page)
{
	int ret = 0, oldpage;
	oldpage = yt8011_select_page(phydev, page);
	if (oldpage < 0)
		goto err_restore_page;
	ret = __ytphy_write_ext(phydev, 0x1008, 0x2119);
	if (ret < 0)
		goto err_restore_page;
	ret = __ytphy_write_ext(phydev, 0x1092, 0x712);
	if (ret < 0)
		goto err_restore_page;
	ret = __ytphy_write_ext(phydev, 0x90bc, 0x7676);
	if (ret < 0)
		goto err_restore_page;
	ret = __ytphy_write_ext(phydev, 0x90b9, 0x620b);
	if (ret < 0)
		goto err_restore_page;
	ret = __ytphy_write_ext(phydev, 0x2001, 0x6418);
	if (ret < 0)
		goto err_restore_page;
	ret = __ytphy_write_ext(phydev, 0x1019, 0x3712);
	if (ret < 0)
		goto err_restore_page;
	ret = __ytphy_write_ext(phydev, 0x101a, 0x3713);
	if (ret < 0)
		goto err_restore_page;
	ret = __ytphy_write_ext(phydev, 0x2005, 0x810);
	if (ret < 0)
		goto err_restore_page;
	ret = __ytphy_write_ext(phydev, 0x2015, 0x1012);
	if (ret < 0)
		goto err_restore_page;
	ret = __ytphy_write_ext(phydev, 0x2013, 0xff06);
	if (ret < 0)
		goto err_restore_page;
	ret = __ytphy_write_ext(phydev, 0x3017, 0x4);
	if (ret < 0)
		goto err_restore_page;
	ret = __ytphy_write_ext(phydev, 0x3027, 0xffe8);
	if (ret < 0)
		goto err_restore_page;
	ret = __ytphy_write_ext(phydev, 0x3026, 0x1b58);
	if (ret < 0)
		goto err_restore_page;
	ret = __ytphy_write_ext(phydev, 0x301e, 0xb40);
	if (ret < 0)
		goto err_restore_page;
	ret = __ytphy_write_ext(phydev, 0x3019, 0xffd4);
	if (ret < 0)
		goto err_restore_page;
	ret = __ytphy_write_ext(phydev, 0x3014, 0x1115);
	if (ret < 0)
		goto err_restore_page;
	ret = __ytphy_write_ext(phydev, 0x301a, 0x7800);
	if (ret < 0)
		goto err_restore_page;
	ret = __ytphy_write_ext(phydev, 0x1000, 0x28);
	if (ret < 0)
		goto err_restore_page;
	ret = __ytphy_write_ext(phydev, 0x1053, 0xf);
	if (ret < 0)
		goto err_restore_page;
	ret = __ytphy_write_ext(phydev, 0x105e, 0xa46c);
	if (ret < 0)
		goto err_restore_page;
	ret = __ytphy_write_ext(phydev, 0x1088, 0x2b);
	if (ret < 0)
		goto err_restore_page;
	ret = __ytphy_write_ext(phydev, 0x1088, 0x2b);
	if (ret < 0)
		goto err_restore_page;
	ret = __ytphy_write_ext(phydev, 0x1088, 0xb);
	if (ret < 0)
		goto err_restore_page;
	ret = __ytphy_write_ext(phydev, 0x3008, 0x141);
	if (ret < 0)
		goto err_restore_page;
	ret = __ytphy_write_ext(phydev, 0x3009, 0x1918);
	if (ret < 0)
		goto err_restore_page;
	ret = __ytphy_write_ext(phydev, 0x9095, 0x1a1a);
	if (ret < 0)
		goto err_restore_page;
	ret = __ytphy_write_ext(phydev, 0x9096, 0x1a10);
	if (ret < 0)
		goto err_restore_page;
	ret = __ytphy_write_ext(phydev, 0x9097, 0x101a);
	if (ret < 0)
		goto err_restore_page;
	ret = __ytphy_write_ext(phydev, 0x9098, 0x01ff);
	if (ret < 0)
		goto err_restore_page;
err_restore_page:
	return yt8011_restore_page(phydev, oldpage, ret);
}
__attribute__((unused))
static int yt8011_config_rgmii_dvddio_3v3_paged(struct phy_device *phydev, int page)
{
	int ret = 0, oldpage;
	oldpage = yt8011_select_page(phydev, page);
	if (oldpage < 0)
		goto err_restore_page;
	if (page == YT8011_REG_SPACE_SDS) {
		ret = __ytphy_write_ext(phydev, 0x0062, 0x0000);
		if (ret < 0)
			goto err_restore_page;
	} else if (page == YT8011_REG_SPACE_UTP) {
		ret = __ytphy_write_ext(phydev, 0x9031, 0xb200);
		if (ret < 0)
			goto err_restore_page;
		ret = __ytphy_write_ext(phydev, 0x903b, 0x0040);
		if (ret < 0)
			goto err_restore_page;
		ret = __ytphy_write_ext(phydev, 0x903e, 0x3b3b);
		if (ret < 0)
			goto err_restore_page;
		ret = __ytphy_write_ext(phydev, 0x903c, 0xf);
		if (ret < 0)
			goto err_restore_page;
		ret = __ytphy_write_ext(phydev, 0x903d, 0x1000);
		if (ret < 0)
			goto err_restore_page;
		ret = __ytphy_write_ext(phydev, 0x9038, 0x0000);
		if (ret < 0)
			goto err_restore_page;
	}
err_restore_page:
	return yt8011_restore_page(phydev, oldpage, ret);
}
__attribute__((unused))
static int yt8011_config_rgmii_dvddio_2v5_paged(struct phy_device *phydev, int page)
{
	int ret = 0, oldpage;
	oldpage = yt8011_select_page(phydev, page);
	if (oldpage < 0)
		goto err_restore_page;
	if (page == YT8011_REG_SPACE_SDS) {
		ret = __ytphy_write_ext(phydev, 0x0062, 0x0000);
		if (ret < 0)
			goto err_restore_page;
	} else if (page == YT8011_REG_SPACE_UTP) {
		ret = __ytphy_write_ext(phydev, 0x9031, 0xb200);
		if (ret < 0)
			goto err_restore_page;
		ret = __ytphy_write_ext(phydev, 0x9111, 0x5);
		if (ret < 0)
			goto err_restore_page;
		ret = __ytphy_write_ext(phydev, 0x9114, 0x3939);
		if (ret < 0)
			goto err_restore_page;
		ret = __ytphy_write_ext(phydev, 0x9112, 0xf);
		if (ret < 0)
			goto err_restore_page;
		ret = __ytphy_write_ext(phydev, 0x9110, 0x0);
		if (ret < 0)
			goto err_restore_page;
		ret = __ytphy_write_ext(phydev, 0x9113, 0x10);
		if (ret < 0)
			goto err_restore_page;
		ret = __ytphy_write_ext(phydev, 0x903d, 0x2);
		if (ret < 0)
			goto err_restore_page;
	}
err_restore_page:
	return yt8011_restore_page(phydev, oldpage, ret);
}
__attribute__((unused))
static int yt8011_config_rgmii_dvddio_1v8_paged(struct phy_device *phydev, int page)
{
	int ret = 0, oldpage;
	oldpage = yt8011_select_page(phydev, page);
	if (oldpage < 0)
		goto err_restore_page;
	if (page == YT8011_REG_SPACE_SDS) {
		ret = __ytphy_write_ext(phydev, 0x0062, 0x0000);
		if (ret < 0)
			goto err_restore_page;
	} else if (page == YT8011_REG_SPACE_UTP) {
		ret = __ytphy_write_ext(phydev, 0x9031, 0xb200);
		if (ret < 0)
			goto err_restore_page;
		ret = __ytphy_write_ext(phydev, 0x9116, 0x6);
		if (ret < 0)
			goto err_restore_page;
		ret = __ytphy_write_ext(phydev, 0x9119, 0x3939);
		if (ret < 0)
			goto err_restore_page;
		ret = __ytphy_write_ext(phydev, 0x9117, 0xf);
		if (ret < 0)
			goto err_restore_page;
		ret = __ytphy_write_ext(phydev, 0x9115, 0x0);
		if (ret < 0)
			goto err_restore_page;
		ret = __ytphy_write_ext(phydev, 0x9118, 0x20);
		if (ret < 0)
			goto err_restore_page;
		ret = __ytphy_write_ext(phydev, 0x903d, 0x3);
		if (ret < 0)
			goto err_restore_page;
	}
err_restore_page:
	return yt8011_restore_page(phydev, oldpage, ret);
}
/* #define YT801X_RGMII_DVDDIO_3V3 */
/* #define YT801X_RGMII_DVDDIO_2V5 */
/* #define YT801X_RGMII_DVDDIO_1V8 */
static int yt8011_config_init(struct phy_device *phydev)
{
	struct yt8xxx_priv *priv = phydev->priv;
	int ret;
	phydev->autoneg = AUTONEG_DISABLE;
	ret = yt8011_config_paged(phydev, YT8011_REG_SPACE_UTP);
	if (ret < 0)
		return ret;
	if (!(priv->chip_mode)) {	/* rgmii config */
#if defined (YT801X_RGMII_DVDDIO_3V3)
		ret = yt8011_config_rgmii_dvddio_3v3_paged(phydev,
							   YT8011_REG_SPACE_SDS);
		if (ret < 0)
			return ret;
		ret = yt8011_config_rgmii_dvddio_3v3_paged(phydev,
							   YT8011_REG_SPACE_UTP);
		if (ret < 0)
			return ret;
#elif defined (YT801X_RGMII_DVDDIO_2V5)
		ret = yt8011_config_rgmii_dvddio_2v5_paged(phydev,
							   YT8011_REG_SPACE_SDS);
		if (ret < 0)
			return ret;
		ret = yt8011_config_rgmii_dvddio_2v5_paged(phydev,
							   YT8011_REG_SPACE_UTP);
		if (ret < 0)
			return ret;
#elif defined (YT801X_RGMII_DVDDIO_1V8)
		ret = yt8011_config_rgmii_dvddio_1v8_paged(phydev,
							   YT8011_REG_SPACE_SDS);
		if (ret < 0)
			return ret;
		ret = yt8011_config_rgmii_dvddio_1v8_paged(phydev,
							   YT8011_REG_SPACE_UTP);
		if (ret < 0)
			return ret;
#endif
	}
	if (phydev->drv->txtstamp)
		dbg_ptp("@jie.han %s, %s, %d phydev->drv->txtstamp registed.\n",
		__FILE__, __func__, __LINE__);
	else
		dbg_ptp("@jie.han %s, %s, %d phydev->drv->txtstamp not registed.\n",
		__FILE__, __func__, __LINE__);
	if (phydev->drv->rxtstamp)
		dbg_ptp("@jie.han %s, %s, %d phydev->drv->rxtstamp registed.\n",
		__FILE__, __func__, __LINE__);
	else
		dbg_ptp("@jie.han %s, %s, %d phydev->drv->rxtstamp not registed.\n",
		__FILE__, __func__, __LINE__);
	yt8011_soft_reset(phydev);
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
	netdev_info(phydev->attached_dev, "%s done, phy addr: %d\n",
		    __func__, phydev->addr);
#else
	netdev_info(phydev->attached_dev, "%s done, phy addr: %d\n",
		    __func__, phydev->mdio.addr);
#endif
	return 0;
}
static int ytxxxx_automotive_adjust_status(struct phy_device *phydev, int val)
{
	int speed_mode;
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
	int speed = -1;
#else
	int speed = SPEED_UNKNOWN;
#endif
	speed_mode = (val & YTXXXX_SPEED_MODE) >> YTXXXX_SPEED_MODE_BIT;
	switch (speed_mode) {
	case 1:
		speed = SPEED_100;
		break;
	case 2:
		speed = SPEED_1000;
		break;
	default:
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
		speed = -1;
#else
		speed = SPEED_UNKNOWN;
#endif
		break;
	}
	phydev->speed = speed;
	phydev->duplex = DUPLEX_FULL;
	return 0;
}
static int yt8011_read_status(struct phy_device *phydev)
{
	int ret;
	int val;
	int link;
	int link_utp = 0;
	/* UTP */
	ret = ytphy_write_ext(phydev, 0x9000, 0x0);
	if (ret < 0)
		return ret;
	val = phy_read(phydev, REG_PHY_SPEC_STATUS);
	if (val < 0)
		return val;
	link = val & (BIT(YTXXXX_LINK_STATUS_BIT));
	if (link) {
		link_utp = 1;
		ytxxxx_automotive_adjust_status(phydev, val);
	} else {
		link_utp = 0;
	}
	if (link_utp) {
		if (phydev->link == 0)
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
			netdev_info(phydev->attached_dev,
				    "%s, phy addr: %d, link up, media: UTP, mii reg 0x11 = 0x%x\n",
				    __func__, phydev->addr, (unsigned int)val);
#else
			netdev_info(phydev->attached_dev,
				    "%s, phy addr: %d, link up, media: UTP, mii reg 0x11 = 0x%x\n",
				    __func__, phydev->mdio.addr,
				    (unsigned int)val);
#endif
		phydev->link = 1;
	} else {
		if (phydev->link == 1)
#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE)
			netdev_info(phydev->attached_dev,
				    "%s, phy addr: %d, link down\n",
				    __func__, phydev->addr);
#else
			netdev_info(phydev->attached_dev,
				    "%s, phy addr: %d, link down\n",
				    __func__, phydev->mdio.addr);
#endif
		phydev->link = 0;
	}
	if (link_utp)
		ytphy_write_ext(phydev, 0x9000, 0x0);
	return 0;
}
#if (KERNEL_VERSION(5, 6, 0) > LINUX_VERSION_CODE)
static int yt8011_ptp_hwtstamp(struct phy_device *phydev, struct ifreq *ifr)
{
	struct yt8xxx_priv *priv = phydev->priv;
	struct yt_ptp_private *ptp_priv = priv->ptp_priv;
	struct hwtstamp_config cfg;
	u16 cfg0 = 0, cfg1 = 0;
	//u16 role;
	//dbg_ptp("@jie.han %s, %s, %d\n", __FILE__, __func__, __LINE__);
	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_SYNC)
		return -ERANGE;
	ptp_priv->tx_type = cfg.tx_type;
	dbg_ptp("@jie.han %s, %s, %d cfg.rx_filter: %s\n",
		__FILE__, __func__, __LINE__,
		cfg.rx_filter == HWTSTAMP_FILTER_PTP_V2_EVENT ? "HWTSTAMP_FILTER_PTP_V2_EVENT" : "other");
	switch (cfg.rx_filter) {
	case HWTSTAMP_FILTER_NONE:
	case HWTSTAMP_FILTER_PTP_V1_L4_EVENT:
	case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
	case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
		ptp_priv->hwts_rx = 0;
		ptp_priv->layer = 0;
		ptp_priv->version = 0;
		break;
	case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
	case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
	case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
		ptp_priv->hwts_rx = 1;
		ptp_priv->layer = PTP_CLASS_L4;
		ptp_priv->version = PTP_CLASS_V2;
		break;
	case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
	case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
	case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
		//YT8011 default 802.1as enable
		ptp_priv->hwts_rx = 1;
		ptp_priv->layer = PTP_CLASS_L2;
		ptp_priv->version = PTP_CLASS_V2;
		break;
	case HWTSTAMP_FILTER_PTP_V2_EVENT:
	case HWTSTAMP_FILTER_PTP_V2_SYNC:
	case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
		ptp_priv->hwts_rx = 1;
		ptp_priv->layer = PTP_CLASS_L4 | PTP_CLASS_L2;
		ptp_priv->version = PTP_CLASS_V2;
		break;
	default:
		return -ERANGE;
	}
	cfg0 |= OC_2_STEPS;
	dbg_ptp("@jie.han %s, %s, %d role: %s\n",
		__FILE__, __func__, __LINE__,
		priv->role == MASTER ? "MASTER" : "SLAVE");
	if (priv->role == MASTER)
		cfg0 |= (DAC_CLK << 2);
	else
		cfg0 |= (ADC_CLK_AFTER_ROTATOR << 2);
	/* ext reg 0x8000 bit4 BP_1588
	 * 1'b0 = PTP timestamp engine normal operation as default.
	 * 1'b1 = Bypass IEEE1588v2 related functions.
	 */
	cfg0 &= ~BIT(4);
	
	/* ext reg 0x8000 bit7 EN_GATE_1588
	 * 1'b0 = Disable gating 1588 clock domain.
	 * 1'b1 = Enable gating 1588 clock domain as default.
	 */
	cfg0 &= ~BIT(7);
	/* ext reg 0x8001 bit4 PTP_MODE_SEL
	 * 1'b1: PTP mode
	 * 1'b0: gPTP mode(802.1AS) as default
	 */
	if (ptp_priv->layer & PTP_CLASS_L2)
		cfg1 &= ~BIT(4);
	mutex_lock(&ptp_priv->mutex);
	yt8011_ptp_init_paged(phydev, cfg0, cfg1, YT8011_REG_SPACE_UTP);
	mutex_unlock(&ptp_priv->mutex);
	return copy_to_user(ifr->ifr_data, &cfg, sizeof(cfg)) ? -EFAULT : 0;
}
static int parsing_ptp_msgtype_seqid(struct sk_buff *skb,
				     u8 *msgtype, u16 *seqid, int type)
{
	u8 *data = skb_mac_header(skb);
	unsigned int offset = 0;
	u8 *msg_type;
	//u16 *seq_id;
	//dbg_ptp("@jie.han %s, %s, %d\n", __FILE__, __func__, __LINE__);
	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 0;
	}
	if (skb->len + ETH_HLEN < offset + OFF_PTP_SEQUENCE_ID + sizeof(*seqid))
		return 0;
	if (unlikely(type & PTP_CLASS_V1)) {
		if (offset + OFF_PTP_CONTROL >= skb->len)
			return 0;
		msg_type = data + offset + OFF_PTP_CONTROL;
	} else {
		if (offset >= skb->len)
			return 0;
		msg_type = data + offset;
	}
	//memcpy(msgtype, msg_type, sizeof(*msgtype));
	*msgtype = (*msg_type) & 0xf;
	//seq_id = (u16 *)(data + offset + OFF_PTP_SEQUENCE_ID);
	//memcpy(seqid, seq_id, sizeof(*seqid));
	//*seq_id = ntohs(*(u16 *)(data + offset + OFF_PTP_SEQUENCE_ID));
	*seqid = ntohs(*(u16 *)(data + offset + OFF_PTP_SEQUENCE_ID));
	dbg_ptp("@jie.han %s, %s, %d msgtype: %s, seq_id: %d\n",
		__FILE__, __func__, __LINE__,
		*msgtype == SYNC ? "SYNC" :
		*msgtype == DELAY_REQ ? "DELAY_REQ" :
		*msgtype == PDELAY_REQ ? "PDELAY_REQ" :
		*msgtype == PDELAY_RESP ? "PDELAY_RESP" :
		*msgtype == FOLLOW_UP ? "FOLLOW_UP" :
		*msgtype == DELAY_RESP ? "DELAY_RESP" :
		*msgtype == PDELAY_RESP_FOLLOW_UP ? "PDELAY_RESP_FOLLOW_UP" :
		*msgtype == ANNOUNCE ? "ANNOUNCE" :
		*msgtype == SIGNALING ? "SIGNALING" :
		*msgtype == MANAGEMENT ? "MANAGEMENT" : "others", *seqid);
	return 1;
}
static bool yt8011_ptp_rxtstamp(struct phy_device *phydev,
				struct sk_buff *skb, int type)
{
	struct skb_shared_hwtstamps *shhwtstamps;
	struct yt8xxx_priv *priv = phydev->priv;
	u16 sequence_id;
	u32 sec = 0, nsec = 0;
	u8 msg_type;
	int ret;
	dbg_ptp("@jie.han %s, %s, %d\n", __FILE__, __func__, __LINE__);
	if (!priv->ptp_priv->hwts_rx)
		return false;
	if ((type & priv->ptp_priv->version) == 0 ||
		(type & priv->ptp_priv->layer) == 0)
		return false;
	dbg_ptp("@jie.han %s, %s, %d call parsing_ptp_msgtype_seqid()\n",
		__FILE__, __func__, __LINE__);
	parsing_ptp_msgtype_seqid(skb, &msg_type, &sequence_id, type);
	dbg_ptp("@jie.han %s, %s, %d after parsing_ptp_msgtype_seqid() "
		"msg_type: %s, sequence_id: %d\n",
		__FILE__, __func__, __LINE__,
		msg_type == SYNC ? "SYNC" :
		msg_type == DELAY_REQ ? "DELAY_REQ" :
		msg_type == PDELAY_REQ ? "PDELAY_REQ" :
		msg_type == PDELAY_RESP ? "PDELAY_RESP" :
		msg_type == FOLLOW_UP ? "FOLLOW_UP" :
		msg_type == DELAY_RESP ? "DELAY_RESP" :
		msg_type == PDELAY_RESP_FOLLOW_UP ? "PDELAY_RESP_FOLLOW_UP" :
		msg_type == ANNOUNCE ? "ANNOUNCE" :
		msg_type == SIGNALING ? "SIGNALING" :
		msg_type == MANAGEMENT ? "MANAGEMENT" : "other", sequence_id);
	dbg_ptp("@jie.han %s, %s, %d call yt8011_ptp_get_rxtstamp_paged()\n",
		__FILE__, __func__, __LINE__);
	ret = yt8011_ptp_get_rxtstamp_paged(phydev, msg_type,
					    sequence_id, &sec, &nsec,
					    YT8011_REG_SPACE_UTP);
	if (ret > 0) {
		shhwtstamps = skb_hwtstamps(skb);
		shhwtstamps->hwtstamp = ktime_set(sec, nsec);
		dbg_ptp("@jie.han %s, %s, %d "
			"shhwtstamps->hwtstamp: %lld then call netif_rx()\n",
			__FILE__, __func__, __LINE__, shhwtstamps->hwtstamp);
		netif_rx(skb);
		return true;
	} else
		return false;
}
#define SKB_TIMESTAMP_TIMEOUT	10 /* jiffies */
static void yt8011_ptp_txtstamp(struct phy_device *phydev,
				struct sk_buff *skb, int type)
{
	struct yt8xxx_priv *priv = phydev->priv;
	struct yt_ptp_private *ptp_priv = priv->ptp_priv;
	u8 msgtype;
	u16 seqid;
#if 0
	dbg_ptp("@jie.han %s, %s, %d ptp_priv->tx_type: %s",
		__FILE__, __func__, __LINE__,
		ptp_priv->tx_type == HWTSTAMP_TX_ON ? "HWTSTAMP_TX_ON" :
		ptp_priv->tx_type == HWTSTAMP_TX_ONESTEP_SYNC ? "HWTSTAMP_TX_ONESTEP_SYNC" :
		ptp_priv->tx_type == HWTSTAMP_TX_OFF ? "HWTSTAMP_TX_OFF" : "others");
#endif
	//parsing_ptp_msgtype_seqid(skb, &msgtype, &seqid, type);
	switch (ptp_priv->tx_type) {
	case HWTSTAMP_TX_ON:
		dbg_ptp("@jie.han %s, %s, %d ptp_priv->tx_type: HWTSTAMP_TX_ON\n",
			__FILE__, __func__, __LINE__);
		dbg_ptp("@jie.han %s, %s, %d skb: %p, skb->sk: %p, "
			"skb->sk->sk_flags: %#lx, tx_flags: 0x%x\n",
			__FILE__, __func__, __LINE__,
			skb, skb->sk, skb->sk->sk_flags, skb_shinfo(skb)->tx_flags);
		if (!skb->sk) {
			kfree_skb(skb);
			return;
		}
		if (!parsing_ptp_msgtype_seqid(skb, &msgtype, &seqid, type)) {
			pr_err("@jie.han %s, %s, %d parsing skb failed, drop skb.\n",
				__FILE__, __func__, __LINE__);
			kfree_skb(skb);
			return;
		}
		skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
		memset(YT_SKB_CB(skb), 0x0, sizeof(struct yt_ptp_skb_cb));
		YT_SKB_CB(skb)->timeout = jiffies + SKB_TIMESTAMP_TIMEOUT;
		YT_SKB_CB(skb)->seq_id = seqid;
		YT_SKB_CB(skb)->msgtype = msgtype;
		//if (skb->sk)
		/* increment the reference count to prevent premature release */
		sock_hold(skb->sk);
		dbg_ptp("@jie.han %s, %s, %d skb->sk->sk_flags: %#lx\n",
			__FILE__, __func__, __LINE__, skb->sk->sk_flags);
		//dbg_ptp("@jie.han %s, %s, %d skb->sk->sk_refcnt: %d\n",
		//	__FILE__, __func__, __LINE__, atomic_read(&skb->sk->sk_refcnt));
		dbg_ptp("@jie.han %s, %s, %d skb->sk->sk_refcnt: %d\n",
			__FILE__, __func__, __LINE__, skb->sk->sk_refcnt.refs.counter);
		skb_queue_tail(&ptp_priv->tx_queue, skb);
		ptp_schedule_worker(priv->ptp_priv->clock, 0);
		break;
	case HWTSTAMP_TX_ONESTEP_SYNC:
		dbg_ptp("@jie.han %s, %s, %d ptp_priv->tx_type: HWTSTAMP_TX_ONESTEP_SYNC\n",
			__FILE__, __func__, __LINE__);
		//__fallthrough;
	case HWTSTAMP_TX_OFF:
	default:
		kfree_skb(skb);
		break;
	}
}
static int yt8011_ptp_ts_info(struct phy_device *phydev,
			      struct ethtool_ts_info *info)
{
	struct yt8xxx_priv *priv = phydev->priv;
	info->so_timestamping =
		SOF_TIMESTAMPING_TX_SOFTWARE |
		SOF_TIMESTAMPING_RX_SOFTWARE |
		SOF_TIMESTAMPING_SOFTWARE |
		SOF_TIMESTAMPING_TX_HARDWARE |
		SOF_TIMESTAMPING_RX_HARDWARE |
		SOF_TIMESTAMPING_RAW_HARDWARE;
	info->phc_index = ptp_clock_index(priv->ptp_priv->clock);
	dbg_ptp("@jie.han %s, %s, %d info->phc_index: %d\n",
		__FILE__, __func__, __LINE__, info->phc_index);
	info->tx_types =
		(1 << HWTSTAMP_TX_OFF) |
		(1 << HWTSTAMP_TX_ON);
	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 void yt8011_remove(struct phy_device *phydev)
{
	struct yt8xxx_priv *priv = phydev->priv;
	struct yt_ptp_private *ptp_priv = priv->ptp_priv;
	dbg_ptp("@jie.han %s, %s, %d\n", __FILE__, __func__, __LINE__);
	if (ptp_priv->clock)
		ptp_clock_unregister(ptp_priv->clock);
	skb_queue_purge(&ptp_priv->tx_queue);
}
#if (KERNEL_VERSION(4, 4, 302) < LINUX_VERSION_CODE)
static int __yt8011_write_page(struct phy_device *phydev, int page)
{
	int ret;
	ret = __ytphy_read_ext(phydev, YT801X_REG_SMI_MUX);
	if (ret < 0)
		return ret;
	if (page == YT8011_REG_SPACE_UTP)
		return __ytphy_write_ext(phydev, YT801X_REG_SMI_MUX,
					 ret & (~BIT(15)));
	else if (page == YT8011_REG_SPACE_SDS)
		return __ytphy_write_ext(phydev, YT801X_REG_SMI_MUX,
					 ret | BIT(15));
	return 0;
}
static int __yt8011_read_page(struct phy_device *phydev)
{
	int ret;
	
	ret = __ytphy_read_ext(phydev, YT801X_REG_SMI_MUX);
	if (ret < 0)
		return ret;
	if (ret & BIT(15))
		return YT8011_REG_SPACE_SDS;
	else
		return YT8011_REG_SPACE_UTP;
}
static int yt8011_save_page(struct phy_device *phydev)
{
	/* mutex_lock(&phydev->mdio.bus->mdio_lock); */
	phy_lock_mdio_bus(phydev);
	
	return __yt8011_read_page(phydev);
}
static int yt8011_select_page(struct phy_device *phydev, int page)
{
	int ret, oldpage;
	oldpage = ret = yt8011_save_page(phydev);
	if (ret < 0)
		return ret;
	if (oldpage != page) {
		ret = __yt8011_write_page(phydev, page);
		if (ret < 0)
			return ret;
	}
	return oldpage;
}
static int yt8011_restore_page(struct phy_device *phydev, int oldpage, int ret)
{
	int r;
	if (oldpage >= 0) {
		r = __yt8011_write_page(phydev, oldpage);
		/* Propagate the operation return code if the page write
		 * was successful.
		 */
		if (ret >= 0 && r < 0)
			ret = r;
	} else {
		/* Propagate the phy page selection error code */
		ret = oldpage;
	}
	/* mutex_unlock(&phydev->mdio.bus->mdio_lock); */
	phy_unlock_mdio_bus(phydev);
	return ret;
}
#endif
static int yt801x_config_intr(struct phy_device *phydev)
{
	int ret;
	ret = ytphy_write_ext(phydev, YT801X_REG_SMI_MUX,
			      YTPHY_REG_SPACE_UTP);
	if (ret < 0)
		return ret;
	if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
		ret = phy_write(phydev, YTPHY_UTP_INTR_REG,
				YTPHY_INTR_LINK_STATUS);
	return ret;
}
#if (KERNEL_VERSION(5, 10, 232) < LINUX_VERSION_CODE)
static irqreturn_t ytphy_handle_interrupt(struct phy_device *phydev)
{
	int ret;
	ret = phy_read(phydev, YTPHY_UTP_INTR_STATUS_REG);
	if (ret > 0) {
		phy_trigger_machine(phydev);
		return IRQ_HANDLED;
	}
	else
		return IRQ_NONE;
}
static irqreturn_t yt801x_handle_interrupt(struct phy_device *phydev)
{
	int ret;
	ret = ytphy_write_ext(phydev, YT801X_REG_SMI_MUX,
			      YTPHY_REG_SPACE_UTP);
	if (ret < 0)
		return IRQ_NONE;
	ret = phy_read(phydev, YTPHY_UTP_INTR_STATUS_REG);
	if (ret > 0) {
		phy_trigger_machine(phydev);
		return IRQ_HANDLED;
	}
	else
		return IRQ_NONE;
}
#else
static int yt801x_ack_interrupt(struct phy_device *phydev)
{
	int ret;
	ret = ytphy_write_ext(phydev, YT801X_REG_SMI_MUX,
			      YTPHY_REG_SPACE_UTP);
	if (ret < 0)
		return ret;
	ret = phy_read(phydev, YTPHY_UTP_INTR_STATUS_REG);
	return ret < 0 ? ret : 0;
}
#endif
static struct phy_driver ytphy_drvs[] = {
	{
		.phy_id		= PHY_ID_YT8011,
		.name		= "YT8011 Automotive Gigabit Ethernet",
		.phy_id_mask	= MOTORCOMM_PHY_ID_MASK,
		.features	= PHY_GBIT_FEATURES,
		.config_intr	= yt801x_config_intr,
#if (KERNEL_VERSION(5, 10, 232) < LINUX_VERSION_CODE)
		.handle_interrupt	= yt801x_handle_interrupt,
#else
		.ack_interrupt	= yt801x_ack_interrupt,
#endif
		.probe		= yt8011_probe,
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
#else
		.soft_reset	= yt8011_soft_reset,
#endif
		.config_aneg	= yt8011_config_aneg,
#if (KERNEL_VERSION(3, 14, 79) < LINUX_VERSION_CODE)
		.aneg_done	= yt8011_aneg_done,
#endif
		.config_init	= yt8011_config_init,
		.read_status	= yt8011_read_status,
#if (KERNEL_VERSION(5, 6, 0) > LINUX_VERSION_CODE)
		.ts_info	= yt8011_ptp_ts_info,
		.hwtstamp	= yt8011_ptp_hwtstamp,
		.rxtstamp	= yt8011_ptp_rxtstamp,
		.txtstamp	= yt8011_ptp_txtstamp,
#endif
		.remove		= yt8011_remove,
		//.suspend		= yt8011_suspend,
	},
};
#if (KERNEL_VERSION(4, 0, 0) > LINUX_VERSION_CODE)
static int ytphy_drivers_register(struct phy_driver *phy_drvs, int size)
{
	int i, j;
	int ret;
	for (i = 0; i < size; i++) {
		ret = phy_driver_register(&phy_drvs[i]);
		if (ret)
			goto err;
	}
	return 0;
err:
	for (j = 0; j < i; j++)
		phy_driver_unregister(&phy_drvs[j]);
	return ret;
}
static void ytphy_drivers_unregister(struct phy_driver *phy_drvs, int size)
{
	int i;
	for (i = 0; i < size; i++)
		phy_driver_unregister(&phy_drvs[i]);
}
static int __init ytphy_init(void)
{
	return ytphy_drivers_register(ytphy_drvs, ARRAY_SIZE(ytphy_drvs));
}
static void __exit ytphy_exit(void)
{
	pr_info(MODULE_NAME ": Module unloaded\n");
	ytphy_drivers_unregister(ytphy_drvs, ARRAY_SIZE(ytphy_drvs));
}
module_init(ytphy_init);
module_exit(ytphy_exit);
#else
/* for linux 4.x ~ */
/* module_phy_driver(ytphy_drvs);*/
static int __init phy_module_init(void)
{
	return phy_drivers_register(ytphy_drvs, ARRAY_SIZE(ytphy_drvs),
				    THIS_MODULE);
}
static void __exit phy_module_exit(void)
{
	pr_info(MODULE_NAME ": Module unloaded\n");
	phy_drivers_unregister(ytphy_drvs, ARRAY_SIZE(ytphy_drvs));
}
module_init(phy_module_init);
module_exit(phy_module_exit);
#endif
MODULE_DESCRIPTION("Motorcomm PHY driver");
MODULE_VERSION(YTPHY_LINUX_VERSION);	/* for modinfo xxxx.ko in userspace */
MODULE_AUTHOR("Jie Han");
MODULE_LICENSE("GPL");
static struct mdio_device_id __maybe_unused motorcomm_tbl[] = {
	{ PHY_ID_YT8011, MOTORCOMM_PHY_ID_MASK },
	{ }
};
MODULE_DEVICE_TABLE(mdio, motorcomm_tbl);
 
				 
		 
					 
				