diff --git a/fs/Kconfig b/fs/Kconfig
index 64d420e3c475..ae117c2e3ce2 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -139,6 +139,8 @@ source "fs/autofs/Kconfig"
 source "fs/fuse/Kconfig"
 source "fs/overlayfs/Kconfig"
 
+source "fs/data-temperature/Kconfig"
+
 menu "Caches"
 
 source "fs/netfs/Kconfig"
diff --git a/fs/Makefile b/fs/Makefile
index 15df0a923d3a..c7e6ccac633d 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -129,3 +129,4 @@ obj-$(CONFIG_EROFS_FS)		+= erofs/
 obj-$(CONFIG_VBOXSF_FS)		+= vboxsf/
 obj-$(CONFIG_ZONEFS_FS)		+= zonefs/
 obj-$(CONFIG_BPF_LSM)		+= bpf_fs_kfuncs.o
+obj-$(CONFIG_DATA_TEMPERATURE)	+= data-temperature/
diff --git a/fs/data-temperature/Kconfig b/fs/data-temperature/Kconfig
new file mode 100644
index 000000000000..1cade2741982
--- /dev/null
+++ b/fs/data-temperature/Kconfig
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config DATA_TEMPERATURE
+	bool "Data temperature approach for efficient data placement"
+	help
+	  Enable data "temperature" estimation for efficient data
+	  placement policy. This approach is file based and
+	  it estimates "temperature" for every file independently.
+	  The goal of the approach is to provide valuable hints
+	  to file system or/and SSD for isolation and proper
+	  managament of data with different temperatures.
diff --git a/fs/data-temperature/Makefile b/fs/data-temperature/Makefile
new file mode 100644
index 000000000000..8e089a681360
--- /dev/null
+++ b/fs/data-temperature/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_DATA_TEMPERATURE) += data_temperature.o
diff --git a/fs/data-temperature/data_temperature.c b/fs/data-temperature/data_temperature.c
new file mode 100644
index 000000000000..ea43fbfc3976
--- /dev/null
+++ b/fs/data-temperature/data_temperature.c
@@ -0,0 +1,347 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Data "temperature" paradigm implementation
+ *
+ * Copyright (c) 2024-2025 Viacheslav Dubeyko <slava@dubeyko.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pagemap.h>
+#include <linux/data_temperature.h>
+#include <linux/fs.h>
+
+#define TIME_IS_UNKNOWN		(U64_MAX)
+
+struct kmem_cache *data_temperature_info_cachep;
+
+static inline
+void create_data_temperature_info(struct data_temperature *dt_info)
+{
+	if (!dt_info)
+		return;
+
+	atomic_set(&dt_info->temperature, 0);
+	dt_info->updated_blocks = 0;
+	dt_info->dirty_blocks = 0;
+	dt_info->start_timestamp = TIME_IS_UNKNOWN;
+	dt_info->end_timestamp = TIME_IS_UNKNOWN;
+	dt_info->state = DATA_TEMPERATURE_CREATED;
+}
+
+static inline
+void free_data_temperature_info(struct data_temperature *dt_info)
+{
+	if (!dt_info)
+		return;
+
+	kmem_cache_free(data_temperature_info_cachep, dt_info);
+}
+
+int __set_data_temperature_info(struct inode *inode)
+{
+	struct data_temperature *dt_info;
+
+	dt_info = kmem_cache_zalloc(data_temperature_info_cachep, GFP_KERNEL);
+	if (!dt_info)
+		return -ENOMEM;
+
+	spin_lock_init(&dt_info->change_lock);
+	create_data_temperature_info(dt_info);
+
+	if (cmpxchg_release(&inode->i_data_temperature_info,
+					NULL, dt_info) != NULL) {
+		free_data_temperature_info(dt_info);
+		get_data_temperature_info(inode);
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(__set_data_temperature_info);
+
+void __remove_data_temperature_info(struct inode *inode)
+{
+	free_data_temperature_info(inode->i_data_temperature_info);
+	inode->i_data_temperature_info = NULL;
+}
+EXPORT_SYMBOL_GPL(__remove_data_temperature_info);
+
+int __get_data_temperature(const struct inode *inode)
+{
+	struct data_temperature *dt_info;
+
+	if (!S_ISREG(inode->i_mode))
+		return 0;
+
+	dt_info = get_data_temperature_info(inode);
+	if (IS_ERR_OR_NULL(dt_info))
+		return 0;
+
+	return atomic_read(&dt_info->temperature);
+}
+EXPORT_SYMBOL_GPL(__get_data_temperature);
+
+static inline
+bool is_timestamp_invalid(struct data_temperature *dt_info)
+{
+	if (!dt_info)
+		return false;
+
+	if (dt_info->start_timestamp == TIME_IS_UNKNOWN ||
+	    dt_info->end_timestamp == TIME_IS_UNKNOWN)
+		return true;
+
+	if (dt_info->start_timestamp > dt_info->end_timestamp)
+		return true;
+
+	return false;
+}
+
+static inline
+u64 get_current_timestamp(void)
+{
+	return ktime_get_boottime_ns();
+}
+
+static inline
+void start_account_data_temperature_info(struct data_temperature *dt_info)
+{
+	if (!dt_info)
+		return;
+
+	dt_info->dirty_blocks = 1;
+	dt_info->start_timestamp = get_current_timestamp();
+	dt_info->end_timestamp = TIME_IS_UNKNOWN;
+	dt_info->state = DATA_TEMPERATURE_UPDATE_STARTED;
+}
+
+static inline
+void __increase_data_temperature(struct inode *inode,
+				 struct data_temperature *dt_info)
+{
+	u64 bytes_count;
+	u64 file_blocks;
+	u32 block_bytes;
+	int dirty_blocks_ratio;
+	int updated_blocks_ratio;
+	int old_temperature;
+	int calculated;
+
+	if (!inode || !dt_info)
+		return;
+
+	block_bytes = 1 << inode->i_blkbits;
+	bytes_count = i_size_read(inode) + block_bytes - 1;
+	file_blocks = bytes_count >> inode->i_blkbits;
+
+	dt_info->dirty_blocks++;
+
+	if (file_blocks > 0) {
+		old_temperature = atomic_read(&dt_info->temperature);
+
+		dirty_blocks_ratio = div_u64(dt_info->dirty_blocks,
+						file_blocks);
+		updated_blocks_ratio = div_u64(dt_info->updated_blocks,
+						file_blocks);
+		calculated = max_t(int, dirty_blocks_ratio,
+					updated_blocks_ratio);
+
+		if (calculated > 0 && old_temperature < calculated)
+			atomic_set(&dt_info->temperature, calculated);
+	}
+}
+
+static inline
+void __decrease_data_temperature(struct inode *inode,
+				 struct data_temperature *dt_info)
+{
+	u64 timestamp;
+	u64 time_range;
+	u64 time_diff;
+	u64 bytes_count;
+	u64 file_blocks;
+	u32 block_bytes;
+	u64 blks_per_temperature_degree;
+	u64 ns_per_block;
+	u64 temperature_diff;
+
+	if (!inode || !dt_info)
+		return;
+
+	if (is_timestamp_invalid(dt_info)) {
+		create_data_temperature_info(dt_info);
+		return;
+	}
+
+	timestamp = get_current_timestamp();
+
+	if (dt_info->end_timestamp > timestamp) {
+		create_data_temperature_info(dt_info);
+		return;
+	}
+
+	time_range = dt_info->end_timestamp - dt_info->start_timestamp;
+	time_diff = timestamp - dt_info->end_timestamp;
+
+	block_bytes = 1 << inode->i_blkbits;
+	bytes_count = i_size_read(inode) + block_bytes - 1;
+	file_blocks = bytes_count >> inode->i_blkbits;
+
+	blks_per_temperature_degree = file_blocks;
+	if (blks_per_temperature_degree == 0) {
+		start_account_data_temperature_info(dt_info);
+		return;
+	}
+
+	if (dt_info->updated_blocks == 0 || time_range == 0) {
+		start_account_data_temperature_info(dt_info);
+		return;
+	}
+
+	ns_per_block = div_u64(time_range, dt_info->updated_blocks);
+	if (ns_per_block == 0)
+		ns_per_block = 1;
+
+	if (time_diff == 0) {
+		start_account_data_temperature_info(dt_info);
+		return;
+	}
+
+	temperature_diff = div_u64(time_diff, ns_per_block);
+	temperature_diff = div_u64(temperature_diff,
+					blks_per_temperature_degree);
+
+	if (temperature_diff == 0)
+		return;
+
+	if (temperature_diff <= atomic_read(&dt_info->temperature)) {
+		atomic_sub(temperature_diff, &dt_info->temperature);
+		dt_info->updated_blocks -=
+			temperature_diff * blks_per_temperature_degree;
+	} else {
+		atomic_set(&dt_info->temperature, 0);
+		dt_info->updated_blocks = 0;
+	}
+}
+
+int __increase_data_temperature_by_dirty_folio(struct folio *folio)
+{
+	struct inode *inode;
+	struct data_temperature *dt_info;
+
+	if (!folio || !folio->mapping)
+		return 0;
+
+	inode = folio_inode(folio);
+
+	if (!S_ISREG(inode->i_mode))
+		return 0;
+
+	dt_info = get_data_temperature_info(inode);
+	if (IS_ERR_OR_NULL(dt_info))
+		return 0;
+
+	spin_lock(&dt_info->change_lock);
+	switch (dt_info->state) {
+	case DATA_TEMPERATURE_CREATED:
+		atomic_set(&dt_info->temperature, 0);
+		start_account_data_temperature_info(dt_info);
+		break;
+
+	case DATA_TEMPERATURE_UPDATE_STARTED:
+		__increase_data_temperature(inode, dt_info);
+		break;
+
+	case DATA_TEMPERATURE_UPDATE_FINISHED:
+		__decrease_data_temperature(inode, dt_info);
+		start_account_data_temperature_info(dt_info);
+		break;
+
+	default:
+		/* do nothing */
+		break;
+	}
+	spin_unlock(&dt_info->change_lock);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(__increase_data_temperature_by_dirty_folio);
+
+static inline
+void decrement_dirty_blocks(struct data_temperature *dt_info)
+{
+	if (!dt_info)
+		return;
+
+	if (dt_info->dirty_blocks > 0) {
+		dt_info->dirty_blocks--;
+		dt_info->updated_blocks++;
+	}
+}
+
+static inline
+void finish_increasing_data_temperature(struct data_temperature *dt_info)
+{
+	if (!dt_info)
+		return;
+
+	if (dt_info->dirty_blocks == 0) {
+		dt_info->end_timestamp = get_current_timestamp();
+		dt_info->state = DATA_TEMPERATURE_UPDATE_FINISHED;
+	}
+}
+
+int __account_flushed_folio_by_data_temperature(struct folio *folio)
+{
+	struct inode *inode;
+	struct data_temperature *dt_info;
+
+	if (!folio || !folio->mapping)
+		return 0;
+
+	inode = folio_inode(folio);
+
+	if (!S_ISREG(inode->i_mode))
+		return 0;
+
+	dt_info = get_data_temperature_info(inode);
+	if (IS_ERR_OR_NULL(dt_info))
+		return 0;
+
+	spin_lock(&dt_info->change_lock);
+	switch (dt_info->state) {
+	case DATA_TEMPERATURE_CREATED:
+		create_data_temperature_info(dt_info);
+		break;
+
+	case DATA_TEMPERATURE_UPDATE_STARTED:
+		if (dt_info->dirty_blocks > 0)
+			decrement_dirty_blocks(dt_info);
+		if (dt_info->dirty_blocks == 0)
+			finish_increasing_data_temperature(dt_info);
+		break;
+
+	case DATA_TEMPERATURE_UPDATE_FINISHED:
+		/* do nothing */
+		break;
+
+	default:
+		/* do nothing */
+		break;
+	}
+	spin_unlock(&dt_info->change_lock);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(__account_flushed_folio_by_data_temperature);
+
+static int __init data_temperature_init(void)
+{
+	data_temperature_info_cachep = KMEM_CACHE(data_temperature,
+						  SLAB_RECLAIM_ACCOUNT);
+	if (!data_temperature_info_cachep)
+		return -ENOMEM;
+
+	return 0;
+}
+late_initcall(data_temperature_init)
diff --git a/include/linux/data_temperature.h b/include/linux/data_temperature.h
new file mode 100644
index 000000000000..40abf6322385
--- /dev/null
+++ b/include/linux/data_temperature.h
@@ -0,0 +1,124 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Data "temperature" paradigm declarations
+ *
+ * Copyright (c) 2024-2025 Viacheslav Dubeyko <slava@dubeyko.com>
+ */
+
+#ifndef _LINUX_DATA_TEMPERATURE_H
+#define _LINUX_DATA_TEMPERATURE_H
+
+/*
+ * struct data_temperature - data temperature definition
+ * @temperature: current temperature of a file
+ * @change_lock: modification lock
+ * @state: current state of data temperature object
+ * @dirty_blocks: current number of dirty blocks in page cache
+ * @updated_blocks: number of updated blocks [start_timestamp, end_timestamp]
+ * @start_timestamp: starting timestamp of update operations
+ * @end_timestamp: finishing timestamp of update operations
+ */
+struct data_temperature {
+	atomic_t temperature;
+
+	spinlock_t change_lock;
+	int state;
+	u64 dirty_blocks;
+	u64 updated_blocks;
+	u64 start_timestamp;
+	u64 end_timestamp;
+};
+
+enum data_temperature_state {
+	DATA_TEMPERATURE_UNKNOWN_STATE,
+	DATA_TEMPERATURE_CREATED,
+	DATA_TEMPERATURE_UPDATE_STARTED,
+	DATA_TEMPERATURE_UPDATE_FINISHED,
+	DATA_TEMPERATURE_STATE_MAX
+};
+
+#ifdef CONFIG_DATA_TEMPERATURE
+
+int __set_data_temperature_info(struct inode *inode);
+void __remove_data_temperature_info(struct inode *inode);
+int __get_data_temperature(const struct inode *inode);
+int __increase_data_temperature_by_dirty_folio(struct folio *folio);
+int __account_flushed_folio_by_data_temperature(struct folio *folio);
+
+static inline
+struct data_temperature *get_data_temperature_info(const struct inode *inode)
+{
+	return smp_load_acquire(&inode->i_data_temperature_info);
+}
+
+static inline
+int set_data_temperature_info(struct inode *inode)
+{
+	return __set_data_temperature_info(inode);
+}
+
+static inline
+void remove_data_temperature_info(struct inode *inode)
+{
+	__remove_data_temperature_info(inode);
+}
+
+static inline
+int get_data_temperature(const struct inode *inode)
+{
+	return __get_data_temperature(inode);
+}
+
+static inline
+int increase_data_temperature_by_dirty_folio(struct folio *folio)
+{
+	return __increase_data_temperature_by_dirty_folio(folio);
+}
+
+static inline
+int account_flushed_folio_by_data_temperature(struct folio *folio)
+{
+	return __account_flushed_folio_by_data_temperature(folio);
+}
+
+#else  /* !CONFIG_DATA_TEMPERATURE */
+
+static inline
+int set_data_temperature_info(struct inode *inode)
+{
+	return 0;
+}
+
+static inline
+void remove_data_temperature_info(struct inode *inode)
+{
+	return;
+}
+
+static inline
+struct data_temperature *get_data_temperature_info(const struct inode *inode)
+{
+	return ERR_PTR(-EOPNOTSUPP);
+}
+
+static inline
+int get_data_temperature(const struct inode *inode)
+{
+	return 0;
+}
+
+static inline
+int increase_data_temperature_by_dirty_folio(struct folio *folio)
+{
+	return 0;
+}
+
+static inline
+int account_flushed_folio_by_data_temperature(struct folio *folio)
+{
+	return 0;
+}
+
+#endif	/* CONFIG_DATA_TEMPERATURE */
+
+#endif	/* _LINUX_DATA_TEMPERATURE_H */
diff --git a/include/linux/fs.h b/include/linux/fs.h
index a4af70367f8a..57c4810a28a0 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -753,6 +753,10 @@ struct inode {
 	struct fsverity_info	*i_verity_info;
 #endif
 
+#ifdef CONFIG_DATA_TEMPERATURE
+	struct data_temperature		*i_data_temperature_info;
+#endif
+
 	void			*i_private; /* fs or device private pointer */
 } __randomize_layout;
 
diff --git a/mm/page-writeback.c b/mm/page-writeback.c
index d9861e42b2bd..5de458b7fefc 100644
--- a/mm/page-writeback.c
+++ b/mm/page-writeback.c
@@ -38,6 +38,7 @@
 #include <linux/sched/rt.h>
 #include <linux/sched/signal.h>
 #include <linux/mm_inline.h>
+#include <linux/data_temperature.h>
 #include <trace/events/writeback.h>
 
 #include "internal.h"
@@ -2775,6 +2776,10 @@ static void folio_account_dirtied(struct folio *folio,
 		__this_cpu_add(bdp_ratelimits, nr);
 
 		mem_cgroup_track_foreign_dirty(folio, wb);
+
+#ifdef CONFIG_DATA_TEMPERATURE
+		increase_data_temperature_by_dirty_folio(folio);
+#endif	/* CONFIG_DATA_TEMPERATURE */
 	}
 }
 
@@ -3006,6 +3011,10 @@ bool folio_clear_dirty_for_io(struct folio *folio)
 
 	VM_BUG_ON_FOLIO(!folio_test_locked(folio), folio);
 
+#ifdef CONFIG_DATA_TEMPERATURE
+	account_flushed_folio_by_data_temperature(folio);
+#endif	/* CONFIG_DATA_TEMPERATURE */
+
 	if (mapping && mapping_can_writeback(mapping)) {
 		struct inode *inode = mapping->host;
 		struct bdi_writeback *wb;
