diff --git a/Documentation/networking/ethtool-netlink.txt b/Documentation/networking/ethtool-netlink.txt
index cffa508ca6c6..74c62f11810c 100644
--- a/Documentation/networking/ethtool-netlink.txt
+++ b/Documentation/networking/ethtool-netlink.txt
@@ -240,6 +240,11 @@ Kernel response contents:
         ETHA_DRVINFO_FWVERSION		(string)	firmware version
         ETHA_DRVINFO_BUSINFO		(string)	device bus address
         ETHA_DRVINFO_EROM_VER		(string)	expansion ROM version
+    ETHA_INFO_TSINFO		(nested)	timestamping information
+        ETHA_TSINFO_TIMESTAMPING	(bitset)	supported flags
+        ETHA_TSINFO_PHC_INDEX		(u32)		associated PHC
+        ETHA_TSINFO_TX_TYPES		(bitset)	Tx types
+        ETHA_TSINFO_RX_FILTERS		(bitset)	Rx filters
 
 The meaning of DRVINFO attributes follows the corresponding fields of
 ETHTOOL_GDRVINFO response. Second part with various counts and sizes is
@@ -247,6 +252,9 @@ omitted as these are not really needed (and if they are, they can be easily
 found by different means). Driver version is also omitted as it is rather
 misleading in most cases.
 
+ETHA_TSINFO_PHC_INDEX can be unsigned as there is no need for special value;
+if no PHC is associated, the attribute is not present.
+
 GET_INFO requests allow dumps.
 
 
@@ -322,7 +330,7 @@ ETHTOOL_SCHANNELS		n/a
 ETHTOOL_SET_DUMP		n/a
 ETHTOOL_GET_DUMP_FLAG		n/a
 ETHTOOL_GET_DUMP_DATA		n/a
-ETHTOOL_GET_TS_INFO		n/a
+ETHTOOL_GET_TS_INFO		ETHNL_CMD_GET_INFO
 ETHTOOL_GMODULEINFO		n/a
 ETHTOOL_GMODULEEEPROM		n/a
 ETHTOOL_GEEE			n/a
