diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c
index 4b7bec74e89f..3ff89817e421 100644
--- a/drivers/thunderbolt/nhi.c
+++ b/drivers/thunderbolt/nhi.c
@@ -20,6 +20,7 @@
 #include <linux/delay.h>
 #include <linux/property.h>
 #include <linux/string_helpers.h>
+#include <linux/trace.h>
 
 #include "nhi.h"
 #include "nhi_regs.h"
@@ -50,6 +51,19 @@ static bool host_reset = true;
 module_param(host_reset, bool, 0444);
 MODULE_PARM_DESC(host_reset, "reset USBv2 host router (default: true)");
 
+#ifdef CONFIG_TRACING
+/*
+ * __tb_debug_trace - enable debug logs to the thunderbolt tracefs instance
+ */
+bool __tb_debug_trace;
+EXPORT_SYMBOL(__tb_debug_trace);
+
+MODULE_PARM_DESC(trace, "enable debug logs to the thunderbolt tracefs instance (default: false) (bool)");
+module_param_named(trace, __tb_debug_trace, bool, 0600);
+
+static struct trace_array *trace_arr;
+#endif
+
 static int ring_interrupt_index(const struct tb_ring *ring)
 {
 	int bit = ring->hop;
@@ -1539,6 +1553,71 @@ static struct pci_driver nhi_driver = {
 	.driver.pm = &nhi_pm_ops,
 };
 
+#ifdef CONFIG_TRACING
+
+/**
+ * tb_trace_init - initializes the thunderbolt trace array
+ *
+ * This function fetches (or creates) the thunderbolt trace array.
+ * This should be called once on thunderbolt subsystem creation
+ * and matched with tb_trace_cleanup().
+ */
+void tb_trace_init(void)
+{
+	int ret;
+
+	trace_arr = trace_array_get_by_name("thunderbolt");
+	if (!trace_arr)
+		return;
+
+	ret = trace_array_init_printk(trace_arr);
+	if (ret)
+		tb_trace_cleanup();
+}
+
+/**
+ * tb_trace_printf - adds an entry to the thunderbolt tracefs instance
+ * @format: printf format of the message to add to the trace
+ *
+ * This function adds a new entry in the thunderbolt tracefs instance
+ */
+void tb_trace_printf(const struct device *dev, const char *format, ...)
+{
+	struct va_format vaf;
+	va_list args;
+
+	va_start(args, format);
+	vaf.fmt = format;
+	vaf.va = &args;
+
+	if (dev)
+		trace_array_printk(trace_arr, _THIS_IP_, "%s %s: %pV",
+				   dev_driver_string(dev), dev_name(dev),
+				   &vaf);
+	else
+		trace_array_printk(trace_arr, _THIS_IP_,
+				   "(NULL device *): %pV", &vaf);
+	va_end(args);
+}
+
+/**
+ * tb_trace_cleanup - destroys the thunderbolt trace array
+ *
+ * This function destroys the thunderbolt trace array created
+ * with tb_trace_init. This should be called once on thunderbolt
+ * subsystem close and matched with tb_trace_init().
+ */
+void tb_trace_cleanup(void)
+{
+	if (trace_arr) {
+		trace_array_put(trace_arr);
+		trace_array_destroy(trace_arr);
+		trace_arr = NULL;
+	}
+}
+
+#endif
+
 static int __init nhi_init(void)
 {
 	int ret;
@@ -1549,11 +1628,13 @@ static int __init nhi_init(void)
 	ret = pci_register_driver(&nhi_driver);
 	if (ret)
 		tb_domain_exit();
+	tb_trace_init();
 	return ret;
 }
 
 static void __exit nhi_unload(void)
 {
+	tb_trace_cleanup();
 	pci_unregister_driver(&nhi_driver);
 	tb_domain_exit();
 }
diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
index 57a9b272cb94..3d874182b996 100644
--- a/drivers/thunderbolt/tb.h
+++ b/drivers/thunderbolt/tb.h
@@ -14,6 +14,7 @@
 #include <linux/thunderbolt.h>
 #include <linux/uuid.h>
 #include <linux/bitfield.h>
+#include <linux/kern_levels.h>
 
 #include "tb_regs.h"
 #include "ctl.h"
@@ -685,11 +686,60 @@ static inline int tb_port_write(struct tb_port *port, const void *buffer,
 			    length);
 }
 
-#define tb_err(tb, fmt, arg...) dev_err(&(tb)->nhi->pdev->dev, fmt, ## arg)
-#define tb_WARN(tb, fmt, arg...) dev_WARN(&(tb)->nhi->pdev->dev, fmt, ## arg)
-#define tb_warn(tb, fmt, arg...) dev_warn(&(tb)->nhi->pdev->dev, fmt, ## arg)
-#define tb_info(tb, fmt, arg...) dev_info(&(tb)->nhi->pdev->dev, fmt, ## arg)
-#define tb_dbg(tb, fmt, arg...) dev_dbg(&(tb)->nhi->pdev->dev, fmt, ## arg)
+#ifdef CONFIG_TRACING
+extern bool __tb_debug_trace;
+
+void tb_trace_init(void);
+__printf(2, 3)
+void tb_trace_printf(const struct device *dev, const char *format, ...);
+void tb_trace_cleanup(void);
+
+static inline bool tb_dbg_trace_enabled(void)
+{
+	return unlikely(__tb_debug_trace);
+}
+
+#else
+static inline void tb_trace_init(void)
+{
+}
+
+__printf(2, 3)
+static inline void tb_trace_printf(const struct device *dev,
+				   const char *format, ...)
+{
+}
+
+static inline void tb_trace_cleanup(void)
+{
+}
+
+static inline bool tb_dbg_trace_enabled(void)
+{
+	return 0;
+}
+#endif
+
+#define __LOG_TRACE_PRINT(log_func, tb, fmt, arg...)			\
+	do {								\
+		const struct device *dev = &(tb)->nhi->pdev->dev;	\
+									\
+		log_func(dev, fmt,  ## arg);				\
+									\
+		if (tb_dbg_trace_enabled())				\
+			tb_trace_printf(dev, fmt, ## arg);		\
+	} while (0)
+
+#define tb_err(tb, fmt, arg...) \
+	__LOG_TRACE_PRINT(dev_err, tb, fmt, ## arg)
+#define tb_WARN(tb, fmt, arg...) \
+	dev_WARN(&(tb)->nhi->pdev->dev, fmt, ## arg)
+#define tb_warn(tb, fmt, arg...) \
+	__LOG_TRACE_PRINT(dev_warn, tb, fmt, ## arg)
+#define tb_info(tb, fmt, arg...) \
+	__LOG_TRACE_PRINT(dev_info, tb, fmt, ## arg)
+#define tb_dbg(tb, fmt, arg...) \
+	__LOG_TRACE_PRINT(dev_dbg, tb, fmt, ## arg)
 
 #define __TB_SW_PRINT(level, sw, fmt, arg...)           \
 	do {                                            \
