aboutsummaryrefslogtreecommitdiff
/*
 * SPDX-FileCopyrightText: Copyright 2021-2024 Arm Limited and/or its affiliates <open-source-office@arm.com>
 * SPDX-License-Identifier: GPL-2.0-only
 *
 * This program is free software and is provided to you under the terms of the
 * GNU General Public License version 2 as published by the Free Software
 * Foundation, and any use by you of this program is subject to the terms
 * of such GNU licence.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, you can access it online at
 * http://www.gnu.org/licenses/gpl-2.0.html.
 */

#include <linux/dma-mapping.h>
#include <linux/dma-direct.h>
#include <linux/firmware.h>
#include <linux/io.h>
#include <linux/irqreturn.h>
#include <linux/kernel.h>
#include <linux/mailbox_client.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/remoteproc.h>
#include <linux/reset.h>
#include <linux/version.h>
#include <linux/workqueue.h>

/****************************************************************************
 * Defines
 ****************************************************************************/

#define DMA_ADDR_BITS 32 /* Number of address bits */

#define ETHOSU_RPROC_DRIVER_VERSION "0.0.1"

#define DEFAULT_FW_FILE "arm-ethos-u65.fw"
#define DEFAULT_AUTO_BOOT false

/* firmware naming module parameter */
static char fw_filename_param[256] = DEFAULT_FW_FILE;

/* As the remoteproc is setup at probe, just allow the filename readonly */
module_param_string(filename, fw_filename_param, sizeof(fw_filename_param),
		    0444);
MODULE_PARM_DESC(filename,
		 "Filename for firmware image for Ethos-U remoteproc");

static bool auto_boot = DEFAULT_AUTO_BOOT;
module_param(auto_boot, bool, 0);
MODULE_PARM_DESC(auto_boot, "Set to one to auto boot at load.");

#define RSC_MAPPING RSC_VENDOR_START + 1

/**
 * struct fw_rsc_map_range - memory map range
 * @da:		Start device address of the memory address range
 * @pa:		Start physical address of the memory address range
 * @len:	length of memory address range
 *
 * Memory range to translate between physical and device addresses.
 */
struct fw_rsc_map_range {
	uint32_t da;
	uint32_t pa;
	uint32_t len;
} __packed;

/**
 * struct fw_rsc_mapping - memory map for address translation
 * @num_ranges:	Number of ranges in the memory map
 * @range:	Array of the ranges in the memory map
 *
 * This resource entry requests the host to provide information for how to
 * translate between physical and device addresses.
 */
struct fw_rsc_mapping {
	uint8_t                 num_ranges;
	struct fw_rsc_map_range range[0];
} __packed;

struct ethosu_rproc {
	struct device           *dev;
	struct reset_control    *rstc;
	struct mbox_client      mbox_client;
	struct mbox_chan        *ch_rx;
	struct mbox_chan        *ch_tx;
	struct workqueue_struct *wq;
	struct work_struct      work;
};

/* declaration is in remoteproc_internal.h */
extern irqreturn_t rproc_vq_interrupt(struct rproc *rproc,
				      int vq_id);

static void ethosu_mbox_bottom(struct work_struct *work)
{
	struct ethosu_rproc *erproc = container_of(
		work, struct ethosu_rproc, work);
	struct rproc *rproc = dev_get_drvdata(erproc->dev);

	dev_dbg(&rproc->dev, "Handle interrupt");

	rproc_vq_interrupt(rproc, 0);
}

static void ethosu_mbox_top(struct mbox_client *client,
			    void *message)
{
	struct ethosu_rproc *erproc = container_of(
		client, struct ethosu_rproc, mbox_client);

	queue_work(erproc->wq, &erproc->work);
}

static int ethosu_mem_alloc(struct rproc *rproc,
			    struct rproc_mem_entry *mem)
{
	struct device *dev = rproc->dev.parent;
	void *va;

	if (mem->is_iomem)
		va = (__force void *)devm_ioremap(dev, mem->dma, mem->len);
	else
		va = devm_memremap(dev, mem->dma, mem->len, MEMREMAP_WC);

	if (IS_ERR_OR_NULL(va)) {
		dev_err(dev, "Failed to remap address. pa=%pa, len=%zu",
			&mem->dma,
			mem->len);

		return -ENOMEM;
	}

	mem->va = va;

	return 0;
}

static int ethosu_mem_release(struct rproc *rproc,
			      struct rproc_mem_entry *mem)
{
	struct device *dev = rproc->dev.parent;

	if (!mem->va) {
		dev_warn(dev,
			 "Memory release. No mapping for memory %s pa=%p len=%zu",
			 mem->name, &mem->dma, mem->len);
		goto done;
	}

