From patchwork Fri Jul 4 09:45:00 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Thomas Petazzoni X-Patchwork-Id: 4479161 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.19.201]) by patchwork1.web.kernel.org (Postfix) with ESMTP id C41E79F26C for ; Fri, 4 Jul 2014 09:45:29 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id B0C572040F for ; Fri, 4 Jul 2014 09:45:28 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 6EAAE203E6 for ; Fri, 4 Jul 2014 09:45:27 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1755354AbaGDJp0 (ORCPT ); Fri, 4 Jul 2014 05:45:26 -0400 Received: from top.free-electrons.com ([176.31.233.9]:56890 "EHLO mail.free-electrons.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1755353AbaGDJpZ (ORCPT ); Fri, 4 Jul 2014 05:45:25 -0400 Received: by mail.free-electrons.com (Postfix, from userid 106) id 3612780F; Fri, 4 Jul 2014 11:45:35 +0200 (CEST) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Spam-Level: X-Spam-Status: No, score=-6.9 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_HI, T_RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=ham version=3.3.1 Received: from localhost (col31-4-88-188-83-94.fbx.proxad.net [88.188.83.94]) by mail.free-electrons.com (Postfix) with ESMTPSA id 7E679797; Fri, 4 Jul 2014 11:45:34 +0200 (CEST) From: Thomas Petazzoni To: Mike Turquette , Viresh Kumar , "Rafael J. Wysocki" , Jason Cooper , Andrew Lunn , Sebastian Hesselbarth , Gregory Clement Cc: linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org, Tawfik Bayouk , Nadav Haklai , Lior Amsalem , Ezequiel Garcia , Thomas Petazzoni Subject: [PATCH 5/8] ARM: mvebu: extend PMSU code to support dynamic frequency scaling Date: Fri, 4 Jul 2014 11:45:00 +0200 Message-Id: <1404467103-29644-6-git-send-email-thomas.petazzoni@free-electrons.com> X-Mailer: git-send-email 2.0.0 In-Reply-To: <1404467103-29644-1-git-send-email-thomas.petazzoni@free-electrons.com> References: <1404467103-29644-1-git-send-email-thomas.petazzoni@free-electrons.com> Sender: linux-pm-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-pm@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP This commit adds the necessary code in the Marvell EBU PMSU driver to support dynamic frequency scaling. In essence, what this new code does is that it registers a clock notifier of the CPU clocks. The notifier function listens to the newly introduced APPLY_RATE_CHANGE event, and uses that to finalize the frequency transition by doing the part of the procedure that involves the PMSU. Signed-off-by: Thomas Petazzoni --- arch/arm/mach-mvebu/pmsu.c | 168 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) diff --git a/arch/arm/mach-mvebu/pmsu.c b/arch/arm/mach-mvebu/pmsu.c index 53a55c8..a114e4a 100644 --- a/arch/arm/mach-mvebu/pmsu.c +++ b/arch/arm/mach-mvebu/pmsu.c @@ -18,20 +18,25 @@ #define pr_fmt(fmt) "mvebu-pmsu: " fmt +#include #include +#include #include #include #include +#include #include #include #include #include +#include #include #include #include #include #include #include "common.h" +#include "armada-370-xp.h" static void __iomem *pmsu_mp_base; @@ -57,6 +62,10 @@ static void __iomem *pmsu_mp_base; #define PMSU_STATUS_AND_MASK_IRQ_MASK BIT(24) #define PMSU_STATUS_AND_MASK_FIQ_MASK BIT(25) +#define PMSU_EVENT_STATUS_AND_MASK(cpu) ((cpu * 0x100) + 0x120) +#define PMSU_EVENT_STATUS_AND_MASK_DFS_DONE BIT(1) +#define PMSU_EVENT_STATUS_AND_MASK_DFS_DONE_MASK BIT(17) + #define PMSU_BOOT_ADDR_REDIRECT_OFFSET(cpu) ((cpu * 0x100) + 0x124) /* PMSU fabric registers */ @@ -296,3 +305,162 @@ int __init armada_370_xp_cpu_pm_init(void) arch_initcall(armada_370_xp_cpu_pm_init); early_initcall(armada_370_xp_pmsu_init); + +static void armada_xp_cpufreq_clk_set_local(void *data) +{ + u32 reg; + u32 cpu = smp_processor_id(); + unsigned long flags; + + local_irq_save(flags); + + /* Prepare to enter idle */ + reg = readl(pmsu_mp_base + PMSU_STATUS_AND_MASK(cpu)); + reg |= PMSU_STATUS_AND_MASK_CPU_IDLE_WAIT | + PMSU_STATUS_AND_MASK_IRQ_MASK | + PMSU_STATUS_AND_MASK_FIQ_MASK; + writel(reg, pmsu_mp_base + PMSU_STATUS_AND_MASK(cpu)); + + /* Request the DFS transition */ + reg = readl(pmsu_mp_base + PMSU_CONTROL_AND_CONFIG(cpu)); + reg |= PMSU_CONTROL_AND_CONFIG_DFS_REQ; + writel(reg, pmsu_mp_base + PMSU_CONTROL_AND_CONFIG(cpu)); + + /* The fact of entering idle will trigger the DFS transition */ + wfi(); + + /* + * We're back from idle, the DFS transition has completed, + * clear the idle wait indication. + */ + reg = readl(pmsu_mp_base + PMSU_STATUS_AND_MASK(cpu)); + reg &= ~PMSU_STATUS_AND_MASK_CPU_IDLE_WAIT; + writel(reg, pmsu_mp_base + PMSU_STATUS_AND_MASK(cpu)); + + local_irq_restore(flags); +} + +struct armada_xp_cpufreq_notifier_block { + struct notifier_block nb; + int cpu; +}; + +static int armada_xp_cpufreq_clk_notify(struct notifier_block *self, + unsigned long action, void *data) +{ + struct armada_xp_cpufreq_notifier_block *nb = + container_of(self, struct armada_xp_cpufreq_notifier_block, nb); + unsigned long timeout; + int cpu = nb->cpu; + u32 reg; + + if (action != APPLY_RATE_CHANGE) + return 0; + + /* Clear any previous DFS DONE event */ + reg = readl(pmsu_mp_base + PMSU_EVENT_STATUS_AND_MASK(cpu)); + reg &= ~PMSU_EVENT_STATUS_AND_MASK_DFS_DONE; + writel(reg, pmsu_mp_base + PMSU_EVENT_STATUS_AND_MASK(cpu)); + + /* Mask the DFS done interrupt, since we are going to poll */ + reg = readl(pmsu_mp_base + PMSU_EVENT_STATUS_AND_MASK(cpu)); + reg |= PMSU_EVENT_STATUS_AND_MASK_DFS_DONE_MASK; + writel(reg, pmsu_mp_base + PMSU_EVENT_STATUS_AND_MASK(cpu)); + + /* Trigger the DFS on the appropriate CPU */ + smp_call_function_single(get_logical_index(cpu), + armada_xp_cpufreq_clk_set_local, NULL, false); + + /* Poll until the DFS done event is generated */ + timeout = jiffies + HZ; + while (time_before(jiffies, timeout)) { + reg = readl(pmsu_mp_base + PMSU_EVENT_STATUS_AND_MASK(cpu)); + if (reg & PMSU_EVENT_STATUS_AND_MASK_DFS_DONE) + break; + udelay(10); + } + + /* Restore the DFS mask to its original state */ + reg = readl(pmsu_mp_base + PMSU_EVENT_STATUS_AND_MASK(cpu)); + reg &= ~BIT(17); + writel(reg, pmsu_mp_base + PMSU_EVENT_STATUS_AND_MASK(cpu)); + + return NOTIFY_DONE; +} + +static int __init armada_xp_pmsu_cpufreq_init(void) +{ + struct device_node *np; + struct resource res; + int i, ret; + + if (!of_machine_is_compatible("marvell,armadaxp")) + return 0; + + /* + * In order to have proper cpufreq handling, we need to ensure + * that the Device Tree description of the CPU clock includes + * the definition of the PMU DFS registers. If not, we do not + * register the clock notifier and the cpufreq driver. This + * piece of code is only for compatibility with old Device + * Trees. + */ + np = of_find_compatible_node(NULL, NULL, "marvell,armada-xp-cpu-clock"); + if (!np) + return 0; + + ret = of_address_to_resource(np, 1, &res); + if (ret) { + pr_warn(FW_WARN "not enabling cpufreq, deprecated armada-xp-cpu-clock binding\n"); + of_node_put(np); + return 0; + } + + of_node_put(np); + + for (i = 0; i < ARMADA_XP_MAX_CPUS; i++) { + struct clk *clk; + struct armada_xp_cpufreq_notifier_block *nbs; + struct device_node *np; + int ret; + + np = of_get_cpu_node(i, NULL); + if (!np) + return -ENODEV; + + clk = of_clk_get(np, 0); + if (!clk) { + pr_err("Cannot get clock for CPU %d\n", i); + of_node_put(np); + return -ENODEV; + } + + nbs = kzalloc(sizeof(struct armada_xp_cpufreq_notifier_block), + GFP_KERNEL); + if (!nbs) { + pr_err("Cannot allocate memory for cpufreq notifier\n"); + clk_put(clk); + of_node_put(np); + return -ENOMEM; + } + + nbs->nb.notifier_call = armada_xp_cpufreq_clk_notify; + nbs->cpu = i; + + ret = clk_notifier_register(clk, &nbs->nb); + if (ret) { + pr_err("Cannot register clock notifier\n"); + kfree(nbs); + clk_put(clk); + of_node_put(np); + return ret; + } + + of_node_put(np); + } + + platform_device_register_simple("armadaxp-cpufreq", -1, NULL, 0); + return 0; +} + +arch_initcall(armada_xp_pmsu_cpufreq_init);