diff --git a/include/uapi/linux/ethtool.h b/include/uapi/linux/ethtool.h
index eaea804972f6..882a542eaaa9 100644
--- a/include/uapi/linux/ethtool.h
+++ b/include/uapi/linux/ethtool.h
@@ -563,6 +563,9 @@ struct ethtool_pauseparam {
  * @ETH_SS_RSS_HASH_FUNCS: RSS hush function names
  * @ETH_SS_PHY_STATS: Statistic names, for use with %ETHTOOL_GPHYSTATS
  * @ETH_SS_PHY_TUNABLES: PHY tunable names
+ * @ETH_SS_TSTAMP_SOF: timestamping flag names
+ * @ETH_SS_TSTAMP_TX_TYPE: timestamping Tx type names
+ * @ETH_SS_TSTAMP_RX_FILTER: timestamping Rx filter names
  */
 enum ethtool_stringset {
 	ETH_SS_TEST		= 0,
@@ -574,6 +577,9 @@ enum ethtool_stringset {
 	ETH_SS_TUNABLES,
 	ETH_SS_PHY_STATS,
 	ETH_SS_PHY_TUNABLES,
+	ETH_SS_TSTAMP_SOF,
+	ETH_SS_TSTAMP_TX_TYPE,
+	ETH_SS_TSTAMP_RX_FILTER,
 
 	ETH_SS_COUNT
 };
diff --git a/include/uapi/linux/ethtool_netlink.h b/include/uapi/linux/ethtool_netlink.h
index 386d4273ac44..e7a6e813150b 100644
--- a/include/uapi/linux/ethtool_netlink.h
+++ b/include/uapi/linux/ethtool_netlink.h
@@ -151,14 +151,17 @@ enum {
 	ETHA_INFO_INFOMASK,			/* u32 */
 	ETHA_INFO_COMPACT,			/* flag */
 	ETHA_INFO_DRVINFO,			/* nest - ETHA_DRVINFO_* */
+	ETHA_INFO_TSINFO,			/* nest - ETHA_TSINFO_* */
 
 	__ETHA_INFO_CNT,
 	ETHA_INFO_MAX = (__ETHA_INFO_CNT - 1)
 };
 
 #define ETH_INFO_IM_DRVINFO			(1U << 0)
+#define ETH_INFO_IM_TSINFO			(1U << 1)
 
-#define ETH_INFO_IM_ALL (ETH_INFO_IM_DRVINFO)
+#define ETH_INFO_IM_ALL (ETH_INFO_IM_DRVINFO | \
+			 ETH_INFO_IM_TSINFO)
 
 enum {
 	ETHA_DRVINFO_UNSPEC,
@@ -171,6 +174,17 @@ enum {
 	ETHA_DRVINFO_MAX = (__ETHA_DRVINFO_CNT - 1)
 };
 
+enum {
+	ETHA_TSINFO_UNSPEC,
+	ETHA_TSINFO_TIMESTAMPING,		/* bitset */
+	ETHA_TSINFO_PHC_INDEX,			/* u32 */
+	ETHA_TSINFO_TX_TYPES,			/* bitset */
+	ETHA_TSINFO_RX_FILTERS,			/* bitset */
+
+	__ETHA_TSINFO_CNT,
+	ETHA_TSINFO_MAX = (__ETHA_TSINFO_CNT - 1)
+};
+
 /* generic netlink info */
 #define ETHTOOL_GENL_NAME "ethtool"
 #define ETHTOOL_GENL_VERSION 1
diff --git a/include/uapi/linux/net_tstamp.h b/include/uapi/linux/net_tstamp.h
index e5b39721c6e4..e5b0472c4416 100644
--- a/include/uapi/linux/net_tstamp.h
+++ b/include/uapi/linux/net_tstamp.h
@@ -30,6 +30,9 @@ enum {
 	SOF_TIMESTAMPING_OPT_STATS = (1<<12),
 	SOF_TIMESTAMPING_OPT_PKTINFO = (1<<13),
 	SOF_TIMESTAMPING_OPT_TX_SWHW = (1<<14),
+	/* when adding a flag, please update so_timestamping_labels array
+	 * in net/ethtool/info.c
+	 */
 
 	SOF_TIMESTAMPING_LAST = SOF_TIMESTAMPING_OPT_TX_SWHW,
 	SOF_TIMESTAMPING_MASK = (SOF_TIMESTAMPING_LAST - 1) |
@@ -90,6 +93,11 @@ enum hwtstamp_tx_types {
 	 * queue.
 	 */
 	HWTSTAMP_TX_ONESTEP_SYNC,
+	/* when adding a value, please update tstamp_tx_type_labels array
+	 * in net/ethtool/info.c
+	 */
+
+	HWTSTAMP_TX_LAST = HWTSTAMP_TX_ONESTEP_SYNC
 };
 
 /* possible values for hwtstamp_config->rx_filter */
@@ -132,6 +140,11 @@ enum hwtstamp_rx_filters {
 
 	/* NTP, UDP, all versions and packet modes */
 	HWTSTAMP_FILTER_NTP_ALL,
+	/* when adding a value, please update tstamp_rx_filter_labels array
+	 * in net/ethtool/info.c
+	 */
+
+	HWTSTAMP_FILTER_LAST = HWTSTAMP_FILTER_NTP_ALL
 };
 
 /* SCM_TIMESTAMPING_PKTINFO control message */
diff --git a/net/ethtool/common.c b/net/ethtool/common.c
index 8da992129321..7e846f4151f9 100644
--- a/net/ethtool/common.c
+++ b/net/ethtool/common.c
@@ -1,6 +1,8 @@
 // SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
 
 #include <linux/rtnetlink.h>
+#include <linux/phy.h>
+#include <linux/net_tstamp.h>
 #include <net/devlink.h>
 #include "common.h"
 
@@ -133,3 +135,25 @@ int __ethtool_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info)
 
 	return 0;
 }
+
+int __ethtool_get_ts_info(struct net_device *dev, struct ethtool_ts_info *info)
+{
+	const struct ethtool_ops *ops = dev->ethtool_ops;
+	struct phy_device *phydev = dev->phydev;
+	int err = 0;
+
+	memset(info, 0, sizeof(*info));
+	info->cmd = ETHTOOL_GET_TS_INFO;
+
+	if (phydev && phydev->drv && phydev->drv->ts_info) {
+		err = phydev->drv->ts_info(phydev, info);
+	} else if (ops->get_ts_info) {
+		err = ops->get_ts_info(dev, info);
+	} else {
+		info->so_timestamping = SOF_TIMESTAMPING_RX_SOFTWARE |
+					SOF_TIMESTAMPING_SOFTWARE;
+		info->phc_index = -1;
+	}
+
+	return err;
+}
diff --git a/net/ethtool/common.h b/net/ethtool/common.h
index e87e58b3a274..02cbee79da35 100644
--- a/net/ethtool/common.h
+++ b/net/ethtool/common.h
@@ -16,4 +16,6 @@ extern const char
 phy_tunable_strings[__ETHTOOL_PHY_TUNABLE_COUNT][ETH_GSTRING_LEN];
 
 int __ethtool_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info);