	if (mem->is_iomem)
		devm_iounmap(dev, (__force __iomem void *)mem->va);
	else
		devm_memunmap(dev, mem->va);

done:

	return 0;
}

static int ethosu_add_carveout(struct rproc *rproc,
			       const phys_addr_t pa,
			       const size_t size,
			       const char *name,
			       bool is_iomem)
{
	struct device *dev = rproc->dev.parent;
	dma_addr_t da;
	struct rproc_mem_entry *mem;

	da = translate_phys_to_dma(dev, pa);
	dev_dbg(dev, "PA to DA. pa=0x%pa, da=0x%pad", &pa, &da);
	if (da == DMA_MAPPING_ERROR) {
		dev_err(dev, "No mapping found for PA. pa=%pa, size=%zu", &pa,
			size);

		return -ENOMEM;
	}

	mem = rproc_mem_entry_init(dev, NULL, pa, size, da, ethosu_mem_alloc,
				   ethosu_mem_release, name);
	if (!mem)
		return -ENOMEM;

	mem->is_iomem = is_iomem;

	dev_dbg(dev, "Add carveout mapping. dma=%pad, da=%x, va=%p, len=%zu",
		&mem->dma, mem->da, mem->va, mem->len);

	rproc_add_carveout(rproc, mem);

	return 0;
}

static int ethosu_rproc_prepare(struct rproc *rproc)
{
	struct device *dev = rproc->dev.parent;
	struct device_node *np = dev->of_node;
	struct of_phandle_iterator it;
	struct resource res;
	int i;
	int ret;

	/* Add carveout for each 'reg' device tree entry */
	for (i = 0; of_address_to_resource(np, i, &res) == 0; i++) {
		dev_dbg(dev, "Found resource. start=%llx, size=%llx",
			res.start, resource_size(&res));

		ret = ethosu_add_carveout(rproc, res.start,
					  resource_size(&res), res.name,
					  !strncmp(res.name, "rom", 3));
		if (ret)
			return ret;
	}

	of_phandle_iterator_init(&it, np, "memory-region", NULL, 0);
	while (of_phandle_iterator_next(&it) == 0) {
		struct reserved_mem *res_mem = of_reserved_mem_lookup(it.node);

		if (!res_mem) {
			dev_err(dev, "Failed to look up memory region. node=%p",
				it.node);

			return -EINVAL;
		}

		dev_dbg(dev,
			"Found memory region. pa=%llx, size=%llu, name=%s",
			res_mem->base, res_mem->size, it.node->name);

		ret = ethosu_add_carveout(rproc, res_mem->base, res_mem->size,
					  it.node->name, false);
		if (ret)
			return ret;
	}

	return 0;
}

static int ethosu_rproc_start(struct rproc *rproc)
{
	struct ethosu_rproc *erproc = (struct ethosu_rproc *)rproc->priv;
	struct device *dev = erproc->dev;

	dev_info(dev, "Starting up Ethos-U subsystem CPU");

	return reset_control_deassert(erproc->rstc);
}

static int ethosu_rproc_stop(struct rproc *rproc)
{
	struct ethosu_rproc *erproc = (struct ethosu_rproc *)rproc->priv;
	struct device *dev = erproc->dev;

	dev_info(dev, "Stopping Ethos-U subsystem CPU");

	return reset_control_assert(erproc->rstc);
}

static void ethosu_rproc_kick(struct rproc *rproc,
			      int vqid)
{
	struct ethosu_rproc *erproc = (struct ethosu_rproc *)rproc->priv;

	dev_dbg(&rproc->dev, "Kicking Ethos-U remoteproc vqid: %d!", vqid);

	mbox_send_message(erproc->ch_tx, (void *)&vqid);
}

static int ethosu_rproc_handle_rsc(struct rproc *rproc,
				   u32 rsc_type,
				   void *rsc,
				   int offset,
				   int avail)
{
	struct ethosu_rproc *erproc = (struct ethosu_rproc *)rproc->priv;
	struct device *dev = erproc->dev;
	struct fw_rsc_mapping *mapping = rsc;
	const struct bus_dma_region *map;
	size_t num_ranges = 0U;
	size_t i;

	if (rsc_type != RSC_MAPPING)
		return RSC_IGNORED;

	if (struct_size(mapping, range, mapping->num_ranges) > avail) {
		dev_err(dev, "mapping rsc is truncated\n");

		return -EINVAL;
	}

	for (map = dev->dma_range_map; map->size; ++map)
		num_ranges++;

	if (num_ranges > mapping->num_ranges) {
		dev_err(dev,
			"Mapping rsc doesn't have enough room for DMA ranges\n");

		return -EINVAL;
	}

	for (i = 0U; i < num_ranges; ++i) {
		struct fw_rsc_map_range *range = &mapping->range[i];
		map = &dev->dma_range_map[i];

		range->da = map->dma_start;
		range->pa = map->cpu_start;
		range->len = map->size;
	}

	dev_dbg(dev, "handle_rsc: Mapping rsc setup");

	return RSC_HANDLED;
}

