| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * ZynqMP R5 Remote Processor driver |
| * |
| */ |
| |
| #include <dt-bindings/power/xlnx-zynqmp-power.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/firmware/xlnx-zynqmp.h> |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/of_address.h> |
| #include <linux/of_platform.h> |
| #include <linux/of_reserved_mem.h> |
| #include <linux/platform_device.h> |
| #include <linux/remoteproc.h> |
| #include <linux/slab.h> |
| |
| #include "remoteproc_internal.h" |
| |
| /* |
| * settings for RPU cluster mode which |
| * reflects possible values of xlnx,cluster-mode dt-property |
| */ |
| enum zynqmp_r5_cluster_mode { |
| SPLIT_MODE = 0, /* When cores run as separate processor */ |
| LOCKSTEP_MODE = 1, /* cores execute same code in lockstep,clk-for-clk */ |
| SINGLE_CPU_MODE = 2, /* core0 is held in reset and only core1 runs */ |
| }; |
| |
| /** |
| * struct mem_bank_data - Memory Bank description |
| * |
| * @addr: Start address of memory bank |
| * @size: Size of Memory bank |
| * @pm_domain_id: Power-domains id of memory bank for firmware to turn on/off |
| * @bank_name: name of the bank for remoteproc framework |
| */ |
| struct mem_bank_data { |
| phys_addr_t addr; |
| size_t size; |
| u32 pm_domain_id; |
| char *bank_name; |
| }; |
| |
| /* |
| * Hardcoded TCM bank values. This will be removed once TCM bindings are |
| * accepted for system-dt specifications and upstreamed in linux kernel |
| */ |
| static const struct mem_bank_data zynqmp_tcm_banks[] = { |
| {0xffe00000UL, 0x10000UL, PD_R5_0_ATCM, "atcm0"}, /* TCM 64KB each */ |
| {0xffe20000UL, 0x10000UL, PD_R5_0_BTCM, "btcm0"}, |
| {0xffe90000UL, 0x10000UL, PD_R5_1_ATCM, "atcm1"}, |
| {0xffeb0000UL, 0x10000UL, PD_R5_1_BTCM, "btcm1"}, |
| }; |
| |
| /** |
| * struct zynqmp_r5_core |
| * |
| * @dev: device of RPU instance |
| * @np: device node of RPU instance |
| * @tcm_bank_count: number TCM banks accessible to this RPU |
| * @tcm_banks: array of each TCM bank data |
| * @rmem_count: Number of reserved mem regions |
| * @rmem: reserved memory region nodes from device tree |
| * @rproc: rproc handle |
| * @pm_domain_id: RPU CPU power domain id |
| */ |
| struct zynqmp_r5_core { |
| struct device *dev; |
| struct device_node *np; |
| int tcm_bank_count; |
| struct mem_bank_data **tcm_banks; |
| int rmem_count; |
| struct reserved_mem **rmem; |
| struct rproc *rproc; |
| u32 pm_domain_id; |
| }; |
| |
| /** |
| * struct zynqmp_r5_cluster |
| * |
| * @dev: r5f subsystem cluster device node |
| * @mode: cluster mode of type zynqmp_r5_cluster_mode |
| * @core_count: number of r5 cores used for this cluster mode |
| * @r5_cores: Array of pointers pointing to r5 core |
| */ |
| struct zynqmp_r5_cluster { |
| struct device *dev; |
| enum zynqmp_r5_cluster_mode mode; |
| int core_count; |
| struct zynqmp_r5_core **r5_cores; |
| }; |
| |
| /* |
| * zynqmp_r5_set_mode() |
| * |
| * set RPU cluster and TCM operation mode |
| * |
| * @r5_core: pointer to zynqmp_r5_core type object |
| * @fw_reg_val: value expected by firmware to configure RPU cluster mode |
| * @tcm_mode: value expected by fw to configure TCM mode (lockstep or split) |
| * |
| * Return: 0 for success and < 0 for failure |
| */ |
| static int zynqmp_r5_set_mode(struct zynqmp_r5_core *r5_core, |
| enum rpu_oper_mode fw_reg_val, |
| enum rpu_tcm_comb tcm_mode) |
| { |
| int ret; |
| |
| ret = zynqmp_pm_set_rpu_mode(r5_core->pm_domain_id, fw_reg_val); |
| if (ret < 0) { |
| dev_err(r5_core->dev, "failed to set RPU mode\n"); |
| return ret; |
| } |
| |
| ret = zynqmp_pm_set_tcm_config(r5_core->pm_domain_id, tcm_mode); |
| if (ret < 0) |
| dev_err(r5_core->dev, "failed to configure TCM\n"); |
| |
| return ret; |
| } |
| |
| /* |
| * zynqmp_r5_rproc_start() |
| * @rproc: single R5 core's corresponding rproc instance |
| * |
| * Start R5 Core from designated boot address. |
| * |
| * return 0 on success, otherwise non-zero value on failure |
| */ |
| static int zynqmp_r5_rproc_start(struct rproc *rproc) |
| { |
| struct zynqmp_r5_core *r5_core = rproc->priv; |
| enum rpu_boot_mem bootmem; |
| int ret; |
| |
| /* |
| * The exception vector pointers (EVP) refer to the base-address of |
| * exception vectors (for reset, IRQ, FIQ, etc). The reset-vector |
| * starts at the base-address and subsequent vectors are on 4-byte |
| * boundaries. |
| * |
| * Exception vectors can start either from 0x0000_0000 (LOVEC) or |
| * from 0xFFFF_0000 (HIVEC) which is mapped in the OCM (On-Chip Memory) |
| * |
| * Usually firmware will put Exception vectors at LOVEC. |
| * |
| * It is not recommend that you change the exception vector. |
| * Changing the EVP to HIVEC will result in increased interrupt latency |
| * and jitter. Also, if the OCM is secured and the Cortex-R5F processor |
| * is non-secured, then the Cortex-R5F processor cannot access the |
| * HIVEC exception vectors in the OCM. |
| */ |
| bootmem = (rproc->bootaddr >= 0xFFFC0000) ? |
| PM_RPU_BOOTMEM_HIVEC : PM_RPU_BOOTMEM_LOVEC; |
| |
| dev_dbg(r5_core->dev, "RPU boot addr 0x%llx from %s.", rproc->bootaddr, |
| bootmem == PM_RPU_BOOTMEM_HIVEC ? "OCM" : "TCM"); |
| |
| ret = zynqmp_pm_request_wake(r5_core->pm_domain_id, 1, |
| bootmem, ZYNQMP_PM_REQUEST_ACK_NO); |
| if (ret) |
| dev_err(r5_core->dev, |
| "failed to start RPU = 0x%x\n", r5_core->pm_domain_id); |
| return ret; |
| } |
| |
| /* |
| * zynqmp_r5_rproc_stop() |
| * @rproc: single R5 core's corresponding rproc instance |
| * |
| * Power down R5 Core. |
| * |
| * return 0 on success, otherwise non-zero value on failure |
| */ |
| static int zynqmp_r5_rproc_stop(struct rproc *rproc) |
| { |
| struct zynqmp_r5_core *r5_core = rproc->priv; |
| int ret; |
| |
| ret = zynqmp_pm_force_pwrdwn(r5_core->pm_domain_id, |
| ZYNQMP_PM_REQUEST_ACK_BLOCKING); |
| if (ret) |
| dev_err(r5_core->dev, "failed to stop remoteproc RPU %d\n", ret); |
| |
| return ret; |
| } |
| |
| /* |
| * zynqmp_r5_mem_region_map() |
| * @rproc: single R5 core's corresponding rproc instance |
| * @mem: mem descriptor to map reserved memory-regions |
| * |
| * Callback to map va for memory-region's carveout. |
| * |
| * return 0 on success, otherwise non-zero value on failure |
| */ |
| static int zynqmp_r5_mem_region_map(struct rproc *rproc, |
| struct rproc_mem_entry *mem) |
| { |
| void __iomem *va; |
| |
| va = ioremap_wc(mem->dma, mem->len); |
| if (IS_ERR_OR_NULL(va)) |
| return -ENOMEM; |
| |
| mem->va = (void *)va; |
| |
| return 0; |
| } |
| |
| /* |
| * zynqmp_r5_rproc_mem_unmap |
| * @rproc: single R5 core's corresponding rproc instance |
| * @mem: mem entry to unmap |
| * |
| * Unmap memory-region carveout |
| * |
| * return: always returns 0 |
| */ |
| static int zynqmp_r5_mem_region_unmap(struct rproc *rproc, |
| struct rproc_mem_entry *mem) |
| { |
| iounmap((void __iomem *)mem->va); |
| return 0; |
| } |
| |
| /* |
| * add_mem_regions_carveout() |
| * @rproc: single R5 core's corresponding rproc instance |
| * |
| * Construct rproc mem carveouts from memory-region property nodes |
| * |
| * return 0 on success, otherwise non-zero value on failure |
| */ |
| static int add_mem_regions_carveout(struct rproc *rproc) |
| { |
| struct rproc_mem_entry *rproc_mem; |
| struct zynqmp_r5_core *r5_core; |
| struct reserved_mem *rmem; |
| int i, num_mem_regions; |
| |
| r5_core = (struct zynqmp_r5_core *)rproc->priv; |
| num_mem_regions = r5_core->rmem_count; |
| |
| for (i = 0; i < num_mem_regions; i++) { |
| rmem = r5_core->rmem[i]; |
| |
| if (!strncmp(rmem->name, "vdev0buffer", strlen("vdev0buffer"))) { |
| /* Init reserved memory for vdev buffer */ |
| rproc_mem = rproc_of_resm_mem_entry_init(&rproc->dev, i, |
| rmem->size, |
| rmem->base, |
| rmem->name); |
| } else { |
| /* Register associated reserved memory regions */ |
| rproc_mem = rproc_mem_entry_init(&rproc->dev, NULL, |
| (dma_addr_t)rmem->base, |
| rmem->size, rmem->base, |
| zynqmp_r5_mem_region_map, |
| zynqmp_r5_mem_region_unmap, |
| rmem->name); |
| } |
| |
| if (!rproc_mem) |
| return -ENOMEM; |
| |
| rproc_add_carveout(rproc, rproc_mem); |
| |
| dev_dbg(&rproc->dev, "reserved mem carveout %s addr=%llx, size=0x%llx", |
| rmem->name, rmem->base, rmem->size); |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * tcm_mem_unmap() |
| * @rproc: single R5 core's corresponding rproc instance |
| * @mem: tcm mem entry to unmap |
| * |
| * Unmap TCM banks when powering down R5 core. |
| * |
| * return always 0 |
| */ |
| static int tcm_mem_unmap(struct rproc *rproc, struct rproc_mem_entry *mem) |
| { |
| iounmap((void __iomem *)mem->va); |
| |
| return 0; |
| } |
| |
| /* |
| * tcm_mem_map() |
| * @rproc: single R5 core's corresponding rproc instance |
| * @mem: tcm memory entry descriptor |
| * |
| * Given TCM bank entry, this func setup virtual address for TCM bank |
| * remoteproc carveout. It also takes care of va to da address translation |
| * |
| * return 0 on success, otherwise non-zero value on failure |
| */ |
| static int tcm_mem_map(struct rproc *rproc, |
| struct rproc_mem_entry *mem) |
| { |
| void __iomem *va; |
| |
| va = ioremap_wc(mem->dma, mem->len); |
| if (IS_ERR_OR_NULL(va)) |
| return -ENOMEM; |
| |
| /* Update memory entry va */ |
| mem->va = (void *)va; |
| |
| /* clear TCMs */ |
| memset_io(va, 0, mem->len); |
| |
| /* |
| * The R5s expect their TCM banks to be at address 0x0 and 0x2000, |
| * while on the Linux side they are at 0xffexxxxx. |
| * |
| * Zero out the high 12 bits of the address. This will give |
| * expected values for TCM Banks 0A and 0B (0x0 and 0x20000). |
| */ |
| mem->da &= 0x000fffff; |
| |
| /* |
| * TCM Banks 1A and 1B still have to be translated. |
| * |
| * Below handle these two banks' absolute addresses (0xffe90000 and |
| * 0xffeb0000) and convert to the expected relative addresses |
| * (0x0 and 0x20000). |
| */ |
| if (mem->da == 0x90000 || mem->da == 0xB0000) |
| mem->da -= 0x90000; |
| |
| /* if translated TCM bank address is not valid report error */ |
| if (mem->da != 0x0 && mem->da != 0x20000) { |
| dev_err(&rproc->dev, "invalid TCM address: %x\n", mem->da); |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| /* |
| * add_tcm_carveout_split_mode() |
| * @rproc: single R5 core's corresponding rproc instance |
| * |
| * allocate and add remoteproc carveout for TCM memory in split mode |
| * |
| * return 0 on success, otherwise non-zero value on failure |
| */ |
| static int add_tcm_carveout_split_mode(struct rproc *rproc) |
| { |
| struct rproc_mem_entry *rproc_mem; |
| struct zynqmp_r5_core *r5_core; |
| int i, num_banks, ret; |
| phys_addr_t bank_addr; |
| struct device *dev; |
| u32 pm_domain_id; |
| size_t bank_size; |
| char *bank_name; |
| |
| r5_core = (struct zynqmp_r5_core *)rproc->priv; |
| dev = r5_core->dev; |
| num_banks = r5_core->tcm_bank_count; |
| |
| /* |
| * Power-on Each 64KB TCM, |
| * register its address space, map and unmap functions |
| * and add carveouts accordingly |
| */ |
| for (i = 0; i < num_banks; i++) { |
| bank_addr = r5_core->tcm_banks[i]->addr; |
| bank_name = r5_core->tcm_banks[i]->bank_name; |
| bank_size = r5_core->tcm_banks[i]->size; |
| pm_domain_id = r5_core->tcm_banks[i]->pm_domain_id; |
| |
| ret = zynqmp_pm_request_node(pm_domain_id, |
| ZYNQMP_PM_CAPABILITY_ACCESS, 0, |
| ZYNQMP_PM_REQUEST_ACK_BLOCKING); |
| if (ret < 0) { |
| dev_err(dev, "failed to turn on TCM 0x%x", pm_domain_id); |
| goto release_tcm_split; |
| } |
| |
| dev_dbg(dev, "TCM carveout split mode %s addr=%llx, size=0x%lx", |
| bank_name, bank_addr, bank_size); |
| |
| rproc_mem = rproc_mem_entry_init(dev, NULL, bank_addr, |
| bank_size, bank_addr, |
| tcm_mem_map, tcm_mem_unmap, |
| bank_name); |
| if (!rproc_mem) { |
| ret = -ENOMEM; |
| zynqmp_pm_release_node(pm_domain_id); |
| goto release_tcm_split; |
| } |
| |
| rproc_add_carveout(rproc, rproc_mem); |
| } |
| |
| return 0; |
| |
| release_tcm_split: |
| /* If failed, Turn off all TCM banks turned on before */ |
| for (i--; i >= 0; i--) { |
| pm_domain_id = r5_core->tcm_banks[i]->pm_domain_id; |
| zynqmp_pm_release_node(pm_domain_id); |
| } |
| return ret; |
| } |
| |
| /* |
| * add_tcm_carveout_lockstep_mode() |
| * @rproc: single R5 core's corresponding rproc instance |
| * |
| * allocate and add remoteproc carveout for TCM memory in lockstep mode |
| * |
| * return 0 on success, otherwise non-zero value on failure |
| */ |
| static int add_tcm_carveout_lockstep_mode(struct rproc *rproc) |
| { |
| struct rproc_mem_entry *rproc_mem; |
| struct zynqmp_r5_core *r5_core; |
| int i, num_banks, ret; |
| phys_addr_t bank_addr; |
| size_t bank_size = 0; |
| struct device *dev; |
| u32 pm_domain_id; |
| char *bank_name; |
| |
| r5_core = (struct zynqmp_r5_core *)rproc->priv; |
| dev = r5_core->dev; |
| |
| /* Go through zynqmp banks for r5 node */ |
| num_banks = r5_core->tcm_bank_count; |
| |
| /* |
| * In lockstep mode, TCM is contiguous memory block |
| * However, each TCM block still needs to be enabled individually. |
| * So, Enable each TCM block individually, but add their size |
| * to create contiguous memory region. |
| */ |
| bank_addr = r5_core->tcm_banks[0]->addr; |
| bank_name = r5_core->tcm_banks[0]->bank_name; |
| |
| for (i = 0; i < num_banks; i++) { |
| bank_size += r5_core->tcm_banks[i]->size; |
| pm_domain_id = r5_core->tcm_banks[i]->pm_domain_id; |
| |
| /* Turn on each TCM bank individually */ |
| ret = zynqmp_pm_request_node(pm_domain_id, |
| ZYNQMP_PM_CAPABILITY_ACCESS, 0, |
| ZYNQMP_PM_REQUEST_ACK_BLOCKING); |
| if (ret < 0) { |
| dev_err(dev, "failed to turn on TCM 0x%x", pm_domain_id); |
| goto release_tcm_lockstep; |
| } |
| } |
| |
| dev_dbg(dev, "TCM add carveout lockstep mode %s addr=0x%llx, size=0x%lx", |
| bank_name, bank_addr, bank_size); |
| |
| /* Register TCM address range, TCM map and unmap functions */ |
| rproc_mem = rproc_mem_entry_init(dev, NULL, bank_addr, |
| bank_size, bank_addr, |
| tcm_mem_map, tcm_mem_unmap, |
| bank_name); |
| if (!rproc_mem) { |
| ret = -ENOMEM; |
| goto release_tcm_lockstep; |
| } |
| |
| /* If registration is success, add carveouts */ |
| rproc_add_carveout(rproc, rproc_mem); |
| |
| return 0; |
| |
| release_tcm_lockstep: |
| /* If failed, Turn off all TCM banks turned on before */ |
| for (i--; i >= 0; i--) { |
| pm_domain_id = r5_core->tcm_banks[i]->pm_domain_id; |
| zynqmp_pm_release_node(pm_domain_id); |
| } |
| return ret; |
| } |
| |
| /* |
| * add_tcm_banks() |
| * @rproc: single R5 core's corresponding rproc instance |
| * |
| * allocate and add remoteproc carveouts for TCM memory based on cluster mode |
| * |
| * return 0 on success, otherwise non-zero value on failure |
| */ |
| static int add_tcm_banks(struct rproc *rproc) |
| { |
| struct zynqmp_r5_cluster *cluster; |
| struct zynqmp_r5_core *r5_core; |
| struct device *dev; |
| |
| r5_core = (struct zynqmp_r5_core *)rproc->priv; |
| if (!r5_core) |
| return -EINVAL; |
| |
| dev = r5_core->dev; |
| |
| cluster = dev_get_drvdata(dev->parent); |
| if (!cluster) { |
| dev_err(dev->parent, "Invalid driver data\n"); |
| return -EINVAL; |
| } |
| |
| /* |
| * In lockstep mode TCM banks are one contiguous memory region of 256Kb |
| * In split mode, each TCM bank is 64Kb and not contiguous. |
| * We add memory carveouts accordingly. |
| */ |
| if (cluster->mode == SPLIT_MODE) |
| return add_tcm_carveout_split_mode(rproc); |
| else if (cluster->mode == LOCKSTEP_MODE) |
| return add_tcm_carveout_lockstep_mode(rproc); |
| |
| return -EINVAL; |
| } |
| |
| /* |
| * zynqmp_r5_parse_fw() |
| * @rproc: single R5 core's corresponding rproc instance |
| * @fw: ptr to firmware to be loaded onto r5 core |
| * |
| * get resource table if available |
| * |
| * return 0 on success, otherwise non-zero value on failure |
| */ |
| static int zynqmp_r5_parse_fw(struct rproc *rproc, const struct firmware *fw) |
| { |
| int ret; |
| |
| ret = rproc_elf_load_rsc_table(rproc, fw); |
| if (ret == -EINVAL) { |
| /* |
| * resource table only required for IPC. |
| * if not present, this is not necessarily an error; |
| * for example, loading r5 hello world application |
| * so simply inform user and keep going. |
| */ |
| dev_info(&rproc->dev, "no resource table found.\n"); |
| ret = 0; |
| } |
| return ret; |
| } |
| |
| /** |
| * zynqmp_r5_rproc_prepare() |
| * adds carveouts for TCM bank and reserved memory regions |
| * |
| * @rproc: Device node of each rproc |
| * |
| * Return: 0 for success else < 0 error code |
| */ |
| static int zynqmp_r5_rproc_prepare(struct rproc *rproc) |
| { |
| int ret; |
| |
| ret = add_tcm_banks(rproc); |
| if (ret) { |
| dev_err(&rproc->dev, "failed to get TCM banks, err %d\n", ret); |
| return ret; |
| } |
| |
| ret = add_mem_regions_carveout(rproc); |
| if (ret) { |
| dev_err(&rproc->dev, "failed to get reserve mem regions %d\n", ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * zynqmp_r5_rproc_unprepare() |
| * Turns off TCM banks using power-domain id |
| * |
| * @rproc: Device node of each rproc |
| * |
| * Return: always 0 |
| */ |
| static int zynqmp_r5_rproc_unprepare(struct rproc *rproc) |
| { |
| struct zynqmp_r5_core *r5_core; |
| u32 pm_domain_id; |
| int i; |
| |
| r5_core = (struct zynqmp_r5_core *)rproc->priv; |
| |
| for (i = 0; i < r5_core->tcm_bank_count; i++) { |
| pm_domain_id = r5_core->tcm_banks[i]->pm_domain_id; |
| if (zynqmp_pm_release_node(pm_domain_id)) |
| dev_warn(r5_core->dev, |
| "can't turn off TCM bank 0x%x", pm_domain_id); |
| } |
| |
| return 0; |
| } |
| |
| static const struct rproc_ops zynqmp_r5_rproc_ops = { |
| .prepare = zynqmp_r5_rproc_prepare, |
| .unprepare = zynqmp_r5_rproc_unprepare, |
| .start = zynqmp_r5_rproc_start, |
| .stop = zynqmp_r5_rproc_stop, |
| .load = rproc_elf_load_segments, |
| .parse_fw = zynqmp_r5_parse_fw, |
| .find_loaded_rsc_table = rproc_elf_find_loaded_rsc_table, |
| .sanity_check = rproc_elf_sanity_check, |
| .get_boot_addr = rproc_elf_get_boot_addr, |
| }; |
| |
| /** |
| * zynqmp_r5_add_rproc_core() |
| * Allocate and add struct rproc object for each r5f core |
| * This is called for each individual r5f core |
| * |
| * @cdev: Device node of each r5 core |
| * |
| * Return: zynqmp_r5_core object for success else error code pointer |
| */ |
| static struct zynqmp_r5_core *zynqmp_r5_add_rproc_core(struct device *cdev) |
| { |
| struct zynqmp_r5_core *r5_core; |
| struct rproc *r5_rproc; |
| int ret; |
| |
| /* Set up DMA mask */ |
| ret = dma_set_coherent_mask(cdev, DMA_BIT_MASK(32)); |
| if (ret) |
| return ERR_PTR(ret); |
| |
| /* Allocate remoteproc instance */ |
| r5_rproc = rproc_alloc(cdev, dev_name(cdev), |
| &zynqmp_r5_rproc_ops, |
| NULL, sizeof(struct zynqmp_r5_core)); |
| if (!r5_rproc) { |
| dev_err(cdev, "failed to allocate memory for rproc instance\n"); |
| return ERR_PTR(-ENOMEM); |
| } |
| |
| r5_rproc->auto_boot = false; |
| r5_core = (struct zynqmp_r5_core *)r5_rproc->priv; |
| r5_core->dev = cdev; |
| r5_core->np = dev_of_node(cdev); |
| if (!r5_core->np) { |
| dev_err(cdev, "can't get device node for r5 core\n"); |
| ret = -EINVAL; |
| goto free_rproc; |
| } |
| |
| /* Add R5 remoteproc core */ |
| ret = rproc_add(r5_rproc); |
| if (ret) { |
| dev_err(cdev, "failed to add r5 remoteproc\n"); |
| goto free_rproc; |
| } |
| |
| r5_core->rproc = r5_rproc; |
| return r5_core; |
| |
| free_rproc: |
| rproc_free(r5_rproc); |
| return ERR_PTR(ret); |
| } |
| |
| /** |
| * zynqmp_r5_get_tcm_node() |
| * Ideally this function should parse tcm node and store information |
| * in r5_core instance. For now, Hardcoded TCM information is used. |
| * This approach is used as TCM bindings for system-dt is being developed |
| * |
| * @cluster: pointer to zynqmp_r5_cluster type object |
| * |
| * Return: 0 for success and < 0 error code for failure. |
| */ |
| static int zynqmp_r5_get_tcm_node(struct zynqmp_r5_cluster *cluster) |
| { |
| struct device *dev = cluster->dev; |
| struct zynqmp_r5_core *r5_core; |
| int tcm_bank_count, tcm_node; |
| int i, j; |
| |
| tcm_bank_count = ARRAY_SIZE(zynqmp_tcm_banks); |
| |
| /* count per core tcm banks */ |
| tcm_bank_count = tcm_bank_count / cluster->core_count; |
| |
| /* |
| * r5 core 0 will use all of TCM banks in lockstep mode. |
| * In split mode, r5 core0 will use 128k and r5 core1 will use another |
| * 128k. Assign TCM banks to each core accordingly |
| */ |
| tcm_node = 0; |
| for (i = 0; i < cluster->core_count; i++) { |
| r5_core = cluster->r5_cores[i]; |
| r5_core->tcm_banks = devm_kcalloc(dev, tcm_bank_count, |
| sizeof(struct mem_bank_data *), |
| GFP_KERNEL); |
| if (!r5_core->tcm_banks) |
| return -ENOMEM; |
| |
| for (j = 0; j < tcm_bank_count; j++) { |
| /* |
| * Use pre-defined TCM reg values. |
| * Eventually this should be replaced by values |
| * parsed from dts. |
| */ |
| r5_core->tcm_banks[j] = |
| (struct mem_bank_data *)&zynqmp_tcm_banks[tcm_node]; |
| tcm_node++; |
| } |
| |
| r5_core->tcm_bank_count = tcm_bank_count; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * zynqmp_r5_get_mem_region_node() |
| * parse memory-region property and get reserved mem regions |
| * |
| * @r5_core: pointer to zynqmp_r5_core type object |
| * |
| * Return: 0 for success and error code for failure. |
| */ |
| static int zynqmp_r5_get_mem_region_node(struct zynqmp_r5_core *r5_core) |
| { |
| struct device_node *np, *rmem_np; |
| struct reserved_mem **rmem; |
| int res_mem_count, i; |
| struct device *dev; |
| |
| dev = r5_core->dev; |
| np = r5_core->np; |
| |
| res_mem_count = of_property_count_elems_of_size(np, "memory-region", |
| sizeof(phandle)); |
| if (res_mem_count <= 0) { |
| dev_warn(dev, "failed to get memory-region property %d\n", |
| res_mem_count); |
| return 0; |
| } |
| |
| rmem = devm_kcalloc(dev, res_mem_count, |
| sizeof(struct reserved_mem *), GFP_KERNEL); |
| if (!rmem) |
| return -ENOMEM; |
| |
| for (i = 0; i < res_mem_count; i++) { |
| rmem_np = of_parse_phandle(np, "memory-region", i); |
| if (!rmem_np) |
| goto release_rmem; |
| |
| rmem[i] = of_reserved_mem_lookup(rmem_np); |
| if (!rmem[i]) { |
| of_node_put(rmem_np); |
| goto release_rmem; |
| } |
| |
| of_node_put(rmem_np); |
| } |
| |
| r5_core->rmem_count = res_mem_count; |
| r5_core->rmem = rmem; |
| return 0; |
| |
| release_rmem: |
| return -EINVAL; |
| } |
| |
| /* |
| * zynqmp_r5_core_init() |
| * Create and initialize zynqmp_r5_core type object |
| * |
| * @cluster: pointer to zynqmp_r5_cluster type object |
| * @fw_reg_val: value expected by firmware to configure RPU cluster mode |
| * @tcm_mode: value expected by fw to configure TCM mode (lockstep or split) |
| * |
| * Return: 0 for success and error code for failure. |
| */ |
| static int zynqmp_r5_core_init(struct zynqmp_r5_cluster *cluster, |
| enum rpu_oper_mode fw_reg_val, |
| enum rpu_tcm_comb tcm_mode) |
| { |
| struct device *dev = cluster->dev; |
| struct zynqmp_r5_core *r5_core; |
| int ret, i; |
| |
| ret = zynqmp_r5_get_tcm_node(cluster); |
| if (ret < 0) { |
| dev_err(dev, "can't get tcm node, err %d\n", ret); |
| return ret; |
| } |
| |
| for (i = 0; i < cluster->core_count; i++) { |
| r5_core = cluster->r5_cores[i]; |
| |
| ret = zynqmp_r5_get_mem_region_node(r5_core); |
| if (ret) |
| dev_warn(dev, "memory-region prop failed %d\n", ret); |
| |
| /* Initialize r5 cores with power-domains parsed from dts */ |
| ret = of_property_read_u32_index(r5_core->np, "power-domains", |
| 1, &r5_core->pm_domain_id); |
| if (ret) { |
| dev_err(dev, "failed to get power-domains property\n"); |
| return ret; |
| } |
| |
| ret = zynqmp_r5_set_mode(r5_core, fw_reg_val, tcm_mode); |
| if (ret) { |
| dev_err(dev, "failed to set r5 cluster mode %d, err %d\n", |
| cluster->mode, ret); |
| return ret; |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * zynqmp_r5_cluster_init() |
| * Create and initialize zynqmp_r5_cluster type object |
| * |
| * @cluster: pointer to zynqmp_r5_cluster type object |
| * |
| * Return: 0 for success and error code for failure. |
| */ |
| static int zynqmp_r5_cluster_init(struct zynqmp_r5_cluster *cluster) |
| { |
| enum zynqmp_r5_cluster_mode cluster_mode = LOCKSTEP_MODE; |
| struct device *dev = cluster->dev; |
| struct device_node *dev_node = dev_of_node(dev); |
| struct platform_device *child_pdev; |
| struct zynqmp_r5_core **r5_cores; |
| enum rpu_oper_mode fw_reg_val; |
| struct device **child_devs; |
| struct device_node *child; |
| enum rpu_tcm_comb tcm_mode; |
| int core_count, ret, i; |
| |
| ret = of_property_read_u32(dev_node, "xlnx,cluster-mode", &cluster_mode); |
| |
| /* |
| * on success returns 0, if not defined then returns -EINVAL, |
| * In that case, default is LOCKSTEP mode. Other than that |
| * returns relative error code < 0. |
| */ |
| if (ret != -EINVAL && ret != 0) { |
| dev_err(dev, "Invalid xlnx,cluster-mode property\n"); |
| return ret; |
| } |
| |
| /* |
| * For now driver only supports split mode and lockstep mode. |
| * fail driver probe if either of that is not set in dts. |
| */ |
| if (cluster_mode == LOCKSTEP_MODE) { |
| tcm_mode = PM_RPU_TCM_COMB; |
| fw_reg_val = PM_RPU_MODE_LOCKSTEP; |
| } else if (cluster_mode == SPLIT_MODE) { |
| tcm_mode = PM_RPU_TCM_SPLIT; |
| fw_reg_val = PM_RPU_MODE_SPLIT; |
| } else { |
| dev_err(dev, "driver does not support cluster mode %d\n", cluster_mode); |
| return -EINVAL; |
| } |
| |
| /* |
| * Number of cores is decided by number of child nodes of |
| * r5f subsystem node in dts. If Split mode is used in dts |
| * 2 child nodes are expected. |
| * In lockstep mode if two child nodes are available, |
| * only use first child node and consider it as core0 |
| * and ignore core1 dt node. |
| */ |
| core_count = of_get_available_child_count(dev_node); |
| if (core_count == 0) { |
| dev_err(dev, "Invalid number of r5 cores %d", core_count); |
| return -EINVAL; |
| } else if (cluster_mode == SPLIT_MODE && core_count != 2) { |
| dev_err(dev, "Invalid number of r5 cores for split mode\n"); |
| return -EINVAL; |
| } else if (cluster_mode == LOCKSTEP_MODE && core_count == 2) { |
| dev_warn(dev, "Only r5 core0 will be used\n"); |
| core_count = 1; |
| } |
| |
| child_devs = kcalloc(core_count, sizeof(struct device *), GFP_KERNEL); |
| if (!child_devs) |
| return -ENOMEM; |
| |
| r5_cores = kcalloc(core_count, |
| sizeof(struct zynqmp_r5_core *), GFP_KERNEL); |
| if (!r5_cores) { |
| kfree(child_devs); |
| return -ENOMEM; |
| } |
| |
| i = 0; |
| for_each_available_child_of_node(dev_node, child) { |
| child_pdev = of_find_device_by_node(child); |
| if (!child_pdev) { |
| of_node_put(child); |
| ret = -ENODEV; |
| goto release_r5_cores; |
| } |
| |
| child_devs[i] = &child_pdev->dev; |
| |
| /* create and add remoteproc instance of type struct rproc */ |
| r5_cores[i] = zynqmp_r5_add_rproc_core(&child_pdev->dev); |
| if (IS_ERR(r5_cores[i])) { |
| of_node_put(child); |
| ret = PTR_ERR(r5_cores[i]); |
| r5_cores[i] = NULL; |
| goto release_r5_cores; |
| } |
| |
| /* |
| * If two child nodes are available in dts in lockstep mode, |
| * then ignore second child node. |
| */ |
| if (cluster_mode == LOCKSTEP_MODE) { |
| of_node_put(child); |
| break; |
| } |
| |
| i++; |
| } |
| |
| cluster->mode = cluster_mode; |
| cluster->core_count = core_count; |
| cluster->r5_cores = r5_cores; |
| |
| ret = zynqmp_r5_core_init(cluster, fw_reg_val, tcm_mode); |
| if (ret < 0) { |
| dev_err(dev, "failed to init r5 core err %d\n", ret); |
| cluster->core_count = 0; |
| cluster->r5_cores = NULL; |
| |
| /* |
| * at this point rproc resources for each core are allocated. |
| * adjust index to free resources in reverse order |
| */ |
| i = core_count - 1; |
| goto release_r5_cores; |
| } |
| |
| kfree(child_devs); |
| return 0; |
| |
| release_r5_cores: |
| while (i >= 0) { |
| put_device(child_devs[i]); |
| if (r5_cores[i]) { |
| of_reserved_mem_device_release(r5_cores[i]->dev); |
| rproc_del(r5_cores[i]->rproc); |
| rproc_free(r5_cores[i]->rproc); |
| } |
| i--; |
| } |
| kfree(r5_cores); |
| kfree(child_devs); |
| return ret; |
| } |
| |
| static void zynqmp_r5_cluster_exit(void *data) |
| { |
| struct platform_device *pdev = (struct platform_device *)data; |
| struct zynqmp_r5_cluster *cluster; |
| struct zynqmp_r5_core *r5_core; |
| int i; |
| |
| cluster = (struct zynqmp_r5_cluster *)platform_get_drvdata(pdev); |
| if (!cluster) |
| return; |
| |
| for (i = 0; i < cluster->core_count; i++) { |
| r5_core = cluster->r5_cores[i]; |
| of_reserved_mem_device_release(r5_core->dev); |
| put_device(r5_core->dev); |
| rproc_del(r5_core->rproc); |
| rproc_free(r5_core->rproc); |
| } |
| |
| kfree(cluster->r5_cores); |
| kfree(cluster); |
| platform_set_drvdata(pdev, NULL); |
| } |
| |
| /* |
| * zynqmp_r5_remoteproc_probe() |
| * parse device-tree, initialize hardware and allocate required resources |
| * and remoteproc ops |
| * |
| * @pdev: domain platform device for R5 cluster |
| * |
| * Return: 0 for success and < 0 for failure. |
| */ |
| static int zynqmp_r5_remoteproc_probe(struct platform_device *pdev) |
| { |
| struct zynqmp_r5_cluster *cluster; |
| struct device *dev = &pdev->dev; |
| int ret; |
| |
| cluster = kzalloc(sizeof(*cluster), GFP_KERNEL); |
| if (!cluster) |
| return -ENOMEM; |
| |
| cluster->dev = dev; |
| |
| ret = devm_of_platform_populate(dev); |
| if (ret) { |
| dev_err_probe(dev, ret, "failed to populate platform dev\n"); |
| kfree(cluster); |
| return ret; |
| } |
| |
| /* wire in so each core can be cleaned up at driver remove */ |
| platform_set_drvdata(pdev, cluster); |
| |
| ret = zynqmp_r5_cluster_init(cluster); |
| if (ret) { |
| kfree(cluster); |
| platform_set_drvdata(pdev, NULL); |
| dev_err_probe(dev, ret, "Invalid r5f subsystem device tree\n"); |
| return ret; |
| } |
| |
| ret = devm_add_action_or_reset(dev, zynqmp_r5_cluster_exit, pdev); |
| if (ret) |
| return ret; |
| |
| return 0; |
| } |
| |
| /* Match table for OF platform binding */ |
| static const struct of_device_id zynqmp_r5_remoteproc_match[] = { |
| { .compatible = "xlnx,zynqmp-r5fss", }, |
| { /* end of list */ }, |
| }; |
| MODULE_DEVICE_TABLE(of, zynqmp_r5_remoteproc_match); |
| |
| static struct platform_driver zynqmp_r5_remoteproc_driver = { |
| .probe = zynqmp_r5_remoteproc_probe, |
| .driver = { |
| .name = "zynqmp_r5_remoteproc", |
| .of_match_table = zynqmp_r5_remoteproc_match, |
| }, |
| }; |
| module_platform_driver(zynqmp_r5_remoteproc_driver); |
| |
| MODULE_DESCRIPTION("Xilinx R5F remote processor driver"); |
| MODULE_AUTHOR("Xilinx Inc."); |
| MODULE_LICENSE("GPL"); |