+int __ethtool_get_ts_info(struct net_device *dev, struct ethtool_ts_info *info);
+
 #endif /* _ETHTOOL_COMMON_H */
diff --git a/net/ethtool/info.c b/net/ethtool/info.c
index cc42993bb05b..68de78533ec7 100644
--- a/net/ethtool/info.c
+++ b/net/ethtool/info.c
@@ -1,15 +1,61 @@
 // SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
 
+#include <linux/net_tstamp.h>
+
 #include "netlink.h"
 #include "common.h"
 #include "bitset.h"
 
+const char *const so_timestamping_labels[] = {
+	"hardware-transmit",		/* SOF_TIMESTAMPING_TX_HARDWARE */
+	"software-transmit",		/* SOF_TIMESTAMPING_TX_SOFTWARE */
+	"hardware-receive",		/* SOF_TIMESTAMPING_RX_HARDWARE */
+	"software-receive",		/* SOF_TIMESTAMPING_RX_SOFTWARE */
+	"software-system-clock",	/* SOF_TIMESTAMPING_SOFTWARE */
+	"hardware-legacy-clock",	/* SOF_TIMESTAMPING_SYS_HARDWARE */
+	"hardware-raw-clock",		/* SOF_TIMESTAMPING_RAW_HARDWARE */
+	"option-id",			/* SOF_TIMESTAMPING_OPT_ID */
+	"sched-transmit",		/* SOF_TIMESTAMPING_TX_SCHED */
+	"ack-transmit",			/* SOF_TIMESTAMPING_TX_ACK */
+	"option-cmsg",			/* SOF_TIMESTAMPING_OPT_CMSG */
+	"option-tsonly",		/* SOF_TIMESTAMPING_OPT_TSONLY */
+	"option-stats",			/* SOF_TIMESTAMPING_OPT_STATS */
+	"option-pktinfo",		/* SOF_TIMESTAMPING_OPT_PKTINFO */
+	"option-tx-swhw",		/* SOF_TIMESTAMPING_OPT_TX_SWHW */
+};
+
+const char *const tstamp_tx_type_labels[] = {
+	[HWTSTAMP_TX_OFF]		= "off",
+	[HWTSTAMP_TX_ON]		= "on",
+	[HWTSTAMP_TX_ONESTEP_SYNC]	= "one-step-sync",
+};
+
+const char *const tstamp_rx_filter_labels[] = {
+	[HWTSTAMP_FILTER_NONE]			= "none",
+	[HWTSTAMP_FILTER_ALL]			= "all",
+	[HWTSTAMP_FILTER_SOME]			= "some",
+	[HWTSTAMP_FILTER_PTP_V1_L4_EVENT]	= "ptpv1-l4-event",
+	[HWTSTAMP_FILTER_PTP_V1_L4_SYNC]	= "ptpv1-l4-sync",
+	[HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ]	= "ptpv1-l4-delay-req",
+	[HWTSTAMP_FILTER_PTP_V2_L4_EVENT]	= "ptpv2-l4-event",
+	[HWTSTAMP_FILTER_PTP_V2_L4_SYNC]	= "ptpv2-l4-sync",
+	[HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ]	= "ptpv2-l4-delay-req",
+	[HWTSTAMP_FILTER_PTP_V2_L2_EVENT]	= "ptpv2-l2-event",
+	[HWTSTAMP_FILTER_PTP_V2_L2_SYNC]	= "ptpv2-l2-sync",
+	[HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ]	= "ptpv2-l2-delay-req",
+	[HWTSTAMP_FILTER_PTP_V2_EVENT]		= "ptpv2-event",
+	[HWTSTAMP_FILTER_PTP_V2_SYNC]		= "ptpv2-sync",
+	[HWTSTAMP_FILTER_PTP_V2_DELAY_REQ]	= "ptpv2-delay-req",
+	[HWTSTAMP_FILTER_NTP_ALL]		= "ntp-all",
+};
+
 struct info_data {
 	struct common_req_info		reqinfo_base;
 
 	/* everything below here will be reset for each device in dumps */
 	struct common_reply_data	repdata_base;
 	struct ethtool_drvinfo		drvinfo;
+	struct ethtool_ts_info		tsinfo;
 };
 
 static const struct nla_policy get_info_policy[ETHA_INFO_MAX + 1] = {
@@ -18,6 +64,7 @@ static const struct nla_policy get_info_policy[ETHA_INFO_MAX + 1] = {
 	[ETHA_INFO_INFOMASK]		= { .type = NLA_U32 },
 	[ETHA_INFO_COMPACT]		= { .type = NLA_FLAG },
 	[ETHA_INFO_DRVINFO]		= { .type = NLA_REJECT },
+	[ETHA_INFO_TSINFO]		= { .type = NLA_REJECT },
 };
 
 /* parse_request() handler */
@@ -67,6 +114,11 @@ static int prepare_info(struct common_req_info *req_info,
 		if (ret < 0)
 			req_mask &= ~ETH_INFO_IM_DRVINFO;
 	}
+	if (req_mask & ETH_INFO_IM_TSINFO) {
+		ret = __ethtool_get_ts_info(dev, &data->tsinfo);
+		if (ret < 0)
+			req_mask &= ~ETH_INFO_IM_TSINFO;
+	}
 	ethnl_after_ops(dev);
 
 	data->repdata_base.info_mask = req_mask;
@@ -87,6 +139,42 @@ static int drvinfo_size(const struct ethtool_drvinfo *drvinfo)
 	return nla_total_size(len);
 }
 
