@@ -197,6 +197,20 @@ config ST_REMOTEPROC
config ST_SLIM_REMOTEPROC
tristate
+config PRUSS_REMOTEPROC
+ tristate "TI PRUSS remoteproc support"
+ depends on TI_PRUSS
+ default n
+ help
+ Support for TI PRU-ICSS remote processors via the remote processor
+ framework.
+
+ Currently supported on AM33xx SoCs.
+
+ Say Y or M here to support the Programmable Realtime Unit (PRU)
+ processors on various TI SoCs. It's safe to say N here if you're
+ not interested in the PRU or if you are unsure.
+
endif # REMOTEPROC
endmenu
@@ -26,3 +26,4 @@ qcom_wcnss_pil-y += qcom_wcnss.o
qcom_wcnss_pil-y += qcom_wcnss_iris.o
obj-$(CONFIG_ST_REMOTEPROC) += st_remoteproc.o
obj-$(CONFIG_ST_SLIM_REMOTEPROC) += st_slim_rproc.o
+obj-$(CONFIG_PRUSS_REMOTEPROC) += pru_rproc.o
new file mode 100644
@@ -0,0 +1,392 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * PRU-ICSS remoteproc driver for various TI SoCs
+ *
+ * Copyright (C) 2014-2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Suman Anna <s-anna@ti.com>
+ * Andrew F. Davis <afd@ti.com>
+ */
+
+#include <linux/bitops.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/remoteproc.h>
+#include <linux/pruss.h>
+
+#include "remoteproc_internal.h"
+#include "pru_rproc.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)
+
+/**
+ * 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_rproc - PRU remoteproc structure
+ * @id: id of the PRU core within the PRUSS
+ * @pruss: back-reference to parent PRUSS structure
+ * @rproc: remoteproc pointer for this PRU core
+ * @mem_regions: data for each of the PRU memory regions
+ * @dram0: PRUSS DRAM0 region
+ * @dram1: PRUSS DRAM1 region
+ * @shrdram: PRUSS SHARED RAM region
+ * @iram_da: device address of Instruction RAM for this PRU
+ * @pdram_da: device address of primary Data RAM for this PRU
+ * @sdram_da: device address of secondary Data RAM for this PRU
+ * @shrdram_da: device address of shared Data RAM
+ * @fw_name: name of firmware image used during loading
+ */
+struct pru_rproc {
+ int id;
+ struct pruss *pruss;
+ struct rproc *rproc;
+ struct pruss_mem_region mem_regions[PRU_MEM_MAX];
+ struct pruss_mem_region dram0;
+ struct pruss_mem_region dram1;
+ struct pruss_mem_region shrdram;
+ u32 iram_da;
+ u32 pdram_da;
+ u32 sdram_da;
+ u32 shrdram_da;
+ const char *fw_name;
+};
+
+static void *pru_d_da_to_va(struct pru_rproc *pru, u32 da, int len);
+
+static inline u32 pru_control_read_reg(struct pru_rproc *pru, unsigned int reg)
+{
+ return readl_relaxed(pru->mem_regions[PRU_MEM_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_MEM_CTRL].va + reg);
+}
+
+static inline u32 pru_debug_read_reg(struct pru_rproc *pru, unsigned int reg)
+{
+ return readl_relaxed(pru->mem_regions[PRU_MEM_DEBUG].va + reg);
+}
+
+static inline
+void pru_debug_write_reg(struct pru_rproc *pru, unsigned int reg, u32 val)
+{
+ writel_relaxed(val, pru->mem_regions[PRU_MEM_DEBUG].va + reg);
+}
+
+/* start a PRU core */
+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%x\n",
+ pru->id, (rproc->bootaddr >> 2));
+
+ val = CTRL_CTRL_EN | ((rproc->bootaddr >> 2) << 16);
+ pru_control_write_reg(pru, PRU_CTRL_CTRL, val);
+
+ /* TODO: INTC setup */
+
+ return 0;
+}
+
+/* stop/disable a PRU core */
+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);
+
+ /* TODO: INTC cleanup */
+
+ 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, int len)
+{
+ struct pruss_mem_region dram0, dram1, shrd_ram;
+ u32 offset;
+ void *va = NULL;
+
+ if (len <= 0)
+ return NULL;
+
+ dram0 = pru->dram0;
+ dram1 = pru->dram1;
+ /* PRU1 has its local RAM addresses reversed */
+ if (pru->id == 1)
+ swap(dram0, dram1);
+ shrd_ram = pru->shrdram;
+
+ 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, int 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_MEM_IRAM].size) {
+ offset = da - pru->iram_da;
+ va = (__force void *)(pru->mem_regions[PRU_MEM_IRAM].va +
+ offset);
+ }
+
+ return va;
+}
+
+/* PRU-specific address translator */
+static void *pru_da_to_va(struct rproc *rproc, u64 da, int len, u32 flags)
+{
+ struct pru_rproc *pru = rproc->priv;
+ void *va;
+ 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)
+ 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_da_to_va,
+};
+
+static int pru_rproc_set_id(struct pru_rproc *pru)
+{
+ int ret = 0;
+ u32 mask1 = 0x34000;
+ u32 mask2 = 0x38000;
+
+ if ((pru->mem_regions[0].pa & mask1) == mask1)
+ pru->id = 0;
+ else if ((pru->mem_regions[0].pa & mask2) == mask2)
+ pru->id = 1;
+ else
+ 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_MEM_MAX] = { "iram", "control", "debug" };
+
+ if (!np) {
+ dev_err(dev, "Non-DT platform device not supported\n");
+ return -ENODEV;
+ }
+
+ 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 = rproc_alloc(dev, pdev->name, &pru_rproc_ops, fw_name,
+ sizeof(*pru));
+ if (!rproc) {
+ dev_err(dev, "rproc_alloc failed\n");
+ return -ENOMEM;
+ }
+ /* 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->pruss = platform_get_drvdata(ppdev);
+ pru->rproc = rproc;
+ pru->fw_name = fw_name;
+
+ ret = pruss_request_mem_region(pru->pruss, PRUSS_MEM_DRAM0,
+ &pru->dram0);
+ if (ret) {
+ dev_err(dev, "couldn't get PRUSS DRAM0: %d\n", ret);
+ return ret;
+ }
+ pruss_release_mem_region(pru->pruss, &pru->dram0);
+
+ ret = pruss_request_mem_region(pru->pruss, PRUSS_MEM_DRAM1,
+ &pru->dram1);
+ if (ret) {
+ dev_err(dev, "couldn't get PRUSS DRAM1: %d\n", ret);
+ return ret;
+ }
+ pruss_release_mem_region(pru->pruss, &pru->dram1);
+
+ ret = pruss_request_mem_region(pru->pruss, PRUSS_MEM_SHRD_RAM2,
+ &pru->shrdram);
+ if (ret) {
+ dev_err(dev, "couldn't get PRUSS Shared RAM: %d\n", ret);
+ return ret;
+ }
+ pruss_release_mem_region(pru->pruss, &pru->shrdram);
+
+ /* XXX: get this from match data if different in the future */
+ pru->iram_da = 0;
+ pru->pdram_da = 0;
+ pru->sdram_da = 0x2000;
+ pru->shrdram_da = 0x10000;
+
+ 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);
+ goto free_rproc;
+ }
+ 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 %p\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)
+ goto free_rproc;
+
+ platform_set_drvdata(pdev, rproc);
+
+ ret = rproc_add(pru->rproc);
+ if (ret) {
+ dev_err(dev, "rproc_add failed: %d\n", ret);
+ goto free_rproc;
+ }
+
+ dev_info(dev, "PRU rproc node %s probed successfully\n", np->full_name);
+
+ return 0;
+
+free_rproc:
+ rproc_free(rproc);
+ return ret;
+}
+
+static int pru_rproc_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rproc *rproc = platform_get_drvdata(pdev);
+
+ dev_info(dev, "%s: removing rproc %s\n", __func__, rproc->name);
+
+ rproc_del(rproc);
+ rproc_free(rproc);
+
+ return 0;
+}
+
+static const struct of_device_id pru_rproc_match[] = {
+ { .compatible = "ti,am3356-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 <s-anna@ti.com>");
+MODULE_DESCRIPTION("PRU-ICSS Remote Processor Driver");
+MODULE_LICENSE("GPL v2");
new file mode 100644
@@ -0,0 +1,65 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
+/*
+ * PRUSS Remote Processor specific types
+ *
+ * Copyright (C) 2014-2018 Texas Instruments Incorporated - http://www.ti.com/
+ * All rights reserved.
+ */
+
+#ifndef _PRU_REMOTEPROC_H_
+#define _PRU_REMOTEPROC_H_
+
+/**
+ * enum pruss_rsc_types - PRU specific resource types
+ *
+ * @PRUSS_RSC_INTRS: Resource holding information on PRU PINTC configuration
+ * @PRUSS_RSC_MAX: Indicates end of known/defined PRU resource types.
+ * This should be the last definition.
+ *
+ * Introduce new custom resource types before PRUSS_RSC_MAX.
+ */
+enum pruss_rsc_types {
+ PRUSS_RSC_INTRS = 1,
+ PRUSS_RSC_MAX = 2,
+};
+
+/**
+ * struct pruss_event_chnl - PRU system events _to_ channel mapping
+ * @event: number of the system event
+ * @chnl: channel number assigned to a given @event
+ *
+ * PRU system events are mapped to channels, and these channels are mapped
+ * to host interrupts. Events can be mapped to channels in a one-to-one or
+ * many-to-one ratio (multiple events per channel), and channels can be
+ * mapped to host interrupts in a one-to-one or many-to-one ratio (multiple
+ * channels per interrupt).
+ *
+ */
+struct pruss_event_chnl {
+ s8 event;
+ s8 chnl;
+};
+
+/**
+ * struct fw_rsc_custom_intrmap - custom resource to define PRU interrupts
+ * @reserved: reserved field providing padding and alignment
+ * @chnl_host_intr_map: array of PRU channels to host interrupt mappings
+ * @event_chnl_map_size: number of event_channel mappings defined in
+ * @event_chnl_map_addr
+ * @event_chnl_map_addr: PRU device address of pointer to array of events to
+ * channel mappings (struct pruss_event_chnl elements)
+ *
+ * PRU system events are mapped to channels, and these channels are mapped
+ * to host interrupts. Events can be mapped to channels in a one-to-one or
+ * many-to-one ratio (multiple events per channel), and channels can be
+ * mapped to host interrupts in a one-to-one or many-to-one ratio (multiple
+ * channels per interrupt).
+ */
+struct fw_rsc_custom_intrmap {
+ u16 reserved;
+ s8 chnl_host_intr_map[10];
+ u32 event_chnl_map_size;
+ u32 event_chnl_map_addr;
+};
+
+#endif /* _PRU_REMOTEPROC_H_ */