Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S932365AbeAHWNM (ORCPT + 1 other); Mon, 8 Jan 2018 17:13:12 -0500 Received: from mail-bn3nam01on0083.outbound.protection.outlook.com ([104.47.33.83]:52294 "EHLO NAM01-BN3-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1757759AbeAHWNH (ORCPT ); Mon, 8 Jan 2018 17:13:07 -0500 From: Jolly Shah To: , , , , , , , CC: , , Jolly Shah , Rajan Vaja Subject: [RFC PATCH] drivers: soc: xilinx: Add ZynqMP power domain driver Date: Mon, 8 Jan 2018 14:12:52 -0800 Message-ID: <1515449572-5398-1-git-send-email-jollys@xilinx.com> X-Mailer: git-send-email 2.7.4 X-TM-AS-Product-Ver: IMSS-7.1.0.1224-8.2.0.1013-23582.004 X-TM-AS-User-Approved-Sender: Yes;Yes X-EOPAttributedMessage: 0 X-MS-Office365-Filtering-HT: Tenant X-Forefront-Antispam-Report: CIP:149.199.60.83;IPV:NLI;CTRY:US;EFV:NLI;SFV:NSPM;SFS:(10009020)(39860400002)(396003)(39380400002)(346002)(376002)(2980300002)(438002)(189003)(199004)(50944005)(478600001)(36756003)(72206003)(36386004)(356003)(51416003)(106002)(6346003)(59450400001)(2201001)(16586007)(110136005)(7696005)(316002)(63266004)(54906003)(8936002)(107886003)(5890100001)(2906002)(4326008)(8676002)(81166006)(47776003)(305945005)(81156014)(48376002)(9786002)(5660300001)(50466002)(39060400002)(106466001)(77096006)(6666003)(6636002)(50226002)(107986001)(2101003);DIR:OUT;SFP:1101;SCL:1;SRVR:MWHPR02MB3392;H:xsj-pvapsmtpgw01;FPR:;SPF:Pass;PTR:unknown-60-83.xilinx.com;MX:1;A:1;LANG:en; X-Microsoft-Exchange-Diagnostics: 1;BL2NAM02FT003;1:PB8Pn3xK3UGceGAHskYncKfzKUhx090ytx0vjwTqUgdp50jY7XAUl9EqKFlZeyJW8wLEwlCaGNQfQUnK348/emDhpxbVJOJYTyeRz35NVsokijvYErGjAROaxNTAesBW MIME-Version: 1.0 Content-Type: text/plain X-MS-PublicTrafficType: Email X-MS-Office365-Filtering-Correlation-Id: 6fede8b4-6e35-483d-6516-08d556e4fd4d X-Microsoft-Antispam: UriScan:;BCL:0;PCL:0;RULEID:(4534020)(4602075)(4627115)(201703031133081)(201702281549075)(5600026)(4604075)(4608076)(2017052603307)(7153060);SRVR:MWHPR02MB3392; X-Microsoft-Exchange-Diagnostics: 1;MWHPR02MB3392;3:efzWen1aj9lfALXpE2k+/Dq7b/Q57K8nKDsxI9bTEg4XRj2yTZtWsoQ33lF++ZJD2fEQBInLB66iJKxenzkO2xINvS7NxQ6Mwegoar6dMaxwS26kklCxWcPPJfjGMcQnyik/sf8yWus72PhE1JIhXx7lmFZjMalPLEd7eaEKAcPfnc24s1gXQPNt86wlcksIYkVqF/JsPYaaAIbt3ZlopW2pKz1jgVb7XYp2yyg3bLI3zvc6ZaI08rEF8OcfTw/254oOvEsl5cxnbgAjEIMJ+Wf+84qhxIwIp/rSsH0HtwkxM41KM0bU+t6pQPh73LVvSk2pD2v7UoX4b11vxhgTXAHFf2BhmgbS2cZB+kZu3RE=;25:LkdgVZuzdrnRem1NN8yLV1PMUJtcjKqpFz2js8NTZ5D5gmzkfNRh1Q97MUCwt68WYhQxs/gLVWlYAjNgSNWExDDFS4U+ybucaOoKeNKPDag9ZGr5KLtzGAUnFXfopJh8wPlxTBvk0NuX/Foq7hPDxmlhzpz6qnNF0B6et7BDxxsvJX1dpSX7sAE5tVto9G15CZt0VcwStQp3tJ8HJxPJOk2XeUX3ypacBnt+B+/Ku0k/vVxQYRCPAMSWRSMdWiajiRE+8qLzth0d/fIhmXew4GUWpBaMPBTBp36DL1n1AGc+4hA2wq4O+fbkGvNkX4JSS3NHC9hQClkVGv1ntcxZ6A== X-MS-TrafficTypeDiagnostic: MWHPR02MB3392: X-Microsoft-Exchange-Diagnostics: 1;MWHPR02MB3392;31:EmzoZaq0KXddIuSx23BcKg1Znw7/db5EuNBL6GRO6g8JOQ4BYIMNRV5CRI+cZXG+nKxLLrjUKDJQ40LrSIBju++7yfCog6EWtm3QTyd8l7q4+dEMHLiPaJWGlN/hMi8OBPgsK0OEdOQc3jPlg1X6MKlshvTceaAWANUDNwlTv6VIqhdHq2YvSTzVpe5WgJGInrRk+XMBibEQb8ZXaomIMGZxfysGlWuhozDoTMrRX1U=;20:718dq9+0rgPHDlybU0HkBYH0YZwh+5aX5H55D8RLKhrpWaMlvckAWUNTvsBT+cO6S4A780wrn/YeREBLL7HjUXL2jIC0M+p614DRPUcbEpQIw6vGZQdjuZm9MLP16bxkUu9Bb6CvCYRrOpdlpKXKDLdAZSTX7b9QeW4R9sSl2AlzFS07dmqg+DgUUpNMoIRa+UFzvASvZ/PrwNjHWaaSYXFkdpsfVyKUt5QytQpLvjrR4DMc+bBvLRQwiMgEdRCxwJzae8aEPl88Sqpr2M47ZpCPW3i7a1Mau7KUZWQvFngOg7TWsPjcoSeuDE/RpKdAcWCY5cOye30olfL6YEwryrGJL7VqSVCF+3FRFVQ5S8tAqRXXkL71LFVaDGv4j/YIiggQy+HlXvGB8X2uAismwRtR/hP6wfD2+a82Ghm+oeTX5ok8euswqbjFWSU+1ReyjsDTHnYnS4ZbVFbsRMaoSqr5bsK7y5Bq+RuycCLS1Hbri3NrkOhYu1OtHs7EIKfl X-Microsoft-Antispam-PRVS: X-Exchange-Antispam-Report-Test: UriScan:(192813158149592)(17755550239193); X-Exchange-Antispam-Report-CFA-Test: BCL:0;PCL:0;RULEID:(6040470)(2401047)(8121501046)(5005006)(3231023)(944501075)(3002001)(10201501046)(93006095)(93004095)(6055026)(6041268)(20161123564045)(20161123558120)(20161123560045)(201703131423095)(201702281528075)(20161123555045)(201703061421075)(201703061406153)(20161123562045)(6072148)(201708071742011);SRVR:MWHPR02MB3392;BCL:0;PCL:0;RULEID:(100000803101)(100110400095);SRVR:MWHPR02MB3392; X-Microsoft-Exchange-Diagnostics: 1;MWHPR02MB3392;4:bn8IPVweETEXA8Hm5J/e33EKxPOb7f/xipyvhTXx7NM3jSOpgv7ApslF6bumWhyitb4Xh5LJTTFRTCNxy1L+FwrWizwd2ikj58mMV7OAt8wbtIfpAdQvgfgPCdkLU31cffZUP6s8EiJCnY+t9AkfEmJ2k639yJxlL7Qrs4BJ9MM8lEIAXriWX9R2YABf/ukBR15/ymEH4VkbURHF8n0Y0YIY266vlAbMLPOxgSf2q/qyETcatccyZ5eFrH2hAjdqL+1MmI6GZS/idLqi2tQWKJ5oqhx8m45euz4DvyxZ3l3Cl5gKPZou/qZRAVbk7va6BzLAXJoBEfSo7w044Cyix0jJap6HSOcyQCnGLCa3TPk= X-Forefront-PRVS: 054642504A X-Microsoft-Exchange-Diagnostics: =?us-ascii?Q?1;MWHPR02MB3392;23:TToSOyoW1of/s6ZTqYPjbRrH1Uj0HcBdlUtee/zSy?= =?us-ascii?Q?Y/hLUcgUeutDbKoSD3B0XrTF0qj+FRwbScIJ0/gPLs0pwFnVHguA/UV/DUFo?= =?us-ascii?Q?9u5H4OdhOmTyIwY1k6CbmwemmhxdfHgftqvhjtEJkzsuEFX4F8zuFVOg1isz?= =?us-ascii?Q?18q+iMG1iYpOAnGiep0yx71/Y+dWKPWtRDrLWdkE0/VNMwSbs4DlubpE3YZy?= =?us-ascii?Q?81K0FRYj/8NTco0bJcTYKUDg9SLCXErEkhDycZDb9gR0kYKgeXvIxKIxNt9O?= =?us-ascii?Q?03n/BSXG876Zp5utz6Sgtik2gpgL+aZefs/vkP8xn53J+rNFILr8GoZRcFeU?= =?us-ascii?Q?SfJuAP+EEVmXwrP4MMNWOgViFql/kPC0oEygua5PpH3JC4qH8/hELcV8DelN?= =?us-ascii?Q?lRmVntvG7bR9PdytExDSZskohqwwJNsVEb7uq5tR+9LL7DCzi0iet3239/Ed?= =?us-ascii?Q?OtOAuQZHfZtckW7Netd4rSMDFFK7EKN7JN/y7IXlieYCSsK1Kfb1xEYzcHgH?= =?us-ascii?Q?rCOwvKECyp75+dZmaU2g882lXrjt+kiMd+8Jtuz8VSGF8h8eyoFCIwXY66lF?= =?us-ascii?Q?tT4fr7EyupYVxKJflXbktL7mT2SlE+8j9mrQVv2EV8qbT5F0Xk2dunQH74nC?= =?us-ascii?Q?OLwXVDeGWEP0nHxj0UZKDYHsjTUHJWjnd2k+benteu4Jjd8E4pY+kDV9f74n?= =?us-ascii?Q?OxF9W0q4avyPBQ+IC9r6wFOENmhCfR7W1Muxt29ZCS0L+Q4xrN2YSJs/YYhG?= =?us-ascii?Q?mHNN6YXJmmzYNxjP3Z1ms6zDXHmj2kWqwshVrOJLTK7PpU7+XHVw5+1IY6CQ?= =?us-ascii?Q?yXglpU+MwZl2zvEM4m5KVdxnA+CLtzKXQnwFcdA9Nwr3MfMAg2G2ejTQfoGj?= =?us-ascii?Q?Z6scKuH64YqxFyNswYS1/cYOfZpO5iVr2VWrtfrG/09uZWw5YCtgbS+ubgiK?= =?us-ascii?Q?QJN1AKK1u4nUuhRe0rgtP0Qz6vEz5PBvdq4mAhwKMoWzuKtiG4G+IbzkR5n/?= =?us-ascii?Q?TqsHEniEwbhq9CPllSHTEO1VzzNJgYsY4yW5DSrDj0ixlE3W7LX3835w+Wx6?= =?us-ascii?Q?t5MmmgqVveC6gHO11tsGjeOdBl28D/ywUV5CPMGFty75RzzYj/qIc1jHZhfb?= =?us-ascii?Q?z2ohUghdmE=3D?= X-Microsoft-Exchange-Diagnostics: 1;MWHPR02MB3392;6:OSifdkP+SYB5O+GK+1bIlsuJUVjXFvOErvU6FYpTCz2+VpN7OGPpkRw5dm5QUKoULqmqrr9Lx3VMZXaErZMx+SHw8VYXpRD+zTNz5D9JHBCNG9GVt6FCaRd9KWKOmnLR0MEE+Vml+n2RHX3zTIXaQ3jluOF4dDsQ8fVxRny0AB903CYPaAqJ+8kyYaWzSmyLRKiA2Emfu/YL46YN4jSLnrFHPuHQeh6hTr7SKlgXPfoamHr5tXWoQxJ4dIgt7TNnBp8EnwRsydk4OZdNoLfjFnF+epgwFqtoQ67JL/EtLLidozi0ZkTr2YYkL+MBfMFJZ1OnMz36ZyvFsWvXmCaxE73Qnluz1OU0T22BEworrJU=;5:wfsFpr02QtMN2b7mVvB/8cr7ft/7wnbts7nSN1XYGCtj2JTyaLilOakBoPi4STYmPpTTeaoK647lu5Sz82lpNIW3XD0vT8Ce3RbFJY/DRcqWDD8kaf8EKnbOU1m09gOqcWIuThEapjzRFNA7NkxJPeXStgc/HdKSuPWbsWBJ4UA=;24:cGNv+zJ1PBAU6cxn+rky4WNeYym1YY9jWuXqdaOoN7MZdHSoVFVq+CZK8AOPNaj8/i2nXk57u0sKQEE5SbGt0H2xbykalR/mGWC2uITx5wI=;7:eBDjYU5SvTKXnPBrsCvOhVSgqFPN4rxZyPKkzDGcVBbdKmUW58Q/XKJUbjqhQASy46si+ademi46X5dHOecvaKrWHRxZU0V4EJRhSKnWzUiN/YNUtkcFOwfYG4lcOn+kSeYYrylvvGA4qxJC8s9t58bVO8DQDqev2SLb6cP2vrMEFO4bvrvRM8PCYBJcnnAAm1V1cZlT7H9y4aHU2thAW5NNGgO9F7vtTHO+EgEX8St+orU7qtEBZ0LBXMAQDnNZ SpamDiagnosticOutput: 1:99 SpamDiagnosticMetadata: NSPM X-OriginatorOrg: xilinx.com X-MS-Exchange-CrossTenant-OriginalArrivalTime: 08 Jan 2018 22:13:04.1751 (UTC) X-MS-Exchange-CrossTenant-Network-Message-Id: 6fede8b4-6e35-483d-6516-08d556e4fd4d 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.83];Helo=[xsj-pvapsmtpgw01] X-MS-Exchange-CrossTenant-FromEntityHeader: HybridOnPrem X-MS-Exchange-Transport-CrossTenantHeadersStamped: MWHPR02MB3392 Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Return-Path: The zynqmp-genpd driver communicates the usage requirements for logical power domains / devices to the platform FW. FW is responsible for choosing appropriate power states, taking Linux' usage information into account. Signed-off-by: Jolly Shah Signed-off-by: Rajan Vaja --- .../devicetree/bindings/power/zynqmp-genpd.txt | 46 +++ drivers/soc/xilinx/zynqmp/Kconfig | 10 +- drivers/soc/xilinx/zynqmp/Makefile | 1 + drivers/soc/xilinx/zynqmp/pm_domains.c | 343 +++++++++++++++++++++ 4 files changed, 399 insertions(+), 1 deletion(-) create mode 100644 Documentation/devicetree/bindings/power/zynqmp-genpd.txt create mode 100644 drivers/soc/xilinx/zynqmp/pm_domains.c diff --git a/Documentation/devicetree/bindings/power/zynqmp-genpd.txt b/Documentation/devicetree/bindings/power/zynqmp-genpd.txt new file mode 100644 index 0000000..25f9711 --- /dev/null +++ b/Documentation/devicetree/bindings/power/zynqmp-genpd.txt @@ -0,0 +1,46 @@ +Device Tree bindings for Xilinx Zynq MPSoC PM domains + +The binding for zynqmp-genpd follow the common generic PM domain binding[1]. + +[1] Documentation/devicetree/bindings/power/power_domain.txt + +== Zynq MPSoC Generic PM Domain Node == + +Required properties: + - compatible: Must be: "xlnx,zynqmp-genpd" + +This node contains a number of subnodes, each representing a single PM domain +that PM domain consumer devices reference. + +== PM Domain Nodes == + +Required properties: + - #power-domain-cells: Number of cells in a PM domain specifier. Must be 0. + - pd-id: List of domain identifiers of as defined by platform firmware. These + identifiers are passed to the PM firmware. + +Example: + zynqmp-genpd { + compatible = "xlnx,zynqmp-genpd"; + + pd_usb0: pd-usb0 { + pd-id = <22>; + #power-domain-cells = <0>; + }; + + pd_sata: pd-sata { + pd-id = <25>; + #power-domain-cells = <0>; + }; + + pd_gpu: pd-gpu { + pd-id = <58 20 21>; + #power-domain-cells = <0x0>; + }; + }; + + sata0: ahci@SATA_AHCI_HBA { + ... + power-domains = <&pd_sata>; + ... + }; diff --git a/drivers/soc/xilinx/zynqmp/Kconfig b/drivers/soc/xilinx/zynqmp/Kconfig index d3c784d..545a66c 100644 --- a/drivers/soc/xilinx/zynqmp/Kconfig +++ b/drivers/soc/xilinx/zynqmp/Kconfig @@ -4,7 +4,6 @@ menu "Zynq MPSoC SoC Drivers" depends on ARCH_ZYNQMP - config ZYNQMP_PM bool "Enable Xilinx Zynq MPSoC Power Management" depends on PM @@ -12,4 +11,13 @@ config ZYNQMP_PM Say yes to enable power management support for ZyqnMP SoC. In doubt, say N. +config ZYNQMP_PM_DOMAINS + bool "Enable Zynq MPSoC generic PM domains" + default y + depends on PM + select PM_GENERIC_DOMAINS + help + Say yes to enable device power management through PM domains + In doubt, say N + endmenu diff --git a/drivers/soc/xilinx/zynqmp/Makefile b/drivers/soc/xilinx/zynqmp/Makefile index 98034f7..3e530be 100644 --- a/drivers/soc/xilinx/zynqmp/Makefile +++ b/drivers/soc/xilinx/zynqmp/Makefile @@ -1 +1,2 @@ obj-$(CONFIG_ZYNQMP_PM) += pm.o +obj-$(CONFIG_ZYNQMP_PM_DOMAINS) += pm_domains.o diff --git a/drivers/soc/xilinx/zynqmp/pm_domains.c b/drivers/soc/xilinx/zynqmp/pm_domains.c new file mode 100644 index 0000000..2ab3829 --- /dev/null +++ b/drivers/soc/xilinx/zynqmp/pm_domains.c @@ -0,0 +1,343 @@ +/* + * ZynqMP Generic PM domain support + * + * Copyright (C) 2014-2017 Xilinx, Inc. + * + * Davorin Mista + * Jolly Shah + * Rajan Vaja + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "zynqmp_gpd" + +/* Flag stating if PM nodes mapped to the PM domain has been requested */ +#define ZYNQMP_PM_DOMAIN_REQUESTED BIT(0) + +/** + * struct zynqmp_pm_domain - Wrapper around struct generic_pm_domain + * @gpd: Generic power domain + * @dev_list: List of devices belong to power domain + * @node_ids: PM node IDs corresponding to device(s) inside PM domain + * @node_id_num: Number of PM node IDs + * @flags: ZynqMP PM domain flags + */ +struct zynqmp_pm_domain { + struct generic_pm_domain gpd; + struct list_head dev_list; + u32 *node_ids; + int node_id_num; + u8 flags; +}; + +/* + * struct zynqmp_domain_device - Device node present in power domain + * @dev: Device + * &list: List member for the devices in domain list + */ +struct zynqmp_domain_device { + struct device *dev; + struct list_head list; +}; + +/** + * zynqmp_gpd_is_active_wakeup_path - Check if device is in wakeup source path + * @dev: Device to check for wakeup source path + * @not_used: Data member (not required) + * + * This function is checks device's child hierarchy and checks if any device is + * set as wakeup source. + * + * Return: 1 if device is in wakeup source path else 0. + */ +static int zynqmp_gpd_is_active_wakeup_path(struct device *dev, void *not_used) +{ + int may_wakeup; + + may_wakeup = device_may_wakeup(dev); + if (may_wakeup) + return may_wakeup; + + return device_for_each_child(dev, NULL, + zynqmp_gpd_is_active_wakeup_path); +} + +/** + * zynqmp_gpd_power_on - Power on PM domain + * @domain: Generic PM domain + * + * This function is called before devices inside a PM domain are resumed, to + * power on PM domain. + * + * Return: 0 on success, error code otherwise. + */ +static int zynqmp_gpd_power_on(struct generic_pm_domain *domain) +{ + int i, status = 0; + struct zynqmp_pm_domain *pd; + const struct zynqmp_eemi_ops *eemi_ops = get_eemi_ops(); + + if (!eemi_ops || !eemi_ops->set_requirement) + return status; + + pd = container_of(domain, struct zynqmp_pm_domain, gpd); + for (i = 0; i < pd->node_id_num; i++) { + status = + eemi_ops->set_requirement(pd->node_ids[i], + ZYNQMP_PM_CAPABILITY_ACCESS, + ZYNQMP_PM_MAX_QOS, + ZYNQMP_PM_REQUEST_ACK_BLOCKING); + if (status) + break; + } + return status; +} + +/** + * zynqmp_gpd_power_off - Power off PM domain + * @domain: Generic PM domain + * + * This function is called after devices inside a PM domain are suspended, to + * power off PM domain. + * + * Return: 0 on success, error code otherwise. + */ +static int zynqmp_gpd_power_off(struct generic_pm_domain *domain) +{ + int i, status = 0; + struct zynqmp_pm_domain *pd; + struct zynqmp_domain_device *zdev, *tmp; + u32 capabilities = 0; + bool may_wakeup = 0; + const struct zynqmp_eemi_ops *eemi_ops = get_eemi_ops(); + + if (!eemi_ops || !eemi_ops->set_requirement) + return status; + + pd = container_of(domain, struct zynqmp_pm_domain, gpd); + + /* If domain is already released there is nothing to be done */ + if (!(pd->flags & ZYNQMP_PM_DOMAIN_REQUESTED)) + return 0; + + list_for_each_entry_safe(zdev, tmp, &pd->dev_list, list) { + /* If device is in wakeup path, set capability to WAKEUP */ + may_wakeup = zynqmp_gpd_is_active_wakeup_path(zdev->dev, NULL); + if (may_wakeup) { + dev_dbg(zdev->dev, "device is in wakeup path in %s\n", + domain->name); + capabilities = ZYNQMP_PM_CAPABILITY_WAKEUP; + break; + } + } + + for (i = pd->node_id_num - 1; i >= 0; i--) { + status = eemi_ops->set_requirement(pd->node_ids[i], + capabilities, 0, + ZYNQMP_PM_REQUEST_ACK_NO); + /** + * If powering down of any node inside this domain fails, + * report and return the error + */ + if (status) { + pr_err("%s error %d, node %u\n", __func__, status, + pd->node_ids[i]); + return status; + } + } + + return status; +} + +/** + * zynqmp_gpd_attach_dev - Attach device to the PM domain + * @domain: Generic PM domain + * @dev: Device to attach + * + * Return: 0 on success, error code otherwise. + */ +static int zynqmp_gpd_attach_dev(struct generic_pm_domain *domain, + struct device *dev) +{ + int i, status; + struct zynqmp_pm_domain *pd; + struct zynqmp_domain_device *zdev; + const struct zynqmp_eemi_ops *eemi_ops = get_eemi_ops(); + + if (!eemi_ops || !eemi_ops->request_node) + return -ENXIO; + + pd = container_of(domain, struct zynqmp_pm_domain, gpd); + + zdev = devm_kzalloc(dev, sizeof(*zdev), GFP_KERNEL); + if (!zdev) + return -ENOMEM; + + zdev->dev = dev; + list_add(&zdev->list, &pd->dev_list); + + /* If this is not the first device to attach there is nothing to do */ + if (domain->device_count) + return 0; + + for (i = 0; i < pd->node_id_num; i++) { + status = eemi_ops->request_node(pd->node_ids[i], 0, 0, + ZYNQMP_PM_REQUEST_ACK_BLOCKING); + /* If requesting a node fails print and return the error */ + if (status) { + pr_err("%s error %d, node %u\n", __func__, status, + pd->node_ids[i]); + list_del(&zdev->list); + zdev->dev = NULL; + devm_kfree(dev, zdev); + return status; + } + } + + pd->flags |= ZYNQMP_PM_DOMAIN_REQUESTED; + + return 0; +} + +/** + * zynqmp_gpd_detach_dev - Detach device from the PM domain + * @domain: Generic PM domain + * @dev: Device to detach + */ +static void zynqmp_gpd_detach_dev(struct generic_pm_domain *domain, + struct device *dev) +{ + int i, status; + struct zynqmp_pm_domain *pd; + struct zynqmp_domain_device *zdev, *tmp; + const struct zynqmp_eemi_ops *eemi_ops = get_eemi_ops(); + + if (!eemi_ops || !eemi_ops->release_node) + return; + + pd = container_of(domain, struct zynqmp_pm_domain, gpd); + + list_for_each_entry_safe(zdev, tmp, &pd->dev_list, list) + if (zdev->dev == dev) { + list_del(&zdev->list); + zdev->dev = NULL; + devm_kfree(dev, zdev); + } + + /* If this is not the last device to detach there is nothing to do */ + if (domain->device_count) + return; + + for (i = 0; i < pd->node_id_num; i++) { + status = eemi_ops->release_node(pd->node_ids[i]); + /* If releasing a node fails print the error and return */ + if (status) { + pr_err("%s error %d, node %u\n", __func__, status, + pd->node_ids[i]); + return; + } + } + + pd->flags &= ~ZYNQMP_PM_DOMAIN_REQUESTED; +} + +/** + * zynqmp_gpd_probe - Initialize ZynqMP specific PM domains + * @pdev: Platform device pointer + * + * Description: This function populates struct zynqmp_pm_domain for each PM + * domain and initalizes generic PM domain. If the "pd-id" DT property + * of a certain domain is missing or invalid, that domain will be skipped. + * + * Return: 0 on success, error code otherwise. + */ +static int __init zynqmp_gpd_probe(struct platform_device *pdev) +{ + int ret; + struct device_node *child_err, *child, *np = pdev->dev.of_node; + + for_each_child_of_node(np, child) { + struct zynqmp_pm_domain *pd; + + pd = devm_kzalloc(&pdev->dev, sizeof(*pd), GFP_KERNEL); + if (!pd) { + ret = -ENOMEM; + goto err_cleanup; + } + + ret = of_property_count_u32_elems(child, "pd-id"); + if (ret <= 0) + goto err_cleanup; + + pd->node_id_num = ret; + pd->node_ids = devm_kcalloc(&pdev->dev, ret, + sizeof(*pd->node_ids), GFP_KERNEL); + if (!pd->node_ids) { + ret = -ENOMEM; + goto err_cleanup; + } + + ret = of_property_read_u32_array(child, "pd-id", pd->node_ids, + pd->node_id_num); + if (ret) + goto err_cleanup; + + pd->gpd.name = kstrdup(child->name, GFP_KERNEL); + pd->gpd.power_off = zynqmp_gpd_power_off; + pd->gpd.power_on = zynqmp_gpd_power_on; + pd->gpd.attach_dev = zynqmp_gpd_attach_dev; + pd->gpd.detach_dev = zynqmp_gpd_detach_dev; + + /* Mark all PM domains as initially powered off */ + pm_genpd_init(&pd->gpd, NULL, true); + + ret = of_genpd_add_provider_simple(child, &pd->gpd); + if (ret) + goto err_cleanup; + + INIT_LIST_HEAD(&pd->dev_list); + } + + return 0; + +err_cleanup: + child_err = child; + for_each_child_of_node(np, child) { + if (child == child_err) + break; + of_genpd_del_provider(child); + } + + return ret; +} + +static const struct of_device_id zynqmp_gpd_of_match[] = { + { .compatible = "xlnx,zynqmp-genpd" }, + {}, +}; + +MODULE_DEVICE_TABLE(of, zynqmp_gpd_of_match); + +static struct platform_driver zynqmp_gpd_platform_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = zynqmp_gpd_of_match, + }, +}; + +static __init int zynqmp_gpd_init(void) +{ + return platform_driver_probe(&zynqmp_gpd_platform_driver, + zynqmp_gpd_probe); +} +subsys_initcall(zynqmp_gpd_init); -- 2.7.4