diff options
author | shanlong.li <shanlong.li@starfivetech.com> | 2022-04-25 14:42:22 +0300 |
---|---|---|
committer | Andy Hu <andy.hu@starfivetech.com> | 2024-06-28 13:47:19 +0300 |
commit | 6a7f4eee226d2d83d3ccebef96635254ee40453d (patch) | |
tree | 64bef5331d411172021fc30d208e3b8675134c95 | |
parent | faa96081e487eaee55cf6235d78a6c99706f1bfe (diff) | |
download | linux-6a7f4eee226d2d83d3ccebef96635254ee40453d.tar.xz |
e24:driver: add e24 drever , use clk/rst api ,syscon spi
add e24 drever, use clk/rst api, syscon spi
Signed-off-by: shanlong.li <shanlong.li@starfivetech.com>
-rw-r--r-- | drivers/Kconfig | 2 | ||||
-rw-r--r-- | drivers/Makefile | 1 | ||||
-rw-r--r-- | drivers/e24/Kconfig | 5 | ||||
-rw-r--r-- | drivers/e24/Makefile | 12 | ||||
-rw-r--r-- | drivers/e24/e24_alloc.c | 241 | ||||
-rw-r--r-- | drivers/e24/e24_alloc.h | 59 | ||||
-rw-r--r-- | drivers/e24/starfive_e24.c | 1500 | ||||
-rw-r--r-- | drivers/e24/starfive_e24.h | 177 | ||||
-rw-r--r-- | drivers/e24/starfive_e24_hw.c | 136 | ||||
-rw-r--r-- | drivers/e24/starfive_e24_hw.h | 110 |
10 files changed, 2243 insertions, 0 deletions
diff --git a/drivers/Kconfig b/drivers/Kconfig index e7fe5fd567fc..d2c3eed0832c 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -237,6 +237,8 @@ source "drivers/counter/Kconfig" source "drivers/most/Kconfig" +source "drivers/e24/Kconfig" + source "drivers/cpufreq/Kconfig" endmenu diff --git a/drivers/Makefile b/drivers/Makefile index be5d40ae1488..ab65c81153be 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -188,3 +188,4 @@ obj-$(CONFIG_GNSS) += gnss/ obj-$(CONFIG_INTERCONNECT) += interconnect/ obj-$(CONFIG_COUNTER) += counter/ obj-$(CONFIG_MOST) += most/ +obj-$(CONFIG_E24) += e24/ diff --git a/drivers/e24/Kconfig b/drivers/e24/Kconfig new file mode 100644 index 000000000000..00de6a2dfad6 --- /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. diff --git a/drivers/e24/Makefile b/drivers/e24/Makefile new file mode 100644 index 000000000000..1d7e753fdbf1 --- /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 + +obj-$(CONFIG_E24) += e24.o + +e24-y := starfive_e24.o starfive_e24_hw.o e24_alloc.o diff --git a/drivers/e24/e24_alloc.c b/drivers/e24/e24_alloc.c new file mode 100644 index 000000000000..c95b65b547f4 --- /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; +} diff --git a/drivers/e24/e24_alloc.h b/drivers/e24/e24_alloc.h new file mode 100644 index 000000000000..4abad6266c6c --- /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 diff --git a/drivers/e24/starfive_e24.c b/drivers/e24/starfive_e24.c new file mode 100644 index 000000000000..7f8eb19e29c8 --- /dev/null +++ b/drivers/e24/starfive_e24.c @@ -0,0 +1,1500 @@ +// SPDX-License-Identifier: GPL-2.0 +/*********************************Copyright (c)********************************************* + ** + ** + ** + **-------------------------------file info------------------------------------------------- + ** Vrsions: V1.0 + ** Filename: starfive_e24.c + ** Creator: shanlong.li + ** Date: 2021/05/20 + ** Description: boot e24 and communication with u74 + ** + **-------------------------------history---------------------------------------------- + ** Name: shanlong.li + ** Versions: V1.0 + ** Date: 2021/05/20 + ** Description: + ** + ** ---------------------------------------------------------------------------------------- + ******************************************************************************************/ +#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 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; + + 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; + return 0; +} + +static int e24_close(struct inode *inode, struct file *filp) +{ + struct e24_device *e24_dev = filp->private_data; + + pm_runtime_put_sync(e24_dev->dev); + 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_close, + .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_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); +} + +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; +} + +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); + + ret = e24_enable_e24(e24_dev); + + if (ret < 0) + return ret; + + e24_halt_e24(e24_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); + + } + + e24_reset_e24(e24_dev); + e24_release_e24(e24_dev); + + return ret; +} + +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; + } + + 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(e24_dev->dev); + if (!pm_runtime_status_suspended(e24_dev->dev)) + e24_runtime_suspend(e24_dev->dev); + + misc_deregister(&e24_dev->miscdev); + release_firmware(e24_dev->firmware); + 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 int e24_remove(struct platform_device *pdev) +{ + return e24_deinit(pdev); +} + +static const struct dev_pm_ops e24_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_pm_ops, + }, +}; + +module_platform_driver(e24_driver); + +MODULE_AUTHOR("StarFive Technology Co. Ltd."); +MODULE_DESCRIPTION("e24 mailbox driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/e24/starfive_e24.h b/drivers/e24/starfive_e24.h new file mode 100644 index 000000000000..e6fcd6cfbc8c --- /dev/null +++ b/drivers/e24/starfive_e24.h @@ -0,0 +1,177 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/*********************************Copyright (c)********************************************* + ** + **-------------------------------file info------------------------------------------------- + ** Vrsions: V1.0 + ** Filename: starfive_e24.h + ** Creator: shanlong.li + ** Date: 2021/05/20 + ** Description: header file + ** + **-------------------------------history---------------------------------------------- + ** Name: shanlong.li + ** Versions: V1.0 + ** Date: 2021/05/20 + ** Description: + ** + ** ---------------------------------------------------------------------------------------- + ******************************************************************************************/ + +#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 diff --git a/drivers/e24/starfive_e24_hw.c b/drivers/e24/starfive_e24_hw.c new file mode 100644 index 000000000000..37b7c621d064 --- /dev/null +++ b/drivers/e24/starfive_e24_hw.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0 +/*********************************Copyright (c)********************************************* + ** + **-------------------------------file info------------------------------------------------- + ** Vrsions: V1.0 + ** Filename: starfive_e24_hw.c + ** Creator: shanlong.li + ** Date: 2021/05/20 + ** Description: e24 start + ** + **-------------------------------history---------------------------------------------- + ** Name: shanlong.li + ** Versions: V1.0 + ** Date: 2021/05/20 + ** Description: + ** + ** ---------------------------------------------------------------------------------------- + ******************************************************************************************/ +#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 0xc0000000 + +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; + + reset_control_assert(mail_arg->rst_core); + pr_debug("e24 disable ...\n"); +} + +static int enable(void *hw_arg) +{ + struct e24_hw_arg *mail_arg = hw_arg; + int ret = 0; + + 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; + } + + 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 struct e24_hw_ops e24_hw_ops = { + .enable = enable, + .reset = reset, + .halt = halt, + .release = release, + .disable = disable, +}; + +struct e24_hw_ops *e24_get_hw_ops(void) +{ + return &e24_hw_ops; +} diff --git a/drivers/e24/starfive_e24_hw.h b/drivers/e24/starfive_e24_hw.h new file mode 100644 index 000000000000..e7bd6ae2720e --- /dev/null +++ b/drivers/e24/starfive_e24_hw.h @@ -0,0 +1,110 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/*********************************Copyright (c)********************************************* + ** + ** + ** + **-------------------------------file info------------------------------------------------- + ** Vrsions: V1.0 + ** Filename: starfive_e24_hw.h + ** Creator: shanlong.li + ** Date: 2021/05/20 + ** Description: header file + ** + **-------------------------------history---------------------------------------------- + ** Name: shanlong.li + ** Versions: V1.0 + ** Date: 2021/05/20 + ** Description: + ** + ** ---------------------------------------------------------------------------------------- + ******************************************************************************************/ + +#ifndef __STARFIVE_E24_HW_H__ +#define __STARFIVE_E24_HW_H__ +/* + * Hardware-specific operation entry points. + */ +struct e24_hw_ops { + /* + * 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 |