// SPDX-License-Identifier: GPL-2.0-only /* * PRU-ICSS remoteproc driver for various TI SoCs * * Copyright (C) 2014-2020 Texas Instruments Incorporated - https://www.ti.com/ * * Author(s): * Suman Anna * Andrew F. Davis * Grzegorz Jaszczyk for Texas Instruments */ #include #include #include #include #include #include "remoteproc_internal.h" #include "remoteproc_elf_helpers.h" /* PRU_ICSS_PRU_CTRL registers */ #define PRU_CTRL_CTRL 0x0000 #define PRU_CTRL_STS 0x0004 /* CTRL register bit-fields */ #define CTRL_CTRL_SOFT_RST_N BIT(0) #define CTRL_CTRL_EN BIT(1) #define CTRL_CTRL_SLEEPING BIT(2) #define CTRL_CTRL_CTR_EN BIT(3) #define CTRL_CTRL_SINGLE_STEP BIT(8) #define CTRL_CTRL_RUNSTATE BIT(15) /* PRU Core IRAM address masks */ #define PRU_IRAM_ADDR_MASK 0x3ffff #define PRU0_IRAM_ADDR_MASK 0x34000 #define PRU1_IRAM_ADDR_MASK 0x38000 /* PRU device addresses for various type of PRU RAMs */ #define PRU_IRAM_DA 0 /* Instruction RAM */ #define PRU_PDRAM_DA 0 /* Primary Data RAM */ #define PRU_SDRAM_DA 0x2000 /* Secondary Data RAM */ #define PRU_SHRDRAM_DA 0x10000 /* Shared Data RAM */ /** * enum pru_iomem - PRU core memory/register range identifiers * * @PRU_IOMEM_IRAM: PRU Instruction RAM range * @PRU_IOMEM_CTRL: PRU Control register range * @PRU_IOMEM_DEBUG: PRU Debug register range * @PRU_IOMEM_MAX: just keep this one at the end */ enum pru_iomem { PRU_IOMEM_IRAM = 0, PRU_IOMEM_CTRL, PRU_IOMEM_DEBUG, PRU_IOMEM_MAX, }; /** * struct pru_rproc - PRU remoteproc structure * @id: id of the PRU core within the PRUSS * @dev: PRU core device pointer * @pruss: back-reference to parent PRUSS structure * @rproc: remoteproc pointer for this PRU core * @mem_regions: data for each of the PRU memory regions * @fw_name: name of firmware image used during loading */ struct pru_rproc { int id; struct device *dev; struct pruss *pruss; struct rproc *rproc; struct pruss_mem_region mem_regions[PRU_IOMEM_MAX]; const char *fw_name; }; static inline u32 pru_control_read_reg(struct pru_rproc *pru, unsigned int reg) { return readl_relaxed(pru->mem_regions[PRU_IOMEM_CTRL].va + reg); } static inline void pru_control_write_reg(struct pru_rproc *pru, unsigned int reg, u32 val) { writel_relaxed(val, pru->mem_regions[PRU_IOMEM_CTRL].va + reg); } static int pru_rproc_start(struct rproc *rproc) { struct device *dev = &rproc->dev; struct pru_rproc *pru = rproc->priv; u32 val; dev_dbg(dev, "starting PRU%d: entry-point = 0x%llx\n", pru->id, (rproc->bootaddr >> 2)); val = CTRL_CTRL_EN | ((rproc->bootaddr >> 2) << 16); pru_control_write_reg(pru, PRU_CTRL_CTRL, val); return 0; } static int pru_rproc_stop(struct rproc *rproc) { struct device *dev = &rproc->dev; struct pru_rproc *pru = rproc->priv; u32 val; dev_dbg(dev, "stopping PRU%d\n", pru->id); val = pru_control_read_reg(pru, PRU_CTRL_CTRL); val &= ~CTRL_CTRL_EN; pru_control_write_reg(pru, PRU_CTRL_CTRL, val); return 0; } /* * Convert PRU device address (data spaces only) to kernel virtual address. * * Each PRU has access to all data memories within the PRUSS, accessible at * different ranges. So, look through both its primary and secondary Data * RAMs as well as any shared Data RAM to convert a PRU device address to * kernel virtual address. Data RAM0 is primary Data RAM for PRU0 and Data * RAM1 is primary Data RAM for PRU1. */ static void *pru_d_da_to_va(struct pru_rproc *pru, u32 da, size_t len) { struct pruss_mem_region dram0, dram1, shrd_ram; struct pruss *pruss = pru->pruss; u32 offset; void *va = NULL; if (len == 0) return NULL; dram0 = pruss->mem_regions[PRUSS_MEM_DRAM0]; dram1 = pruss->mem_regions[PRUSS_MEM_DRAM1]; /* PRU1 has its local RAM addresses reversed */ if (pru->id == 1) swap(dram0, dram1); shrd_ram = pruss->mem_regions[PRUSS_MEM_SHRD_RAM2]; if (da >= PRU_PDRAM_DA && da + len <= PRU_PDRAM_DA + dram0.size) { offset = da - PRU_PDRAM_DA; va = (__force void *)(dram0.va + offset); } else if (da >= PRU_SDRAM_DA && da + len <= PRU_SDRAM_DA + dram1.size) { offset = da - PRU_SDRAM_DA; va = (__force void *)(dram1.va + offset); } else if (da >= PRU_SHRDRAM_DA && da + len <= PRU_SHRDRAM_DA + shrd_ram.size) { offset = da - PRU_SHRDRAM_DA; va = (__force void *)(shrd_ram.va + offset); } return va; } /* * Convert PRU device address (instruction space) to kernel virtual address. * * A PRU does not have an unified address space. Each PRU has its very own * private Instruction RAM, and its device address is identical to that of * its primary Data RAM device address. */ static void *pru_i_da_to_va(struct pru_rproc *pru, u32 da, size_t len) { u32 offset; void *va = NULL; if (len == 0) return NULL; if (da >= PRU_IRAM_DA && da + len <= PRU_IRAM_DA + pru->mem_regions[PRU_IOMEM_IRAM].size) { offset = da - PRU_IRAM_DA; va = (__force void *)(pru->mem_regions[PRU_IOMEM_IRAM].va + offset); } return va; } /* * Provide address translations for only PRU Data RAMs through the remoteproc * core for any PRU client drivers. The PRU Instruction RAM access is restricted * only to the PRU loader code. */ static void *pru_rproc_da_to_va(struct rproc *rproc, u64 da, size_t len) { struct pru_rproc *pru = rproc->priv; return pru_d_da_to_va(pru, da, len); } /* PRU-specific address translator used by PRU loader. */ static void *pru_da_to_va(struct rproc *rproc, u64 da, size_t len, bool is_iram) { struct pru_rproc *pru = rproc->priv; void *va; if (is_iram) va = pru_i_da_to_va(pru, da, len); else va = pru_d_da_to_va(pru, da, len); return va; } static struct rproc_ops pru_rproc_ops = { .start = pru_rproc_start, .stop = pru_rproc_stop, .da_to_va = pru_rproc_da_to_va, }; static int pru_rproc_load_elf_segments(struct rproc *rproc, const struct firmware *fw) { struct device *dev = &rproc->dev; struct elf32_hdr *ehdr; struct elf32_phdr *phdr; int i, ret = 0; const u8 *elf_data = fw->data; ehdr = (struct elf32_hdr *)elf_data; phdr = (struct elf32_phdr *)(elf_data + ehdr->e_phoff); /* go through the available ELF segments */ for (i = 0; i < ehdr->e_phnum; i++, phdr++) { u32 da = phdr->p_paddr; u32 memsz = phdr->p_memsz; u32 filesz = phdr->p_filesz; u32 offset = phdr->p_offset; bool is_iram; void *ptr; if (phdr->p_type != PT_LOAD || !filesz) continue; dev_dbg(dev, "phdr: type %d da 0x%x memsz 0x%x filesz 0x%x\n", phdr->p_type, da, memsz, filesz); if (filesz > memsz) { dev_err(dev, "bad phdr filesz 0x%x memsz 0x%x\n", filesz, memsz); ret = -EINVAL; break; } if (offset + filesz > fw->size) { dev_err(dev, "truncated fw: need 0x%x avail 0x%zx\n", offset + filesz, fw->size); ret = -EINVAL; break; } /* grab the kernel address for this device address */ is_iram = phdr->p_flags & PF_X; ptr = pru_da_to_va(rproc, da, memsz, is_iram); if (!ptr) { dev_err(dev, "bad phdr da 0x%x mem 0x%x\n", da, memsz); ret = -EINVAL; break; } memcpy(ptr, elf_data + phdr->p_offset, filesz); /* skip the memzero logic performed by remoteproc ELF loader */ } return ret; } /* * Use a custom parse_fw callback function for dealing with PRU firmware * specific sections. */ static int pru_rproc_parse_fw(struct rproc *rproc, const struct firmware *fw) { int ret; /* load optional rsc table */ ret = rproc_elf_load_rsc_table(rproc, fw); if (ret == -EINVAL) dev_dbg(&rproc->dev, "no resource table found for this fw\n"); else if (ret) return ret; return 0; } /* * Compute PRU id based on the IRAM addresses. The PRU IRAMs are * always at a particular offset within the PRUSS address space. */ static int pru_rproc_set_id(struct pru_rproc *pru) { int ret = 0; switch (pru->mem_regions[PRU_IOMEM_IRAM].pa & PRU_IRAM_ADDR_MASK) { case PRU0_IRAM_ADDR_MASK: pru->id = 0; break; case PRU1_IRAM_ADDR_MASK: pru->id = 1; break; default: ret = -EINVAL; } return ret; } static int pru_rproc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; struct platform_device *ppdev = to_platform_device(dev->parent); struct pru_rproc *pru; const char *fw_name; struct rproc *rproc = NULL; struct resource *res; int i, ret; const char *mem_names[PRU_IOMEM_MAX] = { "iram", "control", "debug" }; ret = of_property_read_string(np, "firmware-name", &fw_name); if (ret) { dev_err(dev, "unable to retrieve firmware-name %d\n", ret); return ret; } rproc = devm_rproc_alloc(dev, pdev->name, &pru_rproc_ops, fw_name, sizeof(*pru)); if (!rproc) { dev_err(dev, "rproc_alloc failed\n"); return -ENOMEM; } /* use a custom load function to deal with PRU-specific quirks */ rproc->ops->load = pru_rproc_load_elf_segments; /* use a custom parse function to deal with PRU-specific resources */ rproc->ops->parse_fw = pru_rproc_parse_fw; /* error recovery is not supported for PRUs */ rproc->recovery_disabled = true; /* * rproc_add will auto-boot the processor normally, but this is not * desired with PRU client driven boot-flow methodology. A PRU * application/client driver will boot the corresponding PRU * remote-processor as part of its state machine either through the * remoteproc sysfs interface or through the equivalent kernel API. */ rproc->auto_boot = false; pru = rproc->priv; pru->dev = dev; pru->pruss = platform_get_drvdata(ppdev); pru->rproc = rproc; pru->fw_name = fw_name; for (i = 0; i < ARRAY_SIZE(mem_names); i++) { res = platform_get_resource_byname(pdev, IORESOURCE_MEM, mem_names[i]); pru->mem_regions[i].va = devm_ioremap_resource(dev, res); if (IS_ERR(pru->mem_regions[i].va)) { dev_err(dev, "failed to parse and map memory resource %d %s\n", i, mem_names[i]); ret = PTR_ERR(pru->mem_regions[i].va); return ret; } pru->mem_regions[i].pa = res->start; pru->mem_regions[i].size = resource_size(res); dev_dbg(dev, "memory %8s: pa %pa size 0x%zx va %pK\n", mem_names[i], &pru->mem_regions[i].pa, pru->mem_regions[i].size, pru->mem_regions[i].va); } ret = pru_rproc_set_id(pru); if (ret < 0) return ret; platform_set_drvdata(pdev, rproc); ret = devm_rproc_add(dev, pru->rproc); if (ret) { dev_err(dev, "rproc_add failed: %d\n", ret); return ret; } dev_dbg(dev, "PRU rproc node %pOF probed successfully\n", np); return 0; } static int pru_rproc_remove(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct rproc *rproc = platform_get_drvdata(pdev); dev_dbg(dev, "%s: removing rproc %s\n", __func__, rproc->name); return 0; } static const struct of_device_id pru_rproc_match[] = { { .compatible = "ti,am3356-pru", }, { .compatible = "ti,am4376-pru", }, { .compatible = "ti,am5728-pru", }, { .compatible = "ti,k2g-pru", }, {}, }; MODULE_DEVICE_TABLE(of, pru_rproc_match); static struct platform_driver pru_rproc_driver = { .driver = { .name = "pru-rproc", .of_match_table = pru_rproc_match, .suppress_bind_attrs = true, }, .probe = pru_rproc_probe, .remove = pru_rproc_remove, }; module_platform_driver(pru_rproc_driver); MODULE_AUTHOR("Suman Anna "); MODULE_AUTHOR("Andrew F. Davis "); MODULE_AUTHOR("Grzegorz Jaszczyk "); MODULE_DESCRIPTION("PRU-ICSS Remote Processor Driver"); MODULE_LICENSE("GPL v2");