diff --git a/Makefile.am b/Makefile.am
index 862886b77754846b99bcaf9a1b2af30accaa9b04..7389d00ca4447462d1c18fd22020773e513604d1 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -49,6 +49,7 @@ ethtool_SOURCES += \
 		  netlink/plca.c \
 		  netlink/pse-pd.c \
 		  netlink/phy.c \
+		  netlink/tsconfig.c \
 		  uapi/linux/ethtool_netlink.h \
 		  uapi/linux/netlink.h uapi/linux/genetlink.h \
 		  uapi/linux/rtnetlink.h uapi/linux/if_link.h \
diff --git a/ethtool.8.in b/ethtool.8.in
index 7188e6ab5005cd0221ae0d2c6a0ec6a1566181fc..5e4586d45f08624bb696da30c53aedca799f28b7 100644
--- a/ethtool.8.in
+++ b/ethtool.8.in
@@ -347,6 +347,16 @@ ethtool \- query or control network driver and hardware settings
 .BI qualifier
 .IR precise|approx ]
 .HP
+.B ethtool \-\-get\-hwtimestamp\-cfg
+.I devname
+.HP
+.B ethtool \-\-set\-hwtimestamp\-cfg
+.I devname
+.RB [ index
+.IR N
+.BI qualifier
+.IR precise|approx ]
+.HP
 .B ethtool \-x|\-\-show\-rxfh\-indir|\-\-show\-rxfh
 .I devname
 .HP
@@ -1249,6 +1259,21 @@ Qualifier of the ptp hardware clock. Mainly "precise" the default one is
 for IEEE 1588 quality and "approx" is for NICs DMA point.
 .RE
 .TP
+.B \-\-get\-hwtimestamp\-cfg
+Show the selected time stamping PTP hardware clock configuration.
+.TP
+.B \-\-set\-hwtimestamp\-cfg
+Select the device's time stamping PTP hardware clock.
+.RS 4
+.TP
+.BI index \ N
+Index of the ptp hardware clock
+.TP
+.BI qualifier \ precise | approx
+Qualifier of the ptp hardware clock. Mainly "precise" the default one is
+for IEEE 1588 quality and "approx" is for NICs DMA point.
+.RE
+.TP
 .B \-x \-\-show\-rxfh\-indir \-\-show\-rxfh
 Retrieves the receive flow hash indirection table and/or RSS hash key.
 .TP
diff --git a/ethtool.c b/ethtool.c
index cd3434021857e8c441325e321378323481fd98e0..707454be1269ca0b0434af6e453e44b1e52884c4 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -5977,6 +5977,17 @@ static const struct option args[] = {
 		.help	= "Show time stamping capabilities",
 		.xhelp	= "		[ index N qualifier precise|approx ]\n"
 	},
