From b37b26232ebb6c0a61b530f11ccd6eefdf782c04 Mon Sep 17 00:00:00 2001
From: "shanlong.li" <shanlong.li@starfivetech.com>
Date: Fri, 16 Jun 2023 03:02:14 -0700
Subject: [PATCH 18/55] driver:e24: add e24 driver

add e24 driver

Signed-off-by: shanlong.li <shanlong.li@starfivetech.com>
---
 drivers/Kconfig               |    1 +
 drivers/Makefile              |    1 +
 drivers/e24/Kconfig           |    5 +
 drivers/e24/Makefile          |   12 +
 drivers/e24/e24_alloc.c       |  241 ++++++
 drivers/e24/e24_alloc.h       |   59 ++
 drivers/e24/starfive_e24.c    | 1524 +++++++++++++++++++++++++++++++++
 drivers/e24/starfive_e24.h    |  159 ++++
 drivers/e24/starfive_e24_hw.c |  134 +++
 drivers/e24/starfive_e24_hw.h |   94 ++
 10 files changed, 2230 insertions(+)
 create mode 100644 drivers/e24/Kconfig
 create mode 100644 drivers/e24/Makefile
 create mode 100644 drivers/e24/e24_alloc.c
 create mode 100644 drivers/e24/e24_alloc.h
 create mode 100644 drivers/e24/starfive_e24.c
 create mode 100644 drivers/e24/starfive_e24.h
 create mode 100644 drivers/e24/starfive_e24_hw.c
 create mode 100644 drivers/e24/starfive_e24_hw.h

--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -245,4 +245,5 @@ source "drivers/cdx/Kconfig"
 
 source "drivers/dpll/Kconfig"
 
+source "drivers/e24/Kconfig"
 endmenu
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -195,3 +195,4 @@ obj-$(CONFIG_CDX_BUS)		+= cdx/
 obj-$(CONFIG_DPLL)		+= dpll/
 
 obj-$(CONFIG_S390)		+= s390/