+static int tsinfo_size(const struct ethtool_ts_info *tsinfo, bool compact)
+{
+	const unsigned int flags = compact ? ETHNL_BITSET_COMPACT : 0;
+	int len = 0;
+	int ret;
+
+	/* if any of these exceeds 32, we need a different interface to talk to
+	 * NIC drivers anyway
+	 */
+	BUILD_BUG_ON(__SOF_TIMESTAMPING_COUNT > 32);
+	BUILD_BUG_ON(__HWTSTAMP_TX_COUNT > 32);
+	BUILD_BUG_ON(__HWTSTAMP_FILTER_COUNT > 32);
+
+	ret = ethnl_bitset32_size(__SOF_TIMESTAMPING_COUNT,
+				  &tsinfo->so_timestamping, NULL,
+				  so_timestamping_labels, flags);
+	if (ret < 0)
+		return ret;
+	len += ret;
+	ret = ethnl_bitset32_size(__HWTSTAMP_TX_COUNT,
+				  &tsinfo->tx_types, NULL,
+				  tstamp_tx_type_labels, flags);
+	if (ret < 0)
+		return ret;
+	len += ret;
+	ret = ethnl_bitset32_size(__HWTSTAMP_FILTER_COUNT,
+				  &tsinfo->rx_filters, NULL,
+				  tstamp_rx_filter_labels, flags);
+	if (ret < 0)
+		return ret;
+	len += ret;
+	len += nla_total_size(sizeof(u32));
+
+	return nla_total_size(len);
+}
+
 /* reply_size() handler */
 static int info_size(const struct common_req_info *req_info)
 {
@@ -98,6 +186,13 @@ static int info_size(const struct common_req_info *req_info)
 	len += dev_ident_size();
 	if (info_mask & ETH_INFO_IM_DRVINFO)
 		len += drvinfo_size(&data->drvinfo);
+	if (info_mask & ETH_INFO_IM_TSINFO) {
+		int ret = tsinfo_size(&data->tsinfo, req_info->compact);
+
+		if (ret < 0)
+			return ret;
+		len += ret;
+	}
 
 	return len;
 }
