Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752115AbbGOMj7 (ORCPT ); Wed, 15 Jul 2015 08:39:59 -0400 Received: from mail-bl2on0065.outbound.protection.outlook.com ([65.55.169.65]:11136 "EHLO na01-bl2-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1751592AbbGOMj4 (ORCPT ); Wed, 15 Jul 2015 08:39:56 -0400 X-Greylist: delayed 938 seconds by postgrey-1.27 at vger.kernel.org; Wed, 15 Jul 2015 08:39:56 EDT Authentication-Results: spf=fail (sender IP is 149.199.60.96) smtp.mailfrom=xilinx.com; gmail.com; dkim=none (message not signed) header.d=none; From: Wendy Liang To: linux-arm-kernel@lists.infradead.org, linux-kernel@vger.kernel.org, evicetree@vger.kernel.org, ohad@wizery.com CC: michal.simek@xilinx.com, sunnyliangjy@gmail.com, Wendy Liang , Jason Wu , "Edgar E. Iglesias" Subject: [RFC PATCH 1/2] remoteproc: Add zynqmp R5 remoteproc driver Date: Wed, 15 Jul 2015 05:22:54 -0700 Message-ID: <1436962975-13918-2-git-send-email-jliang@xilinx.com> X-Mailer: git-send-email 2.1.1 In-Reply-To: <1436962975-13918-1-git-send-email-jliang@xilinx.com> References: <1436962975-13918-1-git-send-email-jliang@xilinx.com> X-RCIS-Action: ALLOW X-TM-AS-MML: disable X-TM-AS-Product-Ver: IMSS-7.1.0.1679-8.0.0.1202-21678.006 X-TM-AS-Result: No--13.978-7.0-31-10 X-imss-scan-details: No--13.978-7.0-31-10 X-TMASE-MatchedRID: VqiAyJHn9QeAVaFa63RwvW3NvezwBrVmB4Id7CiQcz/xSV7YBeBhS7Rb vFwbz0pKF5R4s7eKQFu1u7bGu6KdZT13WcdbGR6QHLEq6GCOQFbJ5SXtoJPLyJbvQ0TGSwM5Wc5 fBGEOSGgY52gPPdsR7NnUGMJNWTvXL0W1btd8e57J5W6OZe5hhWs80vt+osfcY9mvY0Dq9kZz2s M4iA5Hg0xjkTKpnKAMVhUngAeqP0a+lHppzLAoKyVypP66BP0Q0wHvRbIG2MdGL0g1nVmkYXbkp dDhAgYmkyeevgS86MhPaUktJ/9D+uETiJzZYgN7q32frrMnhEi0aicQ5bmD5iBQRBOQhaJiakcq 2RAHKcr2zHnGPfcJUpUGG00K6CRIwdbU3akH5Azd+fuf9kcapjuvYa1v2IFh8cWgFw6wp7Mbfm9 /D+tKzcvqqEDn0Jt0i6abtrBWHxLRlxaUc/KTYWh77jTUbA6ypfVcx39Kq+7jsTquy0JRiyM+Ir g4jnutddE2WVJDMK3g5tSdF5dBZSZywmJvYiNp+/7/+WpTWHrAmOfzKotToslgi/vLS272+sZIy 0RVhj26VjAxq3TsrLMsKdTPcwdjDXVj0zMhpTa3UCG/IQp2Pl67veYUroY0W+jwVKpqvlIy8H/z u6pBkYGv05DPmxbJGu65HUftg+OPIr9Wpu0YXImV1zb45ieGzV4D+5YmeSIGxREerP+37e2I6hS IgNUDE0OMc8FgFrVusNUJ/BCPmgzyMxeMEX6wFEUknJ/kEl7dB/CxWTRRu25FeHtsUoHu12qbg9 C1acb3mNGH0Kwx+x2i5kXQC/W9sHXH425135JYKVgm72vjJg== X-EOPAttributedMessage: 0 X-Microsoft-Exchange-Diagnostics: 1;BL2FFO11FD055;1:mAGnzeN3+NYzOg0UwjAvcExzF4OurlSOGBef6euYwqXor4z397sTgh1kNPyh6MX8fF2+M3Y6byrj3zRc/CjHQe7hbzy42ng/2savvmbNnO4ZwUJYPZAjZDBn6wCfBax1UHJ2pXs0ul2rW6sk/AwNyw95xNIE3rnUtipIhwJjfKAUiOoLeI1BWjKObHPzgc1ACc2U5r9oyhNMuWfjfVAxwyIca1yj+dbcADbV0tPe3S69214R/VHKEhTPdsnxcofxbgZOaTebL2a7f/P+iWFtXBEm5gxbA4McFIVNCUmh4DCCZsn/yaCuWytRmIlEfg2S6U1YFFzz8eUIbP7fxEALxGr6TpinZNDEwnPWgjWeknH7qg5W7/nPJVg7GaxPSYTek12s4CI2paQBavMD4v+ax80i0NYni2WZfZqSbNWb2+U= X-Forefront-Antispam-Report: CIP:149.199.60.96;CTRY:US;IPV:NLI;EFV:NLI;SFV:NSPM;SFS:(10009020)(6009001)(2980300002)(339900001)(189002)(199003)(51234002)(105606002)(50986999)(5003600100002)(87936001)(86362001)(575784001)(77156002)(48376002)(6806004)(92566002)(106466001)(19580395003)(62966003)(85426001)(76176999)(77096005)(5001920100001)(64026002)(2950100001)(50226001)(33646002)(46102003)(189998001)(5001960100002)(50466002)(110136002)(229853001)(107886002)(19580405001)(71366001)(36756003)(47776003)(5003940100001)(107986001)(2004002)(4001430100001)(217873001);DIR:OUT;SFP:1101;SCL:1;SRVR:BL2FFO11HUB010;H:xsj-tvapsmtpgw01;FPR:;SPF:Fail;MLV:sfv;MX:1;A:1;LANG:en; MIME-Version: 1.0 Content-Type: text/plain X-Microsoft-Exchange-Diagnostics: 1;BL2FFO11HUB010;2:6WQ3nPPoJ1/bSegQZk9eyNMDwWmT0lgY7uu32fSjFdA7RGXAbUy1GdLK32KV2/J7;3:UBakbA9B3nNAQ0fiqQL1vq9miXejkrshwZMJd/37V05uEpJc/aSgEvbEexdfUlepK/kwOE0ib8+8HyVw8v8Y7z+HSGZbnFfhK53MOAhO2cdmsxI/VTrkHGEThgML6xPUjOPSi0LtFUP42Xc3Gm928Rv52YqSBeaPhB1iBRJDBurfjKyA+wFWk0qBexAII7c8bBxkBvpdLbfs3CVIjiAF9ncmSr+Oief1J9WsoT2aX80=;25:0830Ekq4Y0dqiGdAsdybeONTl5sa433SdneO39HxJhE6GBtk/e9CMUmTPoNJtPdFzHkIqEJ760UNLqX+H9N1G38lPangYIrvd5FVVJ3RPp8GCVEg4vA29IFORGHqsfbod3u+Pgt6WB7YosFv9jwzrK30GSIlMk9YjKEvx7QsUo+iTn2k+thSaUaidDG9WSeYHSMOP/HHew4UrlzO9GUW9yx9+M8bRdZ+BcUcSfIuoJW3C+UOJqJPVjhehoFoDy8ITPKLYCNIJjPvgIzqvkmIAA== X-Microsoft-Antispam: UriScan:;BCL:0;PCL:0;RULEID:;SRVR:BL2FFO11HUB010; X-Microsoft-Exchange-Diagnostics: 1;BL2FFO11HUB010;20:wICxAQHgpLKF0v9oHYp/R6/27gXPFYJ2DrNIpEGr3MAk4qdO+RYONHq3+HbT8ROZWbOB8juBId+Xrcz8YIzKh5mBE2BRyE3ki2XC/umy+ffy9coG7PNDY1P/mTynK0ppRdPfDxw5kpxODy3Ngn/HdX1Rp7LVtQWyw9B+I5mDyjTBSFltwEMfqTllQPIORl1fh+8QkRi2MUiY+JCzJWSoYBXKkQXihJFJeuy7cAJ+slNpzA+OA2adp3Vbm7QBxKx2tDB51p4HQ9HL/u5kUtAmL8U/mcgOO8H0r1cIsIcUuU0cISZ+8WJr3K5DiNxAZ+j9y/gKtzRLh7UXUbcqnk8miBeFa/hltR6T3AXGS3r4Y2Y9a/a29HqLQTXYylnTn2BmEqX3hcnFxbW0AGJG35dFL0lKM09nmeA43rMEBgTaQg/cUQEt0B50d9FvOunr/LuH1sosODe+KwjS6rCs23IPwV3YQITm8l62tFhypY+EcvQ7MbGbAF4QyltCblIPXkmF;4:H7WJXjbpADBPqPd4ecUH8t4DG7K+Grs1QeuxWd5XSIKWpmBdfODOyYBiePW4KQ0iRw1Ft0VBQ7QVDkvcbJYh9xE9j65s2IWdOww7HB0C+EhonazMUQT3wtqoZ42YYY2tk41AwpLHqfCv8WPQgP3KqNPUtJt1IUvHMOogIJ7AuQUF0LqdYP3Ny0Rntklly5l5KXDYjXmC4DoubwZ1+eecN7NU/XNZJ0l1BkKvawQdQZLq4+sZfvP0ft4NMf1I1IXmDZ9COCCzCzdF6LwqvjfQhCGPeQw6YX4ABb9fxb6UaSI= BL2FFO11HUB010: X-MS-Exchange-Organization-RulesExecuted X-Microsoft-Antispam-PRVS: X-Exchange-Antispam-Report-Test: UriScan:; X-Exchange-Antispam-Report-CFA-Test: BCL:0;PCL:0;RULEID:(601004)(5005006)(3002001);SRVR:BL2FFO11HUB010;BCL:0;PCL:0;RULEID:;SRVR:BL2FFO11HUB010; X-Forefront-PRVS: 0638FD5066 X-Microsoft-Exchange-Diagnostics: =?us-ascii?Q?1;BL2FFO11HUB010;23:yR3wh6udITW6h1ebCjv1v2uy2h+EctxzCH5c0ikY?= =?us-ascii?Q?C3YWpuYkfMvs3Vgfwbdr0nW0tCVbOPkcB6FdahOntjmgHcVNSFLUUopDKRXG?= =?us-ascii?Q?8UDlAaAf2ETrlU7+46Cd2fw5jSNvCqHFox3whAwEypYiq23RUrjKFBdM4YrG?= =?us-ascii?Q?4OvlDTduq8zgmXFbdnkyj3XcwM5jOn2Xdzh9btV+uCUO7AV3shEwsWjGHNS+?= =?us-ascii?Q?plm0lfPcz2Sgz4YbY4a5BXMgJedUMcN4NOZsVWSeqscjY5loRyw9A2kAGzBe?= =?us-ascii?Q?Fy+ttPpz1OhQvA/xWLpKyCoJrOfDA3MPdqcoCBVtbK9cdrwgVp+VbDpNZrh1?= =?us-ascii?Q?qtKox5i9ZciabMZa0SzXxpPHW5FOMTsvaV0bOxYvUmeYfEIQh+O07JAEUwfz?= =?us-ascii?Q?2VMfhMdejwNV02oHNzzEKFf5LiEbugnQrPq37rtZAbmsZXyMItn4Lzu5XJOr?= =?us-ascii?Q?nsho5A2+5pknZJEKkPrnXKA1wOq0PqUA9quGAb9gyLH00b6qfghLYMjTyDUG?= =?us-ascii?Q?TwyqBzI5Xbj9YhPEao790B6O7w+FV0mux8e1z1/gKww0QQ0ft/bO8rQw7eDk?= =?us-ascii?Q?qKHVsxYqfkZXWm7fGRL9RmS1VOJItQLSuGKc++7pHojlkC3V6G7yugCYGl1c?= =?us-ascii?Q?z9mxLgGSeNLfEo6U+doDUSnnG6aJg8rjoOPfqCJYOaRSNWnTqj9OGgkUo1Cd?= =?us-ascii?Q?lga5bwD7nHBYq8BRiekScEqaAPZJXHnjg+5DxUMy75EKhYAG1k/q+gS4cPqp?= =?us-ascii?Q?5pd2osjkA64aIAQe8tb6rT2sZlWY2NJgkYVJb08uO+xYdrmd3HO8bsej7I2b?= =?us-ascii?Q?HJPLmvYgAITJePa6qT+FYfN7RUNYA1XEKC6k+iFZTy2d4xT79zH0piUYo3/Z?= =?us-ascii?Q?8MZDtzbJdSW9+D7pGa+KWaWUwgGBDCnGBN4+xseMkWGr9XBtawMydFxSAUP/?= =?us-ascii?Q?SB1O1QA3Y10jwaGMjlyomwXqxgiCy0gXqJ18SeFkIiC2Wskvwt/w72otGacr?= =?us-ascii?Q?el5sCyHtYwBq3HD77b4z+V9ILVFB28KgzQNMD7qJ/jDxkkdyU0xdUbnOh3wc?= =?us-ascii?Q?bGRNGbitPBxX1x6zELSjj6K/kyca+JCRT7UpRyEi0MOCcz0sTNWCK6FABfDg?= =?us-ascii?Q?GJoCuzWcVSb5f48hYdtYsSEvCADY+9lG?= X-Microsoft-Exchange-Diagnostics: 1;BL2FFO11HUB010;5:eCdtrr944OniVRJXOnyZFKXdDn7cH2e2nDg7YsuQ7YJR2l6/3W1DBUq/Gnvf4L/mdRvufjNPiD2R3YXrZM2IAmkmCcY+1m00pPDygx0KCHj9gU2a5D7gpxpeLWSMgXHsLLqDv4K3hmPx0YyRhaGh1g==;24:QXVco7UhUXJqq4lgagczUxge5KguP7ZEVdpCFGPSvQQekZK65ZpEUtwDCReNqv2WC8KebHsQpTkJc7JJjgKuCm/bFkt+GnzmUQtImZu7P8c= X-OriginatorOrg: xilinx.com X-MS-Exchange-CrossTenant-OriginalArrivalTime: 15 Jul 2015 12:24:16.5012 (UTC) X-MS-Exchange-CrossTenant-Id: 657af505-d5df-48d0-8300-c31994686c5c X-MS-Exchange-CrossTenant-OriginalAttributedTenantConnectingIp: TenantId=657af505-d5df-48d0-8300-c31994686c5c;Ip=[149.199.60.96];Helo=[xsj-tvapsmtpgw01] X-MS-Exchange-CrossTenant-FromEntityHeader: HybridOnPrem X-MS-Exchange-Transport-CrossTenantHeadersStamped: BL2FFO11HUB010 Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 24497 Lines: 867 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 Signed-off-by: Edgar E. Iglesias Signed-off-by: Wendy Liang --- 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 + * Copyright (C) 2015 Xilinx, Inc. + * + * Based on origin OMAP and Zynq Remote Processor driver + * + * Copyright (C) 2012 Michal Simek + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("ZynqMP R5 remote processor control driver"); -- 2.1.1 -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/