+	{
+		.opts	= "--get-hwtimestamp-cfg",
+		.nlfunc	= nl_gtsconfig,
+		.help	= "Get selected hardware time stamping"
+	},
+	{
+		.opts	= "--set-hwtimestamp-cfg",
+		.nlfunc	= nl_stsconfig,
+		.help	= "Select hardware time stamping",
+		.xhelp	= "		[ index N qualifier precise|approx ]\n"
+	},
 	{
 		.opts	= "-x|--show-rxfh-indir|--show-rxfh",
 		.json	= true,
diff --git a/netlink/extapi.h b/netlink/extapi.h
index 9d6eddfb31dbda87bfd3338f70386f2e1cdffba6..f2bf422a3f2d6f8b9fb068bea6930a33ddec420e 100644
--- a/netlink/extapi.h
+++ b/netlink/extapi.h
@@ -36,6 +36,8 @@ int nl_spause(struct cmd_context *ctx);
 int nl_geee(struct cmd_context *ctx);
 int nl_seee(struct cmd_context *ctx);
 int nl_tsinfo(struct cmd_context *ctx);
+int nl_gtsconfig(struct cmd_context *ctx);
+int nl_stsconfig(struct cmd_context *ctx);
 int nl_cable_test(struct cmd_context *ctx);
 int nl_cable_test_tdr(struct cmd_context *ctx);
 int nl_gtunnels(struct cmd_context *ctx);
@@ -114,6 +116,8 @@ nl_get_eeprom_page(struct cmd_context *ctx __maybe_unused,
 #define nl_geee			NULL
 #define nl_seee			NULL
 #define nl_tsinfo		NULL
+#define nl_gtsconfig		NULL
+#define nl_stsconfig		NULL
 #define nl_cable_test		NULL
 #define nl_cable_test_tdr	NULL
 #define nl_gtunnels		NULL
diff --git a/netlink/ts.h b/netlink/ts.h
new file mode 100644
index 0000000000000000000000000000000000000000..9442b44cb72bee97faa2c57fdc91219c5f3d8d22
--- /dev/null
+++ b/netlink/ts.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * ts.h - netlink timestamping
+ *
+ * Copyright (c) 2025 Bootlin, Kory Maincent <kory.maincent@bootlin.com>
+ */
+
+#ifndef ETHTOOL_NETLINK_TS_H__
+#define ETHTOOL_NETLINK_TS_H__
+
+const char *tsinfo_hwprov_qualifier_names(u32 val);
+int tsinfo_show_hwprov(const struct nlattr *nest);
+int tsinfo_qualifier_parser(struct nl_context *nlctx,
+			    uint16_t type __maybe_unused,
+			    const void *data __maybe_unused,
+			    struct nl_msg_buff *msgbuff __maybe_unused,
+			    void *dest);
+int tsinfo_dump_list(struct nl_context *nlctx, const struct nlattr *attr,
+		     const char *label, const char *if_empty,
+		     unsigned int stringset_id);
+
+#endif /* ETHTOOL_NETLINK_TS_H__ */
diff --git a/netlink/tsconfig.c b/netlink/tsconfig.c
new file mode 100644
index 0000000000000000000000000000000000000000..d427c7bfc7d203ebb03345fff3f0f077395c82be
--- /dev/null
+++ b/netlink/tsconfig.c
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * tsconfig.c - netlink implementation of hardware timestamping
+ *		configuration
+ *
+ * Implementation of "ethtool --get-hwtimestamp-cfg <dev>" and
+ * "ethtool --set-hwtimestamp-cfg <dev> ..."
+ */
+
+#include <errno.h>
+#include <inttypes.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "../internal.h"
+#include "../common.h"
+#include "netlink.h"
+#include "bitset.h"
+#include "parser.h"
+#include "ts.h"
+
+/* TSCONFIG_GET */
+
+int tsconfig_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+	const struct nlattr *tb[ETHTOOL_A_TSCONFIG_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(tb);
+	struct nl_context *nlctx = data;
+	bool silent;
+	int err_ret;
+	int ret;
+
+	silent = nlctx->is_dump;
+	err_ret = silent ? MNL_CB_OK : MNL_CB_ERROR;
+	ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+	if (ret < 0)
+		return err_ret;
+	nlctx->devname = get_dev_name(tb[ETHTOOL_A_TSCONFIG_HEADER]);
+	if (!dev_ok(nlctx))
+		return err_ret;
+
+	if (silent)
+		print_nl();
+	printf("Time stamping configuration for %s:\n", nlctx->devname);
+
+	if (!tb[ETHTOOL_A_TSCONFIG_HWTSTAMP_PROVIDER])
+		return MNL_CB_OK;
+
+	ret = tsinfo_show_hwprov(tb[ETHTOOL_A_TSCONFIG_HWTSTAMP_PROVIDER]);
+	if (ret < 0)
+		return err_ret;
+
+	ret = tsinfo_dump_list(nlctx, tb[ETHTOOL_A_TSCONFIG_TX_TYPES],
+			       "Hardware Transmit Timestamp Mode", " none",
+			       ETH_SS_TS_TX_TYPES);
+	if (ret < 0)
+		return err_ret;
+
+	ret = tsinfo_dump_list(nlctx, tb[ETHTOOL_A_TSCONFIG_RX_FILTERS],
+			       "Hardware Receive Filter Mode", " none",
+			       ETH_SS_TS_RX_FILTERS);
+	if (ret < 0)
+		return err_ret;
+
+	ret = tsinfo_dump_list(nlctx, tb[ETHTOOL_A_TSCONFIG_HWTSTAMP_FLAGS],
+			       "Hardware Flags", " none",
+			       ETH_SS_TS_FLAGS);
+	if (ret < 0)
+		return err_ret;
+
+	return MNL_CB_OK;
+}
+
+int nl_gtsconfig(struct cmd_context *ctx)
+{
+	struct nl_context *nlctx = ctx->nlctx;
+	struct nl_socket *nlsk = nlctx->ethnl_socket;
+	int ret;
+
+	if (netlink_cmd_check(ctx, ETHTOOL_MSG_TSINFO_GET, true))
+		return -EOPNOTSUPP;
+	if (ctx->argc > 0) {
+		fprintf(stderr, "ethtool: unexpected parameter '%s'\n",
+			*ctx->argp);
+		return 1;
+	}
+
+	ret = nlsock_prep_get_request(nlsk, ETHTOOL_MSG_TSCONFIG_GET,
+				      ETHTOOL_A_TSCONFIG_HEADER, 0);
+	if (ret < 0)
+		return ret;
+	return nlsock_send_get_request(nlsk, tsconfig_reply_cb);
+}
+
+/* TSCONFIG_SET */
+
+static const struct param_parser stsconfig_params[] = {
+	{
+		.arg		= "index",
+		.type		= ETHTOOL_A_TS_HWTSTAMP_PROVIDER_INDEX,
+		.group		= ETHTOOL_A_TSCONFIG_HWTSTAMP_PROVIDER,
+		.handler	= nl_parse_direct_u32,
+		.min_argc	= 1,
+	},
+	{
+		.arg		= "qualifier",
+		.type		= ETHTOOL_A_TS_HWTSTAMP_PROVIDER_QUALIFIER,
+		.group		= ETHTOOL_A_TSCONFIG_HWTSTAMP_PROVIDER,
+		.handler	= tsinfo_qualifier_parser,
+		.min_argc	= 1,
+	},
+	{}
+};
+
+int nl_stsconfig(struct cmd_context *ctx)
+{
+	struct nl_context *nlctx = ctx->nlctx;
+	struct nl_msg_buff *msgbuff;
+	struct nl_socket *nlsk;
+	int ret;
+
+	if (netlink_cmd_check(ctx, ETHTOOL_MSG_TSCONFIG_SET, false))
+		return -EOPNOTSUPP;
+
+	nlctx->cmd = "--set-hwtstamp-cfg";
+	nlctx->argp = ctx->argp;
+	nlctx->argc = ctx->argc;
+	nlctx->devname = ctx->devname;
+	nlsk = nlctx->ethnl_socket;
+	msgbuff = &nlsk->msgbuff;
+
+	ret = msg_init(nlctx, msgbuff, ETHTOOL_MSG_TSCONFIG_SET,
+		       NLM_F_REQUEST | NLM_F_ACK);
+	if (ret < 0)
+		return ret;
+	if (ethnla_fill_header(msgbuff, ETHTOOL_A_TSCONFIG_HEADER,
+			       ctx->devname, 0))
+		return -EMSGSIZE;
+
+	ret = nl_parser(nlctx, stsconfig_params, NULL, PARSER_GROUP_NEST, NULL);
+	if (ret < 0)
+		return ret;
+
+	ret = nlsock_sendmsg(nlsk, NULL);
+	if (ret < 0)
+		return ret;
+
+	ret = nlsock_process_reply(nlsk, tsconfig_reply_cb, nlctx);
+	if (ret == 0)
+		return 0;
+	else
+		return nlctx->exit_code ?: 1;
+}
diff --git a/netlink/tsinfo.c b/netlink/tsinfo.c
index c12070fc877b68d925d845caee6fe8a126e19882..187c3ad3e08a9e47dde29831bd41c47f73b404f7 100644
--- a/netlink/tsinfo.c
+++ b/netlink/tsinfo.c
@@ -14,10 +14,11 @@
 #include "netlink.h"
 #include "bitset.h"
 #include "parser.h"
+#include "ts.h"
 
 /* TSINFO_GET */
 
-static const char *tsinfo_hwprov_qualifier_names(u32 val)
+const char *tsinfo_hwprov_qualifier_names(u32 val)
 {
 	switch (val) {
 	case HWTSTAMP_PROVIDER_QUALIFIER_PRECISE:
@@ -29,7 +30,7 @@ static const char *tsinfo_hwprov_qualifier_names(u32 val)
 	}
 }
 
-static int tsinfo_show_hwprov(const struct nlattr *nest)
+int tsinfo_show_hwprov(const struct nlattr *nest)
 {
 	const struct nlattr *tb[ETHTOOL_A_TS_HWTSTAMP_PROVIDER_MAX + 1] = {};
 	DECLARE_ATTR_TB_INFO(tb);
@@ -117,9 +118,9 @@ static void tsinfo_dump_cb(unsigned int idx, const char *name, bool val,
 		printf("\tbit%u\n", idx);
 }
 
-static int tsinfo_dump_list(struct nl_context *nlctx, const struct nlattr *attr,
-			    const char *label, const char *if_empty,
-			    unsigned int stringset_id)
+int tsinfo_dump_list(struct nl_context *nlctx, const struct nlattr *attr,
+		     const char *label, const char *if_empty,
+		     unsigned int stringset_id)
 {
 	const struct stringset *strings = NULL;
 	int ret;
@@ -205,11 +206,11 @@ int tsinfo_reply_cb(const struct nlmsghdr *nlhdr, void *data)
 	return MNL_CB_OK;
 }
 
-static int tsinfo_qualifier_parser(struct nl_context *nlctx,
-				   uint16_t type __maybe_unused,
-				   const void *data __maybe_unused,
-				   struct nl_msg_buff *msgbuff __maybe_unused,
-				   void *dest __maybe_unused)
+int tsinfo_qualifier_parser(struct nl_context *nlctx,
+			    uint16_t type __maybe_unused,
+			    const void *data __maybe_unused,
+			    struct nl_msg_buff *msgbuff __maybe_unused,
+			    void *dest __maybe_unused)
 {
 	const char *arg = *nlctx->argp;
 	u32 val;