@@ -126,6 +221,44 @@ static int fill_drvinfo(struct sk_buff *skb,
 	return ret;
 }
 
+static int fill_tsinfo(struct sk_buff *skb,
+		       const struct ethtool_ts_info *tsinfo, bool compact)
+{
+	const unsigned int flags = compact ? ETHNL_BITSET_COMPACT : 0;
+	struct nlattr *nest = ethnl_nest_start(skb, ETHA_INFO_TSINFO);
+	int ret;
+
+	if (!nest)
+		return -EMSGSIZE;
+	ret = ethnl_put_bitset32(skb, ETHA_TSINFO_TIMESTAMPING,
+				 __SOF_TIMESTAMPING_COUNT,
+				 &tsinfo->so_timestamping, NULL,
+				 so_timestamping_labels, flags);
+	if (ret < 0)
+		goto err;
+	ret = -EMSGSIZE;
+	if (tsinfo->phc_index >= 0 &&
+	    nla_put_u32(skb, ETHA_TSINFO_PHC_INDEX, tsinfo->phc_index))
+		goto err;
+
+	ret = ethnl_put_bitset32(skb, ETHA_TSINFO_TX_TYPES, __HWTSTAMP_TX_COUNT,
+				 &tsinfo->tx_types, NULL, tstamp_tx_type_labels,
+				 flags);
+	if (ret < 0)
+		goto err;
+	ret = ethnl_put_bitset32(skb, ETHA_TSINFO_RX_FILTERS,
+				 __HWTSTAMP_FILTER_COUNT, &tsinfo->rx_filters,
+				 NULL, tstamp_rx_filter_labels, flags);
+	if (ret < 0)
+		goto err;
+
+	nla_nest_end(skb, nest);
+	return 0;
+err:
+	nla_nest_cancel(skb, nest);
+	return ret;
+}
+
 /* fill_reply() handler */
 static int fill_info(struct sk_buff *skb,
 		     const struct common_req_info *req_info)
@@ -140,6 +273,11 @@ static int fill_info(struct sk_buff *skb,
 		if (ret < 0)
 			return ret;
 	}
+	if (info_mask & ETH_INFO_IM_TSINFO) {
+		ret = fill_tsinfo(skb, &data->tsinfo, req_info->compact);
+		if (ret < 0)
+			return ret;
+	}
 
 	return 0;
 }
