[RFC PATCH 1/2] remoteproc: Add zynqmp R5 remoteproc driver
Wendy Liang
wendy.liang at xilinx.com
Wed Jul 15 05:22:54 PDT 2015
R5 is included in Xilinx Zynq UltraScale MPSoC so by adding this
remotproc driver, we can boot the R5 sub-system in different
configurations.
Currently this driver only supports direct HW access with place holder
for SMC and HVC implementations which will be added later.
Signed-off-by: Jason Wu <j.wu at xilinx.com>
Signed-off-by: Edgar E. Iglesias <edgar.iglesias at xilinx.com>
Signed-off-by: Wendy Liang <jliang at xilinx.com>
---
drivers/remoteproc/Kconfig | 9 +
drivers/remoteproc/Makefile | 1 +
drivers/remoteproc/zynqmp_r5_remoteproc.c | 809 ++++++++++++++++++++++++++++++
3 files changed, 819 insertions(+)
create mode 100644 drivers/remoteproc/zynqmp_r5_remoteproc.c
diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig
index 28c711f..737c8e7 100644
--- a/drivers/remoteproc/Kconfig
+++ b/drivers/remoteproc/Kconfig
@@ -77,4 +77,13 @@ config DA8XX_REMOTEPROC
It's safe to say n here if you're not interested in multimedia
offloading.
+config ZYNQMP_R5_REMOTEPROC
+ tristate "ZynqMP_r5 remoteproc support"
+ depends on ARM64
+ select REMOTEPROC
+ select RPMSG
+ help
+ Say y here to support ZynqMP R5 remote processors via the remote
+ processor framework.
+
endmenu
diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile
index 81b04d1..489963b 100644
--- a/drivers/remoteproc/Makefile
+++ b/drivers/remoteproc/Makefile
@@ -11,3 +11,4 @@ obj-$(CONFIG_OMAP_REMOTEPROC) += omap_remoteproc.o
obj-$(CONFIG_STE_MODEM_RPROC) += ste_modem_rproc.o
obj-$(CONFIG_WKUP_M3_RPROC) += wkup_m3_rproc.o
obj-$(CONFIG_DA8XX_REMOTEPROC) += da8xx_remoteproc.o
+obj-$(CONFIG_ZYNQMP_R5_REMOTEPROC) += zynqmp_r5_remoteproc.o
diff --git a/drivers/remoteproc/zynqmp_r5_remoteproc.c b/drivers/remoteproc/zynqmp_r5_remoteproc.c
new file mode 100644
index 0000000..24948af
--- /dev/null
+++ b/drivers/remoteproc/zynqmp_r5_remoteproc.c
@@ -0,0 +1,809 @@
+/*
+ * Zynq R5 Remote Processor driver
+ *
+ * Copyright (C) 2015 Jason Wu <j.wu at xilinx.com>
+ * Copyright (C) 2015 Xilinx, Inc.
+ *
+ * Based on origin OMAP and Zynq Remote Processor driver
+ *
+ * Copyright (C) 2012 Michal Simek <monstr at monstr.eu>
+ * Copyright (C) 2012 PetaLogix
+ * Copyright (C) 2011 Texas Instruments, Inc.
+ * Copyright (C) 2011 Google, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/remoteproc.h>
+#include <linux/interrupt.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <asm/cacheflush.h>
+#include <linux/slab.h>
+#include <linux/cpu.h>
+#include <linux/delay.h>
+
+#include "remoteproc_internal.h"
+
+/* Register offset definitions for RPU. */
+#define RPU_GLBL_CNTL_OFFSET 0x00000000 /* RPU control */
+#define RPU_0_CFG_OFFSET 0x00000100 /* RPU0 configuration */
+#define RPU_1_CFG_OFFSET 0x00000200 /* RPU1 Configuration */
+/* Boot memory bit. high for OCM, low for TCM */
+#define VINITHI_BIT BIT(2)
+/* CPU halt bit, high: processor is running. low: processor is halt */
+#define nCPUHALT_BIT BIT(0)
+/* RPU mode, high: split mode. low: lock step mode */
+#define SLSPLIT_BIT BIT(3)
+/* Clamp mode. high: split mode. low: lock step mode */
+#define SLCLAMP_BIT BIT(4)
+/* TCM mode. high: combine RPU TCMs. low: split TCM for RPU1 and RPU0 */
+#define TCM_COMB_BIT BIT(6)
+
+/* Clock controller low power domain (CRL_APB) for RPU */
+#define CPU_R5_CTRL_OFFSET 0x00000090 /* RPU Global Control*/
+#define RST_LPD_TOP_OFFSET 0x0000023C /* LPD block */
+#define RPU0_RESET_BIT BIT(0) /* RPU CPU0 reset bit */
+
+/* IPI reg offsets */
+#define TRIG_OFFSET 0x00000000
+#define OBS_OFFSET 0x00000004
+#define ISR_OFFSET 0x00000010
+#define IMR_OFFSET 0x00000014
+#define IER_OFFSET 0x00000018
+#define IDR_OFFSET 0x0000001C
+#define IPI_ALL_MASK 0x0F0F0301
+
+/* LOVEC BOOT ADDR -- TCM ADDR */
+#define TCM_0_LOVEC_ADDR 0xFFE00000
+#define TCM_1_LOVEC_ADDR 0xFFE40000
+
+/* HIVEC BOOT ADDR -- OCM ADDR */
+#define OCM_HIVEC_ADDR 0xFFFF0000
+
+#define MAX_INSTANCES 2 /* Support upto 2 RPU */
+
+#define ZYNQMP_SIP_SVC_CPU_START 0x82001004
+#define ZYNQMP_SIP_SVC_CPU_DISABLE 0x82001006
+
+/* Store rproc for IPI handler */
+static struct platform_device *remoteprocdev[MAX_INSTANCES];
+
+/* Register access macros */
+#define reg_read(base, reg) \
+ readl(((void __iomem *)(base)) + (reg))
+#define reg_write(base, reg, val) \
+ writel((val), ((void __iomem *)(base)) + (reg))
+
+#define DEFAULT_FIRMWARE_NAME "rproc-rpu-fw"
+
+/* Module parameter */
+static char *firmware;
+
+struct zynqmp_r5_rproc_pdata;
+
+/**
+ * struct ipi_ops - IPI operation handlers
+ * @clear: Clear IPI
+ * @reset: Reset IPI channel
+ * @set_mask: Destination mask
+ * @trigger: Trigger IPI
+ */
+struct ipi_ops {
+ void (*clear)(struct zynqmp_r5_rproc_pdata *pdata);
+ void (*reset)(struct zynqmp_r5_rproc_pdata *pdata);
+ void (*set_mask)(struct zynqmp_r5_rproc_pdata *pdata);
+ void (*trigger)(struct zynqmp_r5_rproc_pdata *pdata);
+};
+
+/**
+ * struct rpu_ops - RPU operation handlers
+ * @bootdev: Boot device
+ * @core_conf: Core configuration
+ * @halt: Enable/Disable halt
+ * @en_reset: Enable/Disable reset
+ */
+struct rpu_ops {
+ int (*start)(struct zynqmp_r5_rproc_pdata *pdata);
+ void (*stop)(struct zynqmp_r5_rproc_pdata *pdata);
+};
+
+/* enumerations for RPU/IPI control methods */
+enum control_method {
+ SMC = 0,
+ HVC,
+ HW,
+};
+
+/* enumerations for R5 boot device */
+enum rpu_bootmem {
+ TCM = 0,
+ OCM,
+};
+
+/* enumerations for R5 core configurations */
+enum rpu_core_conf {
+ LOCK_STEP = 0,
+ SPLIT,
+};
+
+/**
+ * struct zynqmp_r5_rproc_pdata - zynqmp rpu remote processor instance state
+ * @rproc: rproc handle
+ * @ipi_ops: IPI related operation handlers
+ * @rpu_ops: RPU related operation handlers
+ * @workqueue: workqueue for the RPU remoteproc
+ * @rpu_base: virt ptr to RPU control address registers
+ * @crl_apb_base: virt ptr to CRL_APB address registers for RPU
+ * @ipi_base: virt ptr to IPI channel address registers for APU
+ * @rpu_mode: RPU core configuration
+ * @rpu_id: RPU CPU id
+ * @bootmem: RPU boot memory device used
+ * @vring0: IRQ number used for vring0
+ * @ipi_dest_mask: IPI destination mask for the IPI channel
+ */
+struct zynqmp_r5_rproc_pdata {
+ struct rproc *rproc;
+ struct ipi_ops *ipi_ops;
+ struct rpu_ops *rpu_ops;
+ struct work_struct workqueue;
+ void __iomem *rpu_base;
+ void __iomem *crl_apb_base;
+ void __iomem *ipi_base;
+ enum rpu_core_conf rpu_mode;
+ enum rpu_bootmem bootmem;
+ u32 ipi_dest_mask;
+ u32 rpu_id;
+ u32 vring0;
+};
+
+static int get_elf_entry_addr(struct zynqmp_r5_rproc_pdata *pdata,
+ u32 *elf_entry_p)
+{
+ struct elf32_hdr *ehdr = 0;
+ const struct firmware *firmware_p;
+ struct rproc *rproc = pdata->rproc;
+ int ret;
+
+ ret = request_firmware(&firmware_p, rproc->firmware, &rproc->dev);
+ if (ret < 0) {
+ dev_err(&rproc->dev, "%s: request_firmware failed: %d\n",
+ __func__, ret);
+ return ret;
+ }
+ ehdr = (struct elf32_hdr *)firmware_p->data;
+ *elf_entry_p = (unsigned int)ehdr->e_entry;
+ release_firmware(firmware_p);
+ return 0;
+}
+
+/*
+ * TODO: Update HW RPU operation when the driver is ready
+ */
+static void hw_r5_boot_dev(struct zynqmp_r5_rproc_pdata *pdata)
+{
+ u32 tmp;
+ u32 offset = RPU_1_CFG_OFFSET;
+
+ pr_debug("%s: R5 ID: %d, boot_dev %d\n",
+ __func__, pdata->rpu_id, pdata->bootmem);
+ if (pdata->rpu_id == 0)
+ offset = RPU_0_CFG_OFFSET;
+
+ tmp = reg_read(pdata->rpu_base, offset);
+ if (pdata->bootmem == OCM)
+ tmp |= VINITHI_BIT;
+ else
+ tmp &= ~VINITHI_BIT;
+ reg_write(pdata->rpu_base, offset, tmp);
+}
+
+static void hw_r5_reset(struct zynqmp_r5_rproc_pdata *pdata,
+ bool do_reset)
+{
+ u32 tmp;
+
+ pr_debug("%s: R5 ID: %d, reset %d\n", __func__, pdata->rpu_id,
+ do_reset);
+ tmp = reg_read(pdata->crl_apb_base, RST_LPD_TOP_OFFSET);
+ if (do_reset)
+ tmp |= (RPU0_RESET_BIT << pdata->rpu_id);
+ else
+ tmp &= ~(RPU0_RESET_BIT << pdata->rpu_id);
+ reg_write(pdata->crl_apb_base, RST_LPD_TOP_OFFSET, tmp);
+}
+
+static void hw_r5_halt(struct zynqmp_r5_rproc_pdata *pdata,
+ bool do_halt)
+{
+ u32 tmp;
+ u32 offset = RPU_1_CFG_OFFSET;
+
+ pr_debug("%s: R5 ID: %d, halt %d\n", __func__, pdata->rpu_id,
+ do_halt);
+ if (pdata->rpu_id == 0)
+ offset = RPU_0_CFG_OFFSET;
+
+ tmp = reg_read(pdata->rpu_base, offset);
+ if (do_halt)
+ tmp &= ~nCPUHALT_BIT;
+ else
+ tmp |= nCPUHALT_BIT;
+ reg_write(pdata->rpu_base, offset, tmp);
+}
+
+static void hw_r5_core_config(struct zynqmp_r5_rproc_pdata *pdata)
+{
+ u32 tmp;
+
+ pr_debug("%s: mode: %d\n", __func__, pdata->rpu_mode);
+ tmp = reg_read(pdata->rpu_base, 0);
+ if (pdata->rpu_mode == SPLIT) {
+ tmp |= SLSPLIT_BIT;
+ tmp &= ~TCM_COMB_BIT;
+ tmp &= ~SLCLAMP_BIT;
+ } else {
+ tmp &= ~SLSPLIT_BIT;
+ tmp |= TCM_COMB_BIT;
+ tmp |= SLCLAMP_BIT;
+ }
+ reg_write(pdata->rpu_base, 0, tmp);
+}
+
+static int hw_r5_core_start(struct zynqmp_r5_rproc_pdata *pdata)
+{
+ u32 elf_entry_addr = 0;
+ u32 boot_addr = 0;
+ struct rproc *rproc = pdata->rproc;
+ void __iomem *vector_base = 0;
+ u32 cpu_cfg_offset = RPU_1_CFG_OFFSET;
+ u32 rpu_cfg_reg = 0;
+ int ret;
+ /*
+ * Boot trampoline is simple ASM code below.
+ *
+ * ldr pc, [pc, #-4] ; 4 <. +0x4>
+ */
+ unsigned int bootloader[] = {
+ 0xe51ff004,
+ 0,
+ 0xe59f0004,
+ 0xe5901000,
+ 0xe12fff11,
+ 0xe320f000,
+ };
+
+ /* Set up R5 */
+ hw_r5_core_config(pdata);
+ hw_r5_reset(pdata, true);
+ hw_r5_halt(pdata, true);
+ hw_r5_boot_dev(pdata);
+
+ /* Set up the bootloader */
+ /*
+ * Boot trampoline is simple ASM code below.
+ *
+ * ldr pc, [pc, #-4] ; 4 <. +0x4>
+ */
+ ret = get_elf_entry_addr(pdata, &elf_entry_addr);
+ if (ret < 0) {
+ dev_err(&rproc->dev, "%s: request_firmware failed: %d\n",
+ __func__, ret);
+ return ret;
+ }
+ dev_dbg(&rproc->dev, "%s: elf entry: 0x%x\n", __func__, elf_entry_addr);
+ bootloader[1] = elf_entry_addr;
+ /* Get boot address.
+ * Put the loader to TCM if it is LOWVEC, otherwise, put to OCM.
+ */
+ if (pdata->rpu_id == 0)
+ cpu_cfg_offset = RPU_0_CFG_OFFSET;
+
+ rpu_cfg_reg = reg_read(pdata->rpu_base, cpu_cfg_offset);
+ if ((rpu_cfg_reg & VINITHI_BIT))
+ boot_addr = OCM_HIVEC_ADDR;
+ else if (pdata->rpu_id == 0)
+ boot_addr = TCM_0_LOVEC_ADDR;
+ else
+ boot_addr = TCM_1_LOVEC_ADDR;
+ dev_dbg(&rproc->dev, "%s: boot addr: 0x%x\n", __func__, boot_addr);
+ vector_base = ioremap(boot_addr, sizeof(bootloader));
+ memcpy_toio(vector_base, bootloader, sizeof(bootloader));
+ /* Just for sure synchronize memories */
+ __flush_dcache_area(vector_base, sizeof(bootloader));
+ udelay(500);
+
+ hw_r5_reset(pdata, false);
+ hw_r5_halt(pdata, false);
+ return 0;
+}
+
+static void hw_r5_core_stop(struct zynqmp_r5_rproc_pdata *pdata)
+{
+ hw_r5_reset(pdata, true);
+ hw_r5_halt(pdata, true);
+}
+
+static struct rpu_ops rpu_hw_ops = {
+ .start = hw_r5_core_start,
+ .stop = hw_r5_core_stop,
+};
+
+asmlinkage extern int __invoke_psci_fn_smc(u64, u64, u64, u64);
+static int smc_r5_core_start(struct zynqmp_r5_rproc_pdata *pdata)
+{
+ pr_err("%s: atf smc to be implemented\n", __func__);
+ return -ENODEV;
+}
+
+static void smc_r5_core_stop(struct zynqmp_r5_rproc_pdata *pdata)
+{
+ pr_err("%s: atf smc to be implemented\n", __func__);
+}
+
+static struct rpu_ops rpu_smc_ops = {
+ .start = smc_r5_core_start,
+ .stop = smc_r5_core_stop,
+};
+
+static int hvc_r5_core_start(struct zynqmp_r5_rproc_pdata *pdata)
+{
+ pr_err("%s: hypervisor hvc to be implemented\n", __func__);
+ return -ENODEV;
+}
+
+static void hvc_r5_core_stop(struct zynqmp_r5_rproc_pdata *pdata)
+{
+ pr_err("%s: hypervisor hvc to be implemented\n", __func__);
+}
+
+static struct rpu_ops rpu_hvc_ops = {
+ .start = hvc_r5_core_start,
+ .stop = hvc_r5_core_stop,
+};
+
+/*
+ * TODO: Update HW ipi operation when the driver is ready
+ */
+static void hw_clear_ipi(struct zynqmp_r5_rproc_pdata *pdata)
+{
+ pr_debug("%s: irq issuer %08x clear IPI\n", __func__,
+ pdata->ipi_dest_mask);
+ reg_write(pdata->ipi_base, ISR_OFFSET, pdata->ipi_dest_mask);
+}
+
+static void hw_ipi_reset(struct zynqmp_r5_rproc_pdata *pdata)
+{
+ reg_write(pdata->ipi_base, IDR_OFFSET, IPI_ALL_MASK);
+ reg_write(pdata->ipi_base, ISR_OFFSET, IPI_ALL_MASK);
+ /* add delay to allow ipi to be settle */
+ udelay(10);
+ pr_debug("IPI reset done\n");
+}
+
+static void hw_set_ipi_mask(struct zynqmp_r5_rproc_pdata *pdata)
+{
+ pr_debug("%s: set IPI mask %08x\n", __func__, pdata->ipi_dest_mask);
+ reg_write(pdata->ipi_base, IER_OFFSET, pdata->ipi_dest_mask);
+}
+
+static void hw_trigger_ipi(struct zynqmp_r5_rproc_pdata *pdata)
+{
+ pr_debug("%s: dest %08x\n", __func__, pdata->ipi_dest_mask);
+ reg_write(pdata->ipi_base, TRIG_OFFSET, pdata->ipi_dest_mask);
+}
+
+static void ipi_init(struct zynqmp_r5_rproc_pdata *pdata)
+{
+ pr_debug("%s\n", __func__);
+ pdata->ipi_ops->reset(pdata);
+ pdata->ipi_ops->set_mask(pdata);
+}
+
+static struct ipi_ops ipi_hw_ops = {
+ .clear = hw_clear_ipi,
+ .reset = hw_ipi_reset,
+ .set_mask = hw_set_ipi_mask,
+ .trigger = hw_trigger_ipi,
+};
+
+
+static void smc_clear_ipi(struct zynqmp_r5_rproc_pdata *pdata)
+{
+ pr_err("%s: atf smc to be implemented\n", __func__);
+}
+
+static void smc_ipi_reset(struct zynqmp_r5_rproc_pdata *pdata)
+{
+ pr_err("%s: atf smc to be implemented\n", __func__);
+}
+
+static void smc_set_ipi_mask(struct zynqmp_r5_rproc_pdata *pdata)
+{
+ pr_err("%s: atf smc to be implemented\n", __func__);
+}
+
+static void smc_trigger_ipi(struct zynqmp_r5_rproc_pdata *pdata)
+{
+ pr_err("%s: atf smc to be implemented\n", __func__);
+}
+
+static struct ipi_ops ipi_smc_ops = {
+ .clear = smc_clear_ipi,
+ .reset = smc_ipi_reset,
+ .set_mask = smc_set_ipi_mask,
+ .trigger = smc_trigger_ipi,
+};
+
+static void hvc_clear_ipi(struct zynqmp_r5_rproc_pdata *pdata)
+{
+ pr_err("%s: hypervisor hvc to be implemented\n", __func__);
+}
+
+static void hvc_ipi_reset(struct zynqmp_r5_rproc_pdata *pdata)
+{
+ pr_err("%s: hypervisor hvc to be implemented\n", __func__);
+}
+
+static void hvc_set_ipi_mask(struct zynqmp_r5_rproc_pdata *pdata)
+{
+ pr_err("%s: hypervisor hvc to be implemented\n", __func__);
+}
+
+static void hvc_trigger_ipi(struct zynqmp_r5_rproc_pdata *pdata)
+{
+ pr_err("%s: hypervisor hvc to be implemented\n", __func__);
+}
+
+static struct ipi_ops ipi_hvc_ops = {
+ .clear = hvc_clear_ipi,
+ .reset = hvc_ipi_reset,
+ .set_mask = hvc_set_ipi_mask,
+ .trigger = hvc_trigger_ipi,
+};
+
+static void handle_event(struct zynqmp_r5_rproc_pdata *local)
+{
+ if (rproc_vq_interrupt(local->rproc, 0) == IRQ_NONE)
+ dev_dbg(&remoteprocdev[local->rpu_id]->dev,
+ "no message found in vqid 0\n");
+}
+
+static void handle_event0(struct work_struct *work)
+{
+ struct zynqmp_r5_rproc_pdata *local =
+ platform_get_drvdata(remoteprocdev[0]);
+
+ handle_event(local);
+}
+
+static void handle_event1(struct work_struct *work)
+{
+ struct zynqmp_r5_rproc_pdata *local =
+ platform_get_drvdata(remoteprocdev[1]);
+
+ handle_event(local);
+}
+
+static int zynqmp_r5_rproc_start(struct rproc *rproc)
+{
+ struct device *dev = rproc->dev.parent;
+ struct platform_device *pdev = to_platform_device(dev);
+ struct zynqmp_r5_rproc_pdata *local = platform_get_drvdata(pdev);
+ int ret = 0;
+
+ dev_dbg(dev, "%s\n", __func__);
+ /* limit to two RPU support */
+ if (local->rpu_id == 0)
+ INIT_WORK(&local->workqueue, handle_event0);
+ else
+ INIT_WORK(&local->workqueue, handle_event1);
+
+ remoteprocdev[local->rpu_id] = pdev;
+
+ ret = local->rpu_ops->start(local);
+
+ if (ret < 0) {
+ dev_err(dev, "Failed to start RPU.\n");
+ return ret;
+ }
+ ipi_init(local);
+
+ return 0;
+}
+
+/* kick a firmware */
+static void zynqmp_r5_rproc_kick(struct rproc *rproc, int vqid)
+{
+ struct device *dev = rproc->dev.parent;
+ struct platform_device *pdev = to_platform_device(dev);
+ struct zynqmp_r5_rproc_pdata *local = platform_get_drvdata(pdev);
+
+ dev_dbg(dev, "KICK Firmware to start send messages vqid %d\n", vqid);
+
+ /*
+ * send irq to R5 firmware
+ * Currently vqid is not used because we only got one.
+ */
+ local->ipi_ops->trigger(local);
+}
+
+/* power off the remote processor */
+static int zynqmp_r5_rproc_stop(struct rproc *rproc)
+{
+ struct device *dev = rproc->dev.parent;
+ struct platform_device *pdev = to_platform_device(dev);
+ struct zynqmp_r5_rproc_pdata *local = platform_get_drvdata(pdev);
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ local->rpu_ops->stop(local);
+
+ local->ipi_ops->reset(local);
+
+ return 0;
+}
+
+static struct rproc_ops zynqmp_r5_rproc_ops = {
+ .start = zynqmp_r5_rproc_start,
+ .stop = zynqmp_r5_rproc_stop,
+ .kick = zynqmp_r5_rproc_kick,
+};
+
+static irqreturn_t r5_remoteproc_interrupt(int irq, void *dev_id)
+{
+ struct device *dev = dev_id;
+ struct platform_device *pdev = to_platform_device(dev);
+ struct zynqmp_r5_rproc_pdata *local = platform_get_drvdata(pdev);
+
+ dev_dbg(dev, "KICK Linux because of pending message(irq%d)\n", irq);
+
+ local->ipi_ops->clear(local);
+ schedule_work(&local->workqueue);
+
+ dev_dbg(dev, "KICK Linux handled\n");
+ return IRQ_HANDLED;
+}
+
+static int zynqmp_r5_remoteproc_probe(struct platform_device *pdev)
+{
+ const unsigned char *prop;
+ struct resource *res;
+ int ret = 0;
+ int method = 0;
+ struct zynqmp_r5_rproc_pdata *local;
+
+ local = devm_kzalloc(&pdev->dev, sizeof(struct zynqmp_r5_rproc_pdata),
+ GFP_KERNEL);
+ if (!local)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, local);
+
+ /* Declare vring for firmware */
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "shared");
+ if (!res) {
+ dev_err(&pdev->dev, "invalid address for shared memory\n");
+ return -ENXIO;
+ }
+
+ /* Alloc phys addr from 0 to max_addr for firmware */
+ ret = dma_declare_coherent_memory(&pdev->dev, res->start,
+ res->start, res->end - res->start + 1,
+ DMA_MEMORY_IO);
+ if (!ret) {
+ dev_err(&pdev->dev, "dma_declare_coherent_memory failed\n");
+ return -ENOMEM;
+ }
+
+ /* it may need to extend to 64/48 bit later*/
+ ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32));
+ if (ret) {
+ dev_err(&pdev->dev, "dma_set_coherent_mask: %d\n", ret);
+ goto dma_mask_fault;
+ }
+
+ prop = of_get_property(pdev->dev.of_node, "core_conf", NULL);
+ if (!prop) {
+ dev_warn(&pdev->dev, "default core_conf used: lock-step\n");
+ prop = "lock-step";
+ }
+
+ dev_info(&pdev->dev, "RPU core_conf: %s\n", prop);
+ if (!strcmp(prop, "split0")) {
+ local->rpu_mode = SPLIT;
+ local->rpu_id = 0;
+ } else if (!strcmp(prop, "split1")) {
+ local->rpu_mode = SPLIT;
+ local->rpu_id = 1;
+ } else if (!strcmp(prop, "lock-step")) {
+ local->rpu_mode = LOCK_STEP;
+ local->rpu_id = 0;
+ } else {
+ dev_err(&pdev->dev, "Invalid core_conf mode provided - %s , %d\n",
+ prop, local->rpu_mode);
+ goto dma_mask_fault;
+ }
+
+ prop = of_get_property(pdev->dev.of_node, "method", NULL);
+ if (!prop) {
+ dev_warn(&pdev->dev, "default method used: smc\n");
+ prop = "smc";
+ }
+
+ dev_info(&pdev->dev, "IPI/RPU control method: %s\n", prop);
+ if (!strcmp(prop, "direct")) {
+ method = HW;
+ local->ipi_ops = &ipi_hw_ops;
+ local->rpu_ops = &rpu_hw_ops;
+ } else if (!strcmp(prop, "hvc")) {
+ method = HVC;
+ local->ipi_ops = &ipi_hvc_ops;
+ local->rpu_ops = &rpu_hvc_ops;
+ } else if (!strcmp(prop, "smc")) {
+ method = SMC;
+ local->ipi_ops = &ipi_smc_ops;
+ local->rpu_ops = &rpu_smc_ops;
+ } else {
+ dev_err(&pdev->dev, "Invalid method provided - %s\n",
+ prop);
+ goto dma_mask_fault;
+ }
+
+ /* Handle direct hardware access */
+ /* (TODO: remove once RPU and IPI drivers are ready ) */
+ if (method == HW) {
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "rpu_base");
+ local->rpu_base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(local->rpu_base)) {
+ dev_err(&pdev->dev, "Unable to map RPU I/O memory\n");
+ ret = PTR_ERR(local->rpu_base);
+ goto dma_mask_fault;
+ }
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "apb_base");
+ local->crl_apb_base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(local->crl_apb_base)) {
+ dev_err(&pdev->dev, "Unable to map CRL_APB I/O memory\n");
+ ret = PTR_ERR(local->crl_apb_base);
+ goto dma_mask_fault;
+ }
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ipi");
+ local->ipi_base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(local->ipi_base)) {
+ pr_err("%s: Unable to map IPI\n", __func__);
+ ret = PTR_ERR(local->ipi_base);
+ goto dma_mask_fault;
+ }
+ }
+
+ prop = of_get_property(pdev->dev.of_node, "bootmem", NULL);
+ if (!prop) {
+ dev_warn(&pdev->dev, "default bootmem property used: tcm\n");
+ prop = "tcm";
+ }
+
+ dev_info(&pdev->dev, "RPU bootmem: %s\n", prop);
+ if (!strcmp(prop, "tcm")) {
+ local->bootmem = TCM;
+ } else if (!strcmp(prop, "ocm")) {
+ local->bootmem = OCM;
+ } else {
+ dev_err(&pdev->dev, "Invalid R5 bootmem property - %s\n",
+ prop);
+ goto dma_mask_fault;
+ }
+
+ /* IPI IRQ */
+ local->vring0 = platform_get_irq(pdev, 0);
+ if (local->vring0 < 0) {
+ ret = local->vring0;
+ dev_err(&pdev->dev, "unable to find IPI IRQ\n");
+ goto dma_mask_fault;
+ }
+ ret = devm_request_irq(&pdev->dev, local->vring0,
+ r5_remoteproc_interrupt, 0, dev_name(&pdev->dev),
+ &pdev->dev);
+ if (ret) {
+ dev_err(&pdev->dev, "IRQ %d already allocated\n",
+ local->vring0);
+ goto dma_mask_fault;
+ }
+ dev_dbg(&pdev->dev, "vring0 irq: %d\n", local->vring0);
+
+ ret = of_property_read_u32(pdev->dev.of_node, "ipi_dest_mask",
+ &local->ipi_dest_mask);
+ if (ret < 0) {
+ dev_warn(&pdev->dev, "default ipi_dest_mask used: 0x100\n");
+ local->ipi_dest_mask = 0x100;
+ }
+ dev_info(&pdev->dev, "ipi_dest_mask: 0x%x\n", local->ipi_dest_mask);
+
+ /* Module param firmware first */
+ prop = of_get_property(pdev->dev.of_node, "firmware", NULL);
+ if (firmware)
+ prop = firmware;
+ else if (!prop)
+ prop = DEFAULT_FIRMWARE_NAME;
+
+ if (prop) {
+ dev_dbg(&pdev->dev, "Using firmware: %s\n", prop);
+ local->rproc = rproc_alloc(&pdev->dev, dev_name(&pdev->dev),
+ &zynqmp_r5_rproc_ops, prop, sizeof(struct rproc));
+ if (!local->rproc) {
+ dev_err(&pdev->dev, "rproc allocation failed\n");
+ goto rproc_fault;
+ }
+
+ ret = rproc_add(local->rproc);
+ if (ret) {
+ dev_err(&pdev->dev, "rproc registration failed\n");
+ goto rproc_fault;
+ }
+ } else {
+ ret = -ENODEV;
+ }
+
+ return ret;
+
+rproc_fault:
+ rproc_put(local->rproc);
+
+dma_mask_fault:
+ dma_release_declared_memory(&pdev->dev);
+
+ return 0;
+}
+
+static int zynqmp_r5_remoteproc_remove(struct platform_device *pdev)
+{
+ struct zynqmp_r5_rproc_pdata *local = platform_get_drvdata(pdev);
+
+ dev_info(&pdev->dev, "%s\n", __func__);
+
+ rproc_del(local->rproc);
+ rproc_put(local->rproc);
+
+ dma_release_declared_memory(&pdev->dev);
+
+ return 0;
+}
+
+/* Match table for OF platform binding */
+static const struct of_device_id zynqmp_r5_remoteproc_match[] = {
+ { .compatible = "xlnx,zynqmp-r5-remoteproc-1.0", },
+ { /* end of list */ },
+};
+MODULE_DEVICE_TABLE(of, zynqmp_r5_remoteproc_match);
+
+static struct platform_driver zynqmp_r5_remoteproc_driver = {
+ .probe = zynqmp_r5_remoteproc_probe,
+ .remove = zynqmp_r5_remoteproc_remove,
+ .driver = {
+ .name = "zynqmp_r5_remoteproc",
+ .of_match_table = zynqmp_r5_remoteproc_match,
+ },
+};
+module_platform_driver(zynqmp_r5_remoteproc_driver);
+
+module_param(firmware, charp, 0);
+MODULE_PARM_DESC(firmware, "Override the firmware image name.");
+
+MODULE_AUTHOR("Jason Wu <j.wu at xilinx.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("ZynqMP R5 remote processor control driver");
--
2.1.1
More information about the linux-arm-kernel
mailing list