static const struct rproc_ops ethosu_rproc_ops = {
	.prepare    = &ethosu_rproc_prepare,
	.start      = &ethosu_rproc_start,
	.stop       = &ethosu_rproc_stop,
	.kick       = &ethosu_rproc_kick,
	.handle_rsc = &ethosu_rproc_handle_rsc,
};

static int ethosu_mailbox_init(struct ethosu_rproc *erproc)
{
	struct device *dev = erproc->dev;
	struct mbox_client *cl = &erproc->mbox_client;

	INIT_WORK(&erproc->work, ethosu_mbox_bottom);

	erproc->wq = create_singlethread_workqueue("ethosu_rproc_wq");
	if (!erproc->wq) {
		dev_err(dev, "Failed to create work queue");

		return -EINVAL;
	}

	cl->dev = dev;
	cl->rx_callback = ethosu_mbox_top;
	cl->tx_prepare = NULL;
	cl->tx_done = NULL;
	cl->tx_block = true;
	cl->knows_txdone = false;
	cl->tx_tout = 500;

	erproc->ch_rx = mbox_request_channel_byname(cl, "rx");
	if (IS_ERR(erproc->ch_rx)) {
		dev_err(dev, "Failed to request mbox chan rx");

		return PTR_ERR(erproc->ch_rx);
	}

	erproc->ch_tx = mbox_request_channel_byname(cl, "tx");
	if (IS_ERR(erproc->ch_tx)) {
		dev_dbg(dev, "Using same channel for RX and TX");
		erproc->ch_tx = erproc->ch_rx;
	}

	return 0;
}

static const struct of_device_id ethosu_rproc_match[] = {
	{ .compatible = "arm,ethosu-rproc" },
	{ /* sentinel */ },
};

static int ethosu_rproc_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct device_node *np = dev->of_node;
	struct ethosu_rproc *erproc;
	struct rproc *rproc;
	int ret;

	/* map the first 'memory-region' for DMA-mapping */
	ret = of_reserved_mem_device_init(dev);
	if (ret)
		return ret;

	dma_set_mask_and_coherent(dev, DMA_BIT_MASK(DMA_ADDR_BITS));

	rproc = devm_rproc_alloc(dev, np->name, &ethosu_rproc_ops,
				 fw_filename_param,
				 sizeof(*erproc));
	if (!rproc)
		return -ENOMEM;

	platform_set_drvdata(pdev, rproc);

	/* Configure rproc */
	rproc->has_iommu = false;
	rproc->auto_boot = auto_boot;

	/* Configure Ethos-U rproc */
	erproc = rproc->priv;
	erproc->dev = dev;

	/* Get the reset handler for the subsystem */
	erproc->rstc = devm_reset_control_get_exclusive_by_index(dev, 0);
	if (IS_ERR(erproc->rstc)) {
		dev_err(&pdev->dev, "Failed to get reset controller.");

		return PTR_ERR(erproc->rstc);
	}

	/* Allocate and initialize mailbox client */
	ret = ethosu_mailbox_init(erproc);
	if (ret)
		return ret;

	ret = rproc_add(rproc);
	if (ret) {
		dev_err(dev, "Failed to add rproc");
		goto free_mbox;
	}

	return 0;

free_mbox:
	if (erproc->wq)
		destroy_workqueue(erproc->wq);

	mbox_free_channel(erproc->ch_rx);

	if (erproc->ch_tx != erproc->ch_rx)
		mbox_free_channel(erproc->ch_tx);

	return ret;
}

static int ethosu_rproc_remove(struct platform_device *pdev)
{
	struct rproc *rproc = platform_get_drvdata(pdev);
	struct ethosu_rproc *erproc = rproc->priv;

	if (erproc->wq)
		destroy_workqueue(erproc->wq);

	if (erproc->ch_tx != erproc->ch_rx)
		mbox_free_channel(erproc->ch_tx);

	mbox_free_channel(erproc->ch_rx);

	rproc_del(rproc);

	return 0;
}

static struct platform_driver ethosu_rproc_driver = {
	.probe                  = ethosu_rproc_probe,
	.remove                 = ethosu_rproc_remove,
	.driver                 = {
		.name           = "ethosu-rproc",
		.of_match_table = of_match_ptr(ethosu_rproc_match),
	},
};

module_platform_driver(ethosu_rproc_driver);

MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Arm Ltd");
MODULE_DESCRIPTION("Arm Ethos-U NPU RemoteProc Driver");
MODULE_VERSION(ETHOSU_RPROC_DRIVER_VERSION);