From patchwork Thu Jul 19 15:54:41 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Shawn Guo X-Patchwork-Id: 1217921 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-process-083081@patchwork1.kernel.org Received: from merlin.infradead.org (merlin.infradead.org [205.233.59.134]) by patchwork1.kernel.org (Postfix) with ESMTP id 93B783FC5A for ; Thu, 19 Jul 2012 16:04:59 +0000 (UTC) Received: from localhost ([::1] helo=merlin.infradead.org) by merlin.infradead.org with esmtp (Exim 4.76 #1 (Red Hat Linux)) id 1Srt5d-0001Ek-Iz; Thu, 19 Jul 2012 15:56:33 +0000 Received: from va3ehsobe005.messaging.microsoft.com ([216.32.180.31] helo=va3outboundpool.messaging.microsoft.com) by merlin.infradead.org with esmtps (Exim 4.76 #1 (Red Hat Linux)) id 1Srt4Z-0001CK-En for linux-arm-kernel@lists.infradead.org; Thu, 19 Jul 2012 15:55:31 +0000 Received: from mail72-va3-R.bigfish.com (10.7.14.253) by VA3EHSOBE013.bigfish.com (10.7.40.63) with Microsoft SMTP Server id 14.1.225.23; Thu, 19 Jul 2012 15:55:25 +0000 Received: from mail72-va3 (localhost [127.0.0.1]) by mail72-va3-R.bigfish.com (Postfix) with ESMTP id 6B7C13201CB; Thu, 19 Jul 2012 15:55:25 +0000 (UTC) X-Forefront-Antispam-Report: CIP:70.37.183.190; KIP:(null); UIP:(null); IPV:NLI; H:mail.freescale.net; RD:none; EFVD:NLI X-SpamScore: 7 X-BigFish: VS7(z1039ozzz1202hzz8275dhz2dh87h2a8h668h839hd24he5bhe96hf0ah107ah) X-FB-DOMAIN-IP-MATCH: fail Received: from mail72-va3 (localhost.localdomain [127.0.0.1]) by mail72-va3 (MessageSwitch) id 1342713323262758_23156; Thu, 19 Jul 2012 15:55:23 +0000 (UTC) Received: from VA3EHSMHS030.bigfish.com (unknown [10.7.14.253]) by mail72-va3.bigfish.com (Postfix) with ESMTP id 310FD30004D; Thu, 19 Jul 2012 15:55:23 +0000 (UTC) Received: from mail.freescale.net (70.37.183.190) by VA3EHSMHS030.bigfish.com (10.7.99.40) with Microsoft SMTP Server (TLS) id 14.1.225.23; Thu, 19 Jul 2012 15:55:22 +0000 Received: from az84smr01.freescale.net (10.64.34.197) by 039-SN1MMR1-002.039d.mgd.msft.net (10.84.1.15) with Microsoft SMTP Server (TLS) id 14.2.298.5; Thu, 19 Jul 2012 10:55:20 -0500 Received: from S2101-09.ap.freescale.net ([10.192.185.101]) by az84smr01.freescale.net (8.14.3/8.14.0) with ESMTP id q6JFspvG017494; Thu, 19 Jul 2012 08:55:16 -0700 From: Shawn Guo To: "Rafael J. Wysocki" Subject: [PATCH 3/3] cpufreq: Add a generic cpufreq-cpu0 driver Date: Thu, 19 Jul 2012 23:54:41 +0800 Message-ID: <1342713281-31114-4-git-send-email-shawn.guo@linaro.org> X-Mailer: git-send-email 1.7.5.4 In-Reply-To: <1342713281-31114-1-git-send-email-shawn.guo@linaro.org> References: <1342713281-31114-1-git-send-email-shawn.guo@linaro.org> MIME-Version: 1.0 X-OriginatorOrg: sigmatel.com X-Spam-Note: CRM114 invocation failed X-Spam-Score: -2.6 (--) X-Spam-Report: SpamAssassin version 3.3.2 on merlin.infradead.org summary: Content analysis details: (-2.6 points) pts rule name description ---- ---------------------- -------------------------------------------------- -0.7 RCVD_IN_DNSWL_LOW RBL: Sender listed at http://www.dnswl.org/, low trust [216.32.180.31 listed in list.dnswl.org] -1.9 BAYES_00 BODY: Bayes spam probability is 0 to 1% [score: 0.0000] Cc: Kevin Hilman , Nishanth Menon , Mike Turquette , linux-pm@vger.kernel.org, devicetree-discuss@lists.ozlabs.org, cpufreq@vger.kernel.org, Richard Zhao , Russell King - ARM Linux , Shawn Guo , linux-arm-kernel@lists.infradead.org X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: linux-arm-kernel-bounces@lists.infradead.org Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org It adds a generic cpufreq driver for CPU0 frequency management based on clk, regulator, OPP and device tree support. It can support both uniprocessor (UP) and those symmetric multiprocessor (SMP) systems which share clock and voltage across all CPUs. Signed-off-by: Shawn Guo --- .../devicetree/bindings/cpufreq/cpufreq-cpu0.txt | 58 +++++ drivers/cpufreq/Kconfig | 11 + drivers/cpufreq/Makefile | 2 + drivers/cpufreq/cpufreq-cpu0.c | 235 ++++++++++++++++++++ 4 files changed, 306 insertions(+), 0 deletions(-) create mode 100644 Documentation/devicetree/bindings/cpufreq/cpufreq-cpu0.txt create mode 100644 drivers/cpufreq/cpufreq-cpu0.c diff --git a/Documentation/devicetree/bindings/cpufreq/cpufreq-cpu0.txt b/Documentation/devicetree/bindings/cpufreq/cpufreq-cpu0.txt new file mode 100644 index 0000000..94799bc --- /dev/null +++ b/Documentation/devicetree/bindings/cpufreq/cpufreq-cpu0.txt @@ -0,0 +1,58 @@ +Generic cpufreq driver for CPU0 + +It is a generic cpufreq driver for CPU0 frequency management. It +supports both uniprocessor (UP) and symmetric multiprocessor (SMP) +systems which share clock and voltage across all CPUs. + +Both required and optional properties listed below must be defined +under node /cpus/cpu@0. + +Required properties: +- operating-points: Refer to Documentation/devicetree/bindings/power/opp.txt + for details + +Optional properties: +- transition-latency: Specify the possible maximum transition latency, + in unit of nanoseconds. +- voltage-tolerance: Specify the CPU voltage tolerance in percentage. + +Examples: + +cpus { + #address-cells = <1>; + #size-cells = <0>; + + cpu@0 { + compatible = "arm,cortex-a9"; + reg = <0>; + next-level-cache = <&L2>; + operating-points = < + /* kHz uV en */ + 1200000 1275000 0 + 996000 1225000 1 + 792000 1100000 1 + 672000 1100000 0 + 396000 950000 1 + 198000 850000 1 + >; + transition-latency = <61036>; /* two CLK32 periods */ + }; + + cpu@1 { + compatible = "arm,cortex-a9"; + reg = <1>; + next-level-cache = <&L2>; + }; + + cpu@2 { + compatible = "arm,cortex-a9"; + reg = <2>; + next-level-cache = <&L2>; + }; + + cpu@3 { + compatible = "arm,cortex-a9"; + reg = <3>; + next-level-cache = <&L2>; + }; +}; diff --git a/drivers/cpufreq/Kconfig b/drivers/cpufreq/Kconfig index e24a2a1..e77a1e0 100644 --- a/drivers/cpufreq/Kconfig +++ b/drivers/cpufreq/Kconfig @@ -179,6 +179,17 @@ config CPU_FREQ_GOV_CONSERVATIVE If in doubt, say N. +config GENERIC_CPUFREQ_CPU0 + bool "Generic cpufreq driver for CPU0" + depends on HAVE_CLK && REGULATOR && PM_OPP && OF + select CPU_FREQ_TABLE + help + This adds a generic cpufreq driver for CPU0 frequency management. + It supports both uniprocessor (UP) and symmetric multiprocessor (SMP) + systems which share clock and voltage across all CPUs. + + If in doubt, say N. + menu "x86 CPU frequency scaling drivers" depends on X86 source "drivers/cpufreq/Kconfig.x86" diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index 9531fc2..a378ed2 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -13,6 +13,8 @@ obj-$(CONFIG_CPU_FREQ_GOV_CONSERVATIVE) += cpufreq_conservative.o # CPUfreq cross-arch helpers obj-$(CONFIG_CPU_FREQ_TABLE) += freq_table.o +obj-$(CONFIG_GENERIC_CPUFREQ_CPU0) += cpufreq-cpu0.o + ################################################################################## # x86 drivers. # Link order matters. K8 is preferred to ACPI because of firmware bugs in early diff --git a/drivers/cpufreq/cpufreq-cpu0.c b/drivers/cpufreq/cpufreq-cpu0.c new file mode 100644 index 0000000..f1bb7f37 --- /dev/null +++ b/drivers/cpufreq/cpufreq-cpu0.c @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2012 Freescale Semiconductor, Inc. + * + * Reuse some codes from drivers/cpufreq/omap-cpufreq.c + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* voltage tolerance in percentage */ +static unsigned int voltage_tolerance; + +static struct device *cpu_dev; +static struct clk *cpu_clk; +static struct regulator *cpu_reg; +static struct cpufreq_frequency_table *freq_table; + +static int cpu0_verify_speed(struct cpufreq_policy *policy) +{ + return cpufreq_frequency_table_verify(policy, freq_table); +} + +static unsigned int cpu0_get_speed(unsigned int cpu) +{ + return clk_get_rate(cpu_clk) / 1000; +} + +static int cpu0_set_target(struct cpufreq_policy *policy, + unsigned int target_freq, unsigned int relation) +{ + struct cpufreq_freqs freqs; + struct opp *opp; + unsigned long freq_Hz, volt = 0, volt_old = 0, tol = 0; + unsigned int index, cpu; + int ret = 0; + + ret = cpufreq_frequency_table_target(policy, freq_table, target_freq, + relation, &index); + if (ret) { + pr_err("failed to match target freqency %d: %d\n", + target_freq, ret); + return ret; + } + + freq_Hz = clk_round_rate(cpu_clk, freq_table[index].frequency * 1000); + if (freq_Hz < 0) + freq_Hz = freq_table[index].frequency * 1000; + freqs.new = freq_Hz / 1000; + freqs.old = clk_get_rate(cpu_clk) / 1000; + + if (freqs.old == freqs.new) + return 0; + + for_each_cpu(cpu, policy->cpus) { + freqs.cpu = cpu; + cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + } + + if (cpu_reg) { + opp = opp_find_freq_ceil(cpu_dev, &freq_Hz); + if (IS_ERR(opp)) { + pr_err("failed to find OPP for %ld\n", freq_Hz); + return PTR_ERR(opp); + } + volt = opp_get_voltage(opp); + tol = volt * voltage_tolerance / 100; + volt_old = regulator_get_voltage(cpu_reg); + } + + pr_debug("%u MHz, %ld mV --> %u MHz, %ld mV\n", + freqs.old / 1000, volt_old ? volt_old / 1000 : -1, + freqs.new / 1000, volt ? volt / 1000 : -1); + + /* scaling up? scale voltage before frequency */ + if (cpu_reg && freqs.new > freqs.old) { + if (regulator_set_voltage(cpu_reg, volt - tol, volt + tol)) { + pr_warn("Unable to scale voltage up\n"); + freqs.new = freqs.old; + goto done; + } + } + + ret = clk_set_rate(cpu_clk, freqs.new * 1000); + + /* scaling down? scale voltage after frequency */ + if (cpu_reg && (freqs.new < freqs.old)) { + if (regulator_set_voltage(cpu_reg, volt - tol, volt + tol)) { + pr_warn("Unable to scale voltage down\n"); + ret = clk_set_rate(cpu_clk, freqs.old * 1000); + freqs.new = freqs.old; + goto done; + } + } + +done: + for_each_cpu(cpu, policy->cpus) { + freqs.cpu = cpu; + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + } + + return ret; +} + +static int cpu0_cpufreq_init(struct cpufreq_policy *policy) +{ + struct device_node *np; + int ret; + + /* Initialize resources via CPU0 */ + if (policy->cpu != 0) + return -EINVAL; + + np = of_find_node_by_path("/cpus/cpu@0"); + if (!np) { + pr_err("failed to find cpu0 node\n"); + return -ENOENT; + } + + cpu_dev = get_cpu_device(0); + if (!cpu_dev) { + pr_err("failed to get cpu0 device\n"); + ret = -ENODEV; + goto out_put_node; + } + + cpu_dev->of_node = np; + ret = of_init_opp_table(cpu_dev); + if (ret) { + pr_err("failed to init OPP table\n"); + goto out_put_node; + } + + ret = opp_init_cpufreq_table(cpu_dev, &freq_table); + if (ret) { + pr_err("failed to init cpufreq table\n"); + goto out_put_node; + } + + ret = cpufreq_frequency_table_cpuinfo(policy, freq_table); + if (ret) { + pr_err("invalid frequency table for cpu0\n"); + goto out_free_table; + } + + if (of_property_read_u32(np, "transition-latency", + &policy->cpuinfo.transition_latency)) + policy->cpuinfo.transition_latency = CPUFREQ_ETERNAL; + + of_property_read_u32(np, "voltage-tolerance", &voltage_tolerance); + + cpu_clk = clk_get(cpu_dev, NULL); + if (IS_ERR(cpu_clk)) { + pr_err("failed to get cpu0 clock\n"); + ret = PTR_ERR(cpu_clk); + goto out_free_table; + } + policy->cur = clk_get_rate(cpu_clk) / 1000; + + cpu_reg = regulator_get(cpu_dev, "cpu0"); + if (IS_ERR(cpu_reg)) { + pr_warn("failed to get cpu0 regulator\n"); + cpu_reg = NULL; + } + + /* + * The driver only supports the SMP configuartion where all processors + * share the clock and voltage and clock. Use cpufreq affected_cpus + * interface to have all CPUs scaled together. + */ + policy->shared_type = CPUFREQ_SHARED_TYPE_ANY; + cpumask_setall(policy->cpus); + + cpufreq_frequency_table_get_attr(freq_table, policy->cpu); + + of_node_put(np); + return 0; + +out_free_table: + opp_free_cpufreq_table(cpu_dev, &freq_table); +out_put_node: + of_node_put(np); + return ret; +} + +static int cpu0_cpufreq_exit(struct cpufreq_policy *policy) +{ + cpufreq_frequency_table_put_attr(policy->cpu); + regulator_put(cpu_reg); + clk_put(cpu_clk); + opp_free_cpufreq_table(cpu_dev, &freq_table); + + return 0; +} + +static struct freq_attr *cpu0_cpufreq_attr[] = { + &cpufreq_freq_attr_scaling_available_freqs, + NULL, +}; + +static struct cpufreq_driver cpu0_cpufreq_driver = { + .flags = CPUFREQ_STICKY, + .verify = cpu0_verify_speed, + .target = cpu0_set_target, + .get = cpu0_get_speed, + .init = cpu0_cpufreq_init, + .exit = cpu0_cpufreq_exit, + .name = "generic_cpu0", + .attr = cpu0_cpufreq_attr, +}; + +static int __devinit cpu0_cpufreq_driver_init(void) +{ + return cpufreq_register_driver(&cpu0_cpufreq_driver); +} +late_initcall(cpu0_cpufreq_driver_init); + +MODULE_AUTHOR("Shawn Guo "); +MODULE_DESCRIPTION("Generic cpufreq driver for CPU0"); +MODULE_LICENSE("GPL");