The Programmable Real-Time Unit Subsystem (PRUSS) consists of
dual 32-bit RISC cores (Programmable Real-Time Units, or PRUs)
for program execution. This patch adds a remoteproc platform
driver for managing the individual PRU RISC cores life cycle.

The driver currently supports the AM65xx SoC

Signed-off-by: Keerthy <j-keer...@ti.com>
---
 drivers/remoteproc/Kconfig     |  11 +
 drivers/remoteproc/Makefile    |   1 +
 drivers/remoteproc/pru_rproc.c | 405 +++++++++++++++++++++++++++++++++
 3 files changed, 417 insertions(+)
 create mode 100644 drivers/remoteproc/pru_rproc.c

diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig
index 7c2e4804b5..24e536463b 100644
--- a/drivers/remoteproc/Kconfig
+++ b/drivers/remoteproc/Kconfig
@@ -81,4 +81,15 @@ config REMOTEPROC_TI_POWER
        help
          Say 'y' here to add support for TI power processors such as those
          found on certain TI keystone and OMAP generation SoCs.
+
+config REMOTEPROC_TI_PRU
+       bool "Support for TI's K3 based PRU remoteproc driver"
+       select REMOTEPROC
+       depends on DM
+       depends on TI_PRUSS
+       depends on ARCH_K3
+       depends on OF_CONTROL
+       help
+         Say 'y' here to add support for TI' K3 remoteproc driver.
+
 endmenu
diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile
index 69ae7bd1e8..f0e83451d6 100644
--- a/drivers/remoteproc/Makefile
+++ b/drivers/remoteproc/Makefile
@@ -14,3 +14,4 @@ obj-$(CONFIG_REMOTEPROC_TI_K3_ARM64) += ti_k3_arm64_rproc.o
 obj-$(CONFIG_REMOTEPROC_TI_K3_DSP) += ti_k3_dsp_rproc.o
 obj-$(CONFIG_REMOTEPROC_TI_K3_R5F) += ti_k3_r5f_rproc.o
 obj-$(CONFIG_REMOTEPROC_TI_POWER) += ti_power_proc.o