diff --git a/net/ethtool/ioctl.c b/net/ethtool/ioctl.c
index 844a4f4b1552..18721b5aa353 100644
--- a/net/ethtool/ioctl.c
+++ b/net/ethtool/ioctl.c
@@ -2027,28 +2027,12 @@ static int ethtool_get_dump_data(struct net_device *dev,
 
 static int ethtool_get_ts_info(struct net_device *dev, void __user *useraddr)
 {
-	int err = 0;
+	int err;
 	struct ethtool_ts_info info;
-	const struct ethtool_ops *ops = dev->ethtool_ops;
-	struct phy_device *phydev = dev->phydev;
-
-	memset(&info, 0, sizeof(info));
-	info.cmd = ETHTOOL_GET_TS_INFO;
-
-	if (phydev && phydev->drv && phydev->drv->ts_info) {
-		err = phydev->drv->ts_info(phydev, &info);
-	} else if (ops->get_ts_info) {
-		err = ops->get_ts_info(dev, &info);
-	} else {
-		info.so_timestamping =
-			SOF_TIMESTAMPING_RX_SOFTWARE |
-			SOF_TIMESTAMPING_SOFTWARE;
-		info.phc_index = -1;
-	}
 
+	err = __ethtool_get_ts_info(dev, &info);
 	if (err)
 		return err;
-
 	if (copy_to_user(useraddr, &info, sizeof(info)))
 		err = -EFAULT;
 
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index 48980ae9e963..4ad9a3eee3e5 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -7,14 +7,23 @@
 #include <linux/netdevice.h>
 #include <net/genetlink.h>
 #include <net/sock.h>
+#include <linux/net_tstamp.h>
 
 #define ETHNL_SET_ERRMSG(info, msg) \
 	do { if (info) GENL_SET_ERR_MSG(info, msg); } while (0)
 
+#define __SOF_TIMESTAMPING_COUNT (const_ilog2(SOF_TIMESTAMPING_LAST) + 1)
+#define __HWTSTAMP_TX_COUNT (HWTSTAMP_TX_LAST + 1)
+#define __HWTSTAMP_FILTER_COUNT (HWTSTAMP_FILTER_LAST + 1)
+
 extern u32 ethnl_bcast_seq;
 
 extern struct genl_family ethtool_genl_family;
 
+extern const char *const so_timestamping_labels[];
+extern const char *const tstamp_tx_type_labels[];
+extern const char *const tstamp_rx_filter_labels[];
+
 struct net_device *ethnl_dev_get(struct genl_info *info, struct nlattr *nest);
 int ethnl_fill_dev(struct sk_buff *msg, struct net_device *dev, u16 attrtype);
 
diff --git a/net/ethtool/strset.c b/net/ethtool/strset.c
index 808ab6b4b387..e1b831097ca7 100644
--- a/net/ethtool/strset.c
+++ b/net/ethtool/strset.c
@@ -67,6 +67,24 @@ static const struct strset_info info_template[] = {
 		.count		= ARRAY_SIZE(phy_tunable_strings),
 		.data		= { .legacy = phy_tunable_strings },
 	},
+	[ETH_SS_TSTAMP_SOF] = {
+		.type		= ETH_SS_TYPE_SIMPLE,
+		.per_dev	= false,
+		.count		= __SOF_TIMESTAMPING_COUNT,
+		.data		= { .simple = so_timestamping_labels },
+	},
+	[ETH_SS_TSTAMP_TX_TYPE] = {
+		.type		= ETH_SS_TYPE_SIMPLE,
+		.per_dev	= false,
+		.count		= __HWTSTAMP_TX_COUNT,
+		.data		= { .simple = tstamp_tx_type_labels },
+	},
+	[ETH_SS_TSTAMP_RX_FILTER] = {
+		.type		= ETH_SS_TYPE_SIMPLE,
+		.per_dev	= false,
+		.count		= __HWTSTAMP_FILTER_COUNT,
+		.data		= { .simple = tstamp_rx_filter_labels },
+	},
 };
 
 struct strset_data {
