From patchwork Thu Mar 10 09:00:56 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jon Hunter X-Patchwork-Id: 8554531 Return-Path: X-Original-To: patchwork-linux-pm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork1.web.kernel.org (Postfix) with ESMTP id BA4E29F7CA for ; Thu, 10 Mar 2016 09:01:11 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id B5BD020303 for ; Thu, 10 Mar 2016 09:01:10 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 8B877202FE for ; Thu, 10 Mar 2016 09:01:08 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S935181AbcCJJBG (ORCPT ); Thu, 10 Mar 2016 04:01:06 -0500 Received: from hqemgate15.nvidia.com ([216.228.121.64]:4810 "EHLO hqemgate15.nvidia.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S932691AbcCJJBE (ORCPT ); Thu, 10 Mar 2016 04:01:04 -0500 Received: from hqnvupgp08.nvidia.com (Not Verified[216.228.121.13]) by hqemgate15.nvidia.com id ; Thu, 10 Mar 2016 01:01:03 -0800 Received: from hqemhub02.nvidia.com ([172.20.150.31]) by hqnvupgp08.nvidia.com (PGP Universal service); Thu, 10 Mar 2016 01:00:27 -0800 X-PGP-Universal: processed; by hqnvupgp08.nvidia.com on Thu, 10 Mar 2016 01:00:27 -0800 Received: from jonathanh-lm.nvidia.com (172.20.144.16) by hqemhub02.nvidia.com (172.20.150.31) with Microsoft SMTP Server (TLS) id 8.3.406.0; Thu, 10 Mar 2016 01:01:02 -0800 From: Jon Hunter To: "Rafael J. Wysocki" , Geert Uytterhoeven CC: linux-pm@vger.kernel.org, linux-kernel@vger.kernel.org, linux-tegra@vger.kernel.org, Jon Hunter Subject: [PATCH V3] PM / clk: Add support for obtaining clocks from device-tree Date: Thu, 10 Mar 2016 09:00:56 +0000 Message-ID: <1457600456-4283-1-git-send-email-jonathanh@nvidia.com> X-Mailer: git-send-email 2.1.4 X-NVConfidentiality: public MIME-Version: 1.0 Sender: linux-pm-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-pm@vger.kernel.org X-Spam-Status: No, score=-6.9 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_HI, RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP The PM clocks framework requires clients to pass either a con-id or a valid clk pointer in order to add a clock to a device. Add a new function of_pm_clk_add_clks() to allows device clocks to be retrieved from device-tree and populated for a given device. Note that of_clk_get_from_provider() is not defined if CONFIG_OF and CONFIG_COMMON_CLK are not selected. Therefore, make of_pm_clk_add_clks() dependent on these options. An optional function pointer may be passed to of_pm_clk_add_clks() that can be used to filter the clocks that are added for a device when calling of_pm_clk_add_clks(). In order to handle errors encountered when adding clocks from device-tree, add a function pm_clk_remove_clk() to remove any clocks (using a pointer to the clk structure) that have been added successfully before the error occurred. Signed-off-by: Jon Hunter Tested-by: Geert Uytterhoeven --- Changes v2-v3: - Constified the of_phandle_args parameter - Added data parameter to filter function Changes v1-v2: - Added support for optional filter function as suggested by Geert U. drivers/base/power/clock_ops.c | 108 +++++++++++++++++++++++++++++++++++++++++ include/linux/pm_clock.h | 19 ++++++++ 2 files changed, 127 insertions(+) diff --git a/drivers/base/power/clock_ops.c b/drivers/base/power/clock_ops.c index 272a52ebafc0..d912944a1a9e 100644 --- a/drivers/base/power/clock_ops.c +++ b/drivers/base/power/clock_ops.c @@ -137,6 +137,81 @@ int pm_clk_add_clk(struct device *dev, struct clk *clk) return __pm_clk_add(dev, NULL, clk); } + +#if defined(CONFIG_OF) && defined(CONFIG_COMMON_CLK) +/** + * of_pm_clk_add_clks - Start using device clock(s) for power management. + * @dev: Device whose clock(s) is going to be used for power management. + * @of_pm_clk_filter: Optional function for filtering clocks + * + * Add a series of clocks described in the 'clocks' device-tree node for + * a device to the list of clocks used for the power management of @dev. + * If 'of_pm_clk_filter' is specified, then this filter function will be + * called for each clock found and the clock will be added to the list + * of clocks if this function returns true. Return success if clocks are + * added successfully and return a negative error code if adding a clock + * fails or there are no clocks that match with the filter function. + */ +int of_pm_clk_add_clks(struct device *dev, of_pm_clk_filter fn, void *data) +{ + struct of_phandle_args clkspec; + struct clk **clks; + unsigned int i, count, added = 0; + int ret; + + if (!dev || !dev->of_node) + return -EINVAL; + + count = of_count_phandle_with_args(dev->of_node, "clocks", + "#clock-cells"); + if (count == 0) + return -ENODEV; + + clks = kcalloc(count, sizeof(*clks), GFP_KERNEL); + if (!clks) + return -ENOMEM; + + for (i = 0; i < count; i++) { + if (of_parse_phandle_with_args(dev->of_node, "clocks", + "#clock-cells", i, &clkspec)) + goto error; + + if (fn && !fn(&clkspec, data)) { + of_node_put(clkspec.np); + continue; + } + + clks[added] = of_clk_get_from_provider(&clkspec); + of_node_put(clkspec.np); + + if (IS_ERR(clks[added])) { + ret = PTR_ERR(clks[added]); + goto error; + } + + ret = pm_clk_add_clk(dev, clks[added]); + if (ret) { + clk_put(clks[added]); + goto error; + } + + added++; + } + + kfree(clks); + + return added ? 0 : -ENODEV; + +error: + while (added--) + pm_clk_remove_clk(dev, clks[added]); + + kfree(clks); + + return ret; +} +#endif /* CONFIG_OF && CONFIG_COMMON_CLK */ + /** * __pm_clk_remove - Destroy PM clock entry. * @ce: PM clock entry to destroy. @@ -198,6 +273,39 @@ void pm_clk_remove(struct device *dev, const char *con_id) } /** + * pm_clk_remove_clk - Stop using a device clock for power management. + * @dev: Device whose clock should not be used for PM any more. + * @clk: Clock pointer + * + * Remove the clock pointed to by @clk from the list of clocks used for + * the power management of @dev. + */ +void pm_clk_remove_clk(struct device *dev, struct clk *clk) +{ + struct pm_subsys_data *psd = dev_to_psd(dev); + struct pm_clock_entry *ce; + + if (!psd || !clk) + return; + + spin_lock_irq(&psd->lock); + + list_for_each_entry(ce, &psd->clock_list, node) { + if (clk == ce->clk) + goto remove; + } + + spin_unlock_irq(&psd->lock); + return; + + remove: + list_del(&ce->node); + spin_unlock_irq(&psd->lock); + + __pm_clk_remove(ce); +} + +/** * pm_clk_init - Initialize a device's list of power management clocks. * @dev: Device to initialize the list of PM clocks for. * diff --git a/include/linux/pm_clock.h b/include/linux/pm_clock.h index 25266c600021..2de3a71bd624 100644 --- a/include/linux/pm_clock.h +++ b/include/linux/pm_clock.h @@ -11,6 +11,7 @@ #include #include +#include struct pm_clk_notifier_block { struct notifier_block nb; @@ -43,6 +44,7 @@ extern void pm_clk_destroy(struct device *dev); extern int pm_clk_add(struct device *dev, const char *con_id); extern int pm_clk_add_clk(struct device *dev, struct clk *clk); extern void pm_clk_remove(struct device *dev, const char *con_id); +extern void pm_clk_remove_clk(struct device *dev, struct clk *clk); extern int pm_clk_suspend(struct device *dev); extern int pm_clk_resume(struct device *dev); #else @@ -74,6 +76,9 @@ static inline void pm_clk_remove(struct device *dev, const char *con_id) } #define pm_clk_suspend NULL #define pm_clk_resume NULL +static inline void pm_clk_remove_clk(struct device *dev, struct clk *clk) +{ +} #endif #ifdef CONFIG_HAVE_CLK @@ -86,4 +91,18 @@ static inline void pm_clk_add_notifier(struct bus_type *bus, } #endif +typedef bool (*of_pm_clk_filter)(const struct of_phandle_args *args, + void *data); + +#if defined(CONFIG_PM_CLK) && defined(CONFIG_OF) && defined(CONFIG_COMMON_CLK) +extern int of_pm_clk_add_clks(struct device *dev, of_pm_clk_filter fn, + void *data); +#else +static inline int of_pm_clk_add_clks(struct device *dev, of_pm_clk_filter fn, + void *data) +{ + return -EINVAL; +} +#endif + #endif