+obj-$(CONFIG_REMOTEPROC_TI_PRU) += pru_rproc.o
diff --git a/drivers/remoteproc/pru_rproc.c b/drivers/remoteproc/pru_rproc.c
new file mode 100644
index 0000000000..832cffb1a0
--- /dev/null
+++ b/drivers/remoteproc/pru_rproc.c
@@ -0,0 +1,405 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * PRU-RTU remoteproc driver for various SoCs
+ *
+ * Copyright (C) 2020 Texas Instruments Incorporated - http://www.ti.com/
+ *     Keerthy <j-keer...@ti.com>
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <dm/device_compat.h>
+#include <elf.h>
+#include <dm/of_access.h>
+#include <remoteproc.h>
+#include <errno.h>
+#include <clk.h>
+#include <reset.h>
+#include <regmap.h>
+#include <syscon.h>
+#include <asm/io.h>
+#include <power-domain.h>
+#include <ti-pruss.h>
+
+/* PRU_ICSS_PRU_CTRL registers */
+#define PRU_CTRL_CTRL          0x0000
+#define PRU_CTRL_STS           0x0004
+#define PRU_CTRL_WAKEUP_EN     0x0008
+#define PRU_CTRL_CYCLE         0x000C
+#define PRU_CTRL_STALL         0x0010
+#define PRU_CTRL_CTBIR0                0x0020
+#define PRU_CTRL_CTBIR1                0x0024
+#define PRU_CTRL_CTPPR0                0x0028
+#define PRU_CTRL_CTPPR1                0x002C
+
+/* 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)
+
+#define RPROC_FLAGS_SHIFT      16
+#define RPROC_FLAGS_NONE       0
+#define RPROC_FLAGS_ELF_PHDR   BIT(0 + RPROC_FLAGS_SHIFT)
+#define RPROC_FLAGS_ELF_SHDR   BIT(1 + RPROC_FLAGS_SHIFT)
+
+/**
+ * enum pru_mem - PRU core memory range identifiers
+ */
+enum pru_mem {
+       PRU_MEM_IRAM = 0,
+       PRU_MEM_CTRL,
+       PRU_MEM_DEBUG,
+       PRU_MEM_MAX,
+};
+
+struct pru_privdata {
+       phys_addr_t pru_iram;
+       phys_addr_t pru_ctrl;
+       phys_addr_t pru_debug;
+       fdt_size_t pru_iramsz;
+       fdt_size_t pru_ctrlsz;
+       fdt_size_t pru_debugsz;
+       const char *fw_name;
+       u32 iram_da;
+       u32 pdram_da;
+       u32 sdram_da;
+       u32 shrdram_da;
+       u32 bootaddr;
+       int id;
+       struct pruss *prusspriv;
+};
+
+/**
+ * pru_start() - start the pru processor
+ * @dev:       corresponding k3 remote processor device
+ *
+ * Return: 0 if all goes good, else appropriate error message.
+ */
+static int pru_start(struct udevice *dev)
+{
+       struct pru_privdata *priv;
+       int val = 0;
+
+       priv = dev_get_priv(dev);
+
+       val = CTRL_CTRL_EN | ((priv->bootaddr >> 2) << 16);
+       writel(val, priv->pru_ctrl + PRU_CTRL_CTRL);
+
+       return 0;
+}
+
+/**
+ * pru_stop() - Stop pru processor
+ * @dev:       corresponding k3 remote processor device
+ *
+ * Return: 0 if all goes good, else appropriate error message.
+ */
+static int pru_stop(struct udevice *dev)
+{
+       struct pru_privdata *priv;
+       int val = 0;
+
+       priv = dev_get_priv(dev);
+
+       val = readl(priv->pru_ctrl + PRU_CTRL_CTRL);
+       val &= ~CTRL_CTRL_EN;
+       writel(val, priv->pru_ctrl + PRU_CTRL_CTRL);
+
+       return 0;
+}
+
+/**
+ * pru_init() - Initialize the remote processor
+ * @dev:       rproc device pointer
+ *
+ * Return: 0 if all went ok, else return appropriate error
+ */
+static int pru_init(struct udevice *dev)
+{
+       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_pa(struct pru_privdata *priv, u32 da, int len)
+{
+       u32 offset;
+       void *pa = NULL;
+       phys_addr_t dram0, dram1, shrdram2;
+       u32 dram0sz, dram1sz, shrdram2sz;
+
+       if (len <= 0)
+               return NULL;
+
+       dram0 = priv->prusspriv->pruss_dram0;
+       dram1 = priv->prusspriv->pruss_dram1;
+       shrdram2 = priv->prusspriv->pruss_shrdram2;
+       dram0sz = priv->prusspriv->pruss_dram0sz;
+       dram1sz = priv->prusspriv->pruss_dram1sz;
+       shrdram2sz = priv->prusspriv->pruss_shrdram2sz;
+
+       /* PRU1 has its local RAM addresses reversed */
+       if (priv->id == 1) {
+               dram1 = priv->prusspriv->pruss_dram0;
+               dram0 = priv->prusspriv->pruss_dram1;
+               dram1sz = priv->prusspriv->pruss_dram0sz;
+               dram0sz = priv->prusspriv->pruss_dram1sz;
+       }
+
+       if (da >= priv->pdram_da && da + len <= priv->pdram_da + dram0sz) {
+               offset = da - priv->pdram_da;
+               pa = (__force void *)(dram0 + offset);
+       } else if (da >= priv->sdram_da &&
+                  da + len <= priv->sdram_da + dram1sz) {
+               offset = da - priv->sdram_da;
+               pa = (__force void *)(dram1 + offset);
+       } else if (da >= priv->shrdram_da &&
+                  da + len <= priv->shrdram_da + shrdram2sz) {
+               offset = da - priv->shrdram_da;
+               pa = (__force void *)(shrdram2 + offset);
+       }
+
+       return pa;
+}
+
+/*
+ * 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_pa(struct pru_privdata *priv, u32 da, int len)
+{
+       u32 offset;
+       void *pa = NULL;
+
+       if (len <= 0)
+               return NULL;
+
+       if (da >= priv->iram_da &&
+           da + len <= priv->iram_da + priv->pru_iramsz) {
+               offset = da - priv->iram_da;
+               pa = (__force void *)(priv->pru_iram + offset);
+       }
+
+       return pa;
+}
+
+/* PRU-specific address translator */
+static void *pru_da_to_pa(struct pru_privdata *priv, u64 da, int len, u32 
flags)
+{
+       void *pa;
+       u32 exec_flag;
+
+       exec_flag = ((flags & RPROC_FLAGS_ELF_SHDR) ? flags & SHF_EXECINSTR :
+                    ((flags & RPROC_FLAGS_ELF_PHDR) ? flags & PF_X : 0));
+
+       if (exec_flag)
+               pa = pru_i_da_to_pa(priv, da, len);
+       else
+               pa = pru_d_da_to_pa(priv, da, len);
+
+       return pa;
+}
+
+/*
+ * Custom memory copy implementation for ICSSG PRU/RTU Cores
+ *
+ * The ICSSG PRU/RTU cores have a memory copying issue with IRAM memories, that
+ * is not seen on previous generation SoCs. The data is reflected properly in
+ * the IRAM memories only for integer (4-byte) copies. Any unaligned copies
+ * result in all the other pre-existing bytes zeroed out within that 4-byte
+ * boundary, thereby resulting in wrong text/code in the IRAMs. Also, the
+ * IRAM memory port interface does not allow any 8-byte copies (as commonly
+ * used by ARM64 memcpy implementation) and throws an exception. The DRAM
+ * memory ports do not show this behavior. Use this custom copying function
+ * to properly load the PRU/RTU firmware images on all memories for simplicity.
+ *
+ * TODO: Improve the function to deal with additional corner cases like
+ * unaligned copy sizes or sub-integer trailing bytes when the need arises.
+ */
+static int pru_rproc_memcpy(void *dest, void *src, size_t count)
+{
+       const int *s = src;
+       int *d = dest;
+       int size = count / 4;
+       int *tmp_src = NULL;
+
+       /* limited to 4-byte aligned addresses and copy sizes */
+       if ((long)dest % 4 || count % 4)
+               return -EINVAL;
+
+       /* src offsets in ELF firmware image can be non-aligned */
+       if ((long)src % 4) {
+               tmp_src = malloc(count);
+               if (!tmp_src)
+                       return -ENOMEM;
+
+               memcpy(tmp_src, src, count);
+               s = tmp_src;
+       }
+
+       while (size--)
+               *d++ = *s++;
+
+       kfree(tmp_src);
+
+       return 0;
+}
+
+/**
+ * pru_load() - Load pru firmware
+ * @dev:       corresponding k3 remote processor device
+ * @addr:      Address on the RAM from which firmware is to be loaded
+ * @size:      Size of the pru firmware in bytes
+ *
+ * Return: 0 if all goes good, else appropriate error message.
+ */
+static int pru_load(struct udevice *dev, ulong addr, ulong size)
+{
+       struct pru_privdata *priv;
+       Elf32_Ehdr *ehdr;
+       Elf32_Phdr *phdr;
+       int i, ret = 0;
+
+       priv = dev_get_priv(dev);
+
+       ehdr = (Elf32_Ehdr *)addr;
+       phdr = (Elf32_Phdr *)(addr + 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;
+               void *ptr;
+
+               if (phdr->p_type != PT_LOAD)
+                       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_dbg(dev, "bad phdr filesz 0x%x memsz 0x%x\n",
+                               filesz, memsz);
+                       ret = -EINVAL;
+                       break;
+               }
+
+               if (offset + filesz > size) {
+                       dev_dbg(dev, "truncated fw: need 0x%x avail 0x%zx\n",
+                               offset + filesz, size);
+                       ret = -EINVAL;
+                       break;
+               }
+
+               /* grab the kernel address for this device address */
+               ptr = pru_da_to_pa(priv, da, memsz,
+                                  RPROC_FLAGS_ELF_PHDR | phdr->p_flags);
+               if (!ptr) {
+                       dev_dbg(dev, "bad phdr da 0x%x mem 0x%x\n", da, memsz);
+                       ret = -EINVAL;
+                       break;
+               }
+
+               /* skip the memzero logic performed by remoteproc ELF loader */
+               if (!phdr->p_filesz)
+                       continue;
+
+               ret = pru_rproc_memcpy(ptr,
+                                      (void *)addr + phdr->p_offset, filesz);
+               if (ret) {
+                       dev_dbg(dev, "PRU custom memory copy failed for da 0x%x 
memsz 0x%x\n",
+                               da, memsz);
+                       break;
+               }
+       }
+
+       priv->bootaddr = ehdr->e_entry;
+
+       return ret;
+}
+
+static const struct dm_rproc_ops pru_ops = {
+       .init = pru_init,
+       .start = pru_start,
+       .stop = pru_stop,
+       .load = pru_load,
+};
+
+static void pru_set_id(struct pru_privdata *priv, struct udevice *dev)
+{
+       u32 mask2 = 0x38000;
+
+       if (device_is_compatible(dev, "ti,am654-rtu"))
+               mask2 = 0x6000;
+
+       if ((priv->pru_iram & mask2) == mask2)
+               priv->id = 1;
+       else
+               priv->id = 0;
+}
+
+/**
+ * pru_probe() - Basic probe
+ * @dev:       corresponding k3 remote processor device
+ *
+ * Return: 0 if all goes good, else appropriate error message.
+ */
+static int pru_probe(struct udevice *dev)
+{
+       struct pru_privdata *priv;
+       int lenp;
+       ofnode node;
+
+       node = dev_ofnode(dev);
+
+       priv = dev_get_priv(dev);
+       priv->prusspriv = dev_get_priv(dev->parent);
+
+       priv->pru_iram = devfdt_get_addr_size_index(dev, PRU_MEM_IRAM,
+                                                   &priv->pru_iramsz);
+       priv->pru_ctrl = devfdt_get_addr_size_index(dev, PRU_MEM_CTRL,
+                                                   &priv->pru_ctrlsz);
+       priv->pru_debug = devfdt_get_addr_size_index(dev, PRU_MEM_DEBUG,
+                                                    &priv->pru_debugsz);
+
+       priv->fw_name = ofnode_get_property(node, "firmware-name", &lenp);
+
+       priv->iram_da = 0;
+       priv->pdram_da = 0;
+       priv->sdram_da = 0x2000;
+       priv->shrdram_da = 0x10000;
+
+       pru_set_id(priv, dev);
+
+       return 0;
+}
+
+static const struct udevice_id pru_ids[] = {
+       { .compatible = "ti,am654-pru"},
+       { .compatible = "ti,am654-rtu"},
+       {}
+};
+
+U_BOOT_DRIVER(pru) = {
+       .name = "pru",
+       .of_match = pru_ids,
+       .id = UCLASS_REMOTEPROC,
+       .ops = &pru_ops,
+       .probe = pru_probe,
+       .priv_auto_alloc_size = sizeof(struct pru_privdata),
+};
-- 
2.17.1

Reply via email to