From 2cd539705ba4b510859f18e9815a72950ddd96fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Per=20=C3=85strand?= Date: Mon, 12 Apr 2021 13:46:04 +0200 Subject: Use remoteproc for controlling Ethos-U cpu Implement a remoteproc driver for controlling firmware loading and powering up and down of the CPU on a Ethos-U subsystem. Change-Id: I075d24c780b306aa8152c4a9e24a31df29574f9e --- README.md | 8 + remoteproc/CMakeLists.txt | 2 +- remoteproc/Kbuild | 1 + remoteproc/Kconfig | 8 + remoteproc/ethosu_remoteproc.c | 326 +++++++++++++++++++++++++++++++++++++++++ remoteproc/remoteproc.dtsi | 15 ++ 6 files changed, 359 insertions(+), 1 deletion(-) create mode 100644 remoteproc/ethosu_remoteproc.c diff --git a/README.md b/README.md index c46e1fe..4d48584 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,13 @@ The kernel driver uses the mailbox APIs as a doorbell mechanism. clock-names = "apb_pclk"; }; + ethosu_mcu { + compatible ="arm, ethosu-sgm775-rproc"; + reg = <0x0 0x500f0000 0x0 0x1000>, + <0x0 0x50100000 0x0 0x100000>; + reg-names = "bridge", "firmware"; + }; + ethosu { #address-cells = <2>; #size-cells = <2>; @@ -62,6 +69,7 @@ The kernel driver uses the mailbox APIs as a doorbell mechanism. dma-ranges = <0 0x60000000 0 0x80000000 0 0x20000000>; mboxes= <ðosu_mailbox 0>, <ðosu_mailbox 0>; mbox-names = "tx", "rx"; + ethosu-rproc = <ðosu_mcu>; }; }; ``` diff --git a/remoteproc/CMakeLists.txt b/remoteproc/CMakeLists.txt index 9c88df1..1532f6c 100644 --- a/remoteproc/CMakeLists.txt +++ b/remoteproc/CMakeLists.txt @@ -40,7 +40,7 @@ list(TRANSFORM OBJ PREPEND ${CMAKE_CURRENT_SOURCE_DIR}/) add_custom_target(ethosu-remoteproc-module ALL COMMAND ${CMAKE_MAKE_PROGRAM} -C ${KDIR} EXTRA_CFLAGS=-I${KDIR}/../../../drivers/remoteproc M=${CMAKE_CURRENT_SOURCE_DIR} - CONFIG_ETHOSU_RESET=m + CONFIG_ETHOSU_RESET=m CONFIG_ETHOSU_REMOTEPROC=m CROSS_COMPILE=aarch64-linux-gnu- ARCH=arm64 modules BYPRODUCTS ${CMAKE_CURRENT_SOURCE_DIR}/ethosu_remoteproc.ko diff --git a/remoteproc/Kbuild b/remoteproc/Kbuild index 1f02245..8a88c43 100644 --- a/remoteproc/Kbuild +++ b/remoteproc/Kbuild @@ -19,3 +19,4 @@ # obj-$(CONFIG_ETHOSU_RESET) += ethosu_bridge_reset.o +obj-$(CONFIG_ETHOSU_REMOTEPROC) += ethosu_remoteproc.o diff --git a/remoteproc/Kconfig b/remoteproc/Kconfig index 4884cd2..deca3e6 100644 --- a/remoteproc/Kconfig +++ b/remoteproc/Kconfig @@ -26,3 +26,11 @@ config ARM_ETHOSU_SGM775_RESET reference module for controlling the reset of Cortex-M CPU on the Ethos-U subsystem. +config ARM_ETHOSU_RPROC + tristate "Arm MHUv2 Mailbox" + depends on REMOTEPROC + help + Say Y here if you want to build the Arm Ethos-U remoteproc + reference module for controlling the Cortex-M CPU on the Ethos-U + subsystem. + diff --git a/remoteproc/ethosu_remoteproc.c b/remoteproc/ethosu_remoteproc.c new file mode 100644 index 0000000..9fb561a --- /dev/null +++ b/remoteproc/ethosu_remoteproc.c @@ -0,0 +1,326 @@ +/* + * Copyright (c) 2021 Arm Limited. All rights reserved. + * + * 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. + * + * SPDX-License-Identifier: GPL-2.0-only + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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, DEFAULT_AUTO_BOOT); +MODULE_PARM_DESC(auto_boot, "Set to one to auto boot at load."); + +struct ethosu_rproc { + struct device *dev; + struct reset_control *rstc; + struct rproc_mem_mapping *map; + size_t map_size; +}; + +struct rproc_mem_mapping { + const char *name; + phys_addr_t rproc_addr; + void __iomem *vaddr; + size_t size; +}; + +struct ethosu_rproc_config { + struct fw_config *fw; +}; + +/*****************************************************************************/ + +static int ethosu_rproc_start(struct rproc *rproc) +{ + struct ethosu_rproc *ethosu = (struct ethosu_rproc *)rproc->priv; + struct device *dev = ethosu->dev; + + dev_info(dev, "Starting up Ethos-U subsystem CPU!"); + + return reset_control_deassert(ethosu->rstc); +} + +static int ethosu_rproc_stop(struct rproc *rproc) +{ + struct ethosu_rproc *ethosu = (struct ethosu_rproc *)rproc->priv; + struct device *dev = ethosu->dev; + + dev_info(dev, "Stopping Ethos-U subsystem CPU!"); + + return reset_control_assert(ethosu->rstc); +} + +static void ethosu_rproc_kick(struct rproc *rproc, + int vqid) +{ + return; +} + +static void *ethosu_da_to_va(struct rproc *rproc, + u64 da, + int len) +{ + struct ethosu_rproc *ethosu = (struct ethosu_rproc *)rproc->priv; + int offset; + int i; + + for (i = 0; i < ethosu->map_size; i++) + if (da >= ethosu->map[i].rproc_addr && + da < (ethosu->map[i].rproc_addr + ethosu->map[i].size)) { + offset = da - ethosu->map[i].rproc_addr; + dev_info(ethosu->dev, + "mapping %llx to %p (offset: 0x%x)", da, + (void *)(ethosu->map[i].vaddr + offset), + offset); + + return (void *)(ethosu->map[i].vaddr + offset); + } + + return NULL; +} + +static const struct rproc_ops ethosu_rproc_ops = { + .start = ðosu_rproc_start, + .stop = ðosu_rproc_stop, + .kick = ðosu_rproc_kick, + .da_to_va = ðosu_da_to_va, +}; + +/** + * Since the remote side doesn't yet support rpmsg just return an + * empty resource table when asked about it. + */ +struct resource_table *ethosu_rproc_find_rsc_table(struct rproc *rproc, + const struct firmware *fw, + int *tablesz) +{ + static struct resource_table table = { .ver = 1, }; + struct ethosu_rproc *ethosu = (struct ethosu_rproc *)rproc->priv; + + dev_info(ethosu->dev, "Sizeof struct resource_table : %zu", + sizeof(table)); + *tablesz = sizeof(table); + + return &table; +} + +/*****************************************************************************/ + +static int ethosu_rproc_of_memory_translations(struct platform_device *pdev, + struct ethosu_rproc *ethosu_rproc) +{ + const char *const of_rproc_address_cells = + "#ethosu,rproc-address-cells"; + const char *const of_rproc_ranges = "ethosu,rproc-ranges"; + const char *const of_rproc_ranges_names = "ethosu,rproc-names"; + + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct rproc_mem_mapping *mem_map; + const __be32 *rproc_ranges; + + int addr_cells, rproc_addr_cells, size_cells, cells_for_array_element; + int i, len, cnt, name_cnt, ret = 0; + + if (of_property_read_u32(np, of_rproc_address_cells, + &rproc_addr_cells)) { + dev_info(dev, "%s not defined in dtb", of_rproc_address_cells); + + return -ENODEV; + } + + addr_cells = of_n_addr_cells(np); + size_cells = of_n_size_cells(np); + + dev_dbg(dev, "Using %d remote proc address cells for parsing mapping", + rproc_addr_cells); + dev_dbg(dev, + "Using %d of size %d parent address cells for parsing mapping", + addr_cells, size_cells); + + cells_for_array_element = addr_cells + rproc_addr_cells + size_cells; + + cnt = of_property_count_elems_of_size(np, of_rproc_ranges, + cells_for_array_element); + cnt /= sizeof(u32); + + if (cnt <= 0) { + dev_info(dev, "No remoteproc memory mapping ranges found."); + + return 0; + } + + name_cnt = of_property_count_strings(np, of_rproc_ranges_names); + if (name_cnt > 0 && name_cnt != cnt) { + dev_err(dev, "Mismatch length for %s and %s", of_rproc_ranges, + of_rproc_ranges_names); + + return -EINVAL; + } + + mem_map = devm_kcalloc(dev, cnt, sizeof(*mem_map), GFP_KERNEL); + if (!mem_map) + return -ENOMEM; + + rproc_ranges = of_get_property(np, of_rproc_ranges, &len); + + for (i = 0; i < cnt; i++) { + struct resource *r; + const char *name = NULL; + int n; + + of_property_read_string_index(np, of_rproc_ranges_names, i, + &name); + mem_map[i].name = name; + n = i * cells_for_array_element; + mem_map[i].rproc_addr = + of_read_number(&rproc_ranges[n + addr_cells], + rproc_addr_cells); + mem_map[i].size = + of_read_number(&rproc_ranges[n + addr_cells + + rproc_addr_cells], + size_cells); + + r = platform_get_resource_byname(pdev, IORESOURCE_MEM, name); + if (!r) { + dev_err(&pdev->dev, "Failed to get '%s' resource.\n", + name); + + return -EINVAL; + } + + mem_map[i].vaddr = devm_ioremap_wc(dev, r->start, + mem_map[i].size); + if (IS_ERR(mem_map[i].vaddr)) { + dev_err(dev, "Failed to remap '%s'", name); + + return PTR_ERR(mem_map[i].vaddr); + } + + dev_dbg(dev, + "rproc memory mapping[%i]=%s: da %llx, va, %pa, size %zx:\n", + i, name, mem_map[i].rproc_addr, &mem_map[i].vaddr, + mem_map[i].size); + } + + ethosu_rproc->map = mem_map; + ethosu_rproc->map_size = cnt; + dev_dbg(dev, "rproc memory mapped %zx regions", ethosu_rproc->map_size); + + return ret; +} + +static const struct of_device_id ethosu_rproc_match[] = { + { .compatible = "arm,ethosu-sgm775-rproc" }, +}; + +static int ethosu_rproc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct ethosu_rproc *ethosu_rproc; + struct rproc *rproc; + int ret = -ENODEV; + + rproc = rproc_alloc(dev, np->name, ðosu_rproc_ops, + fw_filename_param, + sizeof(*ethosu_rproc)); + if (!rproc) { + ret = -ENOMEM; + goto out; + } + + /* Configure rproc */ + rproc->has_iommu = false; + rproc->auto_boot = auto_boot; + + platform_set_drvdata(pdev, rproc); + + ethosu_rproc = rproc->priv; + ethosu_rproc->dev = dev; + + /* Get the reset handler for the subsystem */ + ethosu_rproc->rstc = devm_reset_control_get_exclusive_by_index(dev, 0); + if (IS_ERR(ethosu_rproc->rstc)) { + dev_err(&pdev->dev, "Failed to get reset controller.\n"); + ret = PTR_ERR(ethosu_rproc->rstc); + goto free_rproc; + } + + /* Get the translation from device memory to kernel space */ + ret = ethosu_rproc_of_memory_translations(pdev, ethosu_rproc); + if (ret) + goto free_rproc; + + ret = rproc_add(rproc); + +free_rproc: + if (ret) + rproc_free(rproc); + +out: + + return ret; +} + +static int ethosu_rproc_remove(struct platform_device *pdev) +{ + struct rproc *rproc = platform_get_drvdata(pdev); + + rproc_del(rproc); + rproc_free(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); diff --git a/remoteproc/remoteproc.dtsi b/remoteproc/remoteproc.dtsi index 9f1a21c..2e38e17 100644 --- a/remoteproc/remoteproc.dtsi +++ b/remoteproc/remoteproc.dtsi @@ -27,5 +27,20 @@ reg = <0x0 0x500f0000 0x0 0x00001000>; }; + /* configuration for the remote proc driver. */ + ethosu_cpu { + #address-cells = <2>; + #size-cells = <2>; + range; + compatible ="arm,ethosu-sgm775-rproc"; + reg = <0x0 0x50100000 0x0 0x00100000>, + <0x0 0x84000000 0x0 0x00100000>; + reg-names = "rom", "shared"; + resets = <ðosu_reset 0>; + #ethosu,rproc-address-cells = <1>; + ethosu,rproc-ranges = <0x0 0x50100000 0x00000000 0x0 0x00100000 + 0x0 0x84000000 0x64000000 0x0 0x00100000>; + ethosu,rproc-names = "rom", "shared"; + }; }; -- cgit v1.2.1