| /* |
| * 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 <linux/firmware.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/platform_device.h> |
| #include <linux/remoteproc.h> |
| #include <linux/reset.h> |
| #include <linux/version.h> |
| |
| #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; |
| } |
| |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 13, 0) |
| static void *ethosu_da_to_va(struct rproc *rproc, |
| u64 da, |
| int len) |
| #else |
| static void *ethosu_da_to_va(struct rproc *rproc, |
| u64 da, |
| size_t len, |
| bool *is_iomem) |
| #endif |
| { |
| 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-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); |