ZynqMP platform has ARM Cortex-R5 dual core remote processor also known as RPU (Real-time Processing Unit). This remoteproc platform driver is responsible to configure the RPU and manages life-cycle of the RPU along with other platform specific operations.
Signed-off-by: Tanmay Shah tanmay.shah@amd.com --- MAINTAINERS | 1 + drivers/firmware/firmware-zynqmp.c | 20 ++ drivers/remoteproc/Kconfig | 9 + drivers/remoteproc/Makefile | 1 + drivers/remoteproc/xlnx_rproc.c | 411 +++++++++++++++++++++++++++++ include/zynqmp_firmware.h | 16 ++ 6 files changed, 458 insertions(+) create mode 100644 drivers/remoteproc/xlnx_rproc.c
diff --git a/MAINTAINERS b/MAINTAINERS index 8e40da38e7..feaf2ecd90 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1323,6 +1323,7 @@ REMOTEPROC M: Tanmay Shah tanmay.shah@amd.com S: Maintained F: drivers/remoteproc/rproc_virtio.c +F: drivers/remoteproc/xlnx_rproc.c F: include/rproc_virtio.h
RISC-V diff --git a/drivers/firmware/firmware-zynqmp.c b/drivers/firmware/firmware-zynqmp.c index 0897992405..ec6057d4e3 100644 --- a/drivers/firmware/firmware-zynqmp.c +++ b/drivers/firmware/firmware-zynqmp.c @@ -202,6 +202,26 @@ int zynqmp_pm_request_node(const u32 node, const u32 capabilities, qos, ack, NULL); }
+/** + * zynqmp_pm_request_wake - PM call to wake up selected master or subsystem + * @node: Node ID of the master or subsystem + * @set_addr: Specifies whether the address argument is relevant + * @address: Address from which to resume when woken up + * @ack: Flag to specify whether acknowledge requested + * + * Return: status, either success or error+reason + */ +int zynqmp_pm_request_wake(const u32 node, + const bool set_addr, + const u64 address, + const enum zynqmp_pm_request_ack ack) +{ + /* set_addr flag is encoded into 1st bit of address */ + return xilinx_pm_request(PM_REQUEST_WAKEUP, node, + lower_32_bits(address | set_addr), + upper_32_bits(address), ack, NULL); +} + int zynqmp_pm_is_function_supported(const u32 api_id, const u32 id) { int ret; diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig index b758c248e4..9fd8cda3c8 100644 --- a/drivers/remoteproc/Kconfig +++ b/drivers/remoteproc/Kconfig @@ -113,4 +113,13 @@ config REMOTEPROC_VIRTIO drivers provide a set of ops for the real virtio device driver to call.
+config REMOTEPROC_XLNX + bool "Support for AMD-Xilinx platform's remoteproc driver" + select REMOTEPROC_VIRTIO + depends on DM + depends on ZYNQMP_FIRMWARE + help + Say 'y' here to add support for remoteproc platform + driver for various AMD-Xilinx platforms + endmenu diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile index 61fdb87efb..1d0c48820b 100644 --- a/drivers/remoteproc/Makefile +++ b/drivers/remoteproc/Makefile @@ -17,3 +17,4 @@ obj-$(CONFIG_REMOTEPROC_TI_POWER) += ti_power_proc.o obj-$(CONFIG_REMOTEPROC_TI_PRU) += pru_rproc.o obj-$(CONFIG_REMOTEPROC_TI_IPU) += ipu_rproc.o obj-$(CONFIG_REMOTEPROC_VIRTIO) += rproc_virtio.o +obj-$(CONFIG_REMOTEPROC_XLNX) += xlnx_rproc.o diff --git a/drivers/remoteproc/xlnx_rproc.c b/drivers/remoteproc/xlnx_rproc.c new file mode 100644 index 0000000000..f29b958b7f --- /dev/null +++ b/drivers/remoteproc/xlnx_rproc.c @@ -0,0 +1,411 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * AMD-Xilinx remoteproc driver + * + * Copyright (C) 2023, Advanced Micro Devices, Inc. + */ + +#include <common.h> +#include <dm.h> +#include <errno.h> +#include <remoteproc.h> +#include <rpmsg.h> +#include <zynqmp_firmware.h> +#include <asm/dma-mapping.h> +#include <asm/io.h> +#include <dm/device_compat.h> +#include <dm/device-internal.h> +#include <linux/ioport.h> + +#define MAX_CORE 2 +#define RSCTBL_PROP "xlnx,rsc-tbl" +#define RSC_TBL_SIZE 0x400 + +enum xlnx_cluster_mode_dt_prop { + split_mode = 0, + lockstep_mode = 1, + single_cpu_mode = 2, +}; + +enum xlnx_rpu_state { + INVALID_STATE = 0, + DRIVER_PROBE = 1, + RPU_INIT = 2, + RPU_ATTACH = 3, + RPU_DETACH = 4 +}; + +struct rproc xlnx_rproc_cfg_arr[2]; + +/** + * struct xlnx_rproc_core_privdata - contains private data of RPU core driver + * + * @cluster_dev: parent device of each core, device mapped to + * cluster node + * @rpu_mode: one of split, lockstep or single-cpu mode + * @tcm_mode: TCM configuration - split or lockstep + * @state: current state of RPU + * @rproc_cfg: core's corresponding remoteproc data + * @vdev: core's corresponding remoteproc virtio device + * @rsc_pa: core's physical resource table address + * @pd_node: power-domain id of core + * @index: core's child node index in cluster node in device-tree + */ +struct xlnx_rproc_core_privdata { + struct udevice *cluster_dev; + enum rpu_oper_mode rpu_mode; + enum rpu_tcm_comb tcm_mode; + enum xlnx_rpu_state state; + struct rproc rproc_cfg; + struct udevice *vdev; + phys_addr_t rsc_pa; + int pd_node; + int index; +}; + +/** + * struct xlnx_rproc_cluster_privdata - contains privdate data of cluster driver + * + * @cores: Array of pointers to R5 cores within the cluster + * @rpu_cluster_mode: xlnx,cluster-mode dt prop value to configure RPU + * @core_probed: keep count of number of cores probed + * @core_count: number of individual cores available based on cluster-mode + * for lockstep this is 1, for split this is 2. + */ +struct xlnx_rproc_cluster_privdata { + struct xlnx_rproc_core_privdata *cores[MAX_CORE]; + enum xlnx_cluster_mode_dt_prop rpu_cluster_mode; + int core_probed; + int core_count; +}; + +static int xlnx_rproc_detach(struct udevice *udev) +{ + struct xlnx_rproc_core_privdata *core = dev_get_priv(udev); + + if (core->state != RPU_ATTACH) { + debug("RPU %s isn't attached yet\n", udev->name); + return 0; + } + + core->state = RPU_DETACH; + + return 0; +} + +static int xlnx_rproc_attach(struct udevice *udev) +{ + struct xlnx_rproc_core_privdata *core = dev_get_priv(udev); + struct rproc *rproc = rproc_get_cfg(udev); + struct resource_table *rsctbl; + u32 rsc_tbl_start; + int ret; + + /* + * RPU attach will parse and alloc resources only after INIT state. + * Once the resources are allocated we won't be releasing them during + * detach, but we just change the state of RPU. So, during kick, based + * on state we can decided if RPU should be notified or not + */ + if (core->state == RPU_DETACH) { + core->state = RPU_ATTACH; + return 0; + } + + if (core->state != RPU_INIT) { + debug("RPU isn't initialized, can't attach\n"); + return 0; + } + + /* get rsc tbl carveout info */ + ret = dev_read_u32_index(udev, RSCTBL_PROP, 1, &rsc_tbl_start); + if (ret < 0) { + debug("failed to read phandle for prop %s", RSCTBL_PROP); + return ret; + } + + core->rsc_pa = (phys_addr_t)map_physmem(rsc_tbl_start, RSC_TBL_SIZE, + MAP_NOCACHE); + rproc->table_ptr = (struct resource_table *)core->rsc_pa; + + if (!core->rsc_pa) { + dev_info(udev, "rsc tbl not available\n"); + return -EINVAL; + } + + rsctbl = (struct resource_table *)core->rsc_pa; + if (rsctbl->ver != 1) { + debug("fw rsc table version %d not compatible\n", rsctbl->ver); + return -EINVAL; + } + + if (rsctbl->num < 1 || rsctbl->num > 255) { + debug("number of resources are invalid %d\n", rsctbl->num); + return -EINVAL; + } + + if (rproc_attach_resource_table(udev)) { + debug("rsc table not found\n"); + return -EINVAL; + } + + core->state = RPU_ATTACH; + + return 0; +} + +static void xlnx_remove_all_res(struct rproc_mem_entry *mapping) +{ + struct list_head *tmp, *head; + + head = &mapping->node; + if (!head) + return; + + /* remove the list */ + tmp = head; + while (tmp) { + head = head->next; + kfree(tmp); + tmp = head; + } +} + +static int xlnx_add_res(struct udevice *dev, struct rproc_mem_entry *mapping) +{ + struct rproc *rproc = rproc_get_cfg(dev); + + list_add_tail(&mapping->node, &rproc->mappings.node); + + return 0; +} + +static int zynqmp_r5_get_mem_region_node(struct udevice *udev) +{ + struct rproc_mem_entry *mapping; + int num_mem_region = 4, i, ret; + u32 mem_reg_vals[4] = {0}; + ofnode mem_reg_node; + struct rproc *rproc; + + rproc = rproc_get_cfg(udev); + if (!rproc) + return -EINVAL; + + ret = dev_read_u32_array(udev, "memory-region", mem_reg_vals, 4); + if (ret < 0) { + debug("Unable to read memory-region property\n"); + return -EINVAL; + } + + for (i = 0; i < num_mem_region; i++) { + mem_reg_node = ofnode_get_by_phandle(mem_reg_vals[i]); + if (!ofnode_valid(mem_reg_node)) { + debug("Could not parse mem region node\n"); + return -EINVAL; + } + + mapping = kzalloc(sizeof(*mapping), GFP_KERNEL); + if (!mapping) + goto remove_mem_region; + + mapping->dma = ofnode_get_addr(mem_reg_node); + mapping->len = ofnode_get_size(mem_reg_node); + mapping->da = mapping->dma; + mapping->va = map_physmem(mapping->da, mapping->len, MAP_NOCACHE); + + strlcpy(mapping->name, ofnode_get_name(mem_reg_node), + RPMSG_NAME_SIZE); + + debug("dev %s mapping %s: va=0x%p, da=0x%x, dma=0x%llx, len=0x%x\n", + udev->name, mapping->name, mapping->va, mapping->da, + mapping->dma, mapping->len); + xlnx_add_res(udev, mapping); + } + + return 0; + +remove_mem_region: + xlnx_remove_all_res(&rproc->mappings); + + return -EINVAL; +} + +static int xlnx_rproc_init(struct udevice *udev) +{ + struct xlnx_rproc_core_privdata *core = dev_get_priv(udev); + struct rproc *rproc = rproc_get_cfg(udev); + int ret; + + if (core->state != DRIVER_PROBE) + return 0; + + /* + * If for some reason, memory-region property fails then don't fail + * the command as memory-region is not required property + */ + ret = zynqmp_r5_get_mem_region_node(udev); + if (ret) + debug("adding memory-region failed with ret %d\n", ret); + + rproc->support_rpmsg_virtio = true; + + core->state = RPU_INIT; + + return 0; +} + +static int xlnx_rproc_probe(struct udevice *udev) +{ + struct xlnx_rproc_cluster_privdata *cluster = dev_get_priv(udev->parent); + struct xlnx_rproc_core_privdata *core = dev_get_priv(udev); + struct dm_rproc_uclass_pdata *pdata = dev_get_plat(udev); + + /* Assume primary core gets probed first */ + if (cluster->core_probed >= cluster->core_count) { + debug("core %d isn't used in mode %d\n", + cluster->core_probed, cluster->rpu_cluster_mode); + return -EINVAL; + } + + core->index = cluster->core_probed; + cluster->cores[core->index] = core; + + pdata->rproc = &xlnx_rproc_cfg_arr[core->index]; + pdata->rproc->rproc_id = core->index; + + INIT_LIST_HEAD(&pdata->rproc->mappings.node); + + if (cluster->rpu_cluster_mode == split_mode) { + core->tcm_mode = PM_RPU_TCM_SPLIT; + core->rpu_mode = PM_RPU_MODE_SPLIT; + } else if (cluster->rpu_cluster_mode == lockstep_mode) { + core->tcm_mode = PM_RPU_TCM_COMB; + core->rpu_mode = PM_RPU_MODE_LOCKSTEP; + } else if (cluster->rpu_cluster_mode == single_cpu_mode) { + debug("single cpu cluster mode not supported\n"); + return -EINVAL; + } + + pdata->rproc->support_rpmsg_virtio = true; + + cluster->core_probed++; + core->state = DRIVER_PROBE; + + cluster->cores[core->index] = core; + INIT_LIST_HEAD(&pdata->rproc->mappings.node); + + return 0; +} + +static struct resource_table * +xlnx_rpu_rproc_get_loaded_rsc_table(struct udevice *dev, int *table_sz) +{ + struct xlnx_rproc_core_privdata *core = dev_get_priv(dev); + + *table_sz = RSC_TBL_SIZE; + + return (struct resource_table *)core->rsc_pa; +} + +static int xlnx_rproc_kick(struct udevice *dev, int notify_id) +{ + struct xlnx_rproc_core_privdata *core = dev_get_priv(dev); + + if (core->state != RPU_ATTACH) { + debug("error: RPU %s state=%d\n", dev->name, core->state); + return -EINVAL; + } + + return 0; +} + +static int xlnx_rproc_of_to_plat(struct udevice *udev) +{ + struct xlnx_rproc_core_privdata *core = dev_get_priv(udev); + int ret; + + ret = dev_read_u32_index(udev, "power-domains", 1, &core->pd_node); + if (ret) { + debug("failed to read power-domains property\n"); + return -EINVAL; + } + + return 0; +} + +static const struct dm_rproc_ops xlnx_rproc_ops = { + .init = xlnx_rproc_init, + .attach = xlnx_rproc_attach, + .detach = xlnx_rproc_detach, + .get_loaded_rsc_table = xlnx_rpu_rproc_get_loaded_rsc_table, + .add_res = xlnx_add_res, + .kick = xlnx_rproc_kick, +}; + +static const struct udevice_id xlnx_rproc_ids[] = { + { .compatible = "xlnx,zynqmp-r5f" } +}; + +U_BOOT_DRIVER(xlnx_rproc) = { + .name = "xlnx-rproc", + .of_match = xlnx_rproc_ids, + .id = UCLASS_REMOTEPROC, + .ops = &xlnx_rproc_ops, + .probe = xlnx_rproc_probe, + .of_to_plat = xlnx_rproc_of_to_plat, + .priv_auto = sizeof(struct xlnx_rproc_core_privdata), + .flags = DM_FLAG_VITAL, +}; + +static int xlnx_rproc_cluster_probe(struct udevice *udev) +{ + struct xlnx_rproc_cluster_privdata *cluster = dev_get_priv(udev); + enum xlnx_cluster_mode_dt_prop cluster_mode; + int ret; + + if (device_get_child_count(udev) < MAX_CORE) { + dev_err(udev, "Invalid number of R5 cores for cluster %s\n", + udev->name); + return -EINVAL; + } + + /* set mode */ + ret = dev_read_u32(udev, "xlnx,cluster-mode", &cluster_mode); + if (ret < 0) + cluster_mode = 1; /* default is lockstep */ + + if (cluster->rpu_cluster_mode < split_mode || + cluster->rpu_cluster_mode > single_cpu_mode) { + debug("invalid cluster mode %d\n", cluster->rpu_cluster_mode); + return -EINVAL; + } + + cluster->rpu_cluster_mode = cluster_mode; + + if (cluster_mode == split_mode) { /* split */ + cluster->core_count = 2; + } else if (cluster_mode == lockstep_mode) { /* lockstep */ + cluster->core_count = 1; + } else if (cluster_mode == single_cpu_mode) { /* single-cpu not supported */ + debug("single cpu cluster mode not supported\n"); + return -EINVAL; + } + + cluster->core_probed = 0; + + return 0; +} + +static const struct udevice_id xlnx_cluster_ids[] = { + { .compatible = "xlnx,zynqmp-r5fss", }, + {} +}; + +U_BOOT_DRIVER(xlnx_cluster) = { + .name = "xlnx_cluster", + .of_match = xlnx_cluster_ids, + .id = UCLASS_MISC, + .probe = xlnx_rproc_cluster_probe, + .priv_auto = sizeof(struct xlnx_rproc_cluster_privdata), +}; diff --git a/include/zynqmp_firmware.h b/include/zynqmp_firmware.h index ce086f48d4..6419ca0c58 100644 --- a/include/zynqmp_firmware.h +++ b/include/zynqmp_firmware.h @@ -450,6 +450,21 @@ enum zynqmp_pm_request_ack { ZYNQMP_PM_REQUEST_ACK_NON_BLOCKING = 3, };
+enum rpu_boot_mem { + PM_RPU_BOOTMEM_LOVEC = 0, + PM_RPU_BOOTMEM_HIVEC = 1, +}; + +enum rpu_tcm_comb { + PM_RPU_TCM_SPLIT = 0, + PM_RPU_TCM_COMB = 1, +}; + +enum rpu_oper_mode { + PM_RPU_MODE_LOCKSTEP = 0, + PM_RPU_MODE_SPLIT = 1, +}; + unsigned int zynqmp_firmware_version(void); int zynqmp_pmufw_node(u32 id); int zynqmp_pmufw_config_close(void); @@ -461,6 +476,7 @@ int zynqmp_pm_request_node(const u32 node, const u32 capabilities, int zynqmp_pm_set_sd_config(u32 node, enum pm_sd_config_type config, u32 value); int zynqmp_pm_set_gem_config(u32 node, enum pm_gem_config_type config, u32 value); +int zynqmp_pm_set_rpu_mode(u32 node, int rpu_mode); int zynqmp_pm_is_function_supported(const u32 api_id, const u32 id); int zynqmp_mmio_read(const u32 address, u32 *value); int zynqmp_mmio_write(const u32 address, const u32 mask, const u32 value);