+obj-$(CONFIG_E24)		+= e24/
--- /dev/null
+++ b/drivers/e24/Kconfig
@@ -0,0 +1,5 @@
+config E24
+	tristate  "E24 support"
+	default m
+	help
+      This module provides the function of E24 device.
--- /dev/null
+++ b/drivers/e24/Makefile
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright(c) 1999 - 2018 Intel Corporation.
+#
+# Makefile for the E24 driver
+#
+ccflags-y += -I$(srctree)/drivers/e24
+#ccflags-y += -DDEBUG
+ccflags-y += -Wunused-variable -Wno-error=missing-prototypes
+
+obj-$(CONFIG_E24) += e24.o
+
+e24-y := starfive_e24.o starfive_e24_hw.o e24_alloc.o
--- /dev/null
+++ b/drivers/e24/e24_alloc.c
@@ -0,0 +1,241 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/atomic.h>
+#include <linux/kernel.h>
+#include <linux/mutex.h>
+#include <linux/printk.h>
+#include <linux/slab.h>
+#include "e24_alloc.h"
+
+struct e24_private_pool {
+	struct e24_allocation_pool pool;
+	struct mutex free_list_lock;
+	phys_addr_t start;
+	u32 size;
+	struct e24_allocation *free_list;
+};
+
+static void e24_private_free(struct e24_allocation *e24_allocation)
+{
+	struct e24_private_pool *pool = container_of(e24_allocation->pool,
+						     struct e24_private_pool,
+						     pool);
+	struct e24_allocation **pcur;
+
+	pr_debug("%s: %pap x %d\n", __func__,
+		 &e24_allocation->start, e24_allocation->size);
+
+	mutex_lock(&pool->free_list_lock);
+
+	for (pcur = &pool->free_list; ; pcur = &(*pcur)->next) {
+		struct e24_allocation *cur = *pcur;
+
+		if (cur && cur->start + cur->size == e24_allocation->start) {
+			struct e24_allocation *next = cur->next;
+
+			pr_debug("merging block tail: %pap x 0x%x ->\n",
+				 &cur->start, cur->size);
+			cur->size += e24_allocation->size;
+			pr_debug("... -> %pap x 0x%x\n",
+				 &cur->start, cur->size);
+			kfree(e24_allocation);
+
+			if (next && cur->start + cur->size == next->start) {
+				pr_debug("merging with next block: %pap x 0x%x ->\n",
+					 &cur->start, cur->size);
+				cur->size += next->size;
+				cur->next = next->next;
+				pr_debug("... -> %pap x 0x%x\n",
+					 &cur->start, cur->size);
+				kfree(next);
+			}
+			break;
+		}
+
+		if (!cur || e24_allocation->start < cur->start) {
+			if (cur && e24_allocation->start + e24_allocation->size ==
+			    cur->start) {
+				pr_debug("merging block head: %pap x 0x%x ->\n",
+					 &cur->start, cur->size);
+				cur->size += e24_allocation->size;
+				cur->start = e24_allocation->start;
+				pr_debug("... -> %pap x 0x%x\n",
+					 &cur->start, cur->size);
+				kfree(e24_allocation);
+			} else {
+				pr_debug("inserting new free block\n");
+				e24_allocation->next = cur;
+				*pcur = e24_allocation;
+			}
+			break;
+		}
+	}
+
+	mutex_unlock(&pool->free_list_lock);
+}
+
+static long e24_private_alloc(struct e24_allocation_pool *pool,
+			      u32 size, u32 align,
+			      struct e24_allocation **alloc)
+{
+	struct e24_private_pool *ppool = container_of(pool,
+						      struct e24_private_pool,
+						      pool);
+	struct e24_allocation **pcur;
+	struct e24_allocation *cur = NULL;
+	struct e24_allocation *new;
+	phys_addr_t aligned_start = 0;
+	bool found = false;
+
+	if (!size || (align & (align - 1)))
+		return -EINVAL;
+	if (!align)
+		align = 1;
+
+	new = kzalloc(sizeof(struct e24_allocation), GFP_KERNEL);
+	if (!new)
+		return -ENOMEM;
+
+	align = ALIGN(align, PAGE_SIZE);
+	size = ALIGN(size, PAGE_SIZE);
+
+	mutex_lock(&ppool->free_list_lock);
+
+	/* on exit free list is fixed */
+	for (pcur = &ppool->free_list; *pcur; pcur = &(*pcur)->next) {
+		cur = *pcur;
+		aligned_start = ALIGN(cur->start, align);
+
+		if (aligned_start >= cur->start &&
+		    aligned_start - cur->start + size <= cur->size) {
+			if (aligned_start == cur->start) {
+				if (aligned_start + size == cur->start + cur->size) {
+					pr_debug("reusing complete block: %pap x %x\n",
+						 &cur->start, cur->size);
+					*pcur = cur->next;
+				} else {
+					pr_debug("cutting block head: %pap x %x ->\n",
+						 &cur->start, cur->size);
+					cur->size -= aligned_start + size - cur->start;
+					cur->start = aligned_start + size;
+					pr_debug("... -> %pap x %x\n",
+						 &cur->start, cur->size);
+					cur = NULL;
+				}
+			} else {
+				if (aligned_start + size == cur->start + cur->size) {
+					pr_debug("cutting block tail: %pap x %x ->\n",
+						 &cur->start, cur->size);
+					cur->size = aligned_start - cur->start;
+					pr_debug("... -> %pap x %x\n",
+						 &cur->start, cur->size);
+					cur = NULL;
+				} else {
+					pr_debug("splitting block into two: %pap x %x ->\n",
+						 &cur->start, cur->size);
+					new->start = aligned_start + size;
+					new->size = cur->start +
+						cur->size - new->start;
+
+					cur->size = aligned_start - cur->start;
+
+					new->next = cur->next;
+					cur->next = new;
+					pr_debug("... -> %pap x %x + %pap x %x\n",
+						 &cur->start, cur->size,
+						 &new->start, new->size);
+
+					cur = NULL;
+					new = NULL;
+				}
+			}
+			found = true;
+			break;
+		} else {
+			cur = NULL;
+		}
+	}
+
+	mutex_unlock(&ppool->free_list_lock);
+
+	if (!found) {
+		kfree(cur);
+		kfree(new);
+		return -ENOMEM;
+	}
+
+	if (!cur) {
+		cur = new;
+		new = NULL;
+	}
+	if (!cur) {
+		cur = kzalloc(sizeof(struct e24_allocation), GFP_KERNEL);
+		if (!cur)
+			return -ENOMEM;
+	}
+
+	kfree(new);
+	pr_debug("returning: %pap x %x\n", &aligned_start, size);
+	cur->start = aligned_start;
+	cur->size = size;
+	cur->pool = pool;
+	atomic_set(&cur->ref, 0);
+	atomic_inc(&cur->ref);
+	*alloc = cur;
+
+	return 0;
+}
+
+static void e24_private_free_pool(struct e24_allocation_pool *pool)
+{
+	struct e24_private_pool *ppool = container_of(pool,
+						      struct e24_private_pool,
+						      pool);
+	kfree(ppool->free_list);
+	kfree(ppool);
+}
+
+static phys_addr_t e24_private_offset(const struct e24_allocation *allocation)
+{
+	struct e24_private_pool *ppool = container_of(allocation->pool,
+						      struct e24_private_pool,
+						      pool);
+	return allocation->start - ppool->start;
+}
+
+static const struct e24_allocation_ops e24_private_pool_ops = {
+	.alloc = e24_private_alloc,
+	.free = e24_private_free,
+	.free_pool = e24_private_free_pool,
+	.offset = e24_private_offset,
+};
+
+long e24_init_private_pool(struct e24_allocation_pool **ppool,
+			   phys_addr_t start, u32 size)
+{
+	struct e24_private_pool *pool = kmalloc(sizeof(*pool), GFP_KERNEL);
+	struct e24_allocation *allocation = kmalloc(sizeof(*allocation),
+						    GFP_KERNEL);
+
+	if (!pool || !allocation) {
+		kfree(pool);
+		kfree(allocation);
+		return -ENOMEM;
+	}
+
+	*allocation = (struct e24_allocation){
+		.pool = &pool->pool,
+		.start = start,
+		.size = size,
+	};
+	*pool = (struct e24_private_pool){
+		.pool = {
+			.ops = &e24_private_pool_ops,
+		},
+		.start = start,
+		.size = size,
+		.free_list = allocation,
+	};
+	mutex_init(&pool->free_list_lock);
+	*ppool = &pool->pool;
+	return 0;
+}
--- /dev/null
+++ b/drivers/e24/e24_alloc.h
@@ -0,0 +1,59 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef E24_ALLOC_H
+#define E24_ALLOC_H
+
+struct e24_allocation_pool;
+struct e24_allocation;
+
+struct e24_allocation_ops {
+	long (*alloc)(struct e24_allocation_pool *allocation_pool,
+		      u32 size, u32 align, struct e24_allocation **alloc);
+	void (*free)(struct e24_allocation *allocation);
+	void (*free_pool)(struct e24_allocation_pool *allocation_pool);
+	phys_addr_t (*offset)(const struct e24_allocation *allocation);
+};
+
+struct e24_allocation_pool {
+	const struct e24_allocation_ops *ops;
+};
+
+struct e24_allocation {
+	struct e24_allocation_pool *pool;
+	struct e24_allocation *next;
+	phys_addr_t start;
+	u32 size;
+	atomic_t ref;
+};
+
+static inline void e24_free_pool(struct e24_allocation_pool *allocation_pool)
+{
+	allocation_pool->ops->free_pool(allocation_pool);
+}
+
+static inline void e24_free(struct e24_allocation *allocation)
+{
+	return allocation->pool->ops->free(allocation);
+}
+
+static inline long e24_allocate(struct e24_allocation_pool *allocation_pool,
+				u32 size, u32 align,
+				struct e24_allocation **alloc)
+{
+	return allocation_pool->ops->alloc(allocation_pool,
+					   size, align, alloc);
+}
+
+static inline void e24_allocation_put(struct e24_allocation *e24_allocation)
+{
+	if (atomic_dec_and_test(&e24_allocation->ref))
+		e24_allocation->pool->ops->free(e24_allocation);
+}
+
+static inline phys_addr_t e24_allocation_offset(const struct e24_allocation *allocation)
+{
+	return allocation->pool->ops->offset(allocation);
+}
+
+long e24_init_private_pool(struct e24_allocation_pool **ppool, phys_addr_t start, u32 size);
+
+#endif
--- /dev/null
+++ b/drivers/e24/starfive_e24.c
@@ -0,0 +1,1524 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * e24 driver for StarFive JH7110 SoC
+ *
+ * Copyright (c) 2021 StarFive Technology Co., Ltd.
+ * Author: Shanlong Li <shanlong.li@starfivetech.com>
+ */
+#include <linux/version.h>
+#include <linux/atomic.h>
+#include <linux/acpi.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 16, 0)
+#include <linux/dma-mapping.h>
+#else
+#include <linux/dma-direct.h>
+#endif
+#include <linux/firmware.h>
+#include <linux/fs.h>
+#include <linux/hashtable.h>
+#include <linux/highmem.h>
+#include <linux/idr.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/property.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/sort.h>
+#include <linux/mman.h>
+#include <linux/uaccess.h>
+#include <linux/mailbox_controller.h>
+#include <linux/mailbox_client.h>
+
+#include <linux/bsearch.h>
+
+#include "e24_alloc.h"
+#include "starfive_e24.h"
+#include "starfive_e24_hw.h"
+
+#define EMBOX_MAX_MSG_LEN	4
+
+static DEFINE_IDA(e24_nodeid);
+
+struct e24_dsp_cmd {
+	__u32 flags;
+	__u32 in_data_size;
+	__u32 out_data_size;
+	union {
+		__u32 in_data_addr;
+		__u8 in_data[E24_DSP_CMD_INLINE_DATA_SIZE];
+	};
+	union {
+		__u32 out_data_addr;
+		__u8 out_data[E24_DSP_CMD_INLINE_DATA_SIZE];
+	};
+};
+
+struct e24_ioctl_user {
+	u32 flags;
+	u32 in_data_size;
+	u32 out_data_size;
+	u64 in_data_addr;
+	u64 out_data_addr;
+};
+
+struct e24_ioctl_request {
+	struct e24_ioctl_user ioctl_data;
+	phys_addr_t in_data_phys;
+	phys_addr_t out_data_phys;
+	struct e24_mapping *buffer_mapping;
+
+	union {
+		struct e24_mapping in_data_mapping;
+		u8 in_data[E24_DSP_CMD_INLINE_DATA_SIZE];
+	};
+	union {
+		struct e24_mapping out_data_mapping;
+		u8 out_data[E24_DSP_CMD_INLINE_DATA_SIZE];
+	};
+};
+
+static int firmware_command_timeout = 10;
+
+static inline void e24_comm_read(volatile void __iomem *addr, void *p,
+				  size_t sz)
+{
+	size_t sz32 = sz & ~3;
+	u32 v;
+
+	while (sz32) {
+		v = __raw_readl(addr);
+		memcpy(p, &v, sizeof(v));
+		p += 4;
+		addr += 4;
+		sz32 -= 4;
+	}
+	sz &= 3;
+	if (sz) {
+		v = __raw_readl(addr);
+		memcpy(p, &v, sz);
+	}
+}
+
+static inline void e24_comm_write(volatile void __iomem *addr, const void *p,
+				  size_t sz)
+{
+	size_t sz32 = sz & ~3;
+	u32 v;
+
+	while (sz32) {
+		memcpy(&v, p, sizeof(v));
+		__raw_writel(v, addr);
+		p += 4;
+		addr += 4;
+		sz32 -= 4;
+	}
+	sz &= 3;
+	if (sz) {
+		v = 0;
+		memcpy(&v, p, sz);
+		__raw_writel(v, addr);
+	}
+}
+
+static bool e24_cacheable(struct e24_device *e24_dat, unsigned long pfn,
+			  unsigned long n_pages)
+{
+	if (e24_dat->hw_ops->cacheable) {
+		return e24_dat->hw_ops->cacheable(e24_dat->hw_arg, pfn, n_pages);
+	} else {
+		unsigned long i;
+
+		for (i = 0; i < n_pages; ++i)
+			if (!pfn_valid(pfn + i))
+				return false;
+		return true;
+	}
+}
+
+static int e24_compare_address_sort(const void *a, const void *b)
+{
+	const struct e24_address_map_entry *pa = a;
+	const struct e24_address_map_entry *pb = b;
+
+	if (pa->src_addr < pb->src_addr &&
+		pb->src_addr - pa->src_addr >= pa->size)
+		return -1;
+	if (pa->src_addr > pb->src_addr &&
+		pa->src_addr - pb->src_addr >= pb->size)
+		return 1;
+
+	return 0;
+}
+
+static int e24_compare_address_search(const void *a, const void *b)
+{
+	const phys_addr_t *pa = a;
+
+	return e24_compare_address(*pa, b);
+}
+
+struct e24_address_map_entry *
+e24_get_address_mapping(const struct e24_address_map *map, phys_addr_t addr)
+{
+	return bsearch(&addr, map->entry, map->n, sizeof(*map->entry),
+			e24_compare_address_search);
+}
+
+u32 e24_translate_to_dsp(const struct e24_address_map *map, phys_addr_t addr)
+{
+#ifdef E24_MEM_MAP
+	return addr;
+#else
+	struct e24_address_map_entry *entry = e24_get_address_mapping(map, addr);
+
+	if (!entry)
+		return E24_NO_TRANSLATION;
+	return entry->dst_addr + addr - entry->src_addr;
+#endif
+}
+
+static int e24_dma_direction(unsigned int flags)
+{
+	static const enum dma_data_direction e24_dma_direction[] = {
+		[0] = DMA_NONE,
+		[E24_FLAG_READ] = DMA_TO_DEVICE,
+		[E24_FLAG_WRITE] = DMA_FROM_DEVICE,
+		[E24_FLAG_READ_WRITE] = DMA_BIDIRECTIONAL,
+	};
+	return e24_dma_direction[flags & E24_FLAG_READ_WRITE];
+}
+
+static void e24_dma_sync_for_cpu(struct e24_device *e24_dat,
+				 unsigned long virt,
+				 phys_addr_t phys,
+				 unsigned long size,
+				 unsigned long flags)
+{
+	if (e24_dat->hw_ops->dma_sync_for_cpu)
+		e24_dat->hw_ops->dma_sync_for_cpu(e24_dat->hw_arg,
+					      (void *)virt, phys, size,
+					      flags);
+	else
+		dma_sync_single_for_cpu(e24_dat->dev, phys_to_dma(e24_dat->dev, phys), size,
+			e24_dma_direction(flags));
+}
+
+static void starfive_mbox_receive_message(struct mbox_client *client, void *message)
+{
+	struct e24_device *e24_dat = dev_get_drvdata(client->dev);
+
+	complete(&e24_dat->tx_channel->tx_complete);
+}
+
+static struct mbox_chan *
+starfive_mbox_request_channel(struct device *dev, const char *name)
+{
+	struct mbox_client *client;
+	struct mbox_chan *channel;
+
+	client = devm_kzalloc(dev, sizeof(*client), GFP_KERNEL);
+	if (!client)
+		return ERR_PTR(-ENOMEM);
+
+	client->dev		= dev;
+	client->rx_callback	= starfive_mbox_receive_message;
+	client->tx_prepare	= NULL;
+	client->tx_done		= NULL;
+	client->tx_block	= true;
+	client->knows_txdone	= false;
+	client->tx_tout		= 3000;
+
+	channel = mbox_request_channel_byname(client, name);
+	if (IS_ERR(channel)) {
+		dev_warn(dev, "Failed to request %s channel\n", name);
+		return NULL;
+	}
+
+	return channel;
+}
+
+static void e24_vm_open(struct vm_area_struct *vma)
+{
+	struct e24_allocation *cur = vma->vm_private_data;
+
+	atomic_inc(&cur->ref);
+}
+
+static void e24_vm_close(struct vm_area_struct *vma)
+{
+	e24_allocation_put(vma->vm_private_data);
+}
+
+static const struct vm_operations_struct e24_vm_ops = {
+	.open = e24_vm_open,
+	.close = e24_vm_close,
+};
+
+static long e24_synchronize(struct e24_device *dev)
+{
+
+	struct e24_comm *queue = dev->queue;
+	struct e24_dsp_cmd __iomem *cmd = queue->comm;
+	u32 flags;
+	unsigned long deadline = jiffies + 10 * HZ;
+
+	do {
+		flags = __raw_readl(&cmd->flags);
+		/* memory barrier */
+		rmb();
+		if (flags == 0x104)
+			return 0;
+
+		schedule();
+	} while (time_before(jiffies, deadline));
+
+	return -1;
+}
+
+static int e24_open(struct inode *inode, struct file *filp)
+{
+	struct e24_device *e24_dev = container_of(filp->private_data,
+					struct e24_device, miscdev);
+	int rc = 0;
+
+	rc = pm_runtime_get_sync(e24_dev->dev);
+	if (rc < 0)
+		return rc;
+
+	spin_lock_init(&e24_dev->busy_list_lock);
+	filp->private_data = e24_dev;
+	mdelay(1);
+
+	return 0;
+}
+
+int e24_release(struct inode *inode, struct file *filp)
+{
+	struct e24_device *e24_dev = (struct e24_device *)filp->private_data;
+	int rc = 0;
+
+	rc = pm_runtime_put_sync(e24_dev->dev);
+	if (rc < 0)
+		return rc;
+
+	return 0;
+}
+
+static ssize_t mbox_e24_message_write(struct file *filp,
+				       const char __user *userbuf,
+				       size_t count, loff_t *ppos)
+{
+	struct e24_device *edev = filp->private_data;
+	void *data;
+	int ret;
+
+	if (!edev->tx_channel) {
+		dev_err(edev->dev, "Channel cannot do Tx\n");
+		return -EINVAL;
+	}
+
+	if (count > EMBOX_MAX_MSG_LEN) {
+		dev_err(edev->dev,
+			"Message length %zd greater than max allowed %d\n",
+			count, EMBOX_MAX_MSG_LEN);
+		return -EINVAL;
+	}
+
+	edev->message = kzalloc(EMBOX_MAX_MSG_LEN, GFP_KERNEL);
+	if (!edev->message)
+		return -ENOMEM;
+
+	ret = copy_from_user(edev->message, userbuf, count);
+	if (ret) {
+		ret = -EFAULT;
+		goto out;
+	}
+
+	print_hex_dump_bytes("Client: Sending: Message: ", DUMP_PREFIX_ADDRESS,
+			     edev->message, EMBOX_MAX_MSG_LEN);
+	data = edev->message;
+	pr_debug("%s:%d, %d\n", __func__, __LINE__, *((int *)data));
+	ret = mbox_send_message(edev->tx_channel, data);
+
+	if (ret < 0 || !edev->tx_channel->active_req)
+		dev_err(edev->dev, "Failed to send message via mailbox:%d\n", ret);
+
+out:
+	kfree(edev->message);
+	edev->tx_channel->active_req =  NULL;
+
+	return ret < 0 ? ret : count;
+}
+
+static long _e24_copy_user_phys(struct e24_device *edev,
+				unsigned long vaddr, unsigned long size,
+				phys_addr_t paddr, unsigned long flags,
+				bool to_phys)
+{
+	void __iomem *p = ioremap(paddr, size);
+	unsigned long rc;
+
+	if (!p) {
+		dev_err(edev->dev,
+			"couldn't ioremap %pap x 0x%08x\n",
+			&paddr, (u32)size);
+		return -EINVAL;
+	}
+	if (to_phys)
+		rc = raw_copy_from_user(__io_virt(p),
+					(void __user *)vaddr, size);
+	else
+		rc = copy_to_user((void __user *)vaddr,
+					__io_virt(p), size);
+	iounmap(p);
+	if (rc)
+		return -EFAULT;
+	return 0;
+}
+
+static long e24_copy_user_to_phys(struct e24_device *edev,
+				unsigned long vaddr, unsigned long size,
+				phys_addr_t paddr, unsigned long flags)
+{
+	return _e24_copy_user_phys(edev, vaddr, size, paddr, flags, true);
+}
+
+static long e24_copy_user_from_phys(struct e24_device *edev,
+				unsigned long vaddr, unsigned long size,
+				phys_addr_t paddr, unsigned long flags)
+{
+	return _e24_copy_user_phys(edev, vaddr, size, paddr, flags, false);
+}
+
+static long e24_copy_virt_to_phys(struct e24_device *edev,
+				unsigned long flags,
+				unsigned long vaddr, unsigned long size,
+				phys_addr_t *paddr,
+				struct e24_alien_mapping *mapping)
+{
+	phys_addr_t phys;
+	unsigned long align = clamp(vaddr & -vaddr, 16ul, PAGE_SIZE);
+	unsigned long offset = vaddr & (align - 1);
+	struct e24_allocation *allocation;
+	long rc;
+
+	rc = e24_allocate(edev->pool,
+			  size + align, align, &allocation);
+	if (rc < 0)
+		return rc;
+
+	phys = (allocation->start & -align) | offset;
+	if (phys < allocation->start)
+		phys += align;
+
+	if (flags & E24_FLAG_READ) {
+		if (e24_copy_user_to_phys(edev, vaddr,
+					size, phys, flags)) {
+			e24_allocation_put(allocation);
+			return -EFAULT;
+		}
+	}
+
+	*paddr = phys;
+	*mapping = (struct e24_alien_mapping){
+		.vaddr = vaddr,
+		.size = size,
+		.paddr = *paddr,
+		.allocation = allocation,
+		.type = ALIEN_COPY,
+	};
+	pr_debug("%s: copying to pa: %pap\n", __func__, paddr);
+
+	return 0;
+}
+
+static long e24_writeback_alien_mapping(struct e24_device *edev,
+					struct e24_alien_mapping *alien_mapping,
+					unsigned long flags)
+{
+	struct page *page;
+	size_t nr_pages;
+	size_t i;
+	long ret = 0;
+
+	switch (alien_mapping->type) {
+	case ALIEN_GUP:
+		e24_dma_sync_for_cpu(edev,
+				     alien_mapping->vaddr,
+				     alien_mapping->paddr,
+				     alien_mapping->size,
+				     flags);
+		pr_debug("%s: dirtying alien GUP @va = %p, pa = %pap\n",
+			 __func__, (void __user *)alien_mapping->vaddr,
+			 &alien_mapping->paddr);
+		page = pfn_to_page(__phys_to_pfn(alien_mapping->paddr));
+		nr_pages = PFN_UP(alien_mapping->vaddr + alien_mapping->size) -
+			PFN_DOWN(alien_mapping->vaddr);
+		for (i = 0; i < nr_pages; ++i)
+			SetPageDirty(page + i);
+		break;
+
+	case ALIEN_COPY:
+		pr_debug("%s: synchronizing alien copy @pa = %pap back to %p\n",
+			 __func__, &alien_mapping->paddr,
+			 (void __user *)alien_mapping->vaddr);
+		if (e24_copy_user_from_phys(edev,
+					    alien_mapping->vaddr,
+					    alien_mapping->size,
+					    alien_mapping->paddr,
+					    flags))
+			ret = -EINVAL;
+		break;
+
+	default:
+		break;
+	}
+	return ret;
+}
+
+static bool vma_needs_cache_ops(struct vm_area_struct *vma)
+{
+	pgprot_t prot = vma->vm_page_prot;
+
+	return pgprot_val(prot) != pgprot_val(pgprot_noncached(prot)) &&
+		pgprot_val(prot) != pgprot_val(pgprot_writecombine(prot));
+}
+
+static void e24_alien_mapping_destroy(struct e24_alien_mapping *alien_mapping)
+{
+	switch (alien_mapping->type) {
+	case ALIEN_COPY:
+		e24_allocation_put(alien_mapping->allocation);
+		break;
+	default:
+		break;
+	}
+}
+
+static long __e24_unshare_block(struct file *filp, struct e24_mapping *mapping,
+				unsigned long flags)
+{
+	long ret = 0;
+	struct e24_device *edev = filp->private_data;
+
+	switch (mapping->type & ~E24_MAPPING_KERNEL) {
+	case E24_MAPPING_NATIVE:
+		if (flags & E24_FLAG_WRITE) {
+			e24_dma_sync_for_cpu(edev,
+				     mapping->native.vaddr,
+				     mapping->native.m_allocation->start,
+				     mapping->native.m_allocation->size,
+				     flags);
+		}
+		e24_allocation_put(mapping->native.m_allocation);
+		break;
+
+	case E24_MAPPING_ALIEN:
+		if (flags & E24_FLAG_WRITE) {
+			ret = e24_writeback_alien_mapping(edev,
+							  &mapping->alien_mapping,
+							  flags);
+		}
+		e24_alien_mapping_destroy(&mapping->alien_mapping);
+		break;
+
+	case E24_MAPPING_KERNEL:
+		break;
+
+	default:
+		break;
+	}
+
+	mapping->type = E24_MAPPING_NONE;
+
+	return ret;
+}
+
+static long __e24_share_block(struct file *filp,
+			      unsigned long virt, unsigned long size,
+			      unsigned long flags, phys_addr_t *paddr,
+			      struct e24_mapping *mapping)
+{
+	phys_addr_t phys = ~0ul;
+	struct e24_device *edev = filp->private_data;
+	struct mm_struct *mm = current->mm;
+	struct vm_area_struct *vma = find_vma(mm, virt);
+	bool do_cache = true;
+	long rc = -EINVAL;
+
+	if (!vma) {
+		pr_debug("%s: no vma for vaddr/size = 0x%08lx/0x%08lx\n",
+			 __func__, virt, size);
+		return -EINVAL;
+	}
+
+	if (virt + size < virt || vma->vm_start > virt)
+		return -EINVAL;
+
+	if (vma && (vma->vm_file == filp)) {
+		struct e24_device *vm_file = vma->vm_file->private_data;
+		struct e24_allocation *e24_user_allocation = vma->vm_private_data;
+
+		phys = vm_file->shared_mem + (vma->vm_pgoff << PAGE_SHIFT) +
+			virt - vma->vm_start;
+		pr_debug("%s: E24 allocation at 0x%08lx, paddr: %pap\n",
+			 __func__, virt, &phys);
+
+		rc = 0;
+		mapping->type = E24_MAPPING_NATIVE;
+		mapping->native.m_allocation = e24_user_allocation;
+		mapping->native.vaddr = virt;
+		atomic_inc(&e24_user_allocation->ref);
+		do_cache = vma_needs_cache_ops(vma);
+	}
+	if (rc < 0) {
+		struct e24_alien_mapping *alien_mapping =
+			&mapping->alien_mapping;
+
+		/* Otherwise this is alien allocation. */
+		pr_debug("%s: non-E24 allocation at 0x%08lx\n",
+			 __func__, virt);
+
+		rc = e24_copy_virt_to_phys(edev, flags,
+					   virt, size, &phys,
+					   alien_mapping);
+
+		if (rc < 0) {
+			pr_debug("%s: couldn't map virt to phys\n",
+				 __func__);
+			return -EINVAL;
+		}
+
+		phys = alien_mapping->paddr +
+			virt - alien_mapping->vaddr;
+
+		mapping->type = E24_MAPPING_ALIEN;
+	}
+
+	*paddr = phys;
+	pr_debug("%s: mapping = %p, mapping->type = %d\n",
+		 __func__, mapping, mapping->type);
+
+	return 0;
+}
+
+
+static void e24_unmap_request_nowb(struct file *filp, struct e24_ioctl_request *rq)
+{
+	if (rq->ioctl_data.in_data_size > E24_DSP_CMD_INLINE_DATA_SIZE)
+		__e24_unshare_block(filp, &rq->in_data_mapping, 0);
+	if (rq->ioctl_data.out_data_size > E24_DSP_CMD_INLINE_DATA_SIZE)
+		__e24_unshare_block(filp, &rq->out_data_mapping, 0);
+}
+
+static long e24_unmap_request(struct file *filp, struct e24_ioctl_request *rq)
+{
+	long ret = 0;
+
+	if (rq->ioctl_data.in_data_size > E24_DSP_CMD_INLINE_DATA_SIZE)
+		__e24_unshare_block(filp, &rq->in_data_mapping, E24_FLAG_READ);
+
+	if (rq->ioctl_data.out_data_size > E24_DSP_CMD_INLINE_DATA_SIZE) {
+		ret = __e24_unshare_block(filp, &rq->out_data_mapping,
+					 E24_FLAG_WRITE);
+		if (ret < 0)
+			pr_debug("%s: out_data could not be unshared\n", __func__);
+
+	} else {
+		if (copy_to_user((void __user *)(unsigned long)rq->ioctl_data.out_data_addr,
+				 rq->out_data,
+				 rq->ioctl_data.out_data_size)) {
+			pr_debug("%s: out_data could not be copied\n", __func__);
+			ret = -EFAULT;
+		}
+	}
+
+	return ret;
+}
+
+static bool e24_cmd_complete(struct e24_comm *share_com)
+{
+	struct e24_dsp_cmd __iomem *cmd = share_com->comm;
+	u32 flags = __raw_readl(&cmd->flags);
+
+	rmb();
+	return (flags & (E24_CMD_FLAG_REQUEST_VALID |
+			 E24_CMD_FLAG_RESPONSE_VALID)) ==
+		(E24_CMD_FLAG_REQUEST_VALID |
+		 E24_CMD_FLAG_RESPONSE_VALID);
+}
+
+static long e24_complete_poll(struct e24_device *edev, struct e24_comm *comm,
+				  bool (*cmd_complete)(struct e24_comm *p), struct e24_ioctl_request *rq)
+{
+	unsigned long deadline = jiffies + firmware_command_timeout * HZ;
+
+	do {
+		if (cmd_complete(comm)) {
+			pr_debug("%s: poll complete.\n", __func__);
+			return 0;
+		}
+		schedule();
+	} while (time_before(jiffies, deadline));
+
+	pr_debug("%s: poll complete cmd timeout.\n", __func__);
+
+	return -EBUSY;
+}
+
+static long e24_map_request(struct file *filp, struct e24_ioctl_request *rq, struct mm_struct *mm)
+{
+	long ret = 0;
+
+	mmap_read_lock(mm);
+	if (rq->ioctl_data.in_data_size > E24_DSP_CMD_INLINE_DATA_SIZE) {
+		ret = __e24_share_block(filp, rq->ioctl_data.in_data_addr,
+					rq->ioctl_data.in_data_size,
+					E24_FLAG_READ, &rq->in_data_phys,
+					&rq->in_data_mapping);
+		if (ret < 0) {
+			pr_debug("%s: in_data could not be shared\n", __func__);
+			goto share_err;
+		}
+	} else {
+		if (copy_from_user(rq->in_data,
+				   (void __user *)(unsigned long)rq->ioctl_data.in_data_addr,
+				   rq->ioctl_data.in_data_size)) {
+			pr_debug("%s: in_data could not be copied\n",
+				 __func__);
+			ret = -EFAULT;
+			goto share_err;
+		}
+	}
+
+	if (rq->ioctl_data.out_data_size > E24_DSP_CMD_INLINE_DATA_SIZE) {
+		ret = __e24_share_block(filp, rq->ioctl_data.out_data_addr,
+					rq->ioctl_data.out_data_size,
+					E24_FLAG_WRITE, &rq->out_data_phys,
+					&rq->out_data_mapping);
+		if (ret < 0) {
+			pr_debug("%s: out_data could not be shared\n",
+				 __func__);
+			goto share_err;
+		}
+	}
+share_err:
+	mmap_read_unlock(mm);
+	if (ret < 0)
+		e24_unmap_request_nowb(filp, rq);
+	return ret;
+
+}
+
+static void e24_fill_hw_request(struct e24_dsp_cmd __iomem *cmd,
+				struct e24_ioctl_request *rq,
+				const struct e24_address_map *map)
+{
+	__raw_writel(rq->ioctl_data.in_data_size, &cmd->in_data_size);
+	__raw_writel(rq->ioctl_data.out_data_size, &cmd->out_data_size);
+
+	if (rq->ioctl_data.in_data_size > E24_DSP_CMD_INLINE_DATA_SIZE)
+		__raw_writel(e24_translate_to_dsp(map, rq->in_data_phys),
+				&cmd->in_data_addr);
+	else
+		e24_comm_write(&cmd->in_data, rq->in_data,
+			       rq->ioctl_data.in_data_size);
+
+	if (rq->ioctl_data.out_data_size > E24_DSP_CMD_INLINE_DATA_SIZE)
+		__raw_writel(e24_translate_to_dsp(map, rq->out_data_phys),
+				&cmd->out_data_addr);
+
+	wmb();
+	/* update flags */
+	__raw_writel(rq->ioctl_data.flags, &cmd->flags);
+}
+
+static long e24_complete_hw_request(struct e24_dsp_cmd __iomem *cmd,
+				    struct e24_ioctl_request *rq)
+{
+	u32 flags = __raw_readl(&cmd->flags);
+
+	if (rq->ioctl_data.out_data_size <= E24_DSP_CMD_INLINE_DATA_SIZE)
+		e24_comm_read(&cmd->out_data, rq->out_data,
+			      rq->ioctl_data.out_data_size);
+
+	__raw_writel(0, &cmd->flags);
+
+	return (flags & E24_QUEUE_VALID_FLAGS) ? -ENXIO : 0;
+}
+
+static long e24_ioctl_submit_task(struct file *filp,
+				  struct e24_ioctl_user __user *msg)
+{
+	struct e24_device *edev = filp->private_data;
+	struct e24_comm *queue = edev->queue;
+	struct e24_ioctl_request rq_data, *vrq = &rq_data;
+	void *data = &edev->mbox_data;
+	int ret = -ENOMEM;
+	int irq_mode = edev->irq_mode;
+
+	if (copy_from_user(&vrq->ioctl_data, msg, sizeof(*msg)))
+		return -EFAULT;
+
+	if (vrq->ioctl_data.flags & ~E24_QUEUE_VALID_FLAGS) {
+		dev_dbg(edev->dev, "%s: invalid flags 0x%08x\n",
+			__func__, vrq->ioctl_data.flags);
+		return -EINVAL;
+	}
+
+	ret = e24_map_request(filp, vrq, current->mm);
+	if (ret < 0)
+		return ret;
+
+	mutex_lock(&queue->lock);
+	e24_fill_hw_request(queue->comm, vrq, &edev->address_map);
+
+	if (irq_mode) {
+		ret = mbox_send_message(edev->tx_channel, data);
+		mbox_chan_txdone(edev->tx_channel, ret);
+	} else {
+		ret = e24_complete_poll(edev, queue, e24_cmd_complete, vrq);
+	}
+
+	ret = e24_complete_hw_request(queue->comm, vrq);
+	mutex_unlock(&queue->lock);
+
+	if (ret == 0)
+		ret = e24_unmap_request(filp, vrq);
+
+	return ret;
+}
+
+static long e24_ioctl_get_channel(struct file *filp,
+				void __user *msg)
+{
+	struct e24_device *edev = filp->private_data;
+
+	if (edev->tx_channel == NULL)
+		edev->tx_channel = starfive_mbox_request_channel(edev->dev, "tx");
+	if (edev->rx_channel == NULL)
+		edev->rx_channel = starfive_mbox_request_channel(edev->dev, "rx");
+
+	return 0;
+}
+
+static long e24_ioctl_free_channel(struct file *filp,
+				void __user *msg)
+{
+	struct e24_device *edev = filp->private_data;
+
+	if (edev->rx_channel)
+		mbox_free_channel(edev->rx_channel);
+	if (edev->tx_channel)
+		mbox_free_channel(edev->tx_channel);
+
+	edev->rx_channel = NULL;
+	edev->tx_channel = NULL;
+	return 0;
+}
+
+static void e24_allocation_queue(struct e24_device *edev,
+				 struct e24_allocation *e24_pool_allocation)
+{
+	spin_lock(&edev->busy_list_lock);
+
+	e24_pool_allocation->next = edev->busy_list;
+	edev->busy_list = e24_pool_allocation;
+
+	spin_unlock(&edev->busy_list_lock);
+}
+
+static struct e24_allocation *e24_allocation_dequeue(struct e24_device *edev,
+						     phys_addr_t paddr, u32 size)
+{
+	struct e24_allocation **pcur;
+	struct e24_allocation *cur;
+
+	spin_lock(&edev->busy_list_lock);
+
+	for (pcur = &edev->busy_list; (cur = *pcur); pcur = &((*pcur)->next)) {
+		pr_debug("%s: %pap / %pap x %d\n", __func__, &paddr, &cur->start, cur->size);
+		if (paddr >= cur->start && paddr + size - cur->start <= cur->size) {
+			*pcur = cur->next;
+			break;
+		}
+	}
+
+	spin_unlock(&edev->busy_list_lock);
+	return cur;
+}
+
+static int e24_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+	int err;
+	struct e24_device *edev = filp->private_data;
+	unsigned long pfn = vma->vm_pgoff + PFN_DOWN(edev->shared_mem);
+	struct e24_allocation *e24_user_allocation;
+
+	e24_user_allocation = e24_allocation_dequeue(filp->private_data,
+						pfn << PAGE_SHIFT,
+						vma->vm_end - vma->vm_start);
+	if (e24_user_allocation) {
+		pgprot_t prot = vma->vm_page_prot;
+
+		if (!e24_cacheable(edev, pfn,
+				   PFN_DOWN(vma->vm_end - vma->vm_start))) {
+			prot = pgprot_writecombine(prot);
+			vma->vm_page_prot = prot;
+		}
+
+		err = remap_pfn_range(vma, vma->vm_start, pfn,
+				      vma->vm_end - vma->vm_start,
+				      prot);
+
+		vma->vm_private_data = e24_user_allocation;
+		vma->vm_ops = &e24_vm_ops;
+	} else {
+		err = -EINVAL;
+	}
+
+	return err;
+}
+
+static long e24_ioctl_free(struct file *filp,
+			   struct e24_ioctl_alloc __user *p)
+{
+	struct mm_struct *mm = current->mm;
+	struct e24_ioctl_alloc user_ioctl_alloc;
+	struct vm_area_struct *vma;
+	unsigned long start;
+
+	if (copy_from_user(&user_ioctl_alloc, p, sizeof(*p)))
+		return -EFAULT;
+
+	start = user_ioctl_alloc.addr;
+	pr_debug("%s: virt_addr = 0x%08lx\n", __func__, start);
+
+	mmap_read_lock(mm);
+	vma = find_vma(mm, start);
+
+	if (vma && vma->vm_file == filp &&
+	    vma->vm_start <= start && start < vma->vm_end) {
+		size_t size;
+
+		start = vma->vm_start;
+		size = vma->vm_end - vma->vm_start;
+		mmap_read_unlock(mm);
+		pr_debug("%s: 0x%lx x %zu\n", __func__, start, size);
+		return vm_munmap(start, size);
+	}
+	pr_debug("%s: no vma/bad vma for vaddr = 0x%08lx\n", __func__, start);
+	mmap_read_unlock(mm);
+
+	return -EINVAL;
+}
+
+static long e24_ioctl_alloc(struct file *filp,
+			    struct e24_ioctl_alloc __user *p)
+{
+	struct e24_device *edev = filp->private_data;
+	struct e24_allocation *e24_pool_allocation;
+	unsigned long vaddr;
+	struct e24_ioctl_alloc user_ioctl_alloc;
+	long err;
+
+	if (copy_from_user(&user_ioctl_alloc, p, sizeof(*p)))
+		return -EFAULT;
+
+	pr_debug("%s: size = %d, align = %x\n", __func__,
+		 user_ioctl_alloc.size, user_ioctl_alloc.align);
+
+	err = e24_allocate(edev->pool,
+			   user_ioctl_alloc.size,
+			   user_ioctl_alloc.align,
+			   &e24_pool_allocation);
+	if (err)
+		return err;
+
+	e24_allocation_queue(edev, e24_pool_allocation);
+
+	vaddr = vm_mmap(filp, 0, e24_pool_allocation->size,
+			PROT_READ | PROT_WRITE, MAP_SHARED,
+			e24_allocation_offset(e24_pool_allocation));
+
+	user_ioctl_alloc.addr = vaddr;
+
+	if (copy_to_user(p, &user_ioctl_alloc, sizeof(*p))) {
+		vm_munmap(vaddr, user_ioctl_alloc.size);
+		return -EFAULT;
+	}
+	return 0;
+}
+
+static long e24_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+	long retval;
+
+	switch (cmd) {
+	case E24_IOCTL_SEND:
+		retval = e24_ioctl_submit_task(filp, (struct e24_ioctl_user *)arg);
+		break;
+	case E24_IOCTL_GET_CHANNEL:
+		retval = e24_ioctl_get_channel(filp, NULL);
+		break;
+	case E24_IOCTL_FREE_CHANNEL:
+		retval = e24_ioctl_free_channel(filp, NULL);
+		break;
+	case E24_IOCTL_ALLOC:
+		retval = e24_ioctl_alloc(filp, (struct e24_ioctl_alloc __user *)arg);
+		break;
+	case E24_IOCTL_FREE:
+		retval = e24_ioctl_free(filp, (struct e24_ioctl_alloc __user *)arg);
+		break;
+	default:
+		retval = -EINVAL;
+		break;
+	}
+	return retval;
+}
+
+static const struct file_operations e24_fops = {
+	.owner  = THIS_MODULE,
+	.unlocked_ioctl = e24_ioctl,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl = e24_ioctl,
+#endif
+	.open = e24_open,
+	.release = e24_release,
+	.write = mbox_e24_message_write,
+	.mmap = e24_mmap,
+};
+
+void mailbox_task(struct platform_device *pdev)
+{
+	struct e24_device *e24_dev = platform_get_drvdata(pdev);
+
+	e24_dev->tx_channel = starfive_mbox_request_channel(e24_dev->dev, "tx");
+	e24_dev->rx_channel = starfive_mbox_request_channel(e24_dev->dev, "rx");
+	pr_debug("%s:%d.%#llx\n", __func__, __LINE__, (u64)e24_dev->rx_channel);
+}
+
+static long e24_init_mem_pool(struct platform_device *pdev, struct e24_device *devs)
+{
+	struct resource *mem;
+
+	mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ecmd");
+	if (!mem)
+		return -ENODEV;
+
+	devs->comm_phys = mem->start;
+	devs->comm = devm_ioremap(&pdev->dev, mem->start, mem->end - mem->start);
+	mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, "espace");
+	if (!mem)
+		return -ENODEV;
+
+	devs->shared_mem = mem->start;
+	devs->shared_size = resource_size(mem);
+	pr_debug("%s:%d.%llx,%llx\n", __func__, __LINE__, devs->comm_phys, devs->shared_mem);
+	return e24_init_private_pool(&devs->pool, devs->shared_mem, devs->shared_size);
+}
+
+static int e24_init_address_map(struct device *dev,
+			 struct e24_address_map *map)
+{
+#if IS_ENABLED(CONFIG_OF)
+	struct device_node *pnode = dev->of_node;
+	struct device_node *node;
+	int rlen, off;
+	const __be32 *ranges = of_get_property(pnode, "ranges", &rlen);
+	int na, pna, ns;
+	int i;
+
+	if (!ranges) {
+		dev_dbg(dev, "%s: no 'ranges' property in the device tree, no translation at that level\n",
+			__func__);
+		goto empty;
+	}
+
+	node = of_get_next_child(pnode, NULL);
+	if (!node) {
+		dev_warn(dev, "%s: no child node found in the device tree, no translation at that level\n",
+			 __func__);
+		goto empty;
+	}
+
+	na = of_n_addr_cells(node);
+	ns = of_n_size_cells(node);
+	pna = of_n_addr_cells(pnode);
+
+	rlen /= 4;
+	map->n = rlen / (na + pna + ns);
+	map->entry = kmalloc_array(map->n, sizeof(*map->entry), GFP_KERNEL);
+	if (!map->entry)
+		return -ENOMEM;
+
+	dev_dbg(dev,
+		"%s: na = %d, pna = %d, ns = %d, rlen = %d cells, n = %d\n",
+		__func__, na, pna, ns, rlen, map->n);
+
+	for (off = 0, i = 0; off < rlen; off += na + pna + ns, ++i) {
+		map->entry[i].src_addr = of_translate_address(node,
+							      ranges + off);
+		map->entry[i].dst_addr = of_read_number(ranges + off, na);
+		map->entry[i].size = of_read_number(ranges + off + na + pna, ns);
+		dev_dbg(dev,
+			"  src_addr = 0x%llx, dst_addr = 0x%lx, size = 0x%lx\n",
+			(unsigned long long)map->entry[i].src_addr,
+			(unsigned long)map->entry[i].dst_addr,
+			(unsigned long)map->entry[i].size);
+	}
+	sort(map->entry, map->n, sizeof(*map->entry), e24_compare_address_sort, NULL);
+
+	of_node_put(node);
+	return 0;
+
+empty:
+#endif
+	map->n = 1;
+	map->entry = kmalloc(sizeof(*map->entry), GFP_KERNEL);
+	map->entry->src_addr = 0;
+	map->entry->dst_addr = 0;
+	map->entry->size = ~0u;
+	return -ENOMEM;
+}
+
+typedef long e24_init_function(struct platform_device *pdev);
+
+static inline void e24_init(struct e24_device *e24_hw)
+{
+	if (e24_hw->hw_ops->init)
+		e24_hw->hw_ops->init(e24_hw->hw_arg);
+}
+
+static inline void e24_release_e24(struct e24_device *e24_hw)
+{
+	if (e24_hw->hw_ops->reset)
+		e24_hw->hw_ops->release(e24_hw->hw_arg);
+}
+
+static inline void e24_halt_e24(struct e24_device *e24_hw)
+{
+	if (e24_hw->hw_ops->halt)
+		e24_hw->hw_ops->halt(e24_hw->hw_arg);
+}
+
+static inline int e24_enable_e24(struct e24_device *e24_hw)
+{
+	if (e24_hw->hw_ops->enable)
+		return e24_hw->hw_ops->enable(e24_hw->hw_arg);
+	else
+		return -EINVAL;
+}
+
+static inline void e24_reset_e24(struct e24_device *e24_hw)
+{
+	if (e24_hw->hw_ops->reset)
+		e24_hw->hw_ops->reset(e24_hw->hw_arg);
+}
+
+static inline void e24_disable_e24(struct e24_device *e24_hw)
+{
+	if (e24_hw->hw_ops->disable)
+		e24_hw->hw_ops->disable(e24_hw->hw_arg);
+}
+
+static inline void e24_sendirq_e24(struct e24_device *e24_hw)
+{
+	if (e24_hw->hw_ops->send_irq)
+		e24_hw->hw_ops->send_irq(e24_hw->hw_arg);
+}
+
+irqreturn_t e24_irq_handler(int irq, struct e24_device *e24_hw)
+{
+	dev_dbg(e24_hw->dev, "%s\n", __func__);
+	complete(&e24_hw->completion);
+
+	return IRQ_HANDLED;
+}
+EXPORT_SYMBOL(e24_irq_handler);
+
+static phys_addr_t e24_translate_to_cpu(struct e24_device *mail, Elf32_Phdr *phdr)
+{
+	phys_addr_t res;
+	__be32 addr = cpu_to_be32((u32)phdr->p_paddr);
+	struct device_node *node =
+		of_get_next_child(mail->dev->of_node, NULL);
+
+	if (!node)
+		node = mail->dev->of_node;
+
+	res = of_translate_address(node, &addr);
+
+	if (node != mail->dev->of_node)
+		of_node_put(node);
+	return res;
+}
+
+static int e24_load_segment_to_sysmem(struct e24_device *e24, Elf32_Phdr *phdr)
+{
+	phys_addr_t pa = e24_translate_to_cpu(e24, phdr);
+	struct page *page = pfn_to_page(__phys_to_pfn(pa));
+	size_t page_offs = pa & ~PAGE_MASK;
+	size_t offs;
+
+	for (offs = 0; offs < phdr->p_memsz; ++page) {
+		void *p = kmap(page);
+		size_t sz;
+
+		if (!p)
+			return -ENOMEM;
+
+		page_offs &= ~PAGE_MASK;
+		sz = PAGE_SIZE - page_offs;
+
+		if (offs < phdr->p_filesz) {
+			size_t copy_sz = sz;
+
+			if (phdr->p_filesz - offs < copy_sz)
+				copy_sz = phdr->p_filesz - offs;
+
+			copy_sz = ALIGN(copy_sz, 4);
+			memcpy(p + page_offs,
+			       (void *)e24->firmware->data +
+			       phdr->p_offset + offs,
+			       copy_sz);
+			page_offs += copy_sz;
+			offs += copy_sz;
+			sz -= copy_sz;
+		}
+
+		if (offs < phdr->p_memsz && sz) {
+			if (phdr->p_memsz - offs < sz)
+				sz = phdr->p_memsz - offs;
+
+			sz = ALIGN(sz, 4);
+			memset(p + page_offs, 0, sz);
+			page_offs += sz;
+			offs += sz;
+		}
+		kunmap(page);
+	}
+	dma_sync_single_for_device(e24->dev, pa, phdr->p_memsz, DMA_TO_DEVICE);
+	return 0;
+}
+
+static int e24_load_segment_to_iomem(struct e24_device *e24, Elf32_Phdr *phdr)
+{
+	phys_addr_t pa = e24_translate_to_cpu(e24, phdr);
+	void __iomem *p = ioremap(pa, phdr->p_memsz);
+
+	if (!p) {
+		dev_err(e24->dev, "couldn't ioremap %pap x 0x%08x\n",
+			&pa, (u32)phdr->p_memsz);
+		return -EINVAL;
+	}
+	if (e24->hw_ops->memcpy_tohw)
+		e24->hw_ops->memcpy_tohw(p, (void *)e24->firmware->data +
+					 phdr->p_offset, phdr->p_filesz);
+	else
+		memcpy_toio(p, (void *)e24->firmware->data + phdr->p_offset,
+			    ALIGN(phdr->p_filesz, 4));
+
+	if (e24->hw_ops->memset_hw)
+		e24->hw_ops->memset_hw(p + phdr->p_filesz, 0,
+				       phdr->p_memsz - phdr->p_filesz);
+	else
+		memset_io(p + ALIGN(phdr->p_filesz, 4), 0,
+			ALIGN(phdr->p_memsz - ALIGN(phdr->p_filesz, 4), 4));
+
+	iounmap(p);
+	return 0;
+}
+
+static int e24_load_firmware(struct e24_device *e24_dev)
+{
+	Elf32_Ehdr *ehdr = (Elf32_Ehdr *)e24_dev->firmware->data;
+	u32 *dai = (u32 *)e24_dev->firmware->data;
+	int i;
+
+	pr_debug("elf size:%ld,%x,%x\n", e24_dev->firmware->size, dai[0], dai[1]);
+	if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG)) {
+		dev_err(e24_dev->dev, "bad firmware ELF magic\n");
+		return -EINVAL;
+	}
+
+	if (ehdr->e_type != ET_EXEC) {
+		dev_err(e24_dev->dev, "bad firmware ELF type\n");
+		return -EINVAL;
+	}
+
+	if (ehdr->e_machine != EM_RISCV) {
+		dev_err(e24_dev->dev, "bad firmware ELF machine\n");
+		return -EINVAL;
+	}
+
+	if (ehdr->e_phoff >= e24_dev->firmware->size ||
+	    ehdr->e_phoff +
+	    ehdr->e_phentsize * ehdr->e_phnum > e24_dev->firmware->size) {
+		dev_err(e24_dev->dev, "bad firmware ELF PHDR information\n");
+		return -EINVAL;
+	}
+
+	for (i = ehdr->e_phnum; i >= 0 ; i--) {
+		Elf32_Phdr *phdr = (void *)e24_dev->firmware->data +
+			ehdr->e_phoff + i * ehdr->e_phentsize;
+		phys_addr_t pa;
+		int rc;
+
+		/* Only load non-empty loadable segments, R/W/X */
+		if (!(phdr->p_type == PT_LOAD &&
+		      (phdr->p_flags & (PF_X | PF_R | PF_W)) &&
+		      phdr->p_memsz > 0))
+			continue;
+
+		if (phdr->p_offset >= e24_dev->firmware->size ||
+		    phdr->p_offset + phdr->p_filesz > e24_dev->firmware->size) {
+			dev_err(e24_dev->dev, "bad firmware ELF program header entry %d\n", i);
+			return -EINVAL;
+		}
+
+		pa = e24_translate_to_cpu(e24_dev, phdr);
+		if (pa == (phys_addr_t)OF_BAD_ADDR) {
+			dev_err(e24_dev->dev,
+				"device address 0x%08x could not be mapped to host physical address",
+				(u32)phdr->p_paddr);
+			return -EINVAL;
+		}
+		dev_dbg(e24_dev->dev, "loading segment %d (device 0x%08x) to physical %pap\n",
+			i, (u32)phdr->p_paddr, &pa);
+
+		if (pfn_valid(__phys_to_pfn(pa)))
+			rc = e24_load_segment_to_sysmem(e24_dev, phdr);
+		else
+			rc = e24_load_segment_to_iomem(e24_dev, phdr);
+
+		if (rc < 0)
+			return rc;
+	}
+	return 0;
+}
+
+static int e24_boot_firmware(struct device *dev)
+{
+	int ret;
+	struct e24_device *e24_dev = dev_get_drvdata(dev);
+
+	if (e24_dev->firmware_name) {
+		ret = request_firmware(&e24_dev->firmware, e24_dev->firmware_name, e24_dev->dev);
+
+		if (ret < 0)
+			return ret;
+
+		ret = e24_load_firmware(e24_dev);
+		if (ret < 0) {
+			release_firmware(e24_dev->firmware);
+			return ret;
+		}
+
+	}
+
+	release_firmware(e24_dev->firmware);
+	ret = e24_enable_e24(e24_dev);
+	if (ret < 0)
+		return ret;
+
+	e24_reset_e24(e24_dev);
+	e24_release_e24(e24_dev);
+	e24_synchronize(e24_dev);
+
+	return ret;
+}
+
+int e24_runtime_suspend(struct device *dev)
+{
+	struct e24_device *e24_dev = dev_get_drvdata(dev);
+
+	e24_halt_e24(e24_dev);
+	e24_disable_e24(e24_dev);
+
+	return 0;
+}
+
+long e24_init_v0(struct platform_device *pdev)
+{
+	long ret = -EINVAL;
+	int nodeid, i;
+	struct e24_hw_arg *hw_arg;
+	struct e24_device *e24_dev;
+	char nodename[sizeof("eboot") + 2 * sizeof(int)];
+
+	e24_dev = devm_kzalloc(&pdev->dev, sizeof(*e24_dev), GFP_KERNEL);
+	if (e24_dev == NULL) {
+		ret = -ENOMEM;
+		return ret;
+	}
+
+	hw_arg = devm_kzalloc(&pdev->dev, sizeof(*hw_arg), GFP_KERNEL);
+	if (hw_arg == NULL)
+		return ret;
+
+	platform_set_drvdata(pdev, e24_dev);
+	e24_dev->dev = &pdev->dev;
+	e24_dev->hw_arg = hw_arg;
+	e24_dev->hw_ops = e24_get_hw_ops();
+	e24_dev->nodeid = -1;
+	hw_arg->e24 = e24_dev;
+
+	ret = e24_init_mem_pool(pdev, e24_dev);
+	if (ret < 0)
+		goto err;
+
+	ret = e24_init_address_map(e24_dev->dev, &e24_dev->address_map);
+	if (ret < 0)
+		goto err_free_pool;
+
+	e24_dev->n_queues = 1;
+	e24_dev->queue = devm_kmalloc(&pdev->dev,
+				  e24_dev->n_queues * sizeof(*e24_dev->queue),
+				  GFP_KERNEL);
+	if (e24_dev->queue == NULL)
+		goto err_free_map;
+
+	for (i = 0; i < e24_dev->n_queues; i++) {
+		mutex_init(&e24_dev->queue[i].lock);
+		e24_dev->queue[i].comm = e24_dev->comm + E24_CMD_STRIDE * i;
+	}
+
+	ret = of_property_read_u32(pdev->dev.of_node, "irq-mode",
+		&hw_arg->irq_mode);
+	e24_dev->irq_mode = hw_arg->irq_mode;
+	if (hw_arg->irq_mode == 0)
+		dev_info(&pdev->dev, "using polling mode on the device side\n");
+
+	e24_dev->mbox_data = e24_translate_to_dsp(&e24_dev->address_map, e24_dev->comm_phys);
+	ret = device_property_read_string(e24_dev->dev, "firmware-name",
+					  &e24_dev->firmware_name);
+	if (ret == -EINVAL || ret == -ENODATA) {
+		dev_dbg(e24_dev->dev,
+			"no firmware-name property, not loading firmware");
+	} else if (ret < 0) {
+		dev_err(e24_dev->dev, "invalid firmware name (%ld)", ret);
+		goto err_free_map;
+	}
+
+	e24_init(e24_dev);
+	pm_runtime_set_active(e24_dev->dev);
+	pm_runtime_enable(e24_dev->dev);
+	if (!pm_runtime_enabled(e24_dev->dev)) {
+		ret = e24_boot_firmware(e24_dev->dev);
+		if (ret)
+			goto err_pm_disable;
+	}
+	nodeid = ida_simple_get(&e24_nodeid, 0, 0, GFP_KERNEL);
+	if (nodeid < 0) {
+		ret = nodeid;
+		goto err_pm_disable;
+	}
+
+	e24_dev->nodeid = nodeid;
+	sprintf(nodename, "eboot%u", nodeid);
+
+	e24_dev->miscdev = (struct miscdevice){
+		.minor = MISC_DYNAMIC_MINOR,
+		.name = devm_kstrdup(&pdev->dev, nodename, GFP_KERNEL),
+		.nodename = devm_kstrdup(&pdev->dev, nodename, GFP_KERNEL),
+		.fops = &e24_fops,
+	};
+
+	ret = misc_register(&e24_dev->miscdev);
+	if (ret < 0)
+		goto err_free_id;
+
+	return PTR_ERR(e24_dev);
+err_free_id:
+	ida_simple_remove(&e24_nodeid, nodeid);
+
+err_pm_disable:
+	pm_runtime_disable(e24_dev->dev);
+err_free_map:
+	kfree(e24_dev->address_map.entry);
+err_free_pool:
+	e24_free_pool(e24_dev->pool);
+err:
+	dev_err(&pdev->dev, "%s: ret = %ld\n", __func__, ret);
+	return ret;
+}
+
+static const struct of_device_id e24_of_match[] = {
+	{
+		.compatible = "starfive,e24",
+		.data = e24_init_v0,
+	},
+	{
+	},
+};
+MODULE_DEVICE_TABLE(of, e24_of_match);
+
+int e24_deinit(struct platform_device *pdev)
+{
+	struct e24_device *e24_dev = platform_get_drvdata(pdev);
+
+	pm_runtime_disable(&pdev->dev);
+	if (!pm_runtime_status_suspended(e24_dev->dev))
+		e24_runtime_suspend(e24_dev->dev);
+
+	misc_deregister(&e24_dev->miscdev);
+	e24_free_pool(e24_dev->pool);
+	kfree(e24_dev->address_map.entry);
+	ida_simple_remove(&e24_nodeid, e24_dev->nodeid);
+
+	if (e24_dev->rx_channel)
+		mbox_free_channel(e24_dev->rx_channel);
+	if (e24_dev->tx_channel)
+		mbox_free_channel(e24_dev->tx_channel);
+	return 0;
+}
+
+static int e24_probe(struct platform_device *pdev)
+{
+	long ret = -EINVAL;
+	const struct of_device_id *match;
+	e24_init_function *init;
+
+	match = of_match_device(e24_of_match, &pdev->dev);
+	init = match->data;
+	ret = init(pdev);
+
+	return IS_ERR_VALUE(ret) ? ret : 0;
+}
+
+static void e24_remove(struct platform_device *pdev)
+{
+	e24_deinit(pdev);
+}
+
+static const struct dev_pm_ops e24_runtime_pm_ops = {
+	SET_RUNTIME_PM_OPS(e24_runtime_suspend,
+		   e24_boot_firmware, NULL)
+};
+
+static struct platform_driver e24_driver = {
+	.probe = e24_probe,
+	.remove = e24_remove,
+	.driver = {
+		.name = "e24_boot",
+		.of_match_table = of_match_ptr(e24_of_match),
+		.pm = &e24_runtime_pm_ops,
+	},
+};
+
+module_platform_driver(e24_driver);
+
+MODULE_DESCRIPTION("StarFive e24 driver");
+MODULE_AUTHOR("Shanlong Li <shanlong.li@starfivetech.com>");
+MODULE_LICENSE("GPL");
--- /dev/null
+++ b/drivers/e24/starfive_e24.h
@@ -0,0 +1,159 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __STARFIVE_E24_H__
+#define __STARFIVE_E24_H__
+
+#include <linux/types.h>
+#include <linux/completion.h>
+#include <linux/miscdevice.h>
+#include <linux/mutex.h>
+#include <linux/irqreturn.h>
+#include <linux/platform_device.h>
+
+#define E24_IOCTL_MAGIC 'e'
+#define E24_IOCTL_SEND			_IO(E24_IOCTL_MAGIC, 1)
+#define E24_IOCTL_RECV			_IO(E24_IOCTL_MAGIC, 2)
+#define E24_IOCTL_GET_CHANNEL		_IO(E24_IOCTL_MAGIC, 3)
+#define E24_IOCTL_FREE_CHANNEL		_IO(E24_IOCTL_MAGIC, 4)
+#define E24_IOCTL_ALLOC			_IO(E24_IOCTL_MAGIC, 5)
+#define E24_IOCTL_FREE			_IO(E24_IOCTL_MAGIC, 6)
+
+#define E24_DSP_CMD_INLINE_DATA_SIZE	16
+#define E24_NO_TRANSLATION		((u32)~0ul)
+#define E24_CMD_STRIDE			256
+
+#define E24_MEM_MAP
+enum e24_irq_mode {
+	MAIL_IRQ_NONE,
+	MAIL_IRQ_LEVEL,
+	MAIL_IRQ_MAX
+};
+
+enum {
+	E24_FLAG_READ = 0x1,
+	E24_FLAG_WRITE = 0x2,
+	E24_FLAG_READ_WRITE = 0x3,
+};
+
+enum {
+	E24_QUEUE_FLAG_VALID = 0x4,
+	E24_QUEUE_FLAG_PRIO = 0xff00,
+	E24_QUEUE_FLAG_PRIO_SHIFT = 8,
+
+	E24_QUEUE_VALID_FLAGS =
+		E24_QUEUE_FLAG_VALID |
+		E24_QUEUE_FLAG_PRIO,
+};
+
+enum {
+	E24_CMD_FLAG_REQUEST_VALID = 0x00000001,
+	E24_CMD_FLAG_RESPONSE_VALID = 0x00000002,
+	E24_CMD_FLAG_REQUEST_NSID = 0x00000004,
+	E24_CMD_FLAG_RESPONSE_DELIVERY_FAIL = 0x00000008,
+};
+
+struct e24_address_map_entry {
+	phys_addr_t src_addr;
+	u32 dst_addr;
+	u32 size;
+};
+
+struct e24_address_map {
+	unsigned int n;
+	struct e24_address_map_entry *entry;
+};
+
+struct e24_alien_mapping {
+	unsigned long vaddr;
+	unsigned long size;
+	phys_addr_t paddr;
+	void *allocation;
+	enum {
+		ALIEN_GUP,
+		ALIEN_PFN_MAP,
+		ALIEN_COPY,
+	} type;
+};
+
+struct e24_mapping {
+	enum {
+		E24_MAPPING_NONE,
+		E24_MAPPING_NATIVE,
+		E24_MAPPING_ALIEN,
+		E24_MAPPING_KERNEL = 0x4,
+	} type;
+	union {
+		struct {
+			struct e24_allocation *m_allocation;
+			unsigned long vaddr;
+		} native;
+		struct e24_alien_mapping alien_mapping;
+	};
+};
+
+struct e24_ioctl_alloc {
+	u32 size;
+	u32 align;
+	u64 addr;
+};
+
+struct e24_comm {
+	struct mutex lock;
+	void __iomem *comm;
+	struct completion completion;
+	u32 priority;
+};
+
+struct e24_device {
+	struct device *dev;
+	const char *firmware_name;
+	const struct firmware *firmware;
+	struct miscdevice miscdev;
+	const struct e24_hw_ops *hw_ops;
+	void *hw_arg;
+	int irq_mode;
+
+	u32 n_queues;
+	struct completion completion;
+	struct e24_address_map address_map;
+	struct e24_comm *queue;
+	void __iomem *comm;
+	phys_addr_t comm_phys;
+	phys_addr_t shared_mem;
+	phys_addr_t shared_size;
+
+	u32 mbox_data;
+	int nodeid;
+	spinlock_t busy_list_lock;
+
+	struct mbox_chan	*tx_channel;
+	struct mbox_chan	*rx_channel;
+	void			*rx_buffer;
+	void			*message;
+	struct e24_allocation_pool *pool;
+	struct e24_allocation *busy_list;
+};
+
+struct e24_hw_arg {
+	struct e24_device *e24;
+	phys_addr_t regs_phys;
+	struct clk *clk_rtc;
+	struct clk *clk_core;
+	struct clk *clk_dbg;
+	struct reset_control *rst_core;
+	struct regmap *reg_syscon;
+	enum e24_irq_mode irq_mode;
+};
+
+static inline int e24_compare_address(phys_addr_t addr,
+				      const struct e24_address_map_entry *entry)
+{
+	if (addr < entry->src_addr)
+		return -1;
+	if (addr - entry->src_addr < entry->size)
+		return 0;
+	return 1;
+}
+
+irqreturn_t e24_irq_handler(int irq, struct e24_device *e24_hw);
+
+#endif
--- /dev/null
+++ b/drivers/e24/starfive_e24_hw.c
@@ -0,0 +1,134 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/idr.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/clk.h>
+#include <linux/reset.h>
+#include <linux/module.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+#include "starfive_e24.h"
+#include "starfive_e24_hw.h"
+
+#define RET_E24_VECTOR_ADDR	0x6CE00000
+
+static void halt(void *hw_arg)
+{
+	struct e24_hw_arg *mail_arg = hw_arg;
+
+	reset_control_assert(mail_arg->rst_core);
+	pr_debug("e24 halt.\n");
+}
+
+static void release(void *hw_arg)
+{
+	struct e24_hw_arg *mail_arg = hw_arg;
+
+	reset_control_deassert(mail_arg->rst_core);
+	pr_debug("e24 begin run.\n");
+}
+
+static void reset(void *hw_arg)
+{
+	struct e24_hw_arg *mail_arg = hw_arg;
+
+	regmap_update_bits(mail_arg->reg_syscon, 0x24, 0xFFFFFFFF, RET_E24_VECTOR_ADDR);
+	pr_debug("e24 reset vector.\n");
+}
+
+static void disable(void *hw_arg)
+{
+	struct e24_hw_arg *mail_arg = hw_arg;
+
+	clk_disable_unprepare(mail_arg->clk_core);
+	clk_disable_unprepare(mail_arg->clk_dbg);
+	clk_disable_unprepare(mail_arg->clk_rtc);
+
+	pr_debug("e24 disable ...\n");
+
+}
+
+static int enable(void *hw_arg)
+{
+	struct e24_hw_arg *mail_arg = hw_arg;
+	int ret = 0;
+
+	ret = clk_prepare_enable(mail_arg->clk_core);
+	if (ret)
+		return -EAGAIN;
+
+	ret = clk_prepare_enable(mail_arg->clk_dbg);
+	if (ret) {
+		clk_disable_unprepare(mail_arg->clk_core);
+		return -EAGAIN;
+	}
+
+	ret = clk_prepare_enable(mail_arg->clk_rtc);
+	if (ret) {
+		clk_disable_unprepare(mail_arg->clk_core);
+		clk_disable_unprepare(mail_arg->clk_dbg);
+		return -EAGAIN;
+	}
+
+	pr_debug("e24_enable clk ...\n");
+	return 0;
+}
+
+
+static int init(void *hw_arg)
+{
+	struct e24_hw_arg *mail_arg = hw_arg;
+
+	mail_arg->reg_syscon = syscon_regmap_lookup_by_phandle(
+					mail_arg->e24->dev->of_node,
+					"starfive,stg-syscon");
+	if (IS_ERR(mail_arg->reg_syscon)) {
+		dev_err(mail_arg->e24->dev, "No starfive,stg-syscon\n");
+		return PTR_ERR(mail_arg->reg_syscon);
+	}
+
+	mail_arg->clk_core = devm_clk_get_optional(mail_arg->e24->dev, "clk_core");
+	if (IS_ERR(mail_arg->clk_core)) {
+		dev_err(mail_arg->e24->dev, "failed to get e24 clk core\n");
+		return -ENOMEM;
+	}
+
+	mail_arg->clk_dbg = devm_clk_get_optional(mail_arg->e24->dev, "clk_dbg");
+	if (IS_ERR(mail_arg->clk_dbg)) {
+		dev_err(mail_arg->e24->dev, "failed to get e24 clk dbg\n");
+		return -ENOMEM;
+	}
+
+	mail_arg->clk_rtc = devm_clk_get_optional(mail_arg->e24->dev, "clk_rtc");
+	if (IS_ERR(mail_arg->clk_rtc)) {
+		dev_err(mail_arg->e24->dev, "failed to get e24 clk rtc\n");
+		return -ENOMEM;
+	}
+
+	mail_arg->rst_core = devm_reset_control_get_exclusive(mail_arg->e24->dev, "e24_core");
+	if (IS_ERR(mail_arg->rst_core)) {
+		dev_err(mail_arg->e24->dev, "failed to get e24 reset\n");
+		return -ENOMEM;
+	}
+
+	enable(hw_arg);
+
+	return 0;
+}
+
+static struct e24_hw_ops e24_hw_ops = {
+	.init = init,
+	.enable = enable,
+	.reset = reset,
+	.halt = halt,
+	.release = release,
+	.disable = disable,
+};
+
+struct e24_hw_ops *e24_get_hw_ops(void)
+{
+	return &e24_hw_ops;
+}
--- /dev/null
+++ b/drivers/e24/starfive_e24_hw.h
@@ -0,0 +1,94 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __STARFIVE_E24_HW_H__
+#define __STARFIVE_E24_HW_H__
+/*
+ * Hardware-specific operation entry points.
+ */
+struct e24_hw_ops {
+	/*
+	 * Gets the clock for E24.
+	 */
+	int (*init)(void *hw_arg);
+	/*
+	 * Enable power/clock, but keep the core stalled.
+	 */
+	int (*enable)(void *hw_arg);
+	/*
+	 * Diable power/clock.
+	 */
+	void (*disable)(void *hw_arg);
+	/*
+	 * Reset the core.
+	 */
+	void (*reset)(void *hw_arg);
+	/*
+	 * Unstall the core.
+	 */
+	void (*release)(void *hw_arg);
+	/*
+	 * Stall the core.
+	 */
+	void (*halt)(void *hw_arg);
+
+	/* Get HW-specific data to pass to the DSP on synchronization
+	 *
+	 *  param hw_arg: opaque parameter passed to DSP at initialization
+	 *  param sz: return size of sync data here
+	 *  return a buffer allocated with kmalloc that the caller will free
+	 */
+	void *(*get_hw_sync_data)(void *hw_arg, size_t *sz);
+
+	/*
+	 * Send IRQ to the core.
+	 */
+	void (*send_irq)(void *hw_arg);
+
+	/*
+	 * Check whether region of physical memory may be handled by
+	 * dma_sync_* operations
+	 *
+	 * \param hw_arg: opaque parameter passed to DSP at initialization
+	 *                time
+	 */
+	bool (*cacheable)(void *hw_arg, unsigned long pfn, unsigned long n_pages);
+	/*
+	 * Synchronize region of memory for DSP access.
+	 *
+	 * \param hw_arg: opaque parameter passed to DSP at initialization
+	 *                time
+	 */
+	void (*dma_sync_for_device)(void *hw_arg,
+				    void *vaddr, phys_addr_t paddr,
+				    unsigned long sz, unsigned int flags);
+	/*
+	 * Synchronize region of memory for host access.
+	 *
+	 * \param hw_arg: opaque parameter passed to DSP at initialization
+	 *                time
+	 */
+	void (*dma_sync_for_cpu)(void *hw_arg,
+				 void *vaddr, phys_addr_t paddr,
+				 unsigned long sz, unsigned int flags);
+
+	/*
+	 * memcpy data/code to device-specific memory.
+	 */
+	void (*memcpy_tohw)(void __iomem *dst, const void *src, size_t sz);
+	/*
+	 * memset device-specific memory.
+	 */
+	void (*memset_hw)(void __iomem *dst, int c, size_t sz);
+
+	/*
+	 * Check DSP status.
+	 *
+	 * \param hw_arg: opaque parameter passed to DSP at initialization
+	 *                time
+	 * \return whether the core has crashed and needs to be restarted
+	 */
+	bool (*panic_check)(void *hw_arg);
+};
+
+long e24_init_hw(struct platform_device *pdev, void *hw_arg);
+struct e24_hw_ops *e24_get_hw_ops(void);
+#endif
