859 lines
21 KiB
C
859 lines
21 KiB
C
/* SPDX-License-Identifier: GPL-2.0 */
|
|
/*
|
|
* sunxi's Remote Processor Control Driver
|
|
* base on st_remoteproc.c
|
|
*
|
|
* Copyright (C) 2015 Allwinnertech - All Rights Reserved
|
|
*
|
|
* Author: fuyao <fuyao@allwinnertech.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
#include <linux/clk.h>
|
|
#include <linux/clk/sunxi.h>
|
|
#include <linux/clk-provider.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/err.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/mfd/syscon.h>
|
|
#include <linux/module.h>
|
|
#include <linux/iommu.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_reserved_mem.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/remoteproc.h>
|
|
#include <linux/reset.h>
|
|
#include <linux/mailbox_client.h>
|
|
|
|
#include "sunxi_remoteproc_internal.h"
|
|
|
|
#define MBOX_NAME "mbox-chan"
|
|
#define MBOX_TX_NAME "mbox-tx"
|
|
#define MBOX_RX_NAME "mbox-rx"
|
|
|
|
static LIST_HEAD(sunxi_rproc_list);
|
|
|
|
#ifdef CONFIG_SUNXI_REMOTEPROC_WQ
|
|
/* mbox work funciton */
|
|
static void sunxi_rproc_mb_vq_work(struct work_struct *work)
|
|
{
|
|
struct sunxi_mbox *mb = container_of(work, struct sunxi_mbox, vq_work);
|
|
struct rproc *rproc = dev_get_drvdata(mb->client.dev);
|
|
struct sunxi_rproc *ddata = rproc->priv;
|
|
|
|
/* tell remoteproc that a virtqueue is interrupted */
|
|
if (rproc_vq_interrupt(rproc, ddata->notifyid) == IRQ_NONE)
|
|
dev_dbg(&rproc->dev, "no message found in vq%d\n", ddata->notifyid);
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_SUNXI_REMOTEPROC_RT_THREAD
|
|
static int sunxi_rproc_work_thread(void *data)
|
|
{
|
|
struct rproc *rproc = data;
|
|
struct sunxi_rproc *ddata = rproc->priv;
|
|
|
|
dev_dbg(&rproc->dev, "%s thread start.\n", rproc->name);
|
|
|
|
while (1) {
|
|
ddata->notifyid = -1;
|
|
if (wait_event_interruptible(ddata->rq,
|
|
ddata->notifyid >= 0 ||
|
|
kthread_should_stop()))
|
|
break;
|
|
|
|
if (kthread_should_stop()) {
|
|
dev_dbg(&rproc->dev, "%s thread stop.\n", rproc->name);
|
|
break;
|
|
}
|
|
|
|
if (ddata->notifyid < 0)
|
|
continue;
|
|
|
|
/* tell remoteproc that a virtqueue is interrupted */
|
|
if (rproc_vq_interrupt(rproc, ddata->notifyid) == IRQ_NONE)
|
|
dev_dbg(&rproc->dev, "no message found in vq%d\n", ddata->notifyid);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static void sunxi_rproc_mb_rx_callback(struct mbox_client *cl, void *data)
|
|
{
|
|
struct rproc *rproc = dev_get_drvdata(cl->dev);
|
|
struct sunxi_mbox *mb = container_of(cl, struct sunxi_mbox, client);
|
|
struct sunxi_rproc *ddata = rproc->priv;
|
|
|
|
(void)mb;
|
|
|
|
dev_dbg(&rproc->dev, "mbox recv data:0x%x\n", *(uint32_t *)data);
|
|
ddata->notifyid = *(uint32_t *)data;
|
|
|
|
#ifdef CONFIG_SUNXI_REMOTEPROC_WQ
|
|
queue_work(ddata->workqueue, &mb->vq_work);
|
|
#endif
|
|
#ifdef CONFIG_SUNXI_REMOTEPROC_RT_THREAD
|
|
wake_up_interruptible(&ddata->rq);
|
|
#endif
|
|
}
|
|
|
|
static void sunxi_rproc_mb_tx_done(struct mbox_client *cl, void *msg, int r)
|
|
{
|
|
struct rproc *rproc = dev_get_drvdata(cl->dev);
|
|
devm_kfree(&rproc->dev, msg);
|
|
}
|
|
|
|
static int sunxi_rproc_request_mbox(struct rproc *rproc)
|
|
{
|
|
struct sunxi_rproc *ddata = rproc->priv;
|
|
struct device *dev = &rproc->dev;
|
|
const char *name;
|
|
struct mbox_client *cl;
|
|
|
|
strcpy(ddata->mb[0].name, MBOX_NAME);
|
|
ddata->mb[0].client.rx_callback = sunxi_rproc_mb_rx_callback;
|
|
ddata->mb[0].client.tx_block = false;
|
|
ddata->mb[0].client.tx_done = sunxi_rproc_mb_tx_done;
|
|
|
|
name = ddata->mb[0].name;
|
|
cl = &ddata->mb[0].client;
|
|
|
|
/* set device parent */
|
|
cl->dev = dev->parent;
|
|
ddata->mb[0].chan = mbox_request_channel_byname(cl, name);
|
|
if (IS_ERR(ddata->mb[0].chan)) {
|
|
dev_warn(dev, "cannot get %s channel (ret=%ld)\n", name,
|
|
PTR_ERR(ddata->mb[0].chan));
|
|
goto double_channel;
|
|
}
|
|
|
|
#ifdef CONFIG_SUNXI_REMOTEPROC_WQ
|
|
INIT_WORK(&ddata->mb[0].vq_work, sunxi_rproc_mb_vq_work);
|
|
#endif
|
|
ddata->mb[1].chan = NULL;
|
|
|
|
return 0;
|
|
|
|
double_channel:
|
|
strcpy(ddata->mb[0].name, MBOX_TX_NAME);
|
|
strcpy(ddata->mb[1].name, MBOX_RX_NAME);
|
|
ddata->mb[0].client.rx_callback = sunxi_rproc_mb_rx_callback;
|
|
ddata->mb[0].client.tx_block = false;
|
|
ddata->mb[0].client.tx_done = sunxi_rproc_mb_tx_done;
|
|
ddata->mb[0].client.dev = dev->parent;
|
|
ddata->mb[1].client.rx_callback = sunxi_rproc_mb_rx_callback;
|
|
ddata->mb[1].client.tx_block = false;
|
|
ddata->mb[1].client.tx_done = sunxi_rproc_mb_tx_done;
|
|
ddata->mb[1].client.dev = dev->parent;
|
|
|
|
ddata->mb[0].chan = mbox_request_channel_byname(&ddata->mb[0].client,
|
|
ddata->mb[0].name);
|
|
if (IS_ERR(ddata->mb[0].chan)) {
|
|
dev_warn(dev, "cannot get %s channel (ret=%ld)\n", ddata->mb[0].name,
|
|
PTR_ERR(ddata->mb[0].chan));
|
|
goto err_probe;
|
|
}
|
|
|
|
ddata->mb[1].chan = mbox_request_channel_byname(&ddata->mb[1].client,
|
|
ddata->mb[1].name);
|
|
if (IS_ERR(ddata->mb[1].chan)) {
|
|
dev_warn(dev, "cannot get %s channel (ret=%ld)\n", ddata->mb[1].name,
|
|
PTR_ERR(ddata->mb[1].chan));
|
|
goto err_probe;
|
|
}
|
|
#ifdef CONFIG_SUNXI_REMOTEPROC_WQ
|
|
INIT_WORK(&ddata->mb[0].vq_work, sunxi_rproc_mb_vq_work);
|
|
INIT_WORK(&ddata->mb[1].vq_work, sunxi_rproc_mb_vq_work);
|
|
#endif
|
|
|
|
return 0;
|
|
|
|
err_probe:
|
|
return -EPROBE_DEFER;
|
|
}
|
|
|
|
static void sunxi_rproc_free_mbox(struct rproc *rproc)
|
|
{
|
|
struct sunxi_rproc *ddata = rproc->priv;
|
|
|
|
if (ddata->mb[0].chan)
|
|
mbox_free_channel(ddata->mb[0].chan);
|
|
ddata->mb[0].chan = NULL;
|
|
if (ddata->mb[1].chan)
|
|
mbox_free_channel(ddata->mb[1].chan);
|
|
ddata->mb[1].chan = NULL;
|
|
}
|
|
|
|
int sunxi_rproc_start(struct rproc *rproc)
|
|
{
|
|
struct sunxi_rproc *ddata = rproc->priv;
|
|
|
|
#ifdef CONFIG_SUNXI_RPROC_SHARE_IRQ
|
|
sunxi_arch_interrupt_save(ddata->share_irq);
|
|
#endif
|
|
sunxi_core_set_start_addr(ddata->core, rproc->bootaddr);
|
|
|
|
return sunxi_core_start(ddata->core);
|
|
}
|
|
|
|
int sunxi_rproc_stop(struct rproc *rproc)
|
|
{
|
|
int ret;
|
|
struct sunxi_rproc *ddata = rproc->priv;
|
|
|
|
ret = sunxi_core_stop(ddata->core);
|
|
#ifdef CONFIG_SUNXI_RPROC_SHARE_IRQ
|
|
sunxi_arch_interrupt_restore(ddata->share_irq);
|
|
#endif
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void *sunxi_da_to_va(struct rproc *rproc, u64 da, int len)
|
|
{
|
|
struct device *dev = &rproc->dev;
|
|
struct sunxi_rproc *ddata = rproc->priv;
|
|
const struct sunxi_resource_map_table *t;
|
|
int i;
|
|
void *va;
|
|
|
|
for (i = 0; i < ddata->mem_maps_cnt; i++) {
|
|
t = &ddata->mem_maps[i];
|
|
|
|
if (da >= t->da && (da + len) <= (t->da + t->len)) {
|
|
va = t->va + (da - t->da);
|
|
return va;
|
|
}
|
|
}
|
|
|
|
dev_err(dev, "failed da_to_va\n");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void sunxi_rproc_kick(struct rproc *rproc, int notifyid)
|
|
{
|
|
struct sunxi_rproc *ddata = rproc->priv;
|
|
int err;
|
|
struct sunxi_mbox *mb;
|
|
u32 *msg = NULL;
|
|
|
|
dev_dbg(&rproc->dev, "notifyid=%d kick\n", notifyid);
|
|
|
|
mb = &ddata->mb[0];
|
|
|
|
msg = devm_kzalloc(&rproc->dev, sizeof(u32), GFP_KERNEL);
|
|
if (!msg) {
|
|
dev_err(&rproc->dev, "(%s:%d) alloc mem failed\n",
|
|
__func__, __LINE__);
|
|
return;
|
|
}
|
|
*msg = notifyid;
|
|
err = mbox_send_message(mb->chan, (void *)msg);
|
|
if (err < 0) {
|
|
dev_err(&rproc->dev, "(%s:%d) kick failed (%s, err:%d)\n",
|
|
__func__, __LINE__, mb->name, err);
|
|
return;
|
|
}
|
|
}
|
|
|
|
extern struct dentry *sunxi_rproc_create_aw_trace_file(const char *name, struct rproc *rproc,
|
|
struct rproc_debug_trace *trace);
|
|
|
|
static int sunxi_rproc_handle_aw_trace(struct rproc *rproc, void *ptr,
|
|
int offset, int avail)
|
|
{
|
|
struct sunxi_rproc *ddata = rproc->priv;
|
|
struct fw_rsc_aw_trace *rsc = ptr;
|
|
struct device *dev = rproc->dev.parent;
|
|
struct rproc_debug_trace *trace = &ddata->trace;
|
|
char name[64];
|
|
|
|
if (sizeof(*rsc) > avail) {
|
|
dev_err(dev, "trace rsc is truncated\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* make sure reserved bytes are zeroes */
|
|
if (rsc->reserved) {
|
|
dev_err(dev, "trace rsc has non zero reserved bytes\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* set the trace buffer dma properties */
|
|
trace->len = rsc->len;
|
|
trace->da = rsc->da;
|
|
/* set pointer on rproc device */
|
|
trace->rproc = rproc;
|
|
trace->va = rproc_da_to_va(rproc, trace->da, trace->len);
|
|
if (!trace->va) {
|
|
dev_err(dev, "failed to get trace mem's va, da: 0x%08x, len: %zu\n", trace->da, trace->len);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* make sure snprintf always null terminates, even if truncating */
|
|
snprintf(name, sizeof(name), "aw_trace_%s", rsc->name);
|
|
|
|
/* create the debugfs entry */
|
|
trace->tfile = sunxi_rproc_create_aw_trace_file(name, rproc, trace);
|
|
if (!trace->tfile) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
dev_info(dev, "add trace mem '%s', va: %p da: 0x%08x, len: %u\n", name, trace->va, rsc->da, rsc->len);
|
|
return 0;
|
|
}
|
|
|
|
static int sunxi_rproc_handle_rsc(struct rproc *rproc, u32 rsc_type,
|
|
void *ptr, int offset, int avail)
|
|
{
|
|
dev_info(rproc->dev.parent, "handle vendor resource, type: %u\n", rsc_type);
|
|
if (rsc_type == RSC_AW_TRACE) {
|
|
return sunxi_rproc_handle_aw_trace(rproc, ptr, offset, avail);
|
|
}
|
|
|
|
return RSC_IGNORED;
|
|
}
|
|
|
|
static struct rproc_ops sunxi_rproc_ops = {
|
|
.start = sunxi_rproc_start,
|
|
.stop = sunxi_rproc_stop,
|
|
.da_to_va = sunxi_da_to_va,
|
|
.kick = sunxi_rproc_kick,
|
|
.handle_rsc = sunxi_rproc_handle_rsc,
|
|
};
|
|
|
|
/*
|
|
* Ferpmsg_send_offchannel_rawtch state of the processor: 0 is off, 1 is on.
|
|
*/
|
|
static int sunxi_rproc_state(struct rproc *rproc)
|
|
{
|
|
struct device *dev = rproc->dev.parent;
|
|
struct sunxi_rproc *ddata = rproc->priv;
|
|
|
|
if (sunxi_core_is_start(ddata->core)) {
|
|
dev_dbg(dev, "Remote process already running\r\n");
|
|
return 1;
|
|
}
|
|
|
|
dev_dbg(dev, "Remote process need to booot\r\n");
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id sunxi_rproc_match[] = {
|
|
{ .compatible = "allwinner,sun8iw21p1-e907-rproc" },
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, sunxi_rproc_match);
|
|
|
|
static int sunxi_rproc_parse_dt(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct rproc *rproc = platform_get_drvdata(pdev);
|
|
struct sunxi_rproc *ddata = rproc->priv;
|
|
struct device_node *np = dev->of_node;
|
|
struct device_node *fw_np = NULL;
|
|
u32 *map_array;
|
|
const char *fw_name;
|
|
int ret = 0;
|
|
int i;
|
|
|
|
rproc->auto_boot = !of_property_read_bool(np, "no-auto-boot");
|
|
|
|
ret = of_property_read_string(np, "core-name", &ddata->core_name);
|
|
if (ret < 0) {
|
|
dev_err(dev, "fial to get core-name\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
#ifdef CONFIG_SUNXI_RPROC_SHARE_IRQ
|
|
ret = of_property_read_string(np, "share-irq", &ddata->share_irq);
|
|
if (ret < 0) {
|
|
dev_err(dev, "fial to get share-irq\n");
|
|
return -EINVAL;
|
|
}
|
|
#endif
|
|
|
|
ddata->core = sunxi_remote_core_find(ddata->core_name);
|
|
if (!ddata->core) {
|
|
dev_err(dev, "Failed to find remote core(%s)\n", ddata->core_name);
|
|
return -ENXIO;
|
|
}
|
|
rproc->core = ddata->core;
|
|
|
|
ret = of_property_read_string(np, "firmware-name", &fw_name);
|
|
if (ret < 0) {
|
|
dev_err(dev, "failed to get firmware-name\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
fw_np = of_parse_phandle(np, "fw-region", 0);
|
|
if (fw_np) {
|
|
struct resource r;
|
|
int len;
|
|
|
|
ret = of_address_to_resource(fw_np, 0, &r);
|
|
if (!ret) {
|
|
len = resource_size(&r);
|
|
|
|
dev_dbg(dev, "register memory fw:%s, size:0x%x.\n", fw_name, len);
|
|
ret = sunxi_register_memory_fw(fw_name, r.start, len);
|
|
if (ret < 0)
|
|
dev_dbg(dev, "register memory fw:%s failed.\n", fw_name);
|
|
} else
|
|
dev_err(dev, "Failed to get fw-region(ret=%d)\n", ret);
|
|
}
|
|
|
|
ret = of_property_count_elems_of_size(np, "memory-mappings", sizeof(u32) * 3);
|
|
if (ret < 0) {
|
|
dev_err(dev, "fail to get memory-mappings\n");
|
|
return -ENXIO;
|
|
}
|
|
|
|
if (ret < 1) {
|
|
dev_err(dev, "Please define memory-mappings property in dts\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ddata->mem_maps_cnt = ret;
|
|
|
|
ddata->mem_maps = devm_kcalloc(dev, ret,
|
|
sizeof(struct sunxi_resource_map_table), GFP_KERNEL);
|
|
if (!ddata->mem_maps) {
|
|
dev_err(dev, "fail to alloc memory-mappings\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
map_array = devm_kcalloc(dev, ret * 3, sizeof(u32), GFP_KERNEL);
|
|
if (!map_array) {
|
|
dev_err(dev, "fail to alloc map_array\n");
|
|
devm_kfree(dev, ddata->mem_maps);
|
|
ddata->mem_maps = NULL;
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ret = of_property_read_u32_array(np, "memory-mappings", map_array,
|
|
ddata->mem_maps_cnt * 3);
|
|
if (ret < 0) {
|
|
dev_err(dev, "fail to read memory-mappings\n");
|
|
return -ENXIO;
|
|
}
|
|
|
|
for (i = 0; i < ddata->mem_maps_cnt; i++) {
|
|
ddata->mem_maps[i].da = map_array[i * 3 + 0];
|
|
ddata->mem_maps[i].len = map_array[i * 3 + 1];
|
|
ddata->mem_maps[i].pa = map_array[i * 3 + 2];
|
|
ddata->mem_maps[i].va = devm_ioremap_wc(dev, ddata->mem_maps[i].pa,
|
|
ddata->mem_maps[i].len);
|
|
|
|
if (!PTR_ERR(ddata->mem_maps[i].va)) {
|
|
dev_err(dev, "ioremap failed %llx - %llx\n", ddata->mem_maps[i].pa,
|
|
ddata->mem_maps[i].pa + ddata->mem_maps[i].len - 1);
|
|
ret = -ENOMEM;
|
|
goto free_mem_maps;
|
|
}
|
|
|
|
dev_dbg(dev, "memory-mappings[%d]: da: 0x%llx, len: 0x%x, pa: 0x%llx va: 0x%p\n",
|
|
i, ddata->mem_maps[i].da, ddata->mem_maps[i].len,
|
|
ddata->mem_maps[i].pa, ddata->mem_maps[i].va);
|
|
|
|
if (ddata->domain) {
|
|
ret = iommu_map(ddata->domain, ddata->mem_maps[i].da, ddata->mem_maps[i].pa,
|
|
ddata->mem_maps[i].len, IOMMU_READ | IOMMU_WRITE);
|
|
if (ret) {
|
|
dev_err(dev, "failed to iommu_map : %llx -> %llx\n", ddata->mem_maps[i].pa,
|
|
ddata->mem_maps[i].da);
|
|
goto free_iomap;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
ddata->support_standby = of_property_read_bool(np, "support-standby");
|
|
|
|
if (ddata->support_standby) {
|
|
dev_dbg(dev, "%s Support Standby.\n", rproc->name);
|
|
ddata->standby = sunxi_rproc_standby_find(ddata->core_name);
|
|
if (!ddata->standby) {
|
|
dev_err(dev, "Failed to find rproc standby struct\n");
|
|
ret = -ENODEV;
|
|
goto free_iomap;
|
|
}
|
|
|
|
ret = sunxi_rproc_standby_init(dev, ddata->standby);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to init rproc standby\n");
|
|
goto free_iomap;
|
|
}
|
|
} else
|
|
ddata->standby = NULL;
|
|
#endif
|
|
|
|
devm_kfree(dev, map_array);
|
|
|
|
return 0;
|
|
|
|
free_iomap:
|
|
for (; i >= 0; i--)
|
|
iommu_unmap(ddata->domain, ddata->mem_maps[i].da, ddata->mem_maps[i].len);
|
|
free_mem_maps:
|
|
devm_kfree(dev, ddata->mem_maps);
|
|
devm_kfree(dev, map_array);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int sunxi_rproc_register_mem(struct rproc *rproc)
|
|
{
|
|
int i = 0;
|
|
int ret;
|
|
struct device *dev = rproc->dev.parent;
|
|
struct device_node *np;
|
|
struct resource r;
|
|
void *va;
|
|
u32 da;
|
|
int len;
|
|
/* pre-register mem entry */
|
|
while ((np = of_parse_phandle(dev->of_node, "memory-region", i))) {
|
|
ret = of_address_to_resource(np, 0, &r);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to get memory-region(ret=%d)\n", ret);
|
|
return ret;
|
|
}
|
|
len = resource_size(&r);
|
|
|
|
if (strcmp(np->name, "vdev0buffer") == 0) {
|
|
/* Initial reserved memory resources */
|
|
ret = of_reserved_mem_device_init_by_idx(dev, dev->of_node, i);
|
|
dev_dbg(dev, "Register a shared buffer(start:0x%08x len:0x%x)\n",
|
|
r.start, r.end - r.start + 1);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to get shared-memory(ret=%d)\n", ret);
|
|
return ret;
|
|
}
|
|
dma_set_coherent_mask(dev, 0xffffffff);
|
|
va = NULL;
|
|
} else {
|
|
va = devm_ioremap_wc(dev, r.start, len);
|
|
if (!PTR_ERR(va)) {
|
|
dev_err(dev, "Fialed to remap memory-region\n");
|
|
return -ENOMEM;
|
|
}
|
|
}
|
|
|
|
da = r.start;
|
|
|
|
rproc_add_mem_entry(rproc, np->name, r.start, da, va, len);
|
|
|
|
dev_dbg(dev, "memory-region%d:%s 0x%08x [0x%08x - 0x%08x]\n", i,
|
|
np->name, (uint32_t)va, r.start, r.end);
|
|
|
|
i++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void sunxi_rproc_unregister_mem(struct rproc *rproc)
|
|
{
|
|
struct device *dev = rproc->dev.parent;
|
|
|
|
rproc_clean_mem_entry(rproc);
|
|
|
|
of_reserved_mem_device_release(dev);
|
|
}
|
|
|
|
int sunxi_rproc_report_crash(char *name, enum rproc_crash_type type)
|
|
{
|
|
struct sunxi_rproc *ddata, *tmp;
|
|
|
|
list_for_each_entry_safe(ddata, tmp, &sunxi_rproc_list, list) {
|
|
if (!strcmp(ddata->rproc->name, name)) {
|
|
rproc_report_crash(ddata->rproc, type);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
EXPORT_SYMBOL(sunxi_rproc_report_crash);
|
|
|
|
static int sunxi_rproc_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct sunxi_rproc *ddata;
|
|
struct device_node *np = dev->of_node;
|
|
struct rproc *rproc;
|
|
const char *fw_name;
|
|
int enabled;
|
|
int ret;
|
|
bool has_iommu;
|
|
#ifdef CONFIG_SUNXI_REMOTEPROC_RT_THREAD
|
|
struct sched_param param = {
|
|
.sched_priority = CONFIG_SUNXI_RPROC_RT_THREAD_PRIO,
|
|
};
|
|
#endif
|
|
|
|
/* we need to read firmware name at first. */
|
|
ret = of_property_read_string(np, "firmware-name", &fw_name);
|
|
if (ret < 0) {
|
|
dev_err(dev, "failed to get firmware-name\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
rproc = rproc_alloc(dev, np->name, &sunxi_rproc_ops, fw_name, sizeof(*ddata));
|
|
if (!rproc)
|
|
return -ENOMEM;
|
|
|
|
rproc->has_iommu = false;
|
|
rproc->auto_boot = true;
|
|
ddata = rproc->priv;
|
|
ddata->rproc = rproc;
|
|
|
|
sunxi_rproc_fw_ops_reload((struct rproc_fw_ops *)rproc->fw_ops);
|
|
|
|
platform_set_drvdata(pdev, rproc);
|
|
|
|
has_iommu = of_property_read_bool(np, "iommus");
|
|
if (has_iommu) {
|
|
dev_dbg(dev, "use iommu.\r\n");
|
|
rproc->has_iommu = true;
|
|
ddata->domain = iommu_domain_alloc(dev->bus);
|
|
if (!ddata->domain) {
|
|
dev_err(dev, "can't alloc iommu domain\n");
|
|
ret = -ENOMEM;
|
|
goto free_rproc;
|
|
}
|
|
|
|
ret = iommu_attach_device(ddata->domain, dev);
|
|
if (ret)
|
|
dev_err(dev, "can't attach iommu device: %d\n", ret);
|
|
|
|
} else {
|
|
ddata->domain = NULL;
|
|
dev_dbg(dev, "not use iommu.\r\n");
|
|
}
|
|
|
|
#ifdef CONFIG_SUNXI_REMOTEPROC_WQ
|
|
ddata->workqueue = create_workqueue(dev_name(dev));
|
|
if (!ddata->workqueue) {
|
|
dev_err(dev, "Cannot create workqueue\n");
|
|
ret = -ENOMEM;
|
|
goto free_domain;
|
|
}
|
|
#endif
|
|
#ifdef CONFIG_SUNXI_REMOTEPROC_RT_THREAD
|
|
ddata->task = kthread_create(sunxi_rproc_work_thread, rproc,
|
|
"rproc-%s", rproc->name);
|
|
|
|
if (IS_ERR(ddata->task)) {
|
|
dev_err(dev, "Cannot create %s rt thread\n", rproc->name);
|
|
ret = PTR_ERR(ddata->task);
|
|
goto free_domain;
|
|
}
|
|
|
|
sched_setscheduler_nocheck(ddata->task, SCHED_FIFO, ¶m);
|
|
/*
|
|
* We keep the reference to the task struct even if
|
|
* the thread dies to avoid that the interrupt code
|
|
* references an already freed task_struct.
|
|
*/
|
|
get_task_struct(ddata->task);
|
|
init_waitqueue_head(&ddata->rq);
|
|
wake_up_process(ddata->task);
|
|
#endif
|
|
|
|
ret = sunxi_rproc_parse_dt(pdev);
|
|
if (ret)
|
|
goto destroy_workqueue;
|
|
|
|
ret = sunxi_core_init(pdev, ddata->core);
|
|
if (ret) {
|
|
dev_err(dev, "core: %s init failed\n", ddata->core_name);
|
|
goto destroy_workqueue;
|
|
}
|
|
|
|
ret = sunxi_rproc_request_mbox(rproc);
|
|
if (ret < 0) {
|
|
dev_err(dev, "sunxi_rproc_request_mbox failed\n");
|
|
goto free_core;
|
|
}
|
|
|
|
ret = sunxi_rproc_register_mem(rproc);
|
|
if (ret < 0) {
|
|
dev_err(dev, "Fialed to parser memory-region\n");
|
|
goto free_mbox;
|
|
}
|
|
|
|
/* check remote process whether already running */
|
|
enabled = sunxi_rproc_state(rproc);
|
|
if (enabled < 0) {
|
|
ret = enabled;
|
|
goto free_mbox;
|
|
}
|
|
|
|
if (enabled) {
|
|
atomic_inc(&rproc->power);
|
|
rproc->state = RPROC_EARLY_BOOT;
|
|
}
|
|
|
|
dev_dbg(dev, "%s: auto_boot = %s.\n", rproc->name,
|
|
rproc->auto_boot ? "true" : "false");
|
|
|
|
ret = rproc_add(rproc);
|
|
if (ret < 0)
|
|
goto free_mbox;
|
|
|
|
#ifdef CONFIG_SUNXI_RPROC_SHARE_IRQ
|
|
sunxi_arch_create_debug_dir(rproc->dbg_dir, ddata->share_irq);
|
|
#endif
|
|
list_add(&ddata->list, &sunxi_rproc_list);
|
|
|
|
return 0;
|
|
|
|
free_mbox:
|
|
sunxi_rproc_free_mbox(rproc);
|
|
free_core:
|
|
sunxi_core_deinit(ddata->core);
|
|
destroy_workqueue:
|
|
#ifdef CONFIG_SUNXI_REMOTEPROC_WQ
|
|
destroy_workqueue(ddata->workqueue);
|
|
#endif
|
|
#ifdef CONFIG_SUNXI_REMOTEPROC_RT_THREAD
|
|
kthread_stop(ddata->task);
|
|
put_task_struct(ddata->task);
|
|
ddata->task = NULL;
|
|
#endif
|
|
free_domain:
|
|
if (ddata->domain)
|
|
ddata->domain = NULL;
|
|
free_rproc:
|
|
rproc_free(rproc);
|
|
return ret;
|
|
}
|
|
|
|
static int sunxi_rproc_remove(struct platform_device *pdev)
|
|
{
|
|
struct rproc *rproc = platform_get_drvdata(pdev);
|
|
struct sunxi_rproc *ddata = rproc->priv;
|
|
|
|
if (atomic_read(&rproc->power) > 0)
|
|
rproc_shutdown(rproc);
|
|
|
|
sunxi_rproc_unregister_mem(rproc);
|
|
|
|
rproc_del(rproc);
|
|
|
|
sunxi_rproc_free_mbox(rproc);
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
if (ddata->support_standby)
|
|
sunxi_rproc_standby_exit(ddata->standby);
|
|
#endif
|
|
#ifdef CONFIG_SUNXI_REMOTEPROC_WQ
|
|
destroy_workqueue(ddata->workqueue);
|
|
#endif
|
|
#ifdef CONFIG_SUNXI_REMOTEPROC_RT_THREAD
|
|
kthread_stop(ddata->task);
|
|
put_task_struct(ddata->task);
|
|
ddata->task = NULL;
|
|
#endif
|
|
|
|
sunxi_core_deinit(ddata->core);
|
|
|
|
/*
|
|
* all device shared a domain in sunxi-iommu driver,
|
|
* so we don't need to release that domain.
|
|
* */
|
|
if (ddata->domain)
|
|
ddata->domain = NULL;
|
|
|
|
list_del(&ddata->list);
|
|
|
|
rproc_free(rproc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static int sunxi_rproc_suspend(struct device *dev)
|
|
{
|
|
struct rproc *rproc = dev_get_drvdata(dev);
|
|
struct sunxi_rproc *ddata = rproc->priv;
|
|
|
|
if (ddata->support_standby)
|
|
return sunxi_rproc_standby_suspend(ddata->standby);
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static int sunxi_rproc_resume(struct device *dev)
|
|
{
|
|
struct rproc *rproc = dev_get_drvdata(dev);
|
|
struct sunxi_rproc *ddata = rproc->priv;
|
|
|
|
if (ddata->support_standby)
|
|
return sunxi_rproc_standby_resume(ddata->standby);
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static int sunxi_rproc_suspend_noirq(struct device *dev)
|
|
{
|
|
struct rproc *rproc = dev_get_drvdata(dev);
|
|
struct sunxi_rproc *ddata = rproc->priv;
|
|
|
|
if (ddata->support_standby)
|
|
return sunxi_rproc_standby_suspend_noirq(ddata->standby);
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static struct dev_pm_ops sunxi_rproc_pm_ops = {
|
|
.suspend = sunxi_rproc_suspend,
|
|
.resume = sunxi_rproc_resume,
|
|
.suspend_noirq = sunxi_rproc_suspend_noirq,
|
|
};
|
|
#endif
|
|
|
|
static struct platform_driver sunxi_rproc_driver = {
|
|
.probe = sunxi_rproc_probe,
|
|
.remove = sunxi_rproc_remove,
|
|
.driver = {
|
|
.name = "sunxi-rproc",
|
|
.of_match_table = sunxi_rproc_match,
|
|
#ifdef CONFIG_PM_SLEEP
|
|
.pm = &sunxi_rproc_pm_ops,
|
|
#endif
|
|
},
|
|
};
|
|
|
|
static int __init sunxi_rproc_init(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = platform_driver_register(&sunxi_rproc_driver);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void __exit sunxi_rproc_exit(void)
|
|
{
|
|
platform_driver_unregister(&sunxi_rproc_driver);
|
|
}
|
|
|
|
SUNXI_RPROC_INITCALL(sunxi_rproc_init);
|
|
SUNXI_RPROC_EXITCALL(sunxi_rproc_exit);
|
|
|
|
MODULE_DESCRIPTION("SUNXI Remote Processor Control Driver");
|
|
MODULE_AUTHOR("lijiajian <lijiajian@allwinnertech.com>");
|
|
MODULE_LICENSE("GPL v2");
|