diff --git a/drivers/soc/mediatek/apusys/Makefile b/drivers/soc/mediatek/apusys/Makefile
index 8fff18d63dc1..6fb69abcf3b9 100644
--- a/drivers/soc/mediatek/apusys/Makefile
+++ b/drivers/soc/mediatek/apusys/Makefile
@@ -5,3 +5,5 @@ obj-$(CONFIG_MTK_APU) += mtk_apu_pwr.o
 mtk_apu_pwr-objs += apu-pwr.o
 mtk_apu_pwr-objs += apu-pwr-ipi.o
 mtk_apu_pwr-$(CONFIG_MTK_APU_DEBUG) += apu-pwr-dbg.o
+
+obj-$(CONFIG_MTK_APU_DEBUG) += apu-sw-logger.o
diff --git a/drivers/soc/mediatek/apusys/apu-sw-logger.c b/drivers/soc/mediatek/apusys/apu-sw-logger.c
new file mode 100644
index 000000000000..98a9748e92a7
--- /dev/null
+++ b/drivers/soc/mediatek/apusys/apu-sw-logger.c
@@ -0,0 +1,540 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2021 MediaTek Inc.
+ */
+
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/fs.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/remoteproc/mtk-apu-config.h>
+#include <linux/sched/signal.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/timer.h>
+
+#define APUSYS_LOGGER_DIR "apu_logger"
+
+#define LOG_LINE_MAX_LENS SZ_128
+#define APU_LOG_SIZE SZ_1M
+#define APU_LOG_BUF_SIZE SZ_1M
+
+#define LOG_W_PTR 0x0
+#define LOG_R_PTR 0x4
+#define LOG_OV_FLG 0xC
+
+struct sw_logger_seq_data {
+	u32 w_ptr;
+	u32 r_ptr;
+	u32 overflow_flg;
+	int i;
+	int is_finished;
+	char *data;
+	bool startl_first;
+};
+
+struct apu_sw_logger {
+	struct device *dev;
+	struct dentry *dbg_root;
+	dma_addr_t handle;
+	char *sw_log_buf;
+	void __iomem *apu_slog;
+	spinlock_t logger_spinlock; /* logger status update lock */
+	struct sw_logger_seq_data pseqdata_lock;
+	struct sw_logger_seq_data *pseqdata;
+};
+
+static struct dentry *log_root;
+static struct dentry *log_seqlog;
+static struct dentry *log_seqlogl;
+static struct device *sw_logger_dev;
+
+static void sw_logger_buf_invalidate(struct apu_sw_logger *logger)
+{
+	dma_sync_single_for_cpu(logger->dev, logger->handle, APU_LOG_SIZE,
+				DMA_FROM_DEVICE);
+}
+
+static int sw_logger_buf_alloc(struct device *dev)
+{
+	struct apu_sw_logger *logger = dev_get_drvdata(dev);
+	int ret;
+
+	ret = dma_set_coherent_mask(logger->dev, DMA_BIT_MASK(64));
+	if (ret)
+		return ret;
+
+	logger->sw_log_buf = kzalloc(APU_LOG_SIZE, GFP_KERNEL);
+	if (!logger->sw_log_buf)
+		return -ENOMEM;
+
+	logger->handle = dma_map_single(logger->dev, logger->sw_log_buf,
+					APU_LOG_SIZE, DMA_FROM_DEVICE);
+	if (dma_mapping_error(logger->dev, logger->handle) != 0) {
+		kfree(logger->sw_log_buf);
+		logger->sw_log_buf = NULL;
+		return -EFAULT;
+	}
+
+	return 0;
+}
+
+int sw_logger_config_init(struct config_v1 *conf)
+{
+	int ret;
+	unsigned long flags;
+	struct logger_init_info *st_logger_init_info;
+	struct apu_sw_logger *logger;
+
+	if (!conf)
+		return -EINVAL;
+
+	if (!sw_logger_dev) {
+		pr_debug("%s: logger NA and skip logger\n",
+			 __func__);
+		return 0;
+	}
+
+	logger = dev_get_drvdata(sw_logger_dev);
+	if (!logger->sw_log_buf) {
+		ret = sw_logger_buf_alloc(logger->dev);
+		if (ret) {
+			dev_err(logger->dev, "%s: sw_logger_buf_alloc fail\n",
+				__func__);
+			return ret;
+		}
+	}
+
+	spin_lock_irqsave(&logger->logger_spinlock, flags);
+	iowrite32(0, logger->apu_slog + LOG_W_PTR);
+	iowrite32(0, logger->apu_slog + LOG_R_PTR);
+	iowrite32(0, logger->apu_slog + LOG_OV_FLG);
+	spin_unlock_irqrestore(&logger->logger_spinlock, flags);
+
+	st_logger_init_info = (struct logger_init_info *)
+		get_apu_config_user_ptr(conf, LOGGER_INIT_INFO);
+
+	st_logger_init_info->iova = logger->handle;
+
+	return 0;
+}
+EXPORT_SYMBOL(sw_logger_config_init);
+
+void sw_logger_config_remove(void)
+{
+	struct apu_sw_logger *logger = dev_get_drvdata(sw_logger_dev);
+
+	if (logger->handle)
+		dma_unmap_single(logger->dev, logger->handle,
+				 APU_LOG_SIZE, DMA_FROM_DEVICE);
+	kfree(logger->sw_log_buf);
+	logger->sw_log_buf = NULL;
+}
+EXPORT_SYMBOL(sw_logger_config_remove);
+
+/*
+ * seq_start() takes a position as an argument and returns an iterator which
+ * will start reading at that position.
+ * start->show->next->show...->next->show->next->stop->start->stop
+ */
+static void *seq_start(struct seq_file *s, loff_t *pos)
+{
+	struct apu_sw_logger *logger = (struct apu_sw_logger *)s->private;
+	u32 w_ptr, r_ptr, overflow_flg;
+	unsigned long flags;
+
+	if (!logger->sw_log_buf) {
+		pr_err("%s: sw_log_buf == NULL\n", __func__);
+		return NULL;
+	}
+
+	spin_lock_irqsave(&logger->logger_spinlock, flags);
+	w_ptr = ioread32(logger->apu_slog + LOG_W_PTR);
+	r_ptr = ioread32(logger->apu_slog + LOG_R_PTR);
+	overflow_flg = ioread32(logger->apu_slog + LOG_OV_FLG);
+	spin_unlock_irqrestore(&logger->logger_spinlock, flags);
+
+	sw_logger_buf_invalidate(logger);
+
+	if (w_ptr == r_ptr && overflow_flg == 0)
+		return NULL;
+
+	if (!logger->pseqdata) {
+		logger->pseqdata = kzalloc(sizeof(*logger->pseqdata),
+					   GFP_KERNEL);
+		if (logger->pseqdata) {
+			logger->pseqdata->w_ptr = w_ptr;
+			logger->pseqdata->r_ptr = r_ptr;
+			logger->pseqdata->overflow_flg = overflow_flg;
+			if (overflow_flg == 0)
+				logger->pseqdata->i = r_ptr;
+			else
+				logger->pseqdata->i = w_ptr;
+
+			logger->pseqdata->is_finished = 0;
+		}
+	}
+
+	return logger->pseqdata;
+}
+
+/*
+ * seq_start() takes a position as an argument and returns an iterator which
+ * will start reading at that position.
+ */
+static void *seq_startl(struct seq_file *s, loff_t *pos)
+{
+	struct apu_sw_logger *logger = s->private;
+	u32 w_ptr, r_ptr, overflow_flg;
+	struct sw_logger_seq_data *pseqdata_lock = &logger->pseqdata_lock;
+	unsigned long flags;
+
+	if (!logger->sw_log_buf)
+		return NULL;
+
+	spin_lock_irqsave(&logger->logger_spinlock, flags);
+	w_ptr = ioread32(logger->apu_slog + LOG_W_PTR);
+	r_ptr = ioread32(logger->apu_slog + LOG_R_PTR);
+	overflow_flg = ioread32(logger->apu_slog + LOG_OV_FLG);
+	spin_unlock_irqrestore(&logger->logger_spinlock, flags);
+
+	sw_logger_buf_invalidate(logger);
+
+	/* for ctrl-c to force exit the loop */
+	while (!signal_pending(current) && w_ptr == r_ptr) {
+		usleep_range(10000, 12000);
+
+		spin_lock_irqsave(&logger->logger_spinlock, flags);
+		w_ptr = ioread32(logger->apu_slog + LOG_W_PTR);
+		r_ptr = ioread32(logger->apu_slog + LOG_R_PTR);
+		overflow_flg = ioread32(logger->apu_slog + LOG_OV_FLG);
+		spin_unlock_irqrestore(&logger->logger_spinlock, flags);
+
+		sw_logger_buf_invalidate(logger);
+
+		pseqdata_lock->w_ptr = w_ptr;
+		pseqdata_lock->r_ptr = r_ptr;
+		pseqdata_lock->overflow_flg = overflow_flg;
+		pseqdata_lock->i = r_ptr;
+	}
+
+	if (pseqdata_lock->startl_first ||
+	    pseqdata_lock->i == pseqdata_lock->w_ptr) {
+		pseqdata_lock->startl_first = false;
+		pseqdata_lock->w_ptr = w_ptr;
+		pseqdata_lock->r_ptr = r_ptr;
+		pseqdata_lock->overflow_flg = overflow_flg;
+		pseqdata_lock->i = r_ptr;
+	}
+
+	if (signal_pending(current))
+		pseqdata_lock->startl_first = true;
+
+	return pseqdata_lock;
+}
+
+/*
+ * move the iterator forward to the next position in the sequence
+ */
+static void *seq_next(struct seq_file *s, void *v, loff_t *pos)
+{
+	struct sw_logger_seq_data *psdata = v;
+
+	if (!psdata) {
+		pr_err("%s: psdata == NULL\n", __func__);
+		return NULL;
+	}
+
+	psdata->i = (psdata->i + LOG_LINE_MAX_LENS) % APU_LOG_SIZE;
+
+	/* prevent kernel warning */
+	*pos = psdata->i;
+
+	if (psdata->i != psdata->w_ptr)
+		return v;
+
+	psdata->is_finished = 1;
+	return NULL;
+}
+
+/*
+ * move the iterator forward to the next position in the sequence
+ */
+static void *seq_next_lock(struct seq_file *s, void *v, loff_t *pos)
+{
+	struct apu_sw_logger *logger = s->private;
+	struct sw_logger_seq_data *psdata = v;
+
+	if (!psdata) {
+		pr_err("%s: psdata == NULL\n", __func__);
+		return NULL;
+	}
+
+	psdata->i = (psdata->i + LOG_LINE_MAX_LENS) % APU_LOG_SIZE;
+
+	/* prevent kernel warning */
+	*pos = psdata->i;
+
+	if (psdata->i != psdata->w_ptr)
+		return v;
+
+	iowrite32(psdata->i, logger->apu_slog + LOG_R_PTR);
+	return NULL;
+}
+
+/*
+ * stop() is called when iteration is complete (clean up)
+ */
+static void seq_stop(struct seq_file *s, void *v)
+{
+	struct apu_sw_logger *logger = (struct apu_sw_logger *)s->private;
+	unsigned long flags;
+
+	if (logger->pseqdata) {
+		if (logger->pseqdata->is_finished == 1) {
+			spin_lock_irqsave(&logger->logger_spinlock, flags);
+			iowrite32(logger->pseqdata->i,
+				  logger->apu_slog + LOG_R_PTR);
+			/* fixme: assume next overflow won't happen
+			 * until next seq_start
+			 */
+			iowrite32(0, logger->apu_slog + LOG_OV_FLG);
+			spin_unlock_irqrestore(&logger->logger_spinlock, flags);
+			kfree(logger->pseqdata);
+			logger->pseqdata = NULL;
+		}
+	}
+}
+
+/*
+ * stop() is called when iteration is complete (clean up)
+ */
+static void seq_stopl(struct seq_file *s, void *v)
+{
+}
+
+/*
+ * success return 0, otherwise return error code
+ */
+static int seq_show(struct seq_file *s, void *v)
+{
+	struct apu_sw_logger *logger = (struct apu_sw_logger *)s->private;
+	struct sw_logger_seq_data *psdata = v;
+
+	seq_printf(s, "%s", logger->sw_log_buf + psdata->i);
+
+	return 0;
+}
+
+static int seq_showl(struct seq_file *s, void *v)
+{
+	struct apu_sw_logger *logger = (struct apu_sw_logger *)s->private;
+	struct sw_logger_seq_data *psdata = v;
+
+	if (psdata->i != psdata->w_ptr)
+		seq_printf(s, "%s", logger->sw_log_buf + psdata->i);
+
+	return 0;
+}
+
+static const struct seq_operations seq_ops = {
+	.start = seq_start,
+	.next  = seq_next,
+	.stop  = seq_stop,
+	.show  = seq_show
+};
+
+static const struct seq_operations seq_ops_lock = {
+	.start = seq_startl,
+	.next  = seq_next_lock,
+	.stop  = seq_stopl,
+	.show  = seq_showl
+};
+
+static int debug_sqopen_lock(struct inode *inode, struct file *file)
+{
+	struct apu_sw_logger *logger = inode->i_private;
+	int ret;
+
+	ret = seq_open(file, &seq_ops_lock);
+	if (ret)
+		return ret;
+
+	((struct seq_file *)file->private_data)->private = logger;
+
+	return 0;
+}
+
+static int debug_sqopen(struct inode *inode, struct file *file)
+{
+	struct apu_sw_logger *logger = inode->i_private;
+	int ret;
+
+	ret = seq_open(file, &seq_ops);
+	if (ret)
+		return ret;
+
+	((struct seq_file *)file->private_data)->private = logger;
+
+	return 0;
+}
+
+static const struct file_operations seqlog_ops = {
+	.owner   = THIS_MODULE,
+	.open    = debug_sqopen,
+	.read    = seq_read,
+	.llseek  = seq_lseek,
+	.release = seq_release,
+};
+
+static const struct file_operations seqlogl_ops = {
+	.owner   = THIS_MODULE,
+	.open    = debug_sqopen_lock,
+	.read    = seq_read,
+	.llseek  = seq_lseek,
+	.release = seq_release,
+};
+
+static int sw_logger_create_debugfs(struct device *dev)
+{
+	struct apu_sw_logger *logger = dev_get_drvdata(dev);
+	int ret = 0;
+
+	logger->dbg_root = NULL;
+	log_root = debugfs_create_dir(APUSYS_LOGGER_DIR, logger->dbg_root);
+	ret = IS_ERR_OR_NULL(log_root);
+	if (ret) {
+		dev_err(dev, "(%d)failed to create apusys_logger dir\n", ret);
+		goto out;
+	}
+
+	log_seqlog = debugfs_create_file("seq_log", 0444,
+					 log_root, logger, &seqlog_ops);
+	ret = IS_ERR_OR_NULL(log_seqlog);
+	if (ret) {
+		dev_err(dev, "(%d)failed to create apusys_logger node(seqlog)\n",
+			ret);
+		goto out;
+	}
+
+	log_seqlogl = debugfs_create_file("seq_logl", 0444,
+					  log_root, logger, &seqlogl_ops);
+	ret = IS_ERR_OR_NULL(log_seqlogl);
+	if (ret) {
+		dev_err(dev, "(%d)failed to create apusys_logger node(seqlogL)\n",
+			ret);
+		goto out;
+	}
+
+	return 0;
+
+out:
+	debugfs_remove_recursive(log_root);
+	return ret;
+}
+
+static void sw_logger_remove_debugfs(struct device *dev)
+{
+	debugfs_remove_recursive(log_root);
+}
+
+static int sw_logger_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct apu_sw_logger *logger;
+	struct resource *res;
+	int ret = 0;
+
+	logger = devm_kzalloc(dev, sizeof(struct apu_sw_logger), GFP_KERNEL);
+	if (!logger)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, logger);
+
+	logger->dev = dev;
+	sw_logger_dev = dev;
+	spin_lock_init(&logger->logger_spinlock);
+
+	ret = sw_logger_create_debugfs(dev);
+	if (ret) {
+		dev_err(dev, "%s fail\n", __func__);
+		goto remove_debugfs;
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_info(dev, "%s: apu log get resource fail\n", __func__);
+		ret = -ENODEV;
+		goto remove_ioremap;
+	}
+	logger->apu_slog = ioremap(res->start, res->end - res->start + 1);
+	if (IS_ERR((void const *)logger->apu_slog)) {
+		dev_info(dev, "%s: apu_slog remap base fail\n", __func__);
+		ret = -ENOMEM;
+		goto remove_ioremap;
+	}
+
+	return 0;
+
+remove_ioremap:
+	if (logger->apu_slog)
+		iounmap(logger->apu_slog);
+
+remove_debugfs:
+	sw_logger_remove_debugfs(dev);
+
+	return ret;
+}
+
+static int sw_logger_remove(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+
+	sw_logger_remove_debugfs(dev);
+
+	return 0;
+}
+
+static const struct of_device_id sw_logger_of_match[] = {
+	{ .compatible = "mediatek,apu-sw-logger"},
+	{},
+};
+MODULE_DEVICE_TABLE(of, sw_logger_of_match);
+
+static struct platform_driver sw_logger_driver = {
+	.probe = sw_logger_probe,
+	.remove = sw_logger_remove,
+	.driver = {
+		.name = "apu-sw-logger",
+		.of_match_table = of_match_ptr(sw_logger_of_match),
+	}
+};
+
+static int __init sw_logger_init(void)
+{
+	int ret = 0;
+
+	allow_signal(SIGKILL);
+	ret = platform_driver_register(&sw_logger_driver);
+	if (ret != 0) {
+		pr_err("failed to register sw_logger driver");
+		return -ENODEV;
+	}
+
+	return ret;
+}
+
+static void __exit sw_logger_exit(void)
+{
+	platform_driver_unregister(&sw_logger_driver);
+}
+
+module_init(sw_logger_init);
+module_exit(sw_logger_exit);
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("MediaTek APU SW Logger Driver");
+
