* RE: [PATCH 7/9] Thermal: Make PER_ZONE values configurable
From: R, Durgadoss @ 2013-01-09 9:12 UTC (permalink / raw)
To: Greg KH
Cc: Zhang, Rui, linux-pm@vger.kernel.org,
linux-kernel@vger.kernel.org, eduardo.valentin@ti.com,
hongbo.zhang@linaro.org, wni@nvidia.com
In-Reply-To: <20130107192414.GC4465@kroah.com>
Hi Greg,
> -----Original Message-----
> From: Greg KH [mailto:gregkh@linuxfoundation.org]
> Sent: Tuesday, January 08, 2013 12:54 AM
> To: R, Durgadoss
> Cc: Zhang, Rui; linux-pm@vger.kernel.org; linux-kernel@vger.kernel.org;
> eduardo.valentin@ti.com; hongbo.zhang@linaro.org; wni@nvidia.com
> Subject: Re: [PATCH 7/9] Thermal: Make PER_ZONE values configurable
>
> On Mon, Jan 07, 2013 at 12:43:24PM +0530, Durgadoss R wrote:
> > This patch makes MAX_SENSORS_PER_ZONE and
> > MAX_CDEVS_PER_ZONE values configurable. The
> > default value is 1, and range is 1-12.
>
> Why would we ever want to change this? Why make this configurable at
> all, how is a distro supposed to set this value?
>
> Shouldn't it be specified from the driver itself?
These are platform level parameters, that can differ for various platforms.
(Mostly due to board design and thermistor layouts). Stand-alone thermal
sensor drivers are not (need not be) aware of these values.
That's why these values are made configurable.
Thanks,
Durga
^ permalink raw reply
* RE: [PATCH 3/9] Thermal: Add APIs to bind cdev to new zone structure
From: R, Durgadoss @ 2013-01-09 9:21 UTC (permalink / raw)
To: Greg KH
Cc: Zhang, Rui, linux-pm@vger.kernel.org,
linux-kernel@vger.kernel.org, eduardo.valentin@ti.com,
hongbo.zhang@linaro.org, wni@nvidia.com
In-Reply-To: <20130107192602.GD4465@kroah.com>
> -----Original Message-----
> From: Greg KH [mailto:gregkh@linuxfoundation.org]
> Sent: Tuesday, January 08, 2013 12:56 AM
> To: R, Durgadoss
> Cc: Zhang, Rui; linux-pm@vger.kernel.org; linux-kernel@vger.kernel.org;
> eduardo.valentin@ti.com; hongbo.zhang@linaro.org; wni@nvidia.com
> Subject: Re: [PATCH 3/9] Thermal: Add APIs to bind cdev to new zone
> structure
>
> On Mon, Jan 07, 2013 at 12:43:20PM +0530, Durgadoss R wrote:
> > +struct thermal_cooling_device *get_cdev_by_name(const char *name)
> > +{
> > + struct thermal_cooling_device *pos;
> > + struct thermal_cooling_device *cdev = NULL;
> > +
> > + mutex_lock(&cdev_list_lock);
> > + for_each_cdev(pos) {
> > + if (!strnicmp(pos->type, name, THERMAL_NAME_LENGTH)) {
> > + cdev = pos;
> > + break;
> > + }
> > + }
> > + mutex_unlock(&cdev_list_lock);
> > + return cdev;
> > +}
> > +EXPORT_SYMBOL(get_cdev_by_name);
>
> EXPORT_SYMBOL_GPL?
We have all other exports as EXPORT_SYMBOL in this file(thermal_sys.c)
If _GPL is required, the we will do a single patch changing all of them to
EXPORT_SYMBOL_GPL.
>
> You also forgot to increment the reference count, which is required for
> all reference counted objects.
Sorry, I could not get what you are saying here.
Thanks,
Durga
^ permalink raw reply
* Re: [PATCH v11 0/9] ZPODD Patches
From: Aaron Lu @ 2013-01-09 9:37 UTC (permalink / raw)
To: Tejun Heo
Cc: Jeff Garzik, James Bottomley, Rafael J. Wysocki, Alan Stern,
Aaron Lu, Jeff Wu, linux-ide, linux-pm, linux-scsi, linux-acpi
In-Reply-To: <20130107184928.GV3926@htj.dyndns.org>
On 01/08/2013 02:49 AM, Tejun Heo wrote:
> Hello, Aaron.
>
> On Sun, Jan 06, 2013 at 10:48:20AM +0800, Aaron Lu wrote:
>> v11:
>> Introduce event_driven flag in scsi_device to silence the media event
>> poll after ODD is powered off;
>> Removed ata layer PM QOS control, instead, simply limit ACPI state to
>> D3_HOT when choosing state;
>> Make the power off delay a module param named zpodd_poweroff_delay,
>> defaults to 30 seconds.
>
> Other than the nitpicks. The series generally looks good to me.
This is very encouraging, thanks a lot!
Best regards,
Aaron
^ permalink raw reply
* [PATCH] cpufreq: Simplify __cpufreq_remove_dev()
From: Viresh Kumar @ 2013-01-09 11:20 UTC (permalink / raw)
To: rjw
Cc: robin.randhawa, Steve.Bannister, Liviu.Dudau, linaro-dev, patches,
cpufreq, linux-pm, linux-kernel, shawn.guo, Viresh Kumar
__cpufreq_remove_dev() is called on multiple occasions: cpufreq_driver
unregister and cpu removals.
Current implementation of this routine is overly complex without much need. If
the cpu to be removed is the policy->cpu, we remove the policy first and add all
other cpus again from policy->cpus and then finally call __cpufreq_remove_dev()
again to remove the cpu to be deleted. Haahhhh..
There exist a simple solution to removal of a cpu:
- Simply use the old policy structure
- update its fields like: policy->cpu, etc.
- notify any users of cpufreq, which depend on changing policy->cpu
Hence this patch, which tries to implement the above theory. It is tested well
by myself on ARM big.LITTLE TC2 SoC, which has 5 cores (2 A15 and 3 A7). Both
A15's share same struct policy and all A7's share same policy structure.
Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
Tested-by: Viresh Kumar <viresh.kumar@linaro.org>
---
Hi Guys,
I am just an beginner in cpufreq stuff, please ignore any foolish mistakes. :)
Yesterday, i reviewed a cpufreq driver that had some ugly code in init()
routine:
http://www.spinics.net/lists/arm-kernel/msg215348.html
It wasn't ugly due to the author of the patch, but the way
__cpufreq_remove_dev() is implemented. And then i thought to simplify it.
[Probably need to simplify cpufreq_add_dev() too, but that can be done as next
step.]
I have rebased this patch over some other cpufreq core fixes i had posted
earlier:
https://lkml.org/lkml/2012/12/16/5
ARM mail servers are broken and hence this patch can't be applied as is. :(
I have pushed this and the dependency patch here:
http://git.linaro.org/gitweb?p=arm/big.LITTLE/mp.git;a=shortlog;h=refs/heads/cpufreq-fixes-v2
@Shawn: I believe your driver don't require that ugly code anymore (Though i
know there is a situation for that to happen, if we have two cpus, you remove
second one and then add it back. With this cpufreq_add_dev() would call init()
first and then try to match if there are any managed_policies present. But the
issue you pointed out about unregistering the driver would be solved by this
patch.)
drivers/cpufreq/cpufreq.c | 158 +++++++++++++++++-----------------------
drivers/cpufreq/cpufreq_stats.c | 21 +++++-
drivers/cpufreq/freq_table.c | 10 +++
include/linux/cpufreq.h | 5 ++
4 files changed, 101 insertions(+), 93 deletions(-)
diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c
index 271d3be..8df41ad 100644
--- a/drivers/cpufreq/cpufreq.c
+++ b/drivers/cpufreq/cpufreq.c
@@ -1036,6 +1036,22 @@ module_out:
return ret;
}
+static void update_policy_cpu(struct cpufreq_policy *policy, unsigned int cpu)
+{
+ unsigned int old_cpu = policy->cpu;
+ int j;
+
+ policy->cpu = cpu;
+
+ for_each_cpu(j, policy->cpus) {
+ if (!cpu_online(j))
+ continue;
+ per_cpu(cpufreq_policy_cpu, j) = cpu;
+ }
+
+ cpufreq_frequency_table_update_policy_cpu(old_cpu, cpu);
+ cpufreq_stats_update_policy_cpu(old_cpu, cpu);
+}
/**
* __cpufreq_remove_dev - remove a CPU device
@@ -1046,132 +1062,92 @@ module_out:
*/
static int __cpufreq_remove_dev(struct device *dev, struct subsys_interface *sif)
{
- unsigned int cpu = dev->id;
+ unsigned int cpu = dev->id, ret, cpus;
unsigned long flags;
struct cpufreq_policy *data;
struct kobject *kobj;
struct completion *cmp;
-#ifdef CONFIG_SMP
struct device *cpu_dev;
- unsigned int j;
-#endif
- pr_debug("unregistering CPU %u\n", cpu);
+ pr_debug("%s: unregistering CPU %u\n", __func__, cpu);
spin_lock_irqsave(&cpufreq_driver_lock, flags);
data = per_cpu(cpufreq_cpu_data, cpu);
if (!data) {
+ pr_debug("%s: No cpu_data found\n", __func__);
spin_unlock_irqrestore(&cpufreq_driver_lock, flags);
unlock_policy_rwsem_write(cpu);
return -EINVAL;
}
- per_cpu(cpufreq_cpu_data, cpu) = NULL;
-#ifdef CONFIG_SMP
- /* if this isn't the CPU which is the parent of the kobj, we
- * only need to unlink, put and exit
- */
- if (unlikely(cpu != data->cpu)) {
- pr_debug("removing link\n");
+ if (cpufreq_driver->target)
__cpufreq_governor(data, CPUFREQ_GOV_STOP);
- cpumask_clear_cpu(cpu, data->cpus);
- spin_unlock_irqrestore(&cpufreq_driver_lock, flags);
-
- __cpufreq_governor(data, CPUFREQ_GOV_START);
- __cpufreq_governor(data, CPUFREQ_GOV_LIMITS);
-
- kobj = &dev->kobj;
- cpufreq_cpu_put(data);
- unlock_policy_rwsem_write(cpu);
- sysfs_remove_link(kobj, "cpufreq");
- return 0;
- }
-#endif
-
-#ifdef CONFIG_SMP
#ifdef CONFIG_HOTPLUG_CPU
strncpy(per_cpu(cpufreq_cpu_governor, cpu), data->governor->name,
CPUFREQ_NAME_LEN);
#endif
- /* if we have other CPUs still registered, we need to unlink them,
- * or else wait_for_completion below will lock up. Clean the
- * per_cpu(cpufreq_cpu_data) while holding the lock, and remove
- * the sysfs links afterwards.
- */
- if (unlikely(cpumask_weight(data->cpus) > 1)) {
- for_each_cpu(j, data->cpus) {
- if (j == cpu)
- continue;
- per_cpu(cpufreq_cpu_data, j) = NULL;
- }
- }
-
- spin_unlock_irqrestore(&cpufreq_driver_lock, flags);
+ per_cpu(cpufreq_cpu_data, cpu) = NULL;
+ cpus = cpumask_weight(data->cpus);
+ cpumask_clear_cpu(cpu, data->cpus);
- if (unlikely(cpumask_weight(data->cpus) > 1)) {
- for_each_cpu(j, data->cpus) {
- if (j == cpu)
- continue;
- pr_debug("removing link for cpu %u\n", j);
-#ifdef CONFIG_HOTPLUG_CPU
- strncpy(per_cpu(cpufreq_cpu_governor, j),
- data->governor->name, CPUFREQ_NAME_LEN);
-#endif
- cpu_dev = get_cpu_device(j);
- kobj = &cpu_dev->kobj;
+ if (unlikely((cpu == data->cpu) && (cpus > 1))) {
+ /* first sibling now owns the new sysfs dir */
+ cpu_dev = get_cpu_device(cpumask_first(data->cpus));
+ sysfs_remove_link(&cpu_dev->kobj, "cpufreq");
+ ret = kobject_move(&data->kobj, &cpu_dev->kobj);
+ if (ret) {
+ pr_err("%s: Failed to move kobj: %d", __func__, ret);
+ cpumask_set_cpu(cpu, data->cpus);
+ ret = sysfs_create_link(&cpu_dev->kobj, &data->kobj,
+ "cpufreq");
+ spin_unlock_irqrestore(&cpufreq_driver_lock, flags);
unlock_policy_rwsem_write(cpu);
- sysfs_remove_link(kobj, "cpufreq");
- lock_policy_rwsem_write(cpu);
- cpufreq_cpu_put(data);
+ return -EINVAL;
}
+
+ update_policy_cpu(data, cpu_dev->id);
+ pr_debug("%s: policy Kobject moved to cpu: %d from: %d\n",
+ __func__, cpu_dev->id, cpu);
}
-#else
- spin_unlock_irqrestore(&cpufreq_driver_lock, flags);
-#endif
- if (cpufreq_driver->target)
- __cpufreq_governor(data, CPUFREQ_GOV_STOP);
+ spin_unlock_irqrestore(&cpufreq_driver_lock, flags);
- kobj = &data->kobj;
- cmp = &data->kobj_unregister;
+ pr_debug("%s: removing link, cpu: %d\n", __func__, cpu);
+ cpufreq_cpu_put(data);
unlock_policy_rwsem_write(cpu);
- kobject_put(kobj);
+ sysfs_remove_link(&dev->kobj, "cpufreq");
- /* we need to make sure that the underlying kobj is actually
- * not referenced anymore by anybody before we proceed with
- * unloading.
- */
- pr_debug("waiting for dropping of refcount\n");
- wait_for_completion(cmp);
- pr_debug("wait complete\n");
-
- lock_policy_rwsem_write(cpu);
- if (cpufreq_driver->exit)
- cpufreq_driver->exit(data);
- unlock_policy_rwsem_write(cpu);
+ /* If cpu is last user of policy, free policy */
+ if (cpus == 1) {
+ lock_policy_rwsem_write(cpu);
+ kobj = &data->kobj;
+ cmp = &data->kobj_unregister;
+ unlock_policy_rwsem_write(cpu);
+ kobject_put(kobj);
-#ifdef CONFIG_HOTPLUG_CPU
- /* when the CPU which is the parent of the kobj is hotplugged
- * offline, check for siblings, and create cpufreq sysfs interface
- * and symlinks
- */
- if (unlikely(cpumask_weight(data->cpus) > 1)) {
- /* first sibling now owns the new sysfs dir */
- cpumask_clear_cpu(cpu, data->cpus);
- cpufreq_add_dev(get_cpu_device(cpumask_first(data->cpus)), NULL);
+ /* we need to make sure that the underlying kobj is actually
+ * not referenced anymore by anybody before we proceed with
+ * unloading.
+ */
+ pr_debug("waiting for dropping of refcount\n");
+ wait_for_completion(cmp);
+ pr_debug("wait complete\n");
- /* finally remove our own symlink */
lock_policy_rwsem_write(cpu);
- __cpufreq_remove_dev(dev, sif);
- }
-#endif
+ if (cpufreq_driver->exit)
+ cpufreq_driver->exit(data);
+ unlock_policy_rwsem_write(cpu);
- free_cpumask_var(data->related_cpus);
- free_cpumask_var(data->cpus);
- kfree(data);
+ free_cpumask_var(data->related_cpus);
+ free_cpumask_var(data->cpus);
+ kfree(data);
+ } else if (cpufreq_driver->target) {
+ __cpufreq_governor(data, CPUFREQ_GOV_START);
+ __cpufreq_governor(data, CPUFREQ_GOV_LIMITS);
+ }
return 0;
}
diff --git a/drivers/cpufreq/cpufreq_stats.c b/drivers/cpufreq/cpufreq_stats.c
index e40e508..0afface 100644
--- a/drivers/cpufreq/cpufreq_stats.c
+++ b/drivers/cpufreq/cpufreq_stats.c
@@ -170,11 +170,13 @@ static int freq_table_get_index(struct cpufreq_stats *stat, unsigned int freq)
static void cpufreq_stats_free_table(unsigned int cpu)
{
struct cpufreq_stats *stat = per_cpu(cpufreq_stats_table, cpu);
+
if (stat) {
+ pr_debug("%s: Free stat table\n", __func__);
kfree(stat->time_in_state);
kfree(stat);
+ per_cpu(cpufreq_stats_table, cpu) = NULL;
}
- per_cpu(cpufreq_stats_table, cpu) = NULL;
}
/* must be called early in the CPU removal sequence (before
@@ -183,8 +185,10 @@ static void cpufreq_stats_free_table(unsigned int cpu)
static void cpufreq_stats_free_sysfs(unsigned int cpu)
{
struct cpufreq_policy *policy = cpufreq_cpu_get(cpu);
- if (policy && policy->cpu == cpu)
+ if (policy && (cpumask_weight(policy->cpus) == 1)) {
+ pr_debug("%s: Free sysfs stat\n", __func__);
sysfs_remove_group(&policy->kobj, &stats_attr_group);
+ }
if (policy)
cpufreq_cpu_put(policy);
}
@@ -262,6 +266,19 @@ error_get_fail:
return ret;
}
+void cpufreq_stats_update_policy_cpu(unsigned int old_cpu,
+ unsigned int new_cpu)
+{
+ struct cpufreq_stats *stat = per_cpu(cpufreq_stats_table, old_cpu);
+
+ pr_debug("Updating stats_table for new_cpu %u from old_cpu %u\n",
+ new_cpu, old_cpu);
+ per_cpu(cpufreq_stats_table, new_cpu) = per_cpu(cpufreq_stats_table,
+ old_cpu);
+ per_cpu(cpufreq_stats_table, old_cpu) = NULL;
+ stat->cpu = new_cpu;
+}
+
static int cpufreq_stat_notifier_policy(struct notifier_block *nb,
unsigned long val, void *data)
{
diff --git a/drivers/cpufreq/freq_table.c b/drivers/cpufreq/freq_table.c
index 49cda25..a4b384f 100644
--- a/drivers/cpufreq/freq_table.c
+++ b/drivers/cpufreq/freq_table.c
@@ -227,6 +227,16 @@ void cpufreq_frequency_table_put_attr(unsigned int cpu)
}
EXPORT_SYMBOL_GPL(cpufreq_frequency_table_put_attr);
+void cpufreq_frequency_table_update_policy_cpu(unsigned int old_cpu,
+ unsigned int new_cpu)
+{
+ pr_debug("Updating show_table for new_cpu %u from old_cpu %u\n",
+ new_cpu, old_cpu);
+ per_cpu(cpufreq_show_table, new_cpu) = per_cpu(cpufreq_show_table,
+ old_cpu);
+ per_cpu(cpufreq_show_table, old_cpu) = NULL;
+}
+
struct cpufreq_frequency_table *cpufreq_frequency_get_table(unsigned int cpu)
{
return per_cpu(cpufreq_show_table, cpu);
diff --git a/include/linux/cpufreq.h b/include/linux/cpufreq.h
index a55b88e..a42aa05 100644
--- a/include/linux/cpufreq.h
+++ b/include/linux/cpufreq.h
@@ -405,6 +405,11 @@ extern struct freq_attr cpufreq_freq_attr_scaling_available_freqs;
void cpufreq_frequency_table_get_attr(struct cpufreq_frequency_table *table,
unsigned int cpu);
+void cpufreq_frequency_table_update_policy_cpu(unsigned int old_cpu,
+ unsigned int new_cpu);
void cpufreq_frequency_table_put_attr(unsigned int cpu);
+
+void cpufreq_stats_update_policy_cpu(unsigned int old_cpu,
+ unsigned int new_cpu);
#endif /* _LINUX_CPUFREQ_H */
--
1.7.12.rc2.18.g61b472e
^ permalink raw reply related
* Re: [PATCH 2/3] cpufreq: add imx6q-cpufreq driver
From: Viresh Kumar @ 2013-01-09 11:25 UTC (permalink / raw)
To: Shawn Guo
Cc: cpufreq, linux-pm, linux-arm-kernel, Rafael J. Wysocki,
Sascha Hauer
In-Reply-To: <CAKohpo=yahfREsWvCPN6BjoxiifKZWCM19-mLEvao0e4UCT6Vg@mail.gmail.com>
On 8 January 2013 20:49, Viresh Kumar <viresh.kumar@linaro.org> wrote:
> On 8 January 2013 20:34, Shawn Guo <shawn.guo@linaro.org> wrote:
>> I'm seeing that the .init() will be called on cpu1 when removing the
>> module.
>
> Hmm... Can you give me sometime to think about how to fix that?
> You can go ahead with keeping this change in v2.
Hi Shawn,
I have sent a fix for this and cc'd you. Can you please give it a try?
cpufreq: Simplify __cpufreq_remove_dev()
patches are present here.
http://git.linaro.org/gitweb?p=arm/big.LITTLE/mp.git;a=shortlog;h=refs/heads/cpufreq-fixes-v2
^ permalink raw reply
* [PATCH 1/4] ARM: EXYNOS5: Add PPMU device tree support
From: Abhilash Kesavan @ 2013-01-09 12:06 UTC (permalink / raw)
To: myungjoo.ham, linux-kernel, linux-pm, kgene.kim
Cc: kyungmin.park, rjw, jhbird.choi, Abhilash Kesavan
PPMU is required by the devfreq driver. Add a device tree
node for it.
Signed-off-by: Abhilash Kesavan <a.kesavan@samsung.com>
Cc: Jonghwan Choi <jhbird.choi@samsung.com>
Cc: Kukjin Kim <kgene.kim@samsung.com>
---
.../bindings/arm/exynos/ppmu-exynos5.txt | 28 ++++++++++++++++++++
arch/arm/boot/dts/exynos5250.dtsi | 9 ++++++
2 files changed, 37 insertions(+), 0 deletions(-)
create mode 100644 Documentation/devicetree/bindings/arm/exynos/ppmu-exynos5.txt
diff --git a/Documentation/devicetree/bindings/arm/exynos/ppmu-exynos5.txt b/Documentation/devicetree/bindings/arm/exynos/ppmu-exynos5.txt
new file mode 100644
index 0000000..a424dfa
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/exynos/ppmu-exynos5.txt
@@ -0,0 +1,28 @@
+Exynos5 PPMU driver
+-------------------
+
+Performance events are primitive values used to get performance data. These
+events provide information about the behavior of the SoC that can be used
+when analyzing system performance. These events are made visible using the
+PPMU logic.
+Exynos5 PPMU driver is used by the exynos5 devfreq driver to control the bus
+frequency/voltage.
+
+Required properties:
+- compatible: should be one of the following.
+ * samsung,exynos5-ppmu - for exynos5250 type ppmu.
+- reg:
+ * physical base address of the PPMUs (DDR, Right Bus and CPU) and
+ length of memory mapped region.
+
+Example:
+--------
+
+ ppmu {
+ compatible = "samsung,exynos5250-ppmu";
+ reg = <0x10C40000 0x2000
+ 0x10C50000 0x2000
+ 0x10C60000 0x2000
+ 0x10CB0000 0x2000
+ 0x13660000 0x2000>;
+ };
diff --git a/arch/arm/boot/dts/exynos5250.dtsi b/arch/arm/boot/dts/exynos5250.dtsi
index 30485de..d504cba 100644
--- a/arch/arm/boot/dts/exynos5250.dtsi
+++ b/arch/arm/boot/dts/exynos5250.dtsi
@@ -732,4 +732,13 @@
interrupt-parent = <&combiner>;
interrupts = <24 1>;
};
+
+ ppmu {
+ compatible = "samsung,exynos5250-ppmu";
+ reg = <0x10C40000 0x2000 /* PPMU_DDR_C */
+ 0x10C50000 0x2000 /* PPMU_DDR_R1 */
+ 0x10CB0000 0x2000 /* PPMU_DDR_L */
+ 0x13660000 0x2000 /* PPMU_DDR_RIGHT */
+ 0x10C60000 0x2000>; /* PPMU_DDR_CPU */
+ };
};
--
1.7.8.6
^ permalink raw reply related
* [PATCH 2/4] ARM: EXYNOS5: Support Exynos5-bus devfreq driver
From: Abhilash Kesavan @ 2013-01-09 12:06 UTC (permalink / raw)
To: myungjoo.ham, linux-kernel, linux-pm, kgene.kim
Cc: kyungmin.park, rjw, jhbird.choi, Abhilash Kesavan
In-Reply-To: <1357733199-17206-1-git-send-email-a.kesavan@samsung.com>
Setup the INT clock ops to control/vary INT frequency
Signed-off-by: Abhilash Kesavan <a.kesavan@samsung.com>
Cc: Jonghwan Choi <jhbird.choi@samsung.com>
Cc: Kukjin Kim <kgene.kim@samsung.com>
---
arch/arm/mach-exynos/clock-exynos5.c | 143 ++++++++++++++++++++++++
arch/arm/mach-exynos/include/mach/regs-clock.h | 37 ++++++
2 files changed, 180 insertions(+), 0 deletions(-)
diff --git a/arch/arm/mach-exynos/clock-exynos5.c b/arch/arm/mach-exynos/clock-exynos5.c
index 0208c3a..050879c 100644
--- a/arch/arm/mach-exynos/clock-exynos5.c
+++ b/arch/arm/mach-exynos/clock-exynos5.c
@@ -108,6 +108,11 @@ static struct clk exynos5_clk_sclk_usbphy = {
.rate = 48000000,
};
+/* Virtual Bus INT clock */
+static struct clk exynos5_int_clk = {
+ .name = "int_clk",
+};
+
static int exynos5_clksrc_mask_top_ctrl(struct clk *clk, int enable)
{
return s5p_gatectrl(EXYNOS5_CLKSRC_MASK_TOP, clk, enable);
@@ -1426,6 +1431,141 @@ static struct clk *exynos5_clks[] __initdata = {
&clk_fout_cpll,
&clk_fout_mpll_div2,
&exynos5_clk_armclk,
+ &exynos5_int_clk,
+};
+
+#define INT_FREQ(f, a0, a1, a2, a3, a4, a5, b0, b1, b2, b3, \
+ c0, c1, d0, e0) \
+ { \
+ .freq = (f) * 1000000, \
+ .clk_div_top0 = ((a0) << 0 | (a1) << 8 | (a2) << 12 | \
+ (a3) << 16 | (a4) << 20 | (a5) << 28), \
+ .clk_div_top1 = ((b0) << 12 | (b1) << 16 | (b2) << 20 | \
+ (b3) << 24), \
+ .clk_div_lex = ((c0) << 4 | (c1) << 8), \
+ .clk_div_r0x = ((d0) << 4), \
+ .clk_div_r1x = ((e0) << 4), \
+ }
+
+static struct {
+ unsigned long freq;
+ u32 clk_div_top0;
+ u32 clk_div_top1;
+ u32 clk_div_lex;
+ u32 clk_div_r0x;
+ u32 clk_div_r1x;
+} int_freq[] = {
+ /*
+ * values:
+ * freq
+ * clock divider for ACLK66, ACLK166, ACLK200, ACLK266,
+ ACLK333, ACLK300_DISP1
+ * clock divider for ACLK300_GSCL, ACLK400_IOP, ACLK400_ISP, ACLK66_PRE
+ * clock divider for PCLK_LEX, ATCLK_LEX
+ * clock divider for ACLK_PR0X
+ * clock divider for ACLK_PR1X
+ */
+ INT_FREQ(266, 1, 1, 3, 2, 0, 0, 0, 1, 1, 5, 1, 0, 1, 1),
+ INT_FREQ(200, 1, 2, 4, 3, 1, 0, 0, 3, 2, 5, 1, 0, 1, 1),
+ INT_FREQ(160, 1, 3, 4, 4, 2, 0, 0, 3, 3, 5, 1, 0, 1, 1),
+ INT_FREQ(133, 1, 3, 5, 5, 2, 1, 1, 4, 4, 5, 1, 0, 1, 1),
+ INT_FREQ(100, 1, 7, 7, 7, 7, 3, 7, 7, 7, 5, 1, 0, 1, 1),
+};
+
+static unsigned long exynos5_clk_int_get_rate(struct clk *clk)
+{
+ return clk->rate;
+}
+
+static void exynos5_int_set_clkdiv(unsigned int div_index)
+{
+ unsigned int tmp;
+
+ /* Change Divider - TOP0 */
+ tmp = __raw_readl(EXYNOS5_CLKDIV_TOP0);
+
+ tmp &= ~(EXYNOS5_CLKDIV_TOP0_ACLK266_MASK |
+ EXYNOS5_CLKDIV_TOP0_ACLK200_MASK |
+ EXYNOS5_CLKDIV_TOP0_ACLK66_MASK |
+ EXYNOS5_CLKDIV_TOP0_ACLK333_MASK |
+ EXYNOS5_CLKDIV_TOP0_ACLK166_MASK |
+ EXYNOS5_CLKDIV_TOP0_ACLK300_DISP1_MASK);
+
+ tmp |= int_freq[div_index].clk_div_top0;
+
+ __raw_writel(tmp, EXYNOS5_CLKDIV_TOP0);
+
+ /* Wait for TOP0 divider to stabilize */
+ while (__raw_readl(EXYNOS5_CLKDIV_STAT_TOP0) & 0x151101)
+ cpu_relax();
+
+ /* Change Divider - TOP1 */
+ tmp = __raw_readl(EXYNOS5_CLKDIV_TOP1);
+
+ tmp &= ~(EXYNOS5_CLKDIV_TOP1_ACLK400_ISP_MASK |
+ EXYNOS5_CLKDIV_TOP1_ACLK400_IOP_MASK |
+ EXYNOS5_CLKDIV_TOP1_ACLK66_PRE_MASK |
+ EXYNOS5_CLKDIV_TOP1_ACLK300_GSCL_MASK);
+
+ tmp |= int_freq[div_index].clk_div_top1;
+
+ __raw_writel(tmp, EXYNOS5_CLKDIV_TOP1);
+
+ /* Wait for TOP0 and TOP1 dividers to stabilize */
+ while ((__raw_readl(EXYNOS5_CLKDIV_STAT_TOP1) & 0x1110000) &&
+ (__raw_readl(EXYNOS5_CLKDIV_STAT_TOP0) & 0x80000))
+ cpu_relax();
+
+ /* Change Divider - LEX */
+ tmp = int_freq[div_index].clk_div_lex;
+
+ __raw_writel(tmp, EXYNOS5_CLKDIV_LEX);
+
+ /* Wait for LEX divider to stabilize */
+ while (__raw_readl(EXYNOS5_CLKDIV_STAT_LEX) & 0x110)
+ cpu_relax();
+
+ /* Change Divider - R0X */
+ tmp = int_freq[div_index].clk_div_r0x;
+
+ __raw_writel(tmp, EXYNOS5_CLKDIV_R0X);
+
+ /* Wait for R0X divider to stabilize */
+ while (__raw_readl(EXYNOS5_CLKDIV_STAT_R0X) & 0x10)
+ cpu_relax();
+
+ /* Change Divider - R1X */
+ tmp = int_freq[div_index].clk_div_r1x;
+
+ __raw_writel(tmp, EXYNOS5_CLKDIV_R1X);
+
+ /* Wait for R1X divider to stabilize */
+ while (__raw_readl(EXYNOS5_CLKDIV_STAT_R1X) & 0x10)
+ cpu_relax();
+}
+
+static int exynos5_clk_int_set_rate(struct clk *clk, unsigned long rate)
+{
+ int index;
+
+ for (index = 0; index < ARRAY_SIZE(int_freq); index++)
+ if (int_freq[index].freq == rate)
+ break;
+
+ if (index == ARRAY_SIZE(int_freq))
+ return -EINVAL;
+
+ /* Change the system clock divider values */
+ exynos5_int_set_clkdiv(index);
+
+ clk->rate = rate;
+
+ return 0;
+}
+
+static struct clk_ops exynos5_clk_int_ops = {
+ .get_rate = exynos5_clk_int_get_rate,
+ .set_rate = exynos5_clk_int_set_rate
};
static u32 epll_div[][6] = {
@@ -1620,6 +1760,9 @@ void __init_or_cpufreq exynos5_setup_clocks(void)
clk_fout_epll.ops = &exynos5_epll_ops;
+ exynos5_int_clk.ops = &exynos5_clk_int_ops;
+ exynos5_int_clk.rate = aclk_266;
+
if (clk_set_parent(&exynos5_clk_mout_epll.clk, &clk_fout_epll))
printk(KERN_ERR "Unable to set parent %s of clock %s.\n",
clk_fout_epll.name, exynos5_clk_mout_epll.clk.name);
diff --git a/arch/arm/mach-exynos/include/mach/regs-clock.h b/arch/arm/mach-exynos/include/mach/regs-clock.h
index d36ad76..3d3cbc8 100644
--- a/arch/arm/mach-exynos/include/mach/regs-clock.h
+++ b/arch/arm/mach-exynos/include/mach/regs-clock.h
@@ -323,6 +323,9 @@
#define EXYNOS5_CLKDIV_PERIC5 EXYNOS_CLKREG(0x1056C)
#define EXYNOS5_SCLK_DIV_ISP EXYNOS_CLKREG(0x10580)
+#define EXYNOS5_CLKDIV_STAT_TOP0 EXYNOS_CLKREG(0x10610)
+#define EXYNOS5_CLKDIV_STAT_TOP1 EXYNOS_CLKREG(0x10614)
+
#define EXYNOS5_CLKGATE_IP_ACP EXYNOS_CLKREG(0x08800)
#define EXYNOS5_CLKGATE_IP_ISP0 EXYNOS_CLKREG(0x0C800)
#define EXYNOS5_CLKGATE_IP_ISP1 EXYNOS_CLKREG(0x0C804)
@@ -337,6 +340,18 @@
#define EXYNOS5_CLKGATE_IP_PERIS EXYNOS_CLKREG(0x10960)
#define EXYNOS5_CLKGATE_BLOCK EXYNOS_CLKREG(0x10980)
+#define EXYNOS5_CLKGATE_BUS_SYSLFT EXYNOS_CLKREG(0x08920)
+
+#define EXYNOS5_CLKOUT_CMU_TOP EXYNOS_CLKREG(0x10A00)
+
+#define EXYNOS5_CLKDIV_LEX EXYNOS_CLKREG(0x14500)
+#define EXYNOS5_CLKDIV_STAT_LEX EXYNOS_CLKREG(0x14600)
+
+#define EXYNOS5_CLKDIV_R0X EXYNOS_CLKREG(0x18500)
+#define EXYNOS5_CLKDIV_STAT_R0X EXYNOS_CLKREG(0x18600)
+
+#define EXYNOS5_CLKDIV_R1X EXYNOS_CLKREG(0x1C500)
+#define EXYNOS5_CLKDIV_STAT_R1X EXYNOS_CLKREG(0x1C600)
#define EXYNOS5_BPLL_CON0 EXYNOS_CLKREG(0x20110)
#define EXYNOS5_CLKSRC_CDREX EXYNOS_CLKREG(0x20200)
#define EXYNOS5_CLKDIV_CDREX EXYNOS_CLKREG(0x20500)
@@ -347,6 +362,28 @@
#define EXYNOS5_EPLLCON0_LOCKED_SHIFT (29)
+#define EXYNOS5_CLKDIV_TOP0_ACLK300_DISP1_SHIFT (28)
+#define EXYNOS5_CLKDIV_TOP0_ACLK300_DISP1_MASK (0x7 << EXYNOS5_CLKDIV_TOP0_ACLK300_DISP1_SHIFT)
+#define EXYNOS5_CLKDIV_TOP0_ACLK333_SHIFT (20)
+#define EXYNOS5_CLKDIV_TOP0_ACLK333_MASK (0x7 << EXYNOS5_CLKDIV_TOP0_ACLK333_SHIFT)
+#define EXYNOS5_CLKDIV_TOP0_ACLK266_SHIFT (16)
+#define EXYNOS5_CLKDIV_TOP0_ACLK266_MASK (0x7 << EXYNOS5_CLKDIV_TOP0_ACLK266_SHIFT)
+#define EXYNOS5_CLKDIV_TOP0_ACLK200_SHIFT (12)
+#define EXYNOS5_CLKDIV_TOP0_ACLK200_MASK (0x7 << EXYNOS5_CLKDIV_TOP0_ACLK200_SHIFT)
+#define EXYNOS5_CLKDIV_TOP0_ACLK166_SHIFT (8)
+#define EXYNOS5_CLKDIV_TOP0_ACLK166_MASK (0x7 << EXYNOS5_CLKDIV_TOP0_ACLK166_SHIFT)
+#define EXYNOS5_CLKDIV_TOP0_ACLK66_SHIFT (0)
+#define EXYNOS5_CLKDIV_TOP0_ACLK66_MASK (0x7 << EXYNOS5_CLKDIV_TOP0_ACLK66_SHIFT)
+
+#define EXYNOS5_CLKDIV_TOP1_ACLK66_PRE_SHIFT (24)
+#define EXYNOS5_CLKDIV_TOP1_ACLK66_PRE_MASK (0x7 << EXYNOS5_CLKDIV_TOP1_ACLK66_PRE_SHIFT)
+#define EXYNOS5_CLKDIV_TOP1_ACLK400_ISP_SHIFT (20)
+#define EXYNOS5_CLKDIV_TOP1_ACLK400_ISP_MASK (0x7 << EXYNOS5_CLKDIV_TOP1_ACLK400_ISP_SHIFT)
+#define EXYNOS5_CLKDIV_TOP1_ACLK400_IOP_SHIFT (16)
+#define EXYNOS5_CLKDIV_TOP1_ACLK400_IOP_MASK (0x7 << EXYNOS5_CLKDIV_TOP1_ACLK400_IOP_SHIFT)
+#define EXYNOS5_CLKDIV_TOP1_ACLK300_GSCL_SHIFT (12)
+#define EXYNOS5_CLKDIV_TOP1_ACLK300_GSCL_MASK (0x7 << EXYNOS5_CLKDIV_TOP1_ACLK300_GSCL_SHIFT)
+
#define PWR_CTRL1_CORE2_DOWN_RATIO (7 << 28)
#define PWR_CTRL1_CORE1_DOWN_RATIO (7 << 16)
#define PWR_CTRL1_DIV2_DOWN_EN (1 << 9)
--
1.7.8.6
^ permalink raw reply related
* [PATCH 3/4] PM: DEVFREQ: Move exynos4 devfreq driver into a new sub-directory
From: Abhilash Kesavan @ 2013-01-09 12:06 UTC (permalink / raw)
To: myungjoo.ham, linux-kernel, linux-pm, kgene.kim
Cc: kyungmin.park, rjw, jhbird.choi, Abhilash Kesavan
In-Reply-To: <1357733199-17206-1-git-send-email-a.kesavan@samsung.com>
In anticipation of the new exynos5 devfreq and ppmu driver, create
an exynos sub-directory. Move the existing exynos4 devfreq driver
into the same.
Signed-off-by: Abhilash Kesavan <a.kesavan@samsung.com>
Cc: Jonghwan Choi <jhbird.choi@samsung.com>
Cc: Kukjin Kim <kgene.kim@samsung.com>
---
drivers/devfreq/Makefile | 2 +-
drivers/devfreq/exynos/Makefile | 2 +
drivers/devfreq/exynos/exynos4_bus.c | 1113 ++++++++++++++++++++++++++++++++++
drivers/devfreq/exynos4_bus.c | 1113 ----------------------------------
4 files changed, 1116 insertions(+), 1114 deletions(-)
create mode 100644 drivers/devfreq/exynos/Makefile
create mode 100644 drivers/devfreq/exynos/exynos4_bus.c
delete mode 100644 drivers/devfreq/exynos4_bus.c
diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile
index 8c46423..3bc1fef 100644
--- a/drivers/devfreq/Makefile
+++ b/drivers/devfreq/Makefile
@@ -5,4 +5,4 @@ obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE) += governor_powersave.o
obj-$(CONFIG_DEVFREQ_GOV_USERSPACE) += governor_userspace.o
# DEVFREQ Drivers
-obj-$(CONFIG_ARM_EXYNOS4_BUS_DEVFREQ) += exynos4_bus.o
+obj-$(CONFIG_ARM_EXYNOS4_BUS_DEVFREQ) += exynos/
diff --git a/drivers/devfreq/exynos/Makefile b/drivers/devfreq/exynos/Makefile
new file mode 100644
index 0000000..1498823
--- /dev/null
+++ b/drivers/devfreq/exynos/Makefile
@@ -0,0 +1,2 @@
+# Exynos DEVFREQ Drivers
+obj-$(CONFIG_ARM_EXYNOS4_BUS_DEVFREQ) += exynos4_bus.o
diff --git a/drivers/devfreq/exynos/exynos4_bus.c b/drivers/devfreq/exynos/exynos4_bus.c
new file mode 100644
index 0000000..7418372
--- /dev/null
+++ b/drivers/devfreq/exynos/exynos4_bus.c
@@ -0,0 +1,1113 @@
+/* drivers/devfreq/exynos4210_memorybus.c
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ * MyungJoo Ham <myungjoo.ham@samsung.com>
+ *
+ * EXYNOS4 - Memory/Bus clock frequency scaling support in DEVFREQ framework
+ * This version supports EXYNOS4210 only. This changes bus frequencies
+ * and vddint voltages. Exynos4412/4212 should be able to be supported
+ * with minor modifications.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/suspend.h>
+#include <linux/opp.h>
+#include <linux/devfreq.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/module.h>
+
+/* Exynos4 ASV has been in the mailing list, but not upstreamed, yet. */
+#ifdef CONFIG_EXYNOS_ASV
+extern unsigned int exynos_result_of_asv;
+#endif
+
+#include <mach/regs-clock.h>
+
+#include <plat/map-s5p.h>
+
+#define MAX_SAFEVOLT 1200000 /* 1.2V */
+
+enum exynos4_busf_type {
+ TYPE_BUSF_EXYNOS4210,
+ TYPE_BUSF_EXYNOS4x12,
+};
+
+/* Assume that the bus is saturated if the utilization is 40% */
+#define BUS_SATURATION_RATIO 40
+
+enum ppmu_counter {
+ PPMU_PMNCNT0 = 0,
+ PPMU_PMCCNT1,
+ PPMU_PMNCNT2,
+ PPMU_PMNCNT3,
+ PPMU_PMNCNT_MAX,
+};
+struct exynos4_ppmu {
+ void __iomem *hw_base;
+ unsigned int ccnt;
+ unsigned int event;
+ unsigned int count[PPMU_PMNCNT_MAX];
+ bool ccnt_overflow;
+ bool count_overflow[PPMU_PMNCNT_MAX];
+};
+
+enum busclk_level_idx {
+ LV_0 = 0,
+ LV_1,
+ LV_2,
+ LV_3,
+ LV_4,
+ _LV_END
+};
+#define EX4210_LV_MAX LV_2
+#define EX4x12_LV_MAX LV_4
+#define EX4210_LV_NUM (LV_2 + 1)
+#define EX4x12_LV_NUM (LV_4 + 1)
+
+struct busfreq_data {
+ enum exynos4_busf_type type;
+ struct device *dev;
+ struct devfreq *devfreq;
+ bool disabled;
+ struct regulator *vdd_int;
+ struct regulator *vdd_mif; /* Exynos4412/4212 only */
+ struct opp *curr_opp;
+ struct exynos4_ppmu dmc[2];
+
+ struct notifier_block pm_notifier;
+ struct mutex lock;
+
+ /* Dividers calculated at boot/probe-time */
+ unsigned int dmc_divtable[_LV_END]; /* DMC0 */
+ unsigned int top_divtable[_LV_END];
+};
+
+struct bus_opp_table {
+ unsigned int idx;
+ unsigned long clk;
+ unsigned long volt;
+};
+
+/* 4210 controls clock of mif and voltage of int */
+static struct bus_opp_table exynos4210_busclk_table[] = {
+ {LV_0, 400000, 1150000},
+ {LV_1, 267000, 1050000},
+ {LV_2, 133000, 1025000},
+ {0, 0, 0},
+};
+
+/*
+ * MIF is the main control knob clock for exynox4x12 MIF/INT
+ * clock and voltage of both mif/int are controlled.
+ */
+static struct bus_opp_table exynos4x12_mifclk_table[] = {
+ {LV_0, 400000, 1100000},
+ {LV_1, 267000, 1000000},
+ {LV_2, 160000, 950000},
+ {LV_3, 133000, 950000},
+ {LV_4, 100000, 950000},
+ {0, 0, 0},
+};
+
+/*
+ * INT is not the control knob of 4x12. LV_x is not meant to represent
+ * the current performance. (MIF does)
+ */
+static struct bus_opp_table exynos4x12_intclk_table[] = {
+ {LV_0, 200000, 1000000},
+ {LV_1, 160000, 950000},
+ {LV_2, 133000, 925000},
+ {LV_3, 100000, 900000},
+ {0, 0, 0},
+};
+
+/* TODO: asv volt definitions are "__initdata"? */
+/* Some chips have different operating voltages */
+static unsigned int exynos4210_asv_volt[][EX4210_LV_NUM] = {
+ {1150000, 1050000, 1050000},
+ {1125000, 1025000, 1025000},
+ {1100000, 1000000, 1000000},
+ {1075000, 975000, 975000},
+ {1050000, 950000, 950000},
+};
+
+static unsigned int exynos4x12_mif_step_50[][EX4x12_LV_NUM] = {
+ /* 400 267 160 133 100 */
+ {1050000, 950000, 900000, 900000, 900000}, /* ASV0 */
+ {1050000, 950000, 900000, 900000, 900000}, /* ASV1 */
+ {1050000, 950000, 900000, 900000, 900000}, /* ASV2 */
+ {1050000, 900000, 900000, 900000, 900000}, /* ASV3 */
+ {1050000, 900000, 900000, 900000, 850000}, /* ASV4 */
+ {1050000, 900000, 900000, 850000, 850000}, /* ASV5 */
+ {1050000, 900000, 850000, 850000, 850000}, /* ASV6 */
+ {1050000, 900000, 850000, 850000, 850000}, /* ASV7 */
+ {1050000, 900000, 850000, 850000, 850000}, /* ASV8 */
+};
+
+static unsigned int exynos4x12_int_volt[][EX4x12_LV_NUM] = {
+ /* 200 160 133 100 */
+ {1000000, 950000, 925000, 900000}, /* ASV0 */
+ {975000, 925000, 925000, 900000}, /* ASV1 */
+ {950000, 925000, 900000, 875000}, /* ASV2 */
+ {950000, 900000, 900000, 875000}, /* ASV3 */
+ {925000, 875000, 875000, 875000}, /* ASV4 */
+ {900000, 850000, 850000, 850000}, /* ASV5 */
+ {900000, 850000, 850000, 850000}, /* ASV6 */
+ {900000, 850000, 850000, 850000}, /* ASV7 */
+ {900000, 850000, 850000, 850000}, /* ASV8 */
+};
+
+/*** Clock Divider Data for Exynos4210 ***/
+static unsigned int exynos4210_clkdiv_dmc0[][8] = {
+ /*
+ * Clock divider value for following
+ * { DIVACP, DIVACP_PCLK, DIVDPHY, DIVDMC, DIVDMCD
+ * DIVDMCP, DIVCOPY2, DIVCORE_TIMERS }
+ */
+
+ /* DMC L0: 400MHz */
+ { 3, 1, 1, 1, 1, 1, 3, 1 },
+ /* DMC L1: 266.7MHz */
+ { 4, 1, 1, 2, 1, 1, 3, 1 },
+ /* DMC L2: 133MHz */
+ { 5, 1, 1, 5, 1, 1, 3, 1 },
+};
+static unsigned int exynos4210_clkdiv_top[][5] = {
+ /*
+ * Clock divider value for following
+ * { DIVACLK200, DIVACLK100, DIVACLK160, DIVACLK133, DIVONENAND }
+ */
+ /* ACLK200 L0: 200MHz */
+ { 3, 7, 4, 5, 1 },
+ /* ACLK200 L1: 160MHz */
+ { 4, 7, 5, 6, 1 },
+ /* ACLK200 L2: 133MHz */
+ { 5, 7, 7, 7, 1 },
+};
+static unsigned int exynos4210_clkdiv_lr_bus[][2] = {
+ /*
+ * Clock divider value for following
+ * { DIVGDL/R, DIVGPL/R }
+ */
+ /* ACLK_GDL/R L1: 200MHz */
+ { 3, 1 },
+ /* ACLK_GDL/R L2: 160MHz */
+ { 4, 1 },
+ /* ACLK_GDL/R L3: 133MHz */
+ { 5, 1 },
+};
+
+/*** Clock Divider Data for Exynos4212/4412 ***/
+static unsigned int exynos4x12_clkdiv_dmc0[][6] = {
+ /*
+ * Clock divider value for following
+ * { DIVACP, DIVACP_PCLK, DIVDPHY, DIVDMC, DIVDMCD
+ * DIVDMCP}
+ */
+
+ /* DMC L0: 400MHz */
+ {3, 1, 1, 1, 1, 1},
+ /* DMC L1: 266.7MHz */
+ {4, 1, 1, 2, 1, 1},
+ /* DMC L2: 160MHz */
+ {5, 1, 1, 4, 1, 1},
+ /* DMC L3: 133MHz */
+ {5, 1, 1, 5, 1, 1},
+ /* DMC L4: 100MHz */
+ {7, 1, 1, 7, 1, 1},
+};
+static unsigned int exynos4x12_clkdiv_dmc1[][6] = {
+ /*
+ * Clock divider value for following
+ * { G2DACP, DIVC2C, DIVC2C_ACLK }
+ */
+
+ /* DMC L0: 400MHz */
+ {3, 1, 1},
+ /* DMC L1: 266.7MHz */
+ {4, 2, 1},
+ /* DMC L2: 160MHz */
+ {5, 4, 1},
+ /* DMC L3: 133MHz */
+ {5, 5, 1},
+ /* DMC L4: 100MHz */
+ {7, 7, 1},
+};
+static unsigned int exynos4x12_clkdiv_top[][5] = {
+ /*
+ * Clock divider value for following
+ * { DIVACLK266_GPS, DIVACLK100, DIVACLK160,
+ DIVACLK133, DIVONENAND }
+ */
+
+ /* ACLK_GDL/R L0: 200MHz */
+ {2, 7, 4, 5, 1},
+ /* ACLK_GDL/R L1: 200MHz */
+ {2, 7, 4, 5, 1},
+ /* ACLK_GDL/R L2: 160MHz */
+ {4, 7, 5, 7, 1},
+ /* ACLK_GDL/R L3: 133MHz */
+ {4, 7, 5, 7, 1},
+ /* ACLK_GDL/R L4: 100MHz */
+ {7, 7, 7, 7, 1},
+};
+static unsigned int exynos4x12_clkdiv_lr_bus[][2] = {
+ /*
+ * Clock divider value for following
+ * { DIVGDL/R, DIVGPL/R }
+ */
+
+ /* ACLK_GDL/R L0: 200MHz */
+ {3, 1},
+ /* ACLK_GDL/R L1: 200MHz */
+ {3, 1},
+ /* ACLK_GDL/R L2: 160MHz */
+ {4, 1},
+ /* ACLK_GDL/R L3: 133MHz */
+ {5, 1},
+ /* ACLK_GDL/R L4: 100MHz */
+ {7, 1},
+};
+static unsigned int exynos4x12_clkdiv_sclkip[][3] = {
+ /*
+ * Clock divider value for following
+ * { DIVMFC, DIVJPEG, DIVFIMC0~3}
+ */
+
+ /* SCLK_MFC: 200MHz */
+ {3, 3, 4},
+ /* SCLK_MFC: 200MHz */
+ {3, 3, 4},
+ /* SCLK_MFC: 160MHz */
+ {4, 4, 5},
+ /* SCLK_MFC: 133MHz */
+ {5, 5, 5},
+ /* SCLK_MFC: 100MHz */
+ {7, 7, 7},
+};
+
+
+static int exynos4210_set_busclk(struct busfreq_data *data, struct opp *opp)
+{
+ unsigned int index;
+ unsigned int tmp;
+
+ for (index = LV_0; index < EX4210_LV_NUM; index++)
+ if (opp_get_freq(opp) == exynos4210_busclk_table[index].clk)
+ break;
+
+ if (index == EX4210_LV_NUM)
+ return -EINVAL;
+
+ /* Change Divider - DMC0 */
+ tmp = data->dmc_divtable[index];
+
+ __raw_writel(tmp, EXYNOS4_CLKDIV_DMC0);
+
+ do {
+ tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_DMC0);
+ } while (tmp & 0x11111111);
+
+ /* Change Divider - TOP */
+ tmp = data->top_divtable[index];
+
+ __raw_writel(tmp, EXYNOS4_CLKDIV_TOP);
+
+ do {
+ tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_TOP);
+ } while (tmp & 0x11111);
+
+ /* Change Divider - LEFTBUS */
+ tmp = __raw_readl(EXYNOS4_CLKDIV_LEFTBUS);
+
+ tmp &= ~(EXYNOS4_CLKDIV_BUS_GDLR_MASK | EXYNOS4_CLKDIV_BUS_GPLR_MASK);
+
+ tmp |= ((exynos4210_clkdiv_lr_bus[index][0] <<
+ EXYNOS4_CLKDIV_BUS_GDLR_SHIFT) |
+ (exynos4210_clkdiv_lr_bus[index][1] <<
+ EXYNOS4_CLKDIV_BUS_GPLR_SHIFT));
+
+ __raw_writel(tmp, EXYNOS4_CLKDIV_LEFTBUS);
+
+ do {
+ tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_LEFTBUS);
+ } while (tmp & 0x11);
+
+ /* Change Divider - RIGHTBUS */
+ tmp = __raw_readl(EXYNOS4_CLKDIV_RIGHTBUS);
+
+ tmp &= ~(EXYNOS4_CLKDIV_BUS_GDLR_MASK | EXYNOS4_CLKDIV_BUS_GPLR_MASK);
+
+ tmp |= ((exynos4210_clkdiv_lr_bus[index][0] <<
+ EXYNOS4_CLKDIV_BUS_GDLR_SHIFT) |
+ (exynos4210_clkdiv_lr_bus[index][1] <<
+ EXYNOS4_CLKDIV_BUS_GPLR_SHIFT));
+
+ __raw_writel(tmp, EXYNOS4_CLKDIV_RIGHTBUS);
+
+ do {
+ tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_RIGHTBUS);
+ } while (tmp & 0x11);
+
+ return 0;
+}
+
+static int exynos4x12_set_busclk(struct busfreq_data *data, struct opp *opp)
+{
+ unsigned int index;
+ unsigned int tmp;
+
+ for (index = LV_0; index < EX4x12_LV_NUM; index++)
+ if (opp_get_freq(opp) == exynos4x12_mifclk_table[index].clk)
+ break;
+
+ if (index == EX4x12_LV_NUM)
+ return -EINVAL;
+
+ /* Change Divider - DMC0 */
+ tmp = data->dmc_divtable[index];
+
+ __raw_writel(tmp, EXYNOS4_CLKDIV_DMC0);
+
+ do {
+ tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_DMC0);
+ } while (tmp & 0x11111111);
+
+ /* Change Divider - DMC1 */
+ tmp = __raw_readl(EXYNOS4_CLKDIV_DMC1);
+
+ tmp &= ~(EXYNOS4_CLKDIV_DMC1_G2D_ACP_MASK |
+ EXYNOS4_CLKDIV_DMC1_C2C_MASK |
+ EXYNOS4_CLKDIV_DMC1_C2CACLK_MASK);
+
+ tmp |= ((exynos4x12_clkdiv_dmc1[index][0] <<
+ EXYNOS4_CLKDIV_DMC1_G2D_ACP_SHIFT) |
+ (exynos4x12_clkdiv_dmc1[index][1] <<
+ EXYNOS4_CLKDIV_DMC1_C2C_SHIFT) |
+ (exynos4x12_clkdiv_dmc1[index][2] <<
+ EXYNOS4_CLKDIV_DMC1_C2CACLK_SHIFT));
+
+ __raw_writel(tmp, EXYNOS4_CLKDIV_DMC1);
+
+ do {
+ tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_DMC1);
+ } while (tmp & 0x111111);
+
+ /* Change Divider - TOP */
+ tmp = __raw_readl(EXYNOS4_CLKDIV_TOP);
+
+ tmp &= ~(EXYNOS4_CLKDIV_TOP_ACLK266_GPS_MASK |
+ EXYNOS4_CLKDIV_TOP_ACLK100_MASK |
+ EXYNOS4_CLKDIV_TOP_ACLK160_MASK |
+ EXYNOS4_CLKDIV_TOP_ACLK133_MASK |
+ EXYNOS4_CLKDIV_TOP_ONENAND_MASK);
+
+ tmp |= ((exynos4x12_clkdiv_top[index][0] <<
+ EXYNOS4_CLKDIV_TOP_ACLK266_GPS_SHIFT) |
+ (exynos4x12_clkdiv_top[index][1] <<
+ EXYNOS4_CLKDIV_TOP_ACLK100_SHIFT) |
+ (exynos4x12_clkdiv_top[index][2] <<
+ EXYNOS4_CLKDIV_TOP_ACLK160_SHIFT) |
+ (exynos4x12_clkdiv_top[index][3] <<
+ EXYNOS4_CLKDIV_TOP_ACLK133_SHIFT) |
+ (exynos4x12_clkdiv_top[index][4] <<
+ EXYNOS4_CLKDIV_TOP_ONENAND_SHIFT));
+
+ __raw_writel(tmp, EXYNOS4_CLKDIV_TOP);
+
+ do {
+ tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_TOP);
+ } while (tmp & 0x11111);
+
+ /* Change Divider - LEFTBUS */
+ tmp = __raw_readl(EXYNOS4_CLKDIV_LEFTBUS);
+
+ tmp &= ~(EXYNOS4_CLKDIV_BUS_GDLR_MASK | EXYNOS4_CLKDIV_BUS_GPLR_MASK);
+
+ tmp |= ((exynos4x12_clkdiv_lr_bus[index][0] <<
+ EXYNOS4_CLKDIV_BUS_GDLR_SHIFT) |
+ (exynos4x12_clkdiv_lr_bus[index][1] <<
+ EXYNOS4_CLKDIV_BUS_GPLR_SHIFT));
+
+ __raw_writel(tmp, EXYNOS4_CLKDIV_LEFTBUS);
+
+ do {
+ tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_LEFTBUS);
+ } while (tmp & 0x11);
+
+ /* Change Divider - RIGHTBUS */
+ tmp = __raw_readl(EXYNOS4_CLKDIV_RIGHTBUS);
+
+ tmp &= ~(EXYNOS4_CLKDIV_BUS_GDLR_MASK | EXYNOS4_CLKDIV_BUS_GPLR_MASK);
+
+ tmp |= ((exynos4x12_clkdiv_lr_bus[index][0] <<
+ EXYNOS4_CLKDIV_BUS_GDLR_SHIFT) |
+ (exynos4x12_clkdiv_lr_bus[index][1] <<
+ EXYNOS4_CLKDIV_BUS_GPLR_SHIFT));
+
+ __raw_writel(tmp, EXYNOS4_CLKDIV_RIGHTBUS);
+
+ do {
+ tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_RIGHTBUS);
+ } while (tmp & 0x11);
+
+ /* Change Divider - MFC */
+ tmp = __raw_readl(EXYNOS4_CLKDIV_MFC);
+
+ tmp &= ~(EXYNOS4_CLKDIV_MFC_MASK);
+
+ tmp |= ((exynos4x12_clkdiv_sclkip[index][0] <<
+ EXYNOS4_CLKDIV_MFC_SHIFT));
+
+ __raw_writel(tmp, EXYNOS4_CLKDIV_MFC);
+
+ do {
+ tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_MFC);
+ } while (tmp & 0x1);
+
+ /* Change Divider - JPEG */
+ tmp = __raw_readl(EXYNOS4_CLKDIV_CAM1);
+
+ tmp &= ~(EXYNOS4_CLKDIV_CAM1_JPEG_MASK);
+
+ tmp |= ((exynos4x12_clkdiv_sclkip[index][1] <<
+ EXYNOS4_CLKDIV_CAM1_JPEG_SHIFT));
+
+ __raw_writel(tmp, EXYNOS4_CLKDIV_CAM1);
+
+ do {
+ tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_CAM1);
+ } while (tmp & 0x1);
+
+ /* Change Divider - FIMC0~3 */
+ tmp = __raw_readl(EXYNOS4_CLKDIV_CAM);
+
+ tmp &= ~(EXYNOS4_CLKDIV_CAM_FIMC0_MASK | EXYNOS4_CLKDIV_CAM_FIMC1_MASK |
+ EXYNOS4_CLKDIV_CAM_FIMC2_MASK | EXYNOS4_CLKDIV_CAM_FIMC3_MASK);
+
+ tmp |= ((exynos4x12_clkdiv_sclkip[index][2] <<
+ EXYNOS4_CLKDIV_CAM_FIMC0_SHIFT) |
+ (exynos4x12_clkdiv_sclkip[index][2] <<
+ EXYNOS4_CLKDIV_CAM_FIMC1_SHIFT) |
+ (exynos4x12_clkdiv_sclkip[index][2] <<
+ EXYNOS4_CLKDIV_CAM_FIMC2_SHIFT) |
+ (exynos4x12_clkdiv_sclkip[index][2] <<
+ EXYNOS4_CLKDIV_CAM_FIMC3_SHIFT));
+
+ __raw_writel(tmp, EXYNOS4_CLKDIV_CAM);
+
+ do {
+ tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_CAM1);
+ } while (tmp & 0x1111);
+
+ return 0;
+}
+
+
+static void busfreq_mon_reset(struct busfreq_data *data)
+{
+ unsigned int i;
+
+ for (i = 0; i < 2; i++) {
+ void __iomem *ppmu_base = data->dmc[i].hw_base;
+
+ /* Reset PPMU */
+ __raw_writel(0x8000000f, ppmu_base + 0xf010);
+ __raw_writel(0x8000000f, ppmu_base + 0xf050);
+ __raw_writel(0x6, ppmu_base + 0xf000);
+ __raw_writel(0x0, ppmu_base + 0xf100);
+
+ /* Set PPMU Event */
+ data->dmc[i].event = 0x6;
+ __raw_writel(((data->dmc[i].event << 12) | 0x1),
+ ppmu_base + 0xfc);
+
+ /* Start PPMU */
+ __raw_writel(0x1, ppmu_base + 0xf000);
+ }
+}
+
+static void exynos4_read_ppmu(struct busfreq_data *data)
+{
+ int i, j;
+
+ for (i = 0; i < 2; i++) {
+ void __iomem *ppmu_base = data->dmc[i].hw_base;
+ u32 overflow;
+
+ /* Stop PPMU */
+ __raw_writel(0x0, ppmu_base + 0xf000);
+
+ /* Update local data from PPMU */
+ overflow = __raw_readl(ppmu_base + 0xf050);
+
+ data->dmc[i].ccnt = __raw_readl(ppmu_base + 0xf100);
+ data->dmc[i].ccnt_overflow = overflow & (1 << 31);
+
+ for (j = 0; j < PPMU_PMNCNT_MAX; j++) {
+ data->dmc[i].count[j] = __raw_readl(
+ ppmu_base + (0xf110 + (0x10 * j)));
+ data->dmc[i].count_overflow[j] = overflow & (1 << j);
+ }
+ }
+
+ busfreq_mon_reset(data);
+}
+
+static int exynos4x12_get_intspec(unsigned long mifclk)
+{
+ int i = 0;
+
+ while (exynos4x12_intclk_table[i].clk) {
+ if (exynos4x12_intclk_table[i].clk <= mifclk)
+ return i;
+ i++;
+ }
+
+ return -EINVAL;
+}
+
+static int exynos4_bus_setvolt(struct busfreq_data *data, struct opp *opp,
+ struct opp *oldopp)
+{
+ int err = 0, tmp;
+ unsigned long volt = opp_get_voltage(opp);
+
+ switch (data->type) {
+ case TYPE_BUSF_EXYNOS4210:
+ /* OPP represents DMC clock + INT voltage */
+ err = regulator_set_voltage(data->vdd_int, volt,
+ MAX_SAFEVOLT);
+ break;
+ case TYPE_BUSF_EXYNOS4x12:
+ /* OPP represents MIF clock + MIF voltage */
+ err = regulator_set_voltage(data->vdd_mif, volt,
+ MAX_SAFEVOLT);
+ if (err)
+ break;
+
+ tmp = exynos4x12_get_intspec(opp_get_freq(opp));
+ if (tmp < 0) {
+ err = tmp;
+ regulator_set_voltage(data->vdd_mif,
+ opp_get_voltage(oldopp),
+ MAX_SAFEVOLT);
+ break;
+ }
+ err = regulator_set_voltage(data->vdd_int,
+ exynos4x12_intclk_table[tmp].volt,
+ MAX_SAFEVOLT);
+ /* Try to recover */
+ if (err)
+ regulator_set_voltage(data->vdd_mif,
+ opp_get_voltage(oldopp),
+ MAX_SAFEVOLT);
+ break;
+ default:
+ err = -EINVAL;
+ }
+
+ return err;
+}
+
+static int exynos4_bus_target(struct device *dev, unsigned long *_freq,
+ u32 flags)
+{
+ int err = 0;
+ struct platform_device *pdev = container_of(dev, struct platform_device,
+ dev);
+ struct busfreq_data *data = platform_get_drvdata(pdev);
+ struct opp *opp = devfreq_recommended_opp(dev, _freq, flags);
+ unsigned long freq = opp_get_freq(opp);
+ unsigned long old_freq = opp_get_freq(data->curr_opp);
+
+ if (IS_ERR(opp))
+ return PTR_ERR(opp);
+
+ if (old_freq == freq)
+ return 0;
+
+ dev_dbg(dev, "targetting %lukHz %luuV\n", freq, opp_get_voltage(opp));
+
+ mutex_lock(&data->lock);
+
+ if (data->disabled)
+ goto out;
+
+ if (old_freq < freq)
+ err = exynos4_bus_setvolt(data, opp, data->curr_opp);
+ if (err)
+ goto out;
+
+ if (old_freq != freq) {
+ switch (data->type) {
+ case TYPE_BUSF_EXYNOS4210:
+ err = exynos4210_set_busclk(data, opp);
+ break;
+ case TYPE_BUSF_EXYNOS4x12:
+ err = exynos4x12_set_busclk(data, opp);
+ break;
+ default:
+ err = -EINVAL;
+ }
+ }
+ if (err)
+ goto out;
+
+ if (old_freq > freq)
+ err = exynos4_bus_setvolt(data, opp, data->curr_opp);
+ if (err)
+ goto out;
+
+ data->curr_opp = opp;
+out:
+ mutex_unlock(&data->lock);
+ return err;
+}
+
+static int exynos4_get_busier_dmc(struct busfreq_data *data)
+{
+ u64 p0 = data->dmc[0].count[0];
+ u64 p1 = data->dmc[1].count[0];
+
+ p0 *= data->dmc[1].ccnt;
+ p1 *= data->dmc[0].ccnt;
+
+ if (data->dmc[1].ccnt == 0)
+ return 0;
+
+ if (p0 > p1)
+ return 0;
+ return 1;
+}
+
+static int exynos4_bus_get_dev_status(struct device *dev,
+ struct devfreq_dev_status *stat)
+{
+ struct busfreq_data *data = dev_get_drvdata(dev);
+ int busier_dmc;
+ int cycles_x2 = 2; /* 2 x cycles */
+ void __iomem *addr;
+ u32 timing;
+ u32 memctrl;
+
+ exynos4_read_ppmu(data);
+ busier_dmc = exynos4_get_busier_dmc(data);
+ stat->current_frequency = opp_get_freq(data->curr_opp);
+
+ if (busier_dmc)
+ addr = S5P_VA_DMC1;
+ else
+ addr = S5P_VA_DMC0;
+
+ memctrl = __raw_readl(addr + 0x04); /* one of DDR2/3/LPDDR2 */
+ timing = __raw_readl(addr + 0x38); /* CL or WL/RL values */
+
+ switch ((memctrl >> 8) & 0xf) {
+ case 0x4: /* DDR2 */
+ cycles_x2 = ((timing >> 16) & 0xf) * 2;
+ break;
+ case 0x5: /* LPDDR2 */
+ case 0x6: /* DDR3 */
+ cycles_x2 = ((timing >> 8) & 0xf) + ((timing >> 0) & 0xf);
+ break;
+ default:
+ pr_err("%s: Unknown Memory Type(%d).\n", __func__,
+ (memctrl >> 8) & 0xf);
+ return -EINVAL;
+ }
+
+ /* Number of cycles spent on memory access */
+ stat->busy_time = data->dmc[busier_dmc].count[0] / 2 * (cycles_x2 + 2);
+ stat->busy_time *= 100 / BUS_SATURATION_RATIO;
+ stat->total_time = data->dmc[busier_dmc].ccnt;
+
+ /* If the counters have overflown, retry */
+ if (data->dmc[busier_dmc].ccnt_overflow ||
+ data->dmc[busier_dmc].count_overflow[0])
+ return -EAGAIN;
+
+ return 0;
+}
+
+static void exynos4_bus_exit(struct device *dev)
+{
+ struct busfreq_data *data = dev_get_drvdata(dev);
+
+ devfreq_unregister_opp_notifier(dev, data->devfreq);
+}
+
+static struct devfreq_dev_profile exynos4_devfreq_profile = {
+ .initial_freq = 400000,
+ .polling_ms = 50,
+ .target = exynos4_bus_target,
+ .get_dev_status = exynos4_bus_get_dev_status,
+ .exit = exynos4_bus_exit,
+};
+
+static int exynos4210_init_tables(struct busfreq_data *data)
+{
+ u32 tmp;
+ int mgrp;
+ int i, err = 0;
+
+ tmp = __raw_readl(EXYNOS4_CLKDIV_DMC0);
+ for (i = LV_0; i < EX4210_LV_NUM; i++) {
+ tmp &= ~(EXYNOS4_CLKDIV_DMC0_ACP_MASK |
+ EXYNOS4_CLKDIV_DMC0_ACPPCLK_MASK |
+ EXYNOS4_CLKDIV_DMC0_DPHY_MASK |
+ EXYNOS4_CLKDIV_DMC0_DMC_MASK |
+ EXYNOS4_CLKDIV_DMC0_DMCD_MASK |
+ EXYNOS4_CLKDIV_DMC0_DMCP_MASK |
+ EXYNOS4_CLKDIV_DMC0_COPY2_MASK |
+ EXYNOS4_CLKDIV_DMC0_CORETI_MASK);
+
+ tmp |= ((exynos4210_clkdiv_dmc0[i][0] <<
+ EXYNOS4_CLKDIV_DMC0_ACP_SHIFT) |
+ (exynos4210_clkdiv_dmc0[i][1] <<
+ EXYNOS4_CLKDIV_DMC0_ACPPCLK_SHIFT) |
+ (exynos4210_clkdiv_dmc0[i][2] <<
+ EXYNOS4_CLKDIV_DMC0_DPHY_SHIFT) |
+ (exynos4210_clkdiv_dmc0[i][3] <<
+ EXYNOS4_CLKDIV_DMC0_DMC_SHIFT) |
+ (exynos4210_clkdiv_dmc0[i][4] <<
+ EXYNOS4_CLKDIV_DMC0_DMCD_SHIFT) |
+ (exynos4210_clkdiv_dmc0[i][5] <<
+ EXYNOS4_CLKDIV_DMC0_DMCP_SHIFT) |
+ (exynos4210_clkdiv_dmc0[i][6] <<
+ EXYNOS4_CLKDIV_DMC0_COPY2_SHIFT) |
+ (exynos4210_clkdiv_dmc0[i][7] <<
+ EXYNOS4_CLKDIV_DMC0_CORETI_SHIFT));
+
+ data->dmc_divtable[i] = tmp;
+ }
+
+ tmp = __raw_readl(EXYNOS4_CLKDIV_TOP);
+ for (i = LV_0; i < EX4210_LV_NUM; i++) {
+ tmp &= ~(EXYNOS4_CLKDIV_TOP_ACLK200_MASK |
+ EXYNOS4_CLKDIV_TOP_ACLK100_MASK |
+ EXYNOS4_CLKDIV_TOP_ACLK160_MASK |
+ EXYNOS4_CLKDIV_TOP_ACLK133_MASK |
+ EXYNOS4_CLKDIV_TOP_ONENAND_MASK);
+
+ tmp |= ((exynos4210_clkdiv_top[i][0] <<
+ EXYNOS4_CLKDIV_TOP_ACLK200_SHIFT) |
+ (exynos4210_clkdiv_top[i][1] <<
+ EXYNOS4_CLKDIV_TOP_ACLK100_SHIFT) |
+ (exynos4210_clkdiv_top[i][2] <<
+ EXYNOS4_CLKDIV_TOP_ACLK160_SHIFT) |
+ (exynos4210_clkdiv_top[i][3] <<
+ EXYNOS4_CLKDIV_TOP_ACLK133_SHIFT) |
+ (exynos4210_clkdiv_top[i][4] <<
+ EXYNOS4_CLKDIV_TOP_ONENAND_SHIFT));
+
+ data->top_divtable[i] = tmp;
+ }
+
+#ifdef CONFIG_EXYNOS_ASV
+ tmp = exynos4_result_of_asv;
+#else
+ tmp = 0; /* Max voltages for the reliability of the unknown */
+#endif
+
+ pr_debug("ASV Group of Exynos4 is %d\n", tmp);
+ /* Use merged grouping for voltage */
+ switch (tmp) {
+ case 0:
+ mgrp = 0;
+ break;
+ case 1:
+ case 2:
+ mgrp = 1;
+ break;
+ case 3:
+ case 4:
+ mgrp = 2;
+ break;
+ case 5:
+ case 6:
+ mgrp = 3;
+ break;
+ case 7:
+ mgrp = 4;
+ break;
+ default:
+ pr_warn("Unknown ASV Group. Use max voltage.\n");
+ mgrp = 0;
+ }
+
+ for (i = LV_0; i < EX4210_LV_NUM; i++)
+ exynos4210_busclk_table[i].volt = exynos4210_asv_volt[mgrp][i];
+
+ for (i = LV_0; i < EX4210_LV_NUM; i++) {
+ err = opp_add(data->dev, exynos4210_busclk_table[i].clk,
+ exynos4210_busclk_table[i].volt);
+ if (err) {
+ dev_err(data->dev, "Cannot add opp entries.\n");
+ return err;
+ }
+ }
+
+
+ return 0;
+}
+
+static int exynos4x12_init_tables(struct busfreq_data *data)
+{
+ unsigned int i;
+ unsigned int tmp;
+ int ret;
+
+ /* Enable pause function for DREX2 DVFS */
+ tmp = __raw_readl(EXYNOS4_DMC_PAUSE_CTRL);
+ tmp |= EXYNOS4_DMC_PAUSE_ENABLE;
+ __raw_writel(tmp, EXYNOS4_DMC_PAUSE_CTRL);
+
+ tmp = __raw_readl(EXYNOS4_CLKDIV_DMC0);
+
+ for (i = 0; i < EX4x12_LV_NUM; i++) {
+ tmp &= ~(EXYNOS4_CLKDIV_DMC0_ACP_MASK |
+ EXYNOS4_CLKDIV_DMC0_ACPPCLK_MASK |
+ EXYNOS4_CLKDIV_DMC0_DPHY_MASK |
+ EXYNOS4_CLKDIV_DMC0_DMC_MASK |
+ EXYNOS4_CLKDIV_DMC0_DMCD_MASK |
+ EXYNOS4_CLKDIV_DMC0_DMCP_MASK);
+
+ tmp |= ((exynos4x12_clkdiv_dmc0[i][0] <<
+ EXYNOS4_CLKDIV_DMC0_ACP_SHIFT) |
+ (exynos4x12_clkdiv_dmc0[i][1] <<
+ EXYNOS4_CLKDIV_DMC0_ACPPCLK_SHIFT) |
+ (exynos4x12_clkdiv_dmc0[i][2] <<
+ EXYNOS4_CLKDIV_DMC0_DPHY_SHIFT) |
+ (exynos4x12_clkdiv_dmc0[i][3] <<
+ EXYNOS4_CLKDIV_DMC0_DMC_SHIFT) |
+ (exynos4x12_clkdiv_dmc0[i][4] <<
+ EXYNOS4_CLKDIV_DMC0_DMCD_SHIFT) |
+ (exynos4x12_clkdiv_dmc0[i][5] <<
+ EXYNOS4_CLKDIV_DMC0_DMCP_SHIFT));
+
+ data->dmc_divtable[i] = tmp;
+ }
+
+#ifdef CONFIG_EXYNOS_ASV
+ tmp = exynos4_result_of_asv;
+#else
+ tmp = 0; /* Max voltages for the reliability of the unknown */
+#endif
+
+ if (tmp > 8)
+ tmp = 0;
+ pr_debug("ASV Group of Exynos4x12 is %d\n", tmp);
+
+ for (i = 0; i < EX4x12_LV_NUM; i++) {
+ exynos4x12_mifclk_table[i].volt =
+ exynos4x12_mif_step_50[tmp][i];
+ exynos4x12_intclk_table[i].volt =
+ exynos4x12_int_volt[tmp][i];
+ }
+
+ for (i = 0; i < EX4x12_LV_NUM; i++) {
+ ret = opp_add(data->dev, exynos4x12_mifclk_table[i].clk,
+ exynos4x12_mifclk_table[i].volt);
+ if (ret) {
+ dev_err(data->dev, "Fail to add opp entries.\n");
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int exynos4_busfreq_pm_notifier_event(struct notifier_block *this,
+ unsigned long event, void *ptr)
+{
+ struct busfreq_data *data = container_of(this, struct busfreq_data,
+ pm_notifier);
+ struct opp *opp;
+ unsigned long maxfreq = ULONG_MAX;
+ int err = 0;
+
+ switch (event) {
+ case PM_SUSPEND_PREPARE:
+ /* Set Fastest and Deactivate DVFS */
+ mutex_lock(&data->lock);
+
+ data->disabled = true;
+
+ opp = opp_find_freq_floor(data->dev, &maxfreq);
+
+ err = exynos4_bus_setvolt(data, opp, data->curr_opp);
+ if (err)
+ goto unlock;
+
+ switch (data->type) {
+ case TYPE_BUSF_EXYNOS4210:
+ err = exynos4210_set_busclk(data, opp);
+ break;
+ case TYPE_BUSF_EXYNOS4x12:
+ err = exynos4x12_set_busclk(data, opp);
+ break;
+ default:
+ err = -EINVAL;
+ }
+ if (err)
+ goto unlock;
+
+ data->curr_opp = opp;
+unlock:
+ mutex_unlock(&data->lock);
+ if (err)
+ return err;
+ return NOTIFY_OK;
+ case PM_POST_RESTORE:
+ case PM_POST_SUSPEND:
+ /* Reactivate */
+ mutex_lock(&data->lock);
+ data->disabled = false;
+ mutex_unlock(&data->lock);
+ return NOTIFY_OK;
+ }
+
+ return NOTIFY_DONE;
+}
+
+static __devinit int exynos4_busfreq_probe(struct platform_device *pdev)
+{
+ struct busfreq_data *data;
+ struct opp *opp;
+ struct device *dev = &pdev->dev;
+ int err = 0;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(struct busfreq_data), GFP_KERNEL);
+ if (data == NULL) {
+ dev_err(dev, "Cannot allocate memory.\n");
+ return -ENOMEM;
+ }
+
+ data->type = pdev->id_entry->driver_data;
+ data->dmc[0].hw_base = S5P_VA_DMC0;
+ data->dmc[1].hw_base = S5P_VA_DMC1;
+ data->pm_notifier.notifier_call = exynos4_busfreq_pm_notifier_event;
+ data->dev = dev;
+ mutex_init(&data->lock);
+
+ switch (data->type) {
+ case TYPE_BUSF_EXYNOS4210:
+ err = exynos4210_init_tables(data);
+ break;
+ case TYPE_BUSF_EXYNOS4x12:
+ err = exynos4x12_init_tables(data);
+ break;
+ default:
+ dev_err(dev, "Cannot determine the device id %d\n", data->type);
+ err = -EINVAL;
+ }
+ if (err)
+ return err;
+
+ data->vdd_int = devm_regulator_get(dev, "vdd_int");
+ if (IS_ERR(data->vdd_int)) {
+ dev_err(dev, "Cannot get the regulator \"vdd_int\"\n");
+ return PTR_ERR(data->vdd_int);
+ }
+ if (data->type == TYPE_BUSF_EXYNOS4x12) {
+ data->vdd_mif = devm_regulator_get(dev, "vdd_mif");
+ if (IS_ERR(data->vdd_mif)) {
+ dev_err(dev, "Cannot get the regulator \"vdd_mif\"\n");
+ return PTR_ERR(data->vdd_mif);
+ }
+ }
+
+ opp = opp_find_freq_floor(dev, &exynos4_devfreq_profile.initial_freq);
+ if (IS_ERR(opp)) {
+ dev_err(dev, "Invalid initial frequency %lu kHz.\n",
+ exynos4_devfreq_profile.initial_freq);
+ return PTR_ERR(opp);
+ }
+ data->curr_opp = opp;
+
+ platform_set_drvdata(pdev, data);
+
+ busfreq_mon_reset(data);
+
+ data->devfreq = devfreq_add_device(dev, &exynos4_devfreq_profile,
+ "simple_ondemand", NULL);
+ if (IS_ERR(data->devfreq))
+ return PTR_ERR(data->devfreq);
+
+ devfreq_register_opp_notifier(dev, data->devfreq);
+
+ err = register_pm_notifier(&data->pm_notifier);
+ if (err) {
+ dev_err(dev, "Failed to setup pm notifier\n");
+ devfreq_remove_device(data->devfreq);
+ return err;
+ }
+
+ return 0;
+}
+
+static __devexit int exynos4_busfreq_remove(struct platform_device *pdev)
+{
+ struct busfreq_data *data = platform_get_drvdata(pdev);
+
+ unregister_pm_notifier(&data->pm_notifier);
+ devfreq_remove_device(data->devfreq);
+
+ return 0;
+}
+
+static int exynos4_busfreq_resume(struct device *dev)
+{
+ struct busfreq_data *data = dev_get_drvdata(dev);
+
+ busfreq_mon_reset(data);
+ return 0;
+}
+
+static const struct dev_pm_ops exynos4_busfreq_pm = {
+ .resume = exynos4_busfreq_resume,
+};
+
+static const struct platform_device_id exynos4_busfreq_id[] = {
+ { "exynos4210-busfreq", TYPE_BUSF_EXYNOS4210 },
+ { "exynos4412-busfreq", TYPE_BUSF_EXYNOS4x12 },
+ { "exynos4212-busfreq", TYPE_BUSF_EXYNOS4x12 },
+ { },
+};
+
+static struct platform_driver exynos4_busfreq_driver = {
+ .probe = exynos4_busfreq_probe,
+ .remove = __devexit_p(exynos4_busfreq_remove),
+ .id_table = exynos4_busfreq_id,
+ .driver = {
+ .name = "exynos4-busfreq",
+ .owner = THIS_MODULE,
+ .pm = &exynos4_busfreq_pm,
+ },
+};
+
+static int __init exynos4_busfreq_init(void)
+{
+ return platform_driver_register(&exynos4_busfreq_driver);
+}
+late_initcall(exynos4_busfreq_init);
+
+static void __exit exynos4_busfreq_exit(void)
+{
+ platform_driver_unregister(&exynos4_busfreq_driver);
+}
+module_exit(exynos4_busfreq_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("EXYNOS4 busfreq driver with devfreq framework");
+MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
diff --git a/drivers/devfreq/exynos4_bus.c b/drivers/devfreq/exynos4_bus.c
deleted file mode 100644
index 7418372..0000000
--- a/drivers/devfreq/exynos4_bus.c
+++ /dev/null
@@ -1,1113 +0,0 @@
-/* drivers/devfreq/exynos4210_memorybus.c
- *
- * Copyright (c) 2011 Samsung Electronics Co., Ltd.
- * http://www.samsung.com/
- * MyungJoo Ham <myungjoo.ham@samsung.com>
- *
- * EXYNOS4 - Memory/Bus clock frequency scaling support in DEVFREQ framework
- * This version supports EXYNOS4210 only. This changes bus frequencies
- * and vddint voltages. Exynos4412/4212 should be able to be supported
- * with minor modifications.
- *
- * 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.
- *
- */
-
-#include <linux/io.h>
-#include <linux/slab.h>
-#include <linux/mutex.h>
-#include <linux/suspend.h>
-#include <linux/opp.h>
-#include <linux/devfreq.h>
-#include <linux/platform_device.h>
-#include <linux/regulator/consumer.h>
-#include <linux/module.h>
-
-/* Exynos4 ASV has been in the mailing list, but not upstreamed, yet. */
-#ifdef CONFIG_EXYNOS_ASV
-extern unsigned int exynos_result_of_asv;
-#endif
-
-#include <mach/regs-clock.h>
-
-#include <plat/map-s5p.h>
-
-#define MAX_SAFEVOLT 1200000 /* 1.2V */
-
-enum exynos4_busf_type {
- TYPE_BUSF_EXYNOS4210,
- TYPE_BUSF_EXYNOS4x12,
-};
-
-/* Assume that the bus is saturated if the utilization is 40% */
-#define BUS_SATURATION_RATIO 40
-
-enum ppmu_counter {
- PPMU_PMNCNT0 = 0,
- PPMU_PMCCNT1,
- PPMU_PMNCNT2,
- PPMU_PMNCNT3,
- PPMU_PMNCNT_MAX,
-};
-struct exynos4_ppmu {
- void __iomem *hw_base;
- unsigned int ccnt;
- unsigned int event;
- unsigned int count[PPMU_PMNCNT_MAX];
- bool ccnt_overflow;
- bool count_overflow[PPMU_PMNCNT_MAX];
-};
-
-enum busclk_level_idx {
- LV_0 = 0,
- LV_1,
- LV_2,
- LV_3,
- LV_4,
- _LV_END
-};
-#define EX4210_LV_MAX LV_2
-#define EX4x12_LV_MAX LV_4
-#define EX4210_LV_NUM (LV_2 + 1)
-#define EX4x12_LV_NUM (LV_4 + 1)
-
-struct busfreq_data {
- enum exynos4_busf_type type;
- struct device *dev;
- struct devfreq *devfreq;
- bool disabled;
- struct regulator *vdd_int;
- struct regulator *vdd_mif; /* Exynos4412/4212 only */
- struct opp *curr_opp;
- struct exynos4_ppmu dmc[2];
-
- struct notifier_block pm_notifier;
- struct mutex lock;
-
- /* Dividers calculated at boot/probe-time */
- unsigned int dmc_divtable[_LV_END]; /* DMC0 */
- unsigned int top_divtable[_LV_END];
-};
-
-struct bus_opp_table {
- unsigned int idx;
- unsigned long clk;
- unsigned long volt;
-};
-
-/* 4210 controls clock of mif and voltage of int */
-static struct bus_opp_table exynos4210_busclk_table[] = {
- {LV_0, 400000, 1150000},
- {LV_1, 267000, 1050000},
- {LV_2, 133000, 1025000},
- {0, 0, 0},
-};
-
-/*
- * MIF is the main control knob clock for exynox4x12 MIF/INT
- * clock and voltage of both mif/int are controlled.
- */
-static struct bus_opp_table exynos4x12_mifclk_table[] = {
- {LV_0, 400000, 1100000},
- {LV_1, 267000, 1000000},
- {LV_2, 160000, 950000},
- {LV_3, 133000, 950000},
- {LV_4, 100000, 950000},
- {0, 0, 0},
-};
-
-/*
- * INT is not the control knob of 4x12. LV_x is not meant to represent
- * the current performance. (MIF does)
- */
-static struct bus_opp_table exynos4x12_intclk_table[] = {
- {LV_0, 200000, 1000000},
- {LV_1, 160000, 950000},
- {LV_2, 133000, 925000},
- {LV_3, 100000, 900000},
- {0, 0, 0},
-};
-
-/* TODO: asv volt definitions are "__initdata"? */
-/* Some chips have different operating voltages */
-static unsigned int exynos4210_asv_volt[][EX4210_LV_NUM] = {
- {1150000, 1050000, 1050000},
- {1125000, 1025000, 1025000},
- {1100000, 1000000, 1000000},
- {1075000, 975000, 975000},
- {1050000, 950000, 950000},
-};
-
-static unsigned int exynos4x12_mif_step_50[][EX4x12_LV_NUM] = {
- /* 400 267 160 133 100 */
- {1050000, 950000, 900000, 900000, 900000}, /* ASV0 */
- {1050000, 950000, 900000, 900000, 900000}, /* ASV1 */
- {1050000, 950000, 900000, 900000, 900000}, /* ASV2 */
- {1050000, 900000, 900000, 900000, 900000}, /* ASV3 */
- {1050000, 900000, 900000, 900000, 850000}, /* ASV4 */
- {1050000, 900000, 900000, 850000, 850000}, /* ASV5 */
- {1050000, 900000, 850000, 850000, 850000}, /* ASV6 */
- {1050000, 900000, 850000, 850000, 850000}, /* ASV7 */
- {1050000, 900000, 850000, 850000, 850000}, /* ASV8 */
-};
-
-static unsigned int exynos4x12_int_volt[][EX4x12_LV_NUM] = {
- /* 200 160 133 100 */
- {1000000, 950000, 925000, 900000}, /* ASV0 */
- {975000, 925000, 925000, 900000}, /* ASV1 */
- {950000, 925000, 900000, 875000}, /* ASV2 */
- {950000, 900000, 900000, 875000}, /* ASV3 */
- {925000, 875000, 875000, 875000}, /* ASV4 */
- {900000, 850000, 850000, 850000}, /* ASV5 */
- {900000, 850000, 850000, 850000}, /* ASV6 */
- {900000, 850000, 850000, 850000}, /* ASV7 */
- {900000, 850000, 850000, 850000}, /* ASV8 */
-};
-
-/*** Clock Divider Data for Exynos4210 ***/
-static unsigned int exynos4210_clkdiv_dmc0[][8] = {
- /*
- * Clock divider value for following
- * { DIVACP, DIVACP_PCLK, DIVDPHY, DIVDMC, DIVDMCD
- * DIVDMCP, DIVCOPY2, DIVCORE_TIMERS }
- */
-
- /* DMC L0: 400MHz */
- { 3, 1, 1, 1, 1, 1, 3, 1 },
- /* DMC L1: 266.7MHz */
- { 4, 1, 1, 2, 1, 1, 3, 1 },
- /* DMC L2: 133MHz */
- { 5, 1, 1, 5, 1, 1, 3, 1 },
-};
-static unsigned int exynos4210_clkdiv_top[][5] = {
- /*
- * Clock divider value for following
- * { DIVACLK200, DIVACLK100, DIVACLK160, DIVACLK133, DIVONENAND }
- */
- /* ACLK200 L0: 200MHz */
- { 3, 7, 4, 5, 1 },
- /* ACLK200 L1: 160MHz */
- { 4, 7, 5, 6, 1 },
- /* ACLK200 L2: 133MHz */
- { 5, 7, 7, 7, 1 },
-};
-static unsigned int exynos4210_clkdiv_lr_bus[][2] = {
- /*
- * Clock divider value for following
- * { DIVGDL/R, DIVGPL/R }
- */
- /* ACLK_GDL/R L1: 200MHz */
- { 3, 1 },
- /* ACLK_GDL/R L2: 160MHz */
- { 4, 1 },
- /* ACLK_GDL/R L3: 133MHz */
- { 5, 1 },
-};
-
-/*** Clock Divider Data for Exynos4212/4412 ***/
-static unsigned int exynos4x12_clkdiv_dmc0[][6] = {
- /*
- * Clock divider value for following
- * { DIVACP, DIVACP_PCLK, DIVDPHY, DIVDMC, DIVDMCD
- * DIVDMCP}
- */
-
- /* DMC L0: 400MHz */
- {3, 1, 1, 1, 1, 1},
- /* DMC L1: 266.7MHz */
- {4, 1, 1, 2, 1, 1},
- /* DMC L2: 160MHz */
- {5, 1, 1, 4, 1, 1},
- /* DMC L3: 133MHz */
- {5, 1, 1, 5, 1, 1},
- /* DMC L4: 100MHz */
- {7, 1, 1, 7, 1, 1},
-};
-static unsigned int exynos4x12_clkdiv_dmc1[][6] = {
- /*
- * Clock divider value for following
- * { G2DACP, DIVC2C, DIVC2C_ACLK }
- */
-
- /* DMC L0: 400MHz */
- {3, 1, 1},
- /* DMC L1: 266.7MHz */
- {4, 2, 1},
- /* DMC L2: 160MHz */
- {5, 4, 1},
- /* DMC L3: 133MHz */
- {5, 5, 1},
- /* DMC L4: 100MHz */
- {7, 7, 1},
-};
-static unsigned int exynos4x12_clkdiv_top[][5] = {
- /*
- * Clock divider value for following
- * { DIVACLK266_GPS, DIVACLK100, DIVACLK160,
- DIVACLK133, DIVONENAND }
- */
-
- /* ACLK_GDL/R L0: 200MHz */
- {2, 7, 4, 5, 1},
- /* ACLK_GDL/R L1: 200MHz */
- {2, 7, 4, 5, 1},
- /* ACLK_GDL/R L2: 160MHz */
- {4, 7, 5, 7, 1},
- /* ACLK_GDL/R L3: 133MHz */
- {4, 7, 5, 7, 1},
- /* ACLK_GDL/R L4: 100MHz */
- {7, 7, 7, 7, 1},
-};
-static unsigned int exynos4x12_clkdiv_lr_bus[][2] = {
- /*
- * Clock divider value for following
- * { DIVGDL/R, DIVGPL/R }
- */
-
- /* ACLK_GDL/R L0: 200MHz */
- {3, 1},
- /* ACLK_GDL/R L1: 200MHz */
- {3, 1},
- /* ACLK_GDL/R L2: 160MHz */
- {4, 1},
- /* ACLK_GDL/R L3: 133MHz */
- {5, 1},
- /* ACLK_GDL/R L4: 100MHz */
- {7, 1},
-};
-static unsigned int exynos4x12_clkdiv_sclkip[][3] = {
- /*
- * Clock divider value for following
- * { DIVMFC, DIVJPEG, DIVFIMC0~3}
- */
-
- /* SCLK_MFC: 200MHz */
- {3, 3, 4},
- /* SCLK_MFC: 200MHz */
- {3, 3, 4},
- /* SCLK_MFC: 160MHz */
- {4, 4, 5},
- /* SCLK_MFC: 133MHz */
- {5, 5, 5},
- /* SCLK_MFC: 100MHz */
- {7, 7, 7},
-};
-
-
-static int exynos4210_set_busclk(struct busfreq_data *data, struct opp *opp)
-{
- unsigned int index;
- unsigned int tmp;
-
- for (index = LV_0; index < EX4210_LV_NUM; index++)
- if (opp_get_freq(opp) == exynos4210_busclk_table[index].clk)
- break;
-
- if (index == EX4210_LV_NUM)
- return -EINVAL;
-
- /* Change Divider - DMC0 */
- tmp = data->dmc_divtable[index];
-
- __raw_writel(tmp, EXYNOS4_CLKDIV_DMC0);
-
- do {
- tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_DMC0);
- } while (tmp & 0x11111111);
-
- /* Change Divider - TOP */
- tmp = data->top_divtable[index];
-
- __raw_writel(tmp, EXYNOS4_CLKDIV_TOP);
-
- do {
- tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_TOP);
- } while (tmp & 0x11111);
-
- /* Change Divider - LEFTBUS */
- tmp = __raw_readl(EXYNOS4_CLKDIV_LEFTBUS);
-
- tmp &= ~(EXYNOS4_CLKDIV_BUS_GDLR_MASK | EXYNOS4_CLKDIV_BUS_GPLR_MASK);
-
- tmp |= ((exynos4210_clkdiv_lr_bus[index][0] <<
- EXYNOS4_CLKDIV_BUS_GDLR_SHIFT) |
- (exynos4210_clkdiv_lr_bus[index][1] <<
- EXYNOS4_CLKDIV_BUS_GPLR_SHIFT));
-
- __raw_writel(tmp, EXYNOS4_CLKDIV_LEFTBUS);
-
- do {
- tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_LEFTBUS);
- } while (tmp & 0x11);
-
- /* Change Divider - RIGHTBUS */
- tmp = __raw_readl(EXYNOS4_CLKDIV_RIGHTBUS);
-
- tmp &= ~(EXYNOS4_CLKDIV_BUS_GDLR_MASK | EXYNOS4_CLKDIV_BUS_GPLR_MASK);
-
- tmp |= ((exynos4210_clkdiv_lr_bus[index][0] <<
- EXYNOS4_CLKDIV_BUS_GDLR_SHIFT) |
- (exynos4210_clkdiv_lr_bus[index][1] <<
- EXYNOS4_CLKDIV_BUS_GPLR_SHIFT));
-
- __raw_writel(tmp, EXYNOS4_CLKDIV_RIGHTBUS);
-
- do {
- tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_RIGHTBUS);
- } while (tmp & 0x11);
-
- return 0;
-}
-
-static int exynos4x12_set_busclk(struct busfreq_data *data, struct opp *opp)
-{
- unsigned int index;
- unsigned int tmp;
-
- for (index = LV_0; index < EX4x12_LV_NUM; index++)
- if (opp_get_freq(opp) == exynos4x12_mifclk_table[index].clk)
- break;
-
- if (index == EX4x12_LV_NUM)
- return -EINVAL;
-
- /* Change Divider - DMC0 */
- tmp = data->dmc_divtable[index];
-
- __raw_writel(tmp, EXYNOS4_CLKDIV_DMC0);
-
- do {
- tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_DMC0);
- } while (tmp & 0x11111111);
-
- /* Change Divider - DMC1 */
- tmp = __raw_readl(EXYNOS4_CLKDIV_DMC1);
-
- tmp &= ~(EXYNOS4_CLKDIV_DMC1_G2D_ACP_MASK |
- EXYNOS4_CLKDIV_DMC1_C2C_MASK |
- EXYNOS4_CLKDIV_DMC1_C2CACLK_MASK);
-
- tmp |= ((exynos4x12_clkdiv_dmc1[index][0] <<
- EXYNOS4_CLKDIV_DMC1_G2D_ACP_SHIFT) |
- (exynos4x12_clkdiv_dmc1[index][1] <<
- EXYNOS4_CLKDIV_DMC1_C2C_SHIFT) |
- (exynos4x12_clkdiv_dmc1[index][2] <<
- EXYNOS4_CLKDIV_DMC1_C2CACLK_SHIFT));
-
- __raw_writel(tmp, EXYNOS4_CLKDIV_DMC1);
-
- do {
- tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_DMC1);
- } while (tmp & 0x111111);
-
- /* Change Divider - TOP */
- tmp = __raw_readl(EXYNOS4_CLKDIV_TOP);
-
- tmp &= ~(EXYNOS4_CLKDIV_TOP_ACLK266_GPS_MASK |
- EXYNOS4_CLKDIV_TOP_ACLK100_MASK |
- EXYNOS4_CLKDIV_TOP_ACLK160_MASK |
- EXYNOS4_CLKDIV_TOP_ACLK133_MASK |
- EXYNOS4_CLKDIV_TOP_ONENAND_MASK);
-
- tmp |= ((exynos4x12_clkdiv_top[index][0] <<
- EXYNOS4_CLKDIV_TOP_ACLK266_GPS_SHIFT) |
- (exynos4x12_clkdiv_top[index][1] <<
- EXYNOS4_CLKDIV_TOP_ACLK100_SHIFT) |
- (exynos4x12_clkdiv_top[index][2] <<
- EXYNOS4_CLKDIV_TOP_ACLK160_SHIFT) |
- (exynos4x12_clkdiv_top[index][3] <<
- EXYNOS4_CLKDIV_TOP_ACLK133_SHIFT) |
- (exynos4x12_clkdiv_top[index][4] <<
- EXYNOS4_CLKDIV_TOP_ONENAND_SHIFT));
-
- __raw_writel(tmp, EXYNOS4_CLKDIV_TOP);
-
- do {
- tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_TOP);
- } while (tmp & 0x11111);
-
- /* Change Divider - LEFTBUS */
- tmp = __raw_readl(EXYNOS4_CLKDIV_LEFTBUS);
-
- tmp &= ~(EXYNOS4_CLKDIV_BUS_GDLR_MASK | EXYNOS4_CLKDIV_BUS_GPLR_MASK);
-
- tmp |= ((exynos4x12_clkdiv_lr_bus[index][0] <<
- EXYNOS4_CLKDIV_BUS_GDLR_SHIFT) |
- (exynos4x12_clkdiv_lr_bus[index][1] <<
- EXYNOS4_CLKDIV_BUS_GPLR_SHIFT));
-
- __raw_writel(tmp, EXYNOS4_CLKDIV_LEFTBUS);
-
- do {
- tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_LEFTBUS);
- } while (tmp & 0x11);
-
- /* Change Divider - RIGHTBUS */
- tmp = __raw_readl(EXYNOS4_CLKDIV_RIGHTBUS);
-
- tmp &= ~(EXYNOS4_CLKDIV_BUS_GDLR_MASK | EXYNOS4_CLKDIV_BUS_GPLR_MASK);
-
- tmp |= ((exynos4x12_clkdiv_lr_bus[index][0] <<
- EXYNOS4_CLKDIV_BUS_GDLR_SHIFT) |
- (exynos4x12_clkdiv_lr_bus[index][1] <<
- EXYNOS4_CLKDIV_BUS_GPLR_SHIFT));
-
- __raw_writel(tmp, EXYNOS4_CLKDIV_RIGHTBUS);
-
- do {
- tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_RIGHTBUS);
- } while (tmp & 0x11);
-
- /* Change Divider - MFC */
- tmp = __raw_readl(EXYNOS4_CLKDIV_MFC);
-
- tmp &= ~(EXYNOS4_CLKDIV_MFC_MASK);
-
- tmp |= ((exynos4x12_clkdiv_sclkip[index][0] <<
- EXYNOS4_CLKDIV_MFC_SHIFT));
-
- __raw_writel(tmp, EXYNOS4_CLKDIV_MFC);
-
- do {
- tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_MFC);
- } while (tmp & 0x1);
-
- /* Change Divider - JPEG */
- tmp = __raw_readl(EXYNOS4_CLKDIV_CAM1);
-
- tmp &= ~(EXYNOS4_CLKDIV_CAM1_JPEG_MASK);
-
- tmp |= ((exynos4x12_clkdiv_sclkip[index][1] <<
- EXYNOS4_CLKDIV_CAM1_JPEG_SHIFT));
-
- __raw_writel(tmp, EXYNOS4_CLKDIV_CAM1);
-
- do {
- tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_CAM1);
- } while (tmp & 0x1);
-
- /* Change Divider - FIMC0~3 */
- tmp = __raw_readl(EXYNOS4_CLKDIV_CAM);
-
- tmp &= ~(EXYNOS4_CLKDIV_CAM_FIMC0_MASK | EXYNOS4_CLKDIV_CAM_FIMC1_MASK |
- EXYNOS4_CLKDIV_CAM_FIMC2_MASK | EXYNOS4_CLKDIV_CAM_FIMC3_MASK);
-
- tmp |= ((exynos4x12_clkdiv_sclkip[index][2] <<
- EXYNOS4_CLKDIV_CAM_FIMC0_SHIFT) |
- (exynos4x12_clkdiv_sclkip[index][2] <<
- EXYNOS4_CLKDIV_CAM_FIMC1_SHIFT) |
- (exynos4x12_clkdiv_sclkip[index][2] <<
- EXYNOS4_CLKDIV_CAM_FIMC2_SHIFT) |
- (exynos4x12_clkdiv_sclkip[index][2] <<
- EXYNOS4_CLKDIV_CAM_FIMC3_SHIFT));
-
- __raw_writel(tmp, EXYNOS4_CLKDIV_CAM);
-
- do {
- tmp = __raw_readl(EXYNOS4_CLKDIV_STAT_CAM1);
- } while (tmp & 0x1111);
-
- return 0;
-}
-
-
-static void busfreq_mon_reset(struct busfreq_data *data)
-{
- unsigned int i;
-
- for (i = 0; i < 2; i++) {
- void __iomem *ppmu_base = data->dmc[i].hw_base;
-
- /* Reset PPMU */
- __raw_writel(0x8000000f, ppmu_base + 0xf010);
- __raw_writel(0x8000000f, ppmu_base + 0xf050);
- __raw_writel(0x6, ppmu_base + 0xf000);
- __raw_writel(0x0, ppmu_base + 0xf100);
-
- /* Set PPMU Event */
- data->dmc[i].event = 0x6;
- __raw_writel(((data->dmc[i].event << 12) | 0x1),
- ppmu_base + 0xfc);
-
- /* Start PPMU */
- __raw_writel(0x1, ppmu_base + 0xf000);
- }
-}
-
-static void exynos4_read_ppmu(struct busfreq_data *data)
-{
- int i, j;
-
- for (i = 0; i < 2; i++) {
- void __iomem *ppmu_base = data->dmc[i].hw_base;
- u32 overflow;
-
- /* Stop PPMU */
- __raw_writel(0x0, ppmu_base + 0xf000);
-
- /* Update local data from PPMU */
- overflow = __raw_readl(ppmu_base + 0xf050);
-
- data->dmc[i].ccnt = __raw_readl(ppmu_base + 0xf100);
- data->dmc[i].ccnt_overflow = overflow & (1 << 31);
-
- for (j = 0; j < PPMU_PMNCNT_MAX; j++) {
- data->dmc[i].count[j] = __raw_readl(
- ppmu_base + (0xf110 + (0x10 * j)));
- data->dmc[i].count_overflow[j] = overflow & (1 << j);
- }
- }
-
- busfreq_mon_reset(data);
-}
-
-static int exynos4x12_get_intspec(unsigned long mifclk)
-{
- int i = 0;
-
- while (exynos4x12_intclk_table[i].clk) {
- if (exynos4x12_intclk_table[i].clk <= mifclk)
- return i;
- i++;
- }
-
- return -EINVAL;
-}
-
-static int exynos4_bus_setvolt(struct busfreq_data *data, struct opp *opp,
- struct opp *oldopp)
-{
- int err = 0, tmp;
- unsigned long volt = opp_get_voltage(opp);
-
- switch (data->type) {
- case TYPE_BUSF_EXYNOS4210:
- /* OPP represents DMC clock + INT voltage */
- err = regulator_set_voltage(data->vdd_int, volt,
- MAX_SAFEVOLT);
- break;
- case TYPE_BUSF_EXYNOS4x12:
- /* OPP represents MIF clock + MIF voltage */
- err = regulator_set_voltage(data->vdd_mif, volt,
- MAX_SAFEVOLT);
- if (err)
- break;
-
- tmp = exynos4x12_get_intspec(opp_get_freq(opp));
- if (tmp < 0) {
- err = tmp;
- regulator_set_voltage(data->vdd_mif,
- opp_get_voltage(oldopp),
- MAX_SAFEVOLT);
- break;
- }
- err = regulator_set_voltage(data->vdd_int,
- exynos4x12_intclk_table[tmp].volt,
- MAX_SAFEVOLT);
- /* Try to recover */
- if (err)
- regulator_set_voltage(data->vdd_mif,
- opp_get_voltage(oldopp),
- MAX_SAFEVOLT);
- break;
- default:
- err = -EINVAL;
- }
-
- return err;
-}
-
-static int exynos4_bus_target(struct device *dev, unsigned long *_freq,
- u32 flags)
-{
- int err = 0;
- struct platform_device *pdev = container_of(dev, struct platform_device,
- dev);
- struct busfreq_data *data = platform_get_drvdata(pdev);
- struct opp *opp = devfreq_recommended_opp(dev, _freq, flags);
- unsigned long freq = opp_get_freq(opp);
- unsigned long old_freq = opp_get_freq(data->curr_opp);
-
- if (IS_ERR(opp))
- return PTR_ERR(opp);
-
- if (old_freq == freq)
- return 0;
-
- dev_dbg(dev, "targetting %lukHz %luuV\n", freq, opp_get_voltage(opp));
-
- mutex_lock(&data->lock);
-
- if (data->disabled)
- goto out;
-
- if (old_freq < freq)
- err = exynos4_bus_setvolt(data, opp, data->curr_opp);
- if (err)
- goto out;
-
- if (old_freq != freq) {
- switch (data->type) {
- case TYPE_BUSF_EXYNOS4210:
- err = exynos4210_set_busclk(data, opp);
- break;
- case TYPE_BUSF_EXYNOS4x12:
- err = exynos4x12_set_busclk(data, opp);
- break;
- default:
- err = -EINVAL;
- }
- }
- if (err)
- goto out;
-
- if (old_freq > freq)
- err = exynos4_bus_setvolt(data, opp, data->curr_opp);
- if (err)
- goto out;
-
- data->curr_opp = opp;
-out:
- mutex_unlock(&data->lock);
- return err;
-}
-
-static int exynos4_get_busier_dmc(struct busfreq_data *data)
-{
- u64 p0 = data->dmc[0].count[0];
- u64 p1 = data->dmc[1].count[0];
-
- p0 *= data->dmc[1].ccnt;
- p1 *= data->dmc[0].ccnt;
-
- if (data->dmc[1].ccnt == 0)
- return 0;
-
- if (p0 > p1)
- return 0;
- return 1;
-}
-
-static int exynos4_bus_get_dev_status(struct device *dev,
- struct devfreq_dev_status *stat)
-{
- struct busfreq_data *data = dev_get_drvdata(dev);
- int busier_dmc;
- int cycles_x2 = 2; /* 2 x cycles */
- void __iomem *addr;
- u32 timing;
- u32 memctrl;
-
- exynos4_read_ppmu(data);
- busier_dmc = exynos4_get_busier_dmc(data);
- stat->current_frequency = opp_get_freq(data->curr_opp);
-
- if (busier_dmc)
- addr = S5P_VA_DMC1;
- else
- addr = S5P_VA_DMC0;
-
- memctrl = __raw_readl(addr + 0x04); /* one of DDR2/3/LPDDR2 */
- timing = __raw_readl(addr + 0x38); /* CL or WL/RL values */
-
- switch ((memctrl >> 8) & 0xf) {
- case 0x4: /* DDR2 */
- cycles_x2 = ((timing >> 16) & 0xf) * 2;
- break;
- case 0x5: /* LPDDR2 */
- case 0x6: /* DDR3 */
- cycles_x2 = ((timing >> 8) & 0xf) + ((timing >> 0) & 0xf);
- break;
- default:
- pr_err("%s: Unknown Memory Type(%d).\n", __func__,
- (memctrl >> 8) & 0xf);
- return -EINVAL;
- }
-
- /* Number of cycles spent on memory access */
- stat->busy_time = data->dmc[busier_dmc].count[0] / 2 * (cycles_x2 + 2);
- stat->busy_time *= 100 / BUS_SATURATION_RATIO;
- stat->total_time = data->dmc[busier_dmc].ccnt;
-
- /* If the counters have overflown, retry */
- if (data->dmc[busier_dmc].ccnt_overflow ||
- data->dmc[busier_dmc].count_overflow[0])
- return -EAGAIN;
-
- return 0;
-}
-
-static void exynos4_bus_exit(struct device *dev)
-{
- struct busfreq_data *data = dev_get_drvdata(dev);
-
- devfreq_unregister_opp_notifier(dev, data->devfreq);
-}
-
-static struct devfreq_dev_profile exynos4_devfreq_profile = {
- .initial_freq = 400000,
- .polling_ms = 50,
- .target = exynos4_bus_target,
- .get_dev_status = exynos4_bus_get_dev_status,
- .exit = exynos4_bus_exit,
-};
-
-static int exynos4210_init_tables(struct busfreq_data *data)
-{
- u32 tmp;
- int mgrp;
- int i, err = 0;
-
- tmp = __raw_readl(EXYNOS4_CLKDIV_DMC0);
- for (i = LV_0; i < EX4210_LV_NUM; i++) {
- tmp &= ~(EXYNOS4_CLKDIV_DMC0_ACP_MASK |
- EXYNOS4_CLKDIV_DMC0_ACPPCLK_MASK |
- EXYNOS4_CLKDIV_DMC0_DPHY_MASK |
- EXYNOS4_CLKDIV_DMC0_DMC_MASK |
- EXYNOS4_CLKDIV_DMC0_DMCD_MASK |
- EXYNOS4_CLKDIV_DMC0_DMCP_MASK |
- EXYNOS4_CLKDIV_DMC0_COPY2_MASK |
- EXYNOS4_CLKDIV_DMC0_CORETI_MASK);
-
- tmp |= ((exynos4210_clkdiv_dmc0[i][0] <<
- EXYNOS4_CLKDIV_DMC0_ACP_SHIFT) |
- (exynos4210_clkdiv_dmc0[i][1] <<
- EXYNOS4_CLKDIV_DMC0_ACPPCLK_SHIFT) |
- (exynos4210_clkdiv_dmc0[i][2] <<
- EXYNOS4_CLKDIV_DMC0_DPHY_SHIFT) |
- (exynos4210_clkdiv_dmc0[i][3] <<
- EXYNOS4_CLKDIV_DMC0_DMC_SHIFT) |
- (exynos4210_clkdiv_dmc0[i][4] <<
- EXYNOS4_CLKDIV_DMC0_DMCD_SHIFT) |
- (exynos4210_clkdiv_dmc0[i][5] <<
- EXYNOS4_CLKDIV_DMC0_DMCP_SHIFT) |
- (exynos4210_clkdiv_dmc0[i][6] <<
- EXYNOS4_CLKDIV_DMC0_COPY2_SHIFT) |
- (exynos4210_clkdiv_dmc0[i][7] <<
- EXYNOS4_CLKDIV_DMC0_CORETI_SHIFT));
-
- data->dmc_divtable[i] = tmp;
- }
-
- tmp = __raw_readl(EXYNOS4_CLKDIV_TOP);
- for (i = LV_0; i < EX4210_LV_NUM; i++) {
- tmp &= ~(EXYNOS4_CLKDIV_TOP_ACLK200_MASK |
- EXYNOS4_CLKDIV_TOP_ACLK100_MASK |
- EXYNOS4_CLKDIV_TOP_ACLK160_MASK |
- EXYNOS4_CLKDIV_TOP_ACLK133_MASK |
- EXYNOS4_CLKDIV_TOP_ONENAND_MASK);
-
- tmp |= ((exynos4210_clkdiv_top[i][0] <<
- EXYNOS4_CLKDIV_TOP_ACLK200_SHIFT) |
- (exynos4210_clkdiv_top[i][1] <<
- EXYNOS4_CLKDIV_TOP_ACLK100_SHIFT) |
- (exynos4210_clkdiv_top[i][2] <<
- EXYNOS4_CLKDIV_TOP_ACLK160_SHIFT) |
- (exynos4210_clkdiv_top[i][3] <<
- EXYNOS4_CLKDIV_TOP_ACLK133_SHIFT) |
- (exynos4210_clkdiv_top[i][4] <<
- EXYNOS4_CLKDIV_TOP_ONENAND_SHIFT));
-
- data->top_divtable[i] = tmp;
- }
-
-#ifdef CONFIG_EXYNOS_ASV
- tmp = exynos4_result_of_asv;
-#else
- tmp = 0; /* Max voltages for the reliability of the unknown */
-#endif
-
- pr_debug("ASV Group of Exynos4 is %d\n", tmp);
- /* Use merged grouping for voltage */
- switch (tmp) {
- case 0:
- mgrp = 0;
- break;
- case 1:
- case 2:
- mgrp = 1;
- break;
- case 3:
- case 4:
- mgrp = 2;
- break;
- case 5:
- case 6:
- mgrp = 3;
- break;
- case 7:
- mgrp = 4;
- break;
- default:
- pr_warn("Unknown ASV Group. Use max voltage.\n");
- mgrp = 0;
- }
-
- for (i = LV_0; i < EX4210_LV_NUM; i++)
- exynos4210_busclk_table[i].volt = exynos4210_asv_volt[mgrp][i];
-
- for (i = LV_0; i < EX4210_LV_NUM; i++) {
- err = opp_add(data->dev, exynos4210_busclk_table[i].clk,
- exynos4210_busclk_table[i].volt);
- if (err) {
- dev_err(data->dev, "Cannot add opp entries.\n");
- return err;
- }
- }
-
-
- return 0;
-}
-
-static int exynos4x12_init_tables(struct busfreq_data *data)
-{
- unsigned int i;
- unsigned int tmp;
- int ret;
-
- /* Enable pause function for DREX2 DVFS */
- tmp = __raw_readl(EXYNOS4_DMC_PAUSE_CTRL);
- tmp |= EXYNOS4_DMC_PAUSE_ENABLE;
- __raw_writel(tmp, EXYNOS4_DMC_PAUSE_CTRL);
-
- tmp = __raw_readl(EXYNOS4_CLKDIV_DMC0);
-
- for (i = 0; i < EX4x12_LV_NUM; i++) {
- tmp &= ~(EXYNOS4_CLKDIV_DMC0_ACP_MASK |
- EXYNOS4_CLKDIV_DMC0_ACPPCLK_MASK |
- EXYNOS4_CLKDIV_DMC0_DPHY_MASK |
- EXYNOS4_CLKDIV_DMC0_DMC_MASK |
- EXYNOS4_CLKDIV_DMC0_DMCD_MASK |
- EXYNOS4_CLKDIV_DMC0_DMCP_MASK);
-
- tmp |= ((exynos4x12_clkdiv_dmc0[i][0] <<
- EXYNOS4_CLKDIV_DMC0_ACP_SHIFT) |
- (exynos4x12_clkdiv_dmc0[i][1] <<
- EXYNOS4_CLKDIV_DMC0_ACPPCLK_SHIFT) |
- (exynos4x12_clkdiv_dmc0[i][2] <<
- EXYNOS4_CLKDIV_DMC0_DPHY_SHIFT) |
- (exynos4x12_clkdiv_dmc0[i][3] <<
- EXYNOS4_CLKDIV_DMC0_DMC_SHIFT) |
- (exynos4x12_clkdiv_dmc0[i][4] <<
- EXYNOS4_CLKDIV_DMC0_DMCD_SHIFT) |
- (exynos4x12_clkdiv_dmc0[i][5] <<
- EXYNOS4_CLKDIV_DMC0_DMCP_SHIFT));
-
- data->dmc_divtable[i] = tmp;
- }
-
-#ifdef CONFIG_EXYNOS_ASV
- tmp = exynos4_result_of_asv;
-#else
- tmp = 0; /* Max voltages for the reliability of the unknown */
-#endif
-
- if (tmp > 8)
- tmp = 0;
- pr_debug("ASV Group of Exynos4x12 is %d\n", tmp);
-
- for (i = 0; i < EX4x12_LV_NUM; i++) {
- exynos4x12_mifclk_table[i].volt =
- exynos4x12_mif_step_50[tmp][i];
- exynos4x12_intclk_table[i].volt =
- exynos4x12_int_volt[tmp][i];
- }
-
- for (i = 0; i < EX4x12_LV_NUM; i++) {
- ret = opp_add(data->dev, exynos4x12_mifclk_table[i].clk,
- exynos4x12_mifclk_table[i].volt);
- if (ret) {
- dev_err(data->dev, "Fail to add opp entries.\n");
- return ret;
- }
- }
-
- return 0;
-}
-
-static int exynos4_busfreq_pm_notifier_event(struct notifier_block *this,
- unsigned long event, void *ptr)
-{
- struct busfreq_data *data = container_of(this, struct busfreq_data,
- pm_notifier);
- struct opp *opp;
- unsigned long maxfreq = ULONG_MAX;
- int err = 0;
-
- switch (event) {
- case PM_SUSPEND_PREPARE:
- /* Set Fastest and Deactivate DVFS */
- mutex_lock(&data->lock);
-
- data->disabled = true;
-
- opp = opp_find_freq_floor(data->dev, &maxfreq);
-
- err = exynos4_bus_setvolt(data, opp, data->curr_opp);
- if (err)
- goto unlock;
-
- switch (data->type) {
- case TYPE_BUSF_EXYNOS4210:
- err = exynos4210_set_busclk(data, opp);
- break;
- case TYPE_BUSF_EXYNOS4x12:
- err = exynos4x12_set_busclk(data, opp);
- break;
- default:
- err = -EINVAL;
- }
- if (err)
- goto unlock;
-
- data->curr_opp = opp;
-unlock:
- mutex_unlock(&data->lock);
- if (err)
- return err;
- return NOTIFY_OK;
- case PM_POST_RESTORE:
- case PM_POST_SUSPEND:
- /* Reactivate */
- mutex_lock(&data->lock);
- data->disabled = false;
- mutex_unlock(&data->lock);
- return NOTIFY_OK;
- }
-
- return NOTIFY_DONE;
-}
-
-static __devinit int exynos4_busfreq_probe(struct platform_device *pdev)
-{
- struct busfreq_data *data;
- struct opp *opp;
- struct device *dev = &pdev->dev;
- int err = 0;
-
- data = devm_kzalloc(&pdev->dev, sizeof(struct busfreq_data), GFP_KERNEL);
- if (data == NULL) {
- dev_err(dev, "Cannot allocate memory.\n");
- return -ENOMEM;
- }
-
- data->type = pdev->id_entry->driver_data;
- data->dmc[0].hw_base = S5P_VA_DMC0;
- data->dmc[1].hw_base = S5P_VA_DMC1;
- data->pm_notifier.notifier_call = exynos4_busfreq_pm_notifier_event;
- data->dev = dev;
- mutex_init(&data->lock);
-
- switch (data->type) {
- case TYPE_BUSF_EXYNOS4210:
- err = exynos4210_init_tables(data);
- break;
- case TYPE_BUSF_EXYNOS4x12:
- err = exynos4x12_init_tables(data);
- break;
- default:
- dev_err(dev, "Cannot determine the device id %d\n", data->type);
- err = -EINVAL;
- }
- if (err)
- return err;
-
- data->vdd_int = devm_regulator_get(dev, "vdd_int");
- if (IS_ERR(data->vdd_int)) {
- dev_err(dev, "Cannot get the regulator \"vdd_int\"\n");
- return PTR_ERR(data->vdd_int);
- }
- if (data->type == TYPE_BUSF_EXYNOS4x12) {
- data->vdd_mif = devm_regulator_get(dev, "vdd_mif");
- if (IS_ERR(data->vdd_mif)) {
- dev_err(dev, "Cannot get the regulator \"vdd_mif\"\n");
- return PTR_ERR(data->vdd_mif);
- }
- }
-
- opp = opp_find_freq_floor(dev, &exynos4_devfreq_profile.initial_freq);
- if (IS_ERR(opp)) {
- dev_err(dev, "Invalid initial frequency %lu kHz.\n",
- exynos4_devfreq_profile.initial_freq);
- return PTR_ERR(opp);
- }
- data->curr_opp = opp;
-
- platform_set_drvdata(pdev, data);
-
- busfreq_mon_reset(data);
-
- data->devfreq = devfreq_add_device(dev, &exynos4_devfreq_profile,
- "simple_ondemand", NULL);
- if (IS_ERR(data->devfreq))
- return PTR_ERR(data->devfreq);
-
- devfreq_register_opp_notifier(dev, data->devfreq);
-
- err = register_pm_notifier(&data->pm_notifier);
- if (err) {
- dev_err(dev, "Failed to setup pm notifier\n");
- devfreq_remove_device(data->devfreq);
- return err;
- }
-
- return 0;
-}
-
-static __devexit int exynos4_busfreq_remove(struct platform_device *pdev)
-{
- struct busfreq_data *data = platform_get_drvdata(pdev);
-
- unregister_pm_notifier(&data->pm_notifier);
- devfreq_remove_device(data->devfreq);
-
- return 0;
-}
-
-static int exynos4_busfreq_resume(struct device *dev)
-{
- struct busfreq_data *data = dev_get_drvdata(dev);
-
- busfreq_mon_reset(data);
- return 0;
-}
-
-static const struct dev_pm_ops exynos4_busfreq_pm = {
- .resume = exynos4_busfreq_resume,
-};
-
-static const struct platform_device_id exynos4_busfreq_id[] = {
- { "exynos4210-busfreq", TYPE_BUSF_EXYNOS4210 },
- { "exynos4412-busfreq", TYPE_BUSF_EXYNOS4x12 },
- { "exynos4212-busfreq", TYPE_BUSF_EXYNOS4x12 },
- { },
-};
-
-static struct platform_driver exynos4_busfreq_driver = {
- .probe = exynos4_busfreq_probe,
- .remove = __devexit_p(exynos4_busfreq_remove),
- .id_table = exynos4_busfreq_id,
- .driver = {
- .name = "exynos4-busfreq",
- .owner = THIS_MODULE,
- .pm = &exynos4_busfreq_pm,
- },
-};
-
-static int __init exynos4_busfreq_init(void)
-{
- return platform_driver_register(&exynos4_busfreq_driver);
-}
-late_initcall(exynos4_busfreq_init);
-
-static void __exit exynos4_busfreq_exit(void)
-{
- platform_driver_unregister(&exynos4_busfreq_driver);
-}
-module_exit(exynos4_busfreq_exit);
-
-MODULE_LICENSE("GPL");
-MODULE_DESCRIPTION("EXYNOS4 busfreq driver with devfreq framework");
-MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
--
1.7.8.6
^ permalink raw reply related
* [PATCH 4/4] PM/Devfreq: Add Exynos5-bus devfreq driver for Exynos5250
From: Abhilash Kesavan @ 2013-01-09 12:06 UTC (permalink / raw)
To: myungjoo.ham, linux-kernel, linux-pm, kgene.kim
Cc: kyungmin.park, rjw, jhbird.choi, Abhilash Kesavan
In-Reply-To: <1357733199-17206-1-git-send-email-a.kesavan@samsung.com>
Exynos5-bus device devfreq driver monitors PPMU counters and
adjusts operating frequencies and voltages with OPP. ASV should
be used to provide appropriate voltages as per the speed group
of the SoC rather than using a constant 1.025V.
Signed-off-by: Abhilash Kesavan <a.kesavan@samsung.com>
Cc: Jonghwan Choi <jhbird.choi@samsung.com>
Cc: Kukjin Kim <kgene.kim@samsung.com>
---
drivers/devfreq/Kconfig | 10 +
drivers/devfreq/Makefile | 1 +
drivers/devfreq/exynos/Makefile | 1 +
drivers/devfreq/exynos/exynos5_bus.c | 469 +++++++++++++++++++++++++++++++++
drivers/devfreq/exynos/exynos5_ppmu.c | 412 +++++++++++++++++++++++++++++
drivers/devfreq/exynos/exynos_ppmu.c | 56 ++++
include/linux/exynos5_ppmu.h | 26 ++
include/linux/exynos_ppmu.h | 79 ++++++
8 files changed, 1054 insertions(+), 0 deletions(-)
create mode 100644 drivers/devfreq/exynos/exynos5_bus.c
create mode 100644 drivers/devfreq/exynos/exynos5_ppmu.c
create mode 100644 drivers/devfreq/exynos/exynos_ppmu.c
create mode 100644 include/linux/exynos5_ppmu.h
create mode 100644 include/linux/exynos_ppmu.h
diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig
index 0f079be..1560d0d 100644
--- a/drivers/devfreq/Kconfig
+++ b/drivers/devfreq/Kconfig
@@ -78,4 +78,14 @@ config ARM_EXYNOS4_BUS_DEVFREQ
To operate with optimal voltages, ASV support is required
(CONFIG_EXYNOS_ASV).
+config ARM_EXYNOS5_BUS_DEVFREQ
+ bool "ARM Exynos5250 Bus DEVFREQ Driver"
+ depends on SOC_EXYNOS5250
+ select ARCH_HAS_OPP
+ select DEVFREQ_GOV_SIMPLE_ONDEMAND
+ help
+ This adds the DEVFREQ driver for Exynos5250 bus interface (vdd_int).
+ It reads PPMU counters of memory controllers and adjusts the
+ operating frequencies and voltages with OPP support.
+
endif # PM_DEVFREQ
diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile
index 3bc1fef..16138c9 100644
--- a/drivers/devfreq/Makefile
+++ b/drivers/devfreq/Makefile
@@ -6,3 +6,4 @@ obj-$(CONFIG_DEVFREQ_GOV_USERSPACE) += governor_userspace.o
# DEVFREQ Drivers
obj-$(CONFIG_ARM_EXYNOS4_BUS_DEVFREQ) += exynos/
+obj-$(CONFIG_ARM_EXYNOS5_BUS_DEVFREQ) += exynos/
diff --git a/drivers/devfreq/exynos/Makefile b/drivers/devfreq/exynos/Makefile
index 1498823..69713a8 100644
--- a/drivers/devfreq/exynos/Makefile
+++ b/drivers/devfreq/exynos/Makefile
@@ -1,2 +1,3 @@
# Exynos DEVFREQ Drivers
obj-$(CONFIG_ARM_EXYNOS4_BUS_DEVFREQ) += exynos4_bus.o
+obj-$(CONFIG_ARM_EXYNOS5_BUS_DEVFREQ) += exynos_ppmu.o exynos5_ppmu.o exynos5_bus.o
diff --git a/drivers/devfreq/exynos/exynos5_bus.c b/drivers/devfreq/exynos/exynos5_bus.c
new file mode 100644
index 0000000..1d4a4b1
--- /dev/null
+++ b/drivers/devfreq/exynos/exynos5_bus.c
@@ -0,0 +1,469 @@
+/*
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ *
+ * EXYNOS5 INT clock frequency scaling support using DEVFREQ framework
+ * Based on work done by Jonghwan Choi <jhbird.choi@samsung.com>
+ * Support for only EXYNOS5250 is present.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/suspend.h>
+#include <linux/opp.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/pm_qos.h>
+#include <linux/regulator/consumer.h>
+#include <linux/exynos_ppmu.h>
+#include <linux/exynos5_ppmu.h>
+
+#include "../governor.h"
+
+#define MAX_SAFEVOLT 1100000 /* 1.10V */
+/* Assume that the bus is saturated if the utilization is 25% */
+#define INT_BUS_SATURATION_RATIO 25
+#define EXYNOS5_BUS_INT_POLL_TIME msecs_to_jiffies(100)
+
+enum int_level_idx {
+ LV_0,
+ LV_1,
+ LV_2,
+ LV_3,
+ LV_4,
+ _LV_END
+};
+
+struct busfreq_data_int {
+ struct device *dev;
+ struct devfreq *devfreq;
+ bool disabled;
+ struct regulator *vdd_int;
+ unsigned long curr_freq;
+ struct notifier_block pm_notifier;
+ struct mutex lock;
+ struct pm_qos_request int_req;
+ struct clk *int_clk;
+ struct exynos5_ppmu_handle *ppmu;
+ struct delayed_work work;
+ int busy;
+};
+
+struct int_bus_opp_table {
+ unsigned int idx;
+ unsigned long clk;
+ unsigned long volt;
+};
+
+static struct int_bus_opp_table exynos5_int_opp_table[] = {
+ {LV_0, 266000, 1025000},
+ {LV_1, 200000, 1025000},
+ {LV_2, 160000, 1025000},
+ {LV_3, 133000, 1025000},
+ {LV_4, 100000, 1025000},
+ {0, 0, 0},
+};
+
+static int exynos5_int_setvolt(struct busfreq_data_int *data,
+ unsigned long volt)
+{
+ return regulator_set_voltage(data->vdd_int, volt, MAX_SAFEVOLT);
+}
+
+static int exynos5_busfreq_int_target(struct device *dev, unsigned long *_freq,
+ u32 flags)
+{
+ int err = 0;
+ struct platform_device *pdev = container_of(dev, struct platform_device,
+ dev);
+ struct busfreq_data_int *data = platform_get_drvdata(pdev);
+ struct opp *opp;
+ unsigned long old_freq, freq;
+ unsigned long volt;
+
+ rcu_read_lock();
+ opp = devfreq_recommended_opp(dev, _freq, flags);
+ if (IS_ERR(opp)) {
+ rcu_read_unlock();
+ dev_err(dev, "%s: Invalid OPP.\n", __func__);
+ return PTR_ERR(opp);
+ }
+
+ freq = opp_get_freq(opp);
+ volt = opp_get_voltage(opp);
+ rcu_read_unlock();
+
+ old_freq = data->curr_freq;
+
+ if (old_freq == freq)
+ return 0;
+
+ dev_dbg(dev, "targetting %lukHz %luuV\n", freq, volt);
+
+ mutex_lock(&data->lock);
+
+ if (data->disabled)
+ goto out;
+
+ if (freq > exynos5_int_opp_table[_LV_END - 1].clk)
+ pm_qos_update_request(&data->int_req,
+ data->busy * old_freq * 16 / 100000);
+ else
+ pm_qos_update_request(&data->int_req, -1);
+
+ if (old_freq < freq)
+ err = exynos5_int_setvolt(data, volt);
+ if (err)
+ goto out;
+
+ err = clk_set_rate(data->int_clk, freq * 1000);
+
+ if (err)
+ goto out;
+
+ if (old_freq > freq)
+ err = exynos5_int_setvolt(data, volt);
+ if (err)
+ goto out;
+
+ data->curr_freq = freq;
+out:
+ mutex_unlock(&data->lock);
+ return err;
+}
+
+static int exynos5_int_get_dev_status(struct device *dev,
+ struct devfreq_dev_status *stat)
+{
+ struct platform_device *pdev = container_of(dev, struct platform_device,
+ dev);
+ struct busfreq_data_int *data = platform_get_drvdata(pdev);
+
+ stat->current_frequency = data->curr_freq;
+ stat->busy_time = data->busy;
+ stat->total_time = 100;
+
+ return 0;
+}
+
+static void exynos5_int_poll_start(struct busfreq_data_int *data)
+{
+ schedule_delayed_work(&data->work, EXYNOS5_BUS_INT_POLL_TIME);
+}
+
+static void exynos5_int_poll_stop(struct busfreq_data_int *data)
+{
+ cancel_delayed_work_sync(&data->work);
+}
+
+static void exynos5_int_poll(struct work_struct *work)
+{
+ struct delayed_work *dwork;
+ struct busfreq_data_int *data;
+ int ret;
+
+ dwork = to_delayed_work(work);
+ data = container_of(dwork, struct busfreq_data_int, work);
+
+ ret = exynos5_ppmu_get_busy(data->ppmu, PPMU_SET_RIGHT);
+
+ if (ret >= 0) {
+ data->busy = ret;
+ mutex_lock(&data->devfreq->lock);
+ update_devfreq(data->devfreq);
+ mutex_unlock(&data->devfreq->lock);
+ }
+
+ schedule_delayed_work(&data->work, EXYNOS5_BUS_INT_POLL_TIME);
+}
+
+static void exynos5_int_exit(struct device *dev)
+{
+ struct platform_device *pdev = container_of(dev, struct platform_device,
+ dev);
+ struct busfreq_data_int *data = platform_get_drvdata(pdev);
+
+ devfreq_unregister_opp_notifier(dev, data->devfreq);
+}
+
+static struct devfreq_dev_profile exynos5_devfreq_int_profile = {
+ .initial_freq = 160000,
+ .polling_ms = 0,
+ .target = exynos5_busfreq_int_target,
+ .get_dev_status = exynos5_int_get_dev_status,
+ .exit = exynos5_int_exit,
+};
+
+static int exynos5250_init_int_tables(struct busfreq_data_int *data)
+{
+ int i, err = 0;
+
+ for (i = LV_0; i < _LV_END; i++) {
+ err = opp_add(data->dev, exynos5_int_opp_table[i].clk,
+ exynos5_int_opp_table[i].volt);
+ if (err) {
+ dev_err(data->dev, "Cannot add opp entries.\n");
+ return err;
+ }
+ }
+
+ return 0;
+}
+static struct devfreq_simple_ondemand_data exynos5_int_ondemand_data = {
+ .downdifferential = 2,
+ .upthreshold = INT_BUS_SATURATION_RATIO,
+};
+
+static int exynos5_busfreq_int_pm_notifier_event(struct notifier_block *this,
+ unsigned long event, void *ptr)
+{
+ struct busfreq_data_int *data = container_of(this,
+ struct busfreq_data_int, pm_notifier);
+ struct opp *opp;
+ unsigned long maxfreq = ULONG_MAX;
+ unsigned long freq;
+ unsigned long volt;
+ int err = 0;
+
+ switch (event) {
+ case PM_SUSPEND_PREPARE:
+ /* Set Fastest and Deactivate DVFS */
+ mutex_lock(&data->lock);
+
+ data->disabled = true;
+
+ rcu_read_lock();
+ opp = opp_find_freq_floor(data->dev, &maxfreq);
+ if (IS_ERR(opp)) {
+ rcu_read_unlock();
+ err = PTR_ERR(opp);
+ goto unlock;
+ }
+ freq = opp_get_freq(opp);
+ volt = opp_get_voltage(opp);
+ rcu_read_unlock();
+
+ err = exynos5_int_setvolt(data, volt);
+ if (err)
+ goto unlock;
+
+ err = clk_set_rate(data->int_clk, freq * 1000);
+
+ if (err)
+ goto unlock;
+
+ data->curr_freq = freq;
+unlock:
+ mutex_unlock(&data->lock);
+ if (err)
+ return NOTIFY_BAD;
+ return NOTIFY_OK;
+ case PM_POST_RESTORE:
+ case PM_POST_SUSPEND:
+ /* Reactivate */
+ mutex_lock(&data->lock);
+ data->disabled = false;
+ mutex_unlock(&data->lock);
+ return NOTIFY_OK;
+ }
+
+ return NOTIFY_DONE;
+}
+
+static __devinit int exynos5_busfreq_int_probe(struct platform_device *pdev)
+{
+ struct busfreq_data_int *data;
+ struct opp *opp;
+ struct device *dev = &pdev->dev;
+ unsigned long initial_freq;
+ unsigned long initial_volt;
+ int err = 0;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(struct busfreq_data_int),
+ GFP_KERNEL);
+ if (data == NULL) {
+ dev_err(dev, "Cannot allocate memory.\n");
+ return -ENOMEM;
+ }
+
+ data->pm_notifier.notifier_call = exynos5_busfreq_int_pm_notifier_event;
+ data->dev = dev;
+ mutex_init(&data->lock);
+
+ err = exynos5250_init_int_tables(data);
+ if (err)
+ goto err_regulator;
+
+ data->vdd_int = regulator_get(dev, "vdd_int");
+ if (IS_ERR(data->vdd_int)) {
+ dev_err(dev, "Cannot get the regulator \"vdd_int\"\n");
+ err = PTR_ERR(data->vdd_int);
+ goto err_regulator;
+ }
+
+ data->int_clk = clk_get(dev, "int_clk");
+ if (IS_ERR(data->int_clk)) {
+ dev_err(dev, "Cannot get clock \"int_clk\"\n");
+ err = PTR_ERR(data->int_clk);
+ goto err_clock;
+ }
+
+ rcu_read_lock();
+ opp = opp_find_freq_floor(dev,
+ &exynos5_devfreq_int_profile.initial_freq);
+ if (IS_ERR(opp)) {
+ rcu_read_unlock();
+ dev_err(dev, "Invalid initial frequency %lu kHz.\n",
+ exynos5_devfreq_int_profile.initial_freq);
+ err = PTR_ERR(opp);
+ goto err_opp_add;
+ }
+ initial_freq = opp_get_freq(opp);
+ initial_volt = opp_get_voltage(opp);
+ rcu_read_unlock();
+ data->curr_freq = initial_freq;
+
+ err = clk_set_rate(data->int_clk, initial_freq * 1000);
+ if (err) {
+ dev_err(dev, "Failed to set initial frequency\n");
+ goto err_opp_add;
+ }
+
+ err = exynos5_int_setvolt(data, initial_volt);
+ if (err)
+ goto err_opp_add;
+
+ platform_set_drvdata(pdev, data);
+
+ data->ppmu = exynos5_ppmu_get();
+ if (!data->ppmu)
+ goto err_ppmu_get;
+
+ INIT_DELAYED_WORK(&data->work, exynos5_int_poll);
+ exynos5_int_poll_start(data);
+
+ data->devfreq = devfreq_add_device(dev, &exynos5_devfreq_int_profile,
+ "simple_ondemand",
+ &exynos5_int_ondemand_data);
+
+ if (IS_ERR(data->devfreq)) {
+ err = PTR_ERR(data->devfreq);
+ goto err_devfreq_add;
+ }
+
+ devfreq_register_opp_notifier(dev, data->devfreq);
+
+ err = register_pm_notifier(&data->pm_notifier);
+ if (err) {
+ dev_err(dev, "Failed to setup pm notifier\n");
+ goto err_devfreq_add;
+ }
+
+ /* TODO: Add a new QOS class for int/mif bus */
+ pm_qos_add_request(&data->int_req, PM_QOS_NETWORK_THROUGHPUT, -1);
+
+ return 0;
+
+err_devfreq_add:
+ devfreq_remove_device(data->devfreq);
+ exynos5_int_poll_stop(data);
+err_ppmu_get:
+ platform_set_drvdata(pdev, NULL);
+err_opp_add:
+ clk_put(data->int_clk);
+err_clock:
+ regulator_put(data->vdd_int);
+err_regulator:
+ return err;
+}
+
+static __devexit int exynos5_busfreq_int_remove(struct platform_device *pdev)
+{
+ struct busfreq_data_int *data = platform_get_drvdata(pdev);
+
+ pm_qos_remove_request(&data->int_req);
+ unregister_pm_notifier(&data->pm_notifier);
+ devfreq_remove_device(data->devfreq);
+ regulator_put(data->vdd_int);
+ clk_put(data->int_clk);
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int exynos5_busfreq_int_suspend(struct device *dev)
+{
+ struct platform_device *pdev = container_of(dev, struct platform_device,
+ dev);
+ struct busfreq_data_int *data = platform_get_drvdata(pdev);
+
+ exynos5_int_poll_stop(data);
+ return 0;
+}
+
+static int exynos5_busfreq_int_resume(struct device *dev)
+{
+ struct platform_device *pdev = container_of(dev, struct platform_device,
+ dev);
+ struct busfreq_data_int *data = platform_get_drvdata(pdev);
+
+ exynos5_int_poll_start(data);
+ return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(exynos5_busfreq_int_pm, exynos5_busfreq_int_suspend,
+ exynos5_busfreq_int_resume);
+
+/* platform device pointer for exynos5 devfreq device. */
+static struct platform_device *exynos5_devfreq_pdev;
+
+static struct platform_driver exynos5_busfreq_int_driver = {
+ .probe = exynos5_busfreq_int_probe,
+ .remove = __devexit_p(exynos5_busfreq_int_remove),
+ .driver = {
+ .name = "exynos5-bus-int",
+ .owner = THIS_MODULE,
+ .pm = &exynos5_busfreq_int_pm,
+ },
+};
+
+static int __init exynos5_busfreq_int_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&exynos5_busfreq_int_driver);
+ if (ret < 0)
+ goto out;
+
+ exynos5_devfreq_pdev =
+ platform_device_register_simple("exynos5-bus-int", -1, NULL, 0);
+ if (IS_ERR_OR_NULL(exynos5_devfreq_pdev)) {
+ ret = PTR_ERR(exynos5_devfreq_pdev);
+ goto out1;
+ }
+
+ return 0;
+out1:
+ platform_driver_unregister(&exynos5_busfreq_int_driver);
+out:
+ return ret;
+}
+late_initcall(exynos5_busfreq_int_init);
+
+static void __exit exynos5_busfreq_int_exit(void)
+{
+ platform_device_unregister(exynos5_devfreq_pdev);
+ platform_driver_unregister(&exynos5_busfreq_int_driver);
+}
+module_exit(exynos5_busfreq_int_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("EXYNOS5 busfreq driver with devfreq framework");
diff --git a/drivers/devfreq/exynos/exynos5_ppmu.c b/drivers/devfreq/exynos/exynos5_ppmu.c
new file mode 100644
index 0000000..0620f24
--- /dev/null
+++ b/drivers/devfreq/exynos/exynos5_ppmu.c
@@ -0,0 +1,412 @@
+/*
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ *
+ * EXYNOS5 PPMU support
+ * Support for only EXYNOS5250 is present.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/kernel.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/hrtimer.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/exynos_ppmu.h>
+#include <linux/exynos5_ppmu.h>
+
+#include <mach/map.h>
+
+#define FIXED_POINT_OFFSET (0x8)
+#define FIXED_POINT_MASK (0xff)
+
+enum exynos5_ppmu_list {
+ PPMU_DDR_C,
+ PPMU_DDR_R1,
+ PPMU_DDR_L,
+ PPMU_RIGHT,
+ PPMU_CPU,
+ PPMU_END,
+};
+
+struct exynos5_ppmu_handle {
+ struct list_head node;
+ struct exynos_ppmu ppmu[PPMU_END];
+};
+
+static DEFINE_SPINLOCK(exynos5_ppmu_lock);
+static LIST_HEAD(exynos5_ppmu_handle_list);
+static struct exynos_ppmu ppmu[PPMU_END];
+
+static const char *exynos5_ppmu_name[PPMU_END] = {
+ [PPMU_DDR_C] = "DDR_C",
+ [PPMU_DDR_R1] = "DDR_R1",
+ [PPMU_DDR_L] = "DDR_L",
+ [PPMU_RIGHT] = "RIGHT",
+ [PPMU_CPU] = "CPU",
+};
+
+static void exynos5_ppmu_reset(struct exynos_ppmu *ppmu)
+{
+ unsigned long flags;
+ void __iomem *ppmu_base = ppmu->hw_base;
+
+ /* Reset the performance and cycle counters */
+ exynos_ppmu_reset(ppmu_base);
+
+ /* Setup count registers for monitoring read/write transactions */
+ ppmu->event[PPMU_PMNCNT0] = RD_DATA_COUNT;
+ exynos_ppmu_setevent(ppmu_base, PPMU_PMNCNT0,
+ ppmu->event[PPMU_PMNCNT0]);
+ ppmu->event[PPMU_PMCCNT1] = WR_DATA_COUNT;
+ exynos_ppmu_setevent(ppmu_base, PPMU_PMCCNT1,
+ ppmu->event[PPMU_PMCCNT1]);
+ ppmu->event[PPMU_PMNCNT3] = RDWR_DATA_COUNT;
+ exynos_ppmu_setevent(ppmu_base, PPMU_PMNCNT3,
+ ppmu->event[PPMU_PMNCNT3]);
+
+ local_irq_save(flags);
+ ppmu->reset_time = ktime_get();
+ exynos_ppmu_start(ppmu_base);
+ local_irq_restore(flags);
+}
+
+static void exynos5_ppmu_read(struct exynos_ppmu *ppmu)
+{
+ unsigned long flags;
+ ktime_t read_time;
+ ktime_t t;
+ int reg, j;
+ void __iomem *ppmu_base = ppmu->hw_base;
+
+ local_irq_save(flags);
+ read_time = ktime_get();
+ exynos_ppmu_stop(ppmu_base);
+ local_irq_restore(flags);
+
+ /* Update local data from PPMU */
+ ppmu->ccnt = __raw_readl(ppmu_base + PPMU_CCNT);
+ reg = __raw_readl(ppmu_base + PPMU_FLAG);
+ ppmu->ccnt_overflow = reg & PPMU_CCNT_OVERFLOW;
+
+ for (j = PPMU_PMNCNT0; j < PPMU_PMNCNT_MAX; j++) {
+ if (ppmu->event[j] == 0)
+ ppmu->count[j] = 0;
+ else
+ ppmu->count[j] = exynos_ppmu_read(ppmu_base, j);
+ }
+ t = ktime_sub(read_time, ppmu->reset_time);
+ ppmu->ns = ktime_to_ns(t);
+}
+
+static void exynos5_ppmu_add(struct exynos_ppmu *to, struct exynos_ppmu *from)
+{
+ int i, j;
+
+ for (i = 0; i < PPMU_END; i++) {
+ for (j = PPMU_PMNCNT0; j < PPMU_PMNCNT_MAX; j++)
+ to[i].count[j] += from[i].count[j];
+
+ to[i].ccnt += from[i].ccnt;
+ if (to[i].ccnt < from[i].ccnt)
+ to[i].ccnt_overflow = true;
+
+ to[i].ns += from[i].ns;
+
+ if (from[i].ccnt_overflow)
+ to[i].ccnt_overflow = true;
+ }
+}
+
+static void exynos5_ppmu_handle_clear(struct exynos5_ppmu_handle *handle)
+{
+ memset(&handle->ppmu, 0, sizeof(struct exynos_ppmu) * PPMU_END);
+}
+
+static void exynos5_ppmu_update(void)
+{
+ int i;
+ struct exynos5_ppmu_handle *handle;
+
+ for (i = 0; i < PPMU_END; i++) {
+ exynos5_ppmu_read(&ppmu[i]);
+ exynos5_ppmu_reset(&ppmu[i]);
+ }
+
+ list_for_each_entry(handle, &exynos5_ppmu_handle_list, node)
+ exynos5_ppmu_add(handle->ppmu, ppmu);
+}
+
+static int exynos5_ppmu_get_filter(enum exynos5_ppmu_sets filter,
+ enum exynos5_ppmu_list *start, enum exynos5_ppmu_list *end)
+{
+ switch (filter) {
+ case PPMU_SET_DDR:
+ *start = PPMU_DDR_C;
+ *end = PPMU_DDR_L;
+ break;
+ case PPMU_SET_RIGHT:
+ *start = PPMU_RIGHT;
+ *end = PPMU_RIGHT;
+ break;
+ case PPMU_SET_CPU:
+ *start = PPMU_CPU;
+ *end = PPMU_CPU;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int exynos5_ppmu_get_busy(struct exynos5_ppmu_handle *handle,
+ enum exynos5_ppmu_sets filter)
+{
+ unsigned long flags;
+ int i, temp, ret, busy = 0;
+ enum exynos5_ppmu_list start;
+ enum exynos5_ppmu_list end;
+
+ ret = exynos5_ppmu_get_filter(filter, &start, &end);
+ if (ret < 0)
+ return ret;
+
+ spin_lock_irqsave(&exynos5_ppmu_lock, flags);
+
+ exynos5_ppmu_update();
+
+ for (i = start; i <= end; i++) {
+ if (handle->ppmu[i].ccnt_overflow) {
+ busy = -EOVERFLOW;
+ break;
+ }
+ temp = handle->ppmu[i].count[PPMU_PMNCNT3] * 100;
+ if (handle->ppmu[i].ccnt > 0)
+ temp /= handle->ppmu[i].ccnt;
+ if (temp > busy)
+ busy = temp;
+ }
+
+ exynos5_ppmu_handle_clear(handle);
+
+ spin_unlock_irqrestore(&exynos5_ppmu_lock, flags);
+
+ return busy;
+}
+
+static void exynos5_ppmu_put(struct exynos5_ppmu_handle *handle)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&exynos5_ppmu_lock, flags);
+
+ list_del(&handle->node);
+
+ spin_unlock_irqrestore(&exynos5_ppmu_lock, flags);
+
+ kfree(handle);
+}
+
+struct exynos5_ppmu_handle *exynos5_ppmu_get(void)
+{
+ struct exynos5_ppmu_handle *handle;
+ unsigned long flags;
+
+ handle = kzalloc(sizeof(struct exynos5_ppmu_handle), GFP_KERNEL);
+ if (!handle)
+ return NULL;
+
+ spin_lock_irqsave(&exynos5_ppmu_lock, flags);
+
+ exynos5_ppmu_update();
+ list_add_tail(&handle->node, &exynos5_ppmu_handle_list);
+
+ spin_unlock_irqrestore(&exynos5_ppmu_lock, flags);
+
+ return handle;
+}
+
+static void exynos5_ppmu_debug_compute(struct exynos_ppmu *ppmu,
+ enum ppmu_counter i, u32 *sat, u32 *freq, u32 *bw)
+{
+ u64 ns = ppmu->ns;
+ u32 busy = ppmu->count[i];
+ u32 total = ppmu->ccnt;
+
+ u64 s;
+ u64 f;
+ u64 b;
+
+ s = (u64)busy * 100 * (1 << FIXED_POINT_OFFSET);
+ s += total / 2;
+ do_div(s, total);
+
+ f = (u64)total * 1000 * (1 << FIXED_POINT_OFFSET);
+ f += ns / 2;
+ f = div64_u64(f, ns);
+
+ b = (u64)busy * (128 / 8) * 1000 * (1 << FIXED_POINT_OFFSET);
+ b += ns / 2;
+ b = div64_u64(b, ns);
+
+ *sat = s;
+ *freq = f;
+ *bw = b;
+}
+
+static void exynos5_ppmu_debug_show_one_counter(struct seq_file *s,
+ const char *name, const char *type, struct exynos_ppmu *ppmu,
+ enum ppmu_counter i, u32 *bw_total)
+{
+ u32 sat;
+ u32 freq;
+ u32 bw;
+
+ exynos5_ppmu_debug_compute(ppmu, i, &sat, &freq, &bw);
+
+ seq_printf(s, "%-10s %-10s %4u.%02u MBps %3u.%02u MHz %2u.%02u%%\n",
+ name, type,
+ bw >> FIXED_POINT_OFFSET,
+ (bw & FIXED_POINT_MASK) * 100 / (1 << FIXED_POINT_OFFSET),
+ freq >> FIXED_POINT_OFFSET,
+ (freq & FIXED_POINT_MASK) * 100 / (1 << FIXED_POINT_OFFSET),
+ sat >> FIXED_POINT_OFFSET,
+ (sat & FIXED_POINT_MASK) * 100 / (1 << FIXED_POINT_OFFSET));
+
+ *bw_total += bw;
+}
+
+static void exynos5_ppmu_debug_show_one(struct seq_file *s,
+ const char *name, struct exynos_ppmu *ppmu,
+ u32 *bw_total)
+{
+ exynos5_ppmu_debug_show_one_counter(s, name, "read+write",
+ ppmu, PPMU_PMNCNT3, &bw_total[PPMU_PMNCNT3]);
+ exynos5_ppmu_debug_show_one_counter(s, "", "read",
+ ppmu, PPMU_PMNCNT0, &bw_total[PPMU_PMNCNT0]);
+ exynos5_ppmu_debug_show_one_counter(s, "", "write",
+ ppmu, PPMU_PMCCNT1, &bw_total[PPMU_PMCCNT1]);
+
+}
+
+static int exynos5_ppmu_debug_show(struct seq_file *s, void *d)
+{
+ int i;
+ u32 bw_total[PPMU_PMNCNT_MAX];
+ struct exynos5_ppmu_handle *handle;
+ unsigned long flags;
+
+ memset(bw_total, 0, sizeof(bw_total));
+
+ handle = exynos5_ppmu_get();
+ msleep(100);
+
+ spin_lock_irqsave(&exynos5_ppmu_lock, flags);
+
+ exynos5_ppmu_update();
+
+ for (i = 0; i < PPMU_CPU; i++)
+ exynos5_ppmu_debug_show_one(s, exynos5_ppmu_name[i],
+ &handle->ppmu[i], bw_total);
+
+ seq_printf(s, "%-10s %-10s %4u.%02u MBps\n", "total", "read+write",
+ bw_total[PPMU_PMNCNT3] >> FIXED_POINT_OFFSET,
+ (bw_total[PPMU_PMNCNT3] & FIXED_POINT_MASK) *
+ 100 / (1 << FIXED_POINT_OFFSET));
+ seq_printf(s, "%-10s %-10s %4u.%02u MBps\n", "", "read",
+ bw_total[PPMU_PMNCNT0] >> FIXED_POINT_OFFSET,
+ (bw_total[PPMU_PMNCNT0] & FIXED_POINT_MASK) *
+ 100 / (1 << FIXED_POINT_OFFSET));
+ seq_printf(s, "%-10s %-10s %4u.%02u MBps\n", "", "write",
+ bw_total[PPMU_PMCCNT1] >> FIXED_POINT_OFFSET,
+ (bw_total[PPMU_PMCCNT1] & FIXED_POINT_MASK) *
+ 100 / (1 << FIXED_POINT_OFFSET));
+
+ seq_printf(s, "\n");
+
+ exynos5_ppmu_debug_show_one(s, exynos5_ppmu_name[PPMU_CPU],
+ &ppmu[PPMU_CPU], bw_total);
+
+ spin_unlock_irqrestore(&exynos5_ppmu_lock, flags);
+
+ exynos5_ppmu_put(handle);
+
+ return 0;
+}
+
+static int exynos5_ppmu_debug_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, exynos5_ppmu_debug_show, inode->i_private);
+}
+
+static const struct file_operations exynos5_ppmu_debug_fops = {
+ .open = exynos5_ppmu_debug_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int __init exynos5_ppmu_debug_init(void)
+{
+ debugfs_create_file("exynos5_bus", S_IRUGO, NULL, NULL,
+ &exynos5_ppmu_debug_fops);
+ return 0;
+}
+late_initcall(exynos5_ppmu_debug_init);
+
+static int exynos5_ppmu_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+ int i;
+
+ for (i = 0; i < PPMU_END; i++) {
+ /* get the memory region */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, i);
+ if (res == NULL) {
+ dev_err(&pdev->dev, "failed to get memory region resource\n");
+ return -ENOENT;
+ }
+
+ ppmu[i].hw_base = devm_request_and_ioremap(&pdev->dev, res);
+ if (ppmu->hw_base == NULL) {
+ dev_err(&pdev->dev, "failed to ioremap memory region\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int __devexit exynos5_ppmu_remove(struct platform_device *pdev)
+{
+ dev_dbg(&pdev->dev, "%s driver unloaded\n", pdev->name);
+
+ return 0;
+}
+
+static const struct of_device_id exynos5_ppmu_match[] = {
+ {
+ .compatible = "samsung,exynos5250-ppmu",
+ },
+ {},
+};
+
+static struct platform_driver exynos5_ppmu_driver = {
+ .probe = exynos5_ppmu_probe,
+ .remove = __devexit_p(exynos5_ppmu_remove),
+ .driver = {
+ .name = "exynos5-ppmu",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(exynos5_ppmu_match),
+ },
+};
+module_platform_driver(exynos5_ppmu_driver);
diff --git a/drivers/devfreq/exynos/exynos_ppmu.c b/drivers/devfreq/exynos/exynos_ppmu.c
new file mode 100644
index 0000000..da373be
--- /dev/null
+++ b/drivers/devfreq/exynos/exynos_ppmu.c
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ *
+ * EXYNOS - PPMU support
+ *
+ * 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/io.h>
+
+#include <linux/exynos_ppmu.h>
+
+void exynos_ppmu_reset(void __iomem *ppmu_base)
+{
+ __raw_writel(PPMU_CYCLE_RESET | PPMU_COUNTER_RESET, ppmu_base);
+ __raw_writel(PPMU_ENABLE_CYCLE |
+ PPMU_ENABLE_COUNT0 |
+ PPMU_ENABLE_COUNT1 |
+ PPMU_ENABLE_COUNT2 |
+ PPMU_ENABLE_COUNT3,
+ ppmu_base + PPMU_CNTENS);
+}
+
+void exynos_ppmu_setevent(void __iomem *ppmu_base, unsigned int ch,
+ unsigned int evt)
+{
+ __raw_writel(evt, ppmu_base + PPMU_BEVTSEL(ch));
+}
+
+void exynos_ppmu_start(void __iomem *ppmu_base)
+{
+ __raw_writel(PPMU_ENABLE, ppmu_base);
+}
+
+void exynos_ppmu_stop(void __iomem *ppmu_base)
+{
+ __raw_writel(PPMU_DISABLE, ppmu_base);
+}
+
+unsigned int exynos_ppmu_read(void __iomem *ppmu_base, unsigned int ch)
+{
+ unsigned int total;
+
+ if (ch == PPMU_PMNCNT3)
+ total = ((__raw_readl(ppmu_base + PMCNT_OFFSET(ch)) << 8) |
+ __raw_readl(ppmu_base + PMCNT_OFFSET(ch + 1)));
+ else
+ total = __raw_readl(ppmu_base + PMCNT_OFFSET(ch));
+
+ return total;
+}
diff --git a/include/linux/exynos5_ppmu.h b/include/linux/exynos5_ppmu.h
new file mode 100644
index 0000000..9f492c1
--- /dev/null
+++ b/include/linux/exynos5_ppmu.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ *
+ * EXYNOS5 PPMU header
+ *
+ * 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.
+*/
+
+#ifndef __DEVFREQ_EXYNOS5_PPMU_H
+#define __DEVFREQ_EXYNOS5_PPMU_H __FILE__
+
+enum exynos5_ppmu_sets {
+ PPMU_SET_DDR,
+ PPMU_SET_RIGHT,
+ PPMU_SET_CPU,
+};
+
+struct exynos5_ppmu_handle *exynos5_ppmu_get(void);
+extern int exynos5_ppmu_get_busy(struct exynos5_ppmu_handle *handle,
+ enum exynos5_ppmu_sets filter);
+
+#endif /* __DEVFREQ_EXYNOS5_PPMU_H */
+
diff --git a/include/linux/exynos_ppmu.h b/include/linux/exynos_ppmu.h
new file mode 100644
index 0000000..b46d31b
--- /dev/null
+++ b/include/linux/exynos_ppmu.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ *
+ * EXYNOS PPMU header
+ *
+ * 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.
+*/
+
+#ifndef __DEVFREQ_EXYNOS_PPMU_H
+#define __DEVFREQ_EXYNOS_PPMU_H __FILE__
+
+#include <linux/ktime.h>
+
+/* For PPMU Control */
+#define PPMU_ENABLE BIT(0)
+#define PPMU_DISABLE 0x0
+#define PPMU_CYCLE_RESET BIT(1)
+#define PPMU_COUNTER_RESET BIT(2)
+
+#define PPMU_ENABLE_COUNT0 BIT(0)
+#define PPMU_ENABLE_COUNT1 BIT(1)
+#define PPMU_ENABLE_COUNT2 BIT(2)
+#define PPMU_ENABLE_COUNT3 BIT(3)
+#define PPMU_ENABLE_CYCLE BIT(31)
+
+#define PPMU_CNTENS 0x10
+#define PPMU_FLAG 0x50
+#define PPMU_CCNT_OVERFLOW BIT(31)
+#define PPMU_CCNT 0x100
+
+#define PPMU_PMCNT0 0x110
+#define PPMU_PMCNT_OFFSET 0x10
+#define PMCNT_OFFSET(x) (PPMU_PMCNT0 + (PPMU_PMCNT_OFFSET * x))
+
+#define PPMU_BEVT0SEL 0x1000
+#define PPMU_BEVTSEL_OFFSET 0x100
+#define PPMU_BEVTSEL(x) (PPMU_BEVT0SEL + (ch * PPMU_BEVTSEL_OFFSET))
+
+/* For Event Selection */
+#define RD_DATA_COUNT 0x5
+#define WR_DATA_COUNT 0x6
+#define RDWR_DATA_COUNT 0x7
+
+enum ppmu_counter {
+ PPMU_PMNCNT0,
+ PPMU_PMCCNT1,
+ PPMU_PMNCNT2,
+ PPMU_PMNCNT3,
+ PPMU_PMNCNT_MAX,
+};
+
+struct bus_opp_table {
+ unsigned int idx;
+ unsigned long clk;
+ unsigned long volt;
+};
+
+struct exynos_ppmu {
+ void __iomem *hw_base;
+ unsigned int ccnt;
+ unsigned int event[PPMU_PMNCNT_MAX];
+ unsigned int count[PPMU_PMNCNT_MAX];
+ unsigned long long ns;
+ ktime_t reset_time;
+ bool ccnt_overflow;
+ bool count_overflow[PPMU_PMNCNT_MAX];
+};
+
+void exynos_ppmu_reset(void __iomem *ppmu_base);
+void exynos_ppmu_setevent(void __iomem *ppmu_base, unsigned int ch,
+ unsigned int evt);
+void exynos_ppmu_start(void __iomem *ppmu_base);
+void exynos_ppmu_stop(void __iomem *ppmu_base);
+unsigned int exynos_ppmu_read(void __iomem *ppmu_base, unsigned int ch);
+#endif /* __DEVFREQ_EXYNOS_PPMU_H */
+
--
1.7.8.6
^ permalink raw reply related
* Re: [PATCH 4/4] PM/Devfreq: Add Exynos5-bus devfreq driver for Exynos5250
From: Rajagopal Venkat @ 2013-01-09 14:14 UTC (permalink / raw)
To: Abhilash Kesavan
Cc: myungjoo.ham, linux-kernel, linux-pm, kgene.kim, kyungmin.park,
rjw, jhbird.choi
In-Reply-To: <1357733199-17206-4-git-send-email-a.kesavan@samsung.com>
On 9 January 2013 17:36, Abhilash Kesavan <a.kesavan@samsung.com> wrote:
> Exynos5-bus device devfreq driver monitors PPMU counters and
> adjusts operating frequencies and voltages with OPP. ASV should
> be used to provide appropriate voltages as per the speed group
> of the SoC rather than using a constant 1.025V.
>
> Signed-off-by: Abhilash Kesavan <a.kesavan@samsung.com>
> Cc: Jonghwan Choi <jhbird.choi@samsung.com>
> Cc: Kukjin Kim <kgene.kim@samsung.com>
> ---
> drivers/devfreq/Kconfig | 10 +
> drivers/devfreq/Makefile | 1 +
> drivers/devfreq/exynos/Makefile | 1 +
> drivers/devfreq/exynos/exynos5_bus.c | 469 +++++++++++++++++++++++++++++++++
> drivers/devfreq/exynos/exynos5_ppmu.c | 412 +++++++++++++++++++++++++++++
> drivers/devfreq/exynos/exynos_ppmu.c | 56 ++++
> include/linux/exynos5_ppmu.h | 26 ++
> include/linux/exynos_ppmu.h | 79 ++++++
> 8 files changed, 1054 insertions(+), 0 deletions(-)
> create mode 100644 drivers/devfreq/exynos/exynos5_bus.c
> create mode 100644 drivers/devfreq/exynos/exynos5_ppmu.c
> create mode 100644 drivers/devfreq/exynos/exynos_ppmu.c
> create mode 100644 include/linux/exynos5_ppmu.h
> create mode 100644 include/linux/exynos_ppmu.h
>
> diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig
> index 0f079be..1560d0d 100644
> --- a/drivers/devfreq/Kconfig
> +++ b/drivers/devfreq/Kconfig
> @@ -78,4 +78,14 @@ config ARM_EXYNOS4_BUS_DEVFREQ
> To operate with optimal voltages, ASV support is required
> (CONFIG_EXYNOS_ASV).
>
> +config ARM_EXYNOS5_BUS_DEVFREQ
> + bool "ARM Exynos5250 Bus DEVFREQ Driver"
> + depends on SOC_EXYNOS5250
> + select ARCH_HAS_OPP
> + select DEVFREQ_GOV_SIMPLE_ONDEMAND
> + help
> + This adds the DEVFREQ driver for Exynos5250 bus interface (vdd_int).
> + It reads PPMU counters of memory controllers and adjusts the
> + operating frequencies and voltages with OPP support.
> +
> endif # PM_DEVFREQ
> diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile
> index 3bc1fef..16138c9 100644
> --- a/drivers/devfreq/Makefile
> +++ b/drivers/devfreq/Makefile
> @@ -6,3 +6,4 @@ obj-$(CONFIG_DEVFREQ_GOV_USERSPACE) += governor_userspace.o
>
> # DEVFREQ Drivers
> obj-$(CONFIG_ARM_EXYNOS4_BUS_DEVFREQ) += exynos/
> +obj-$(CONFIG_ARM_EXYNOS5_BUS_DEVFREQ) += exynos/
> diff --git a/drivers/devfreq/exynos/Makefile b/drivers/devfreq/exynos/Makefile
> index 1498823..69713a8 100644
> --- a/drivers/devfreq/exynos/Makefile
> +++ b/drivers/devfreq/exynos/Makefile
> @@ -1,2 +1,3 @@
> # Exynos DEVFREQ Drivers
> obj-$(CONFIG_ARM_EXYNOS4_BUS_DEVFREQ) += exynos4_bus.o
> +obj-$(CONFIG_ARM_EXYNOS5_BUS_DEVFREQ) += exynos_ppmu.o exynos5_ppmu.o exynos5_bus.o
> diff --git a/drivers/devfreq/exynos/exynos5_bus.c b/drivers/devfreq/exynos/exynos5_bus.c
> new file mode 100644
> index 0000000..1d4a4b1
> --- /dev/null
> +++ b/drivers/devfreq/exynos/exynos5_bus.c
> @@ -0,0 +1,469 @@
> +/*
> + * Copyright (c) 2012 Samsung Electronics Co., Ltd.
> + * http://www.samsung.com/
> + *
> + * EXYNOS5 INT clock frequency scaling support using DEVFREQ framework
> + * Based on work done by Jonghwan Choi <jhbird.choi@samsung.com>
> + * Support for only EXYNOS5250 is present.
> + *
> + * 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.
> + *
> + */
> +
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +#include <linux/suspend.h>
> +#include <linux/opp.h>
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_qos.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/exynos_ppmu.h>
> +#include <linux/exynos5_ppmu.h>
> +
> +#include "../governor.h"
This header file is meant for governors use. What's the need of it here?
> +
> +#define MAX_SAFEVOLT 1100000 /* 1.10V */
> +/* Assume that the bus is saturated if the utilization is 25% */
> +#define INT_BUS_SATURATION_RATIO 25
> +#define EXYNOS5_BUS_INT_POLL_TIME msecs_to_jiffies(100)
> +
> +enum int_level_idx {
> + LV_0,
> + LV_1,
> + LV_2,
> + LV_3,
> + LV_4,
> + _LV_END
> +};
> +
> +struct busfreq_data_int {
> + struct device *dev;
> + struct devfreq *devfreq;
> + bool disabled;
> + struct regulator *vdd_int;
> + unsigned long curr_freq;
> + struct notifier_block pm_notifier;
> + struct mutex lock;
> + struct pm_qos_request int_req;
> + struct clk *int_clk;
> + struct exynos5_ppmu_handle *ppmu;
> + struct delayed_work work;
> + int busy;
> +};
> +
> +struct int_bus_opp_table {
> + unsigned int idx;
> + unsigned long clk;
> + unsigned long volt;
> +};
> +
> +static struct int_bus_opp_table exynos5_int_opp_table[] = {
> + {LV_0, 266000, 1025000},
> + {LV_1, 200000, 1025000},
> + {LV_2, 160000, 1025000},
> + {LV_3, 133000, 1025000},
> + {LV_4, 100000, 1025000},
> + {0, 0, 0},
> +};
> +
> +static int exynos5_int_setvolt(struct busfreq_data_int *data,
> + unsigned long volt)
> +{
> + return regulator_set_voltage(data->vdd_int, volt, MAX_SAFEVOLT);
> +}
> +
> +static int exynos5_busfreq_int_target(struct device *dev, unsigned long *_freq,
> + u32 flags)
> +{
> + int err = 0;
> + struct platform_device *pdev = container_of(dev, struct platform_device,
> + dev);
> + struct busfreq_data_int *data = platform_get_drvdata(pdev);
> + struct opp *opp;
> + unsigned long old_freq, freq;
> + unsigned long volt;
> +
> + rcu_read_lock();
> + opp = devfreq_recommended_opp(dev, _freq, flags);
> + if (IS_ERR(opp)) {
> + rcu_read_unlock();
> + dev_err(dev, "%s: Invalid OPP.\n", __func__);
> + return PTR_ERR(opp);
> + }
> +
> + freq = opp_get_freq(opp);
> + volt = opp_get_voltage(opp);
> + rcu_read_unlock();
> +
> + old_freq = data->curr_freq;
> +
> + if (old_freq == freq)
> + return 0;
> +
> + dev_dbg(dev, "targetting %lukHz %luuV\n", freq, volt);
> +
> + mutex_lock(&data->lock);
> +
> + if (data->disabled)
> + goto out;
> +
> + if (freq > exynos5_int_opp_table[_LV_END - 1].clk)
> + pm_qos_update_request(&data->int_req,
> + data->busy * old_freq * 16 / 100000);
> + else
> + pm_qos_update_request(&data->int_req, -1);
> +
> + if (old_freq < freq)
> + err = exynos5_int_setvolt(data, volt);
> + if (err)
> + goto out;
> +
> + err = clk_set_rate(data->int_clk, freq * 1000);
> +
> + if (err)
> + goto out;
> +
> + if (old_freq > freq)
> + err = exynos5_int_setvolt(data, volt);
> + if (err)
> + goto out;
> +
> + data->curr_freq = freq;
> +out:
> + mutex_unlock(&data->lock);
> + return err;
> +}
> +
> +static int exynos5_int_get_dev_status(struct device *dev,
> + struct devfreq_dev_status *stat)
> +{
> + struct platform_device *pdev = container_of(dev, struct platform_device,
> + dev);
> + struct busfreq_data_int *data = platform_get_drvdata(pdev);
> +
> + stat->current_frequency = data->curr_freq;
> + stat->busy_time = data->busy;
> + stat->total_time = 100;
How is busy_time is relative to total_time here? busy_time <=
total_time is guaranteed?
> +
> + return 0;
> +}
> +
> +static void exynos5_int_poll_start(struct busfreq_data_int *data)
> +{
> + schedule_delayed_work(&data->work, EXYNOS5_BUS_INT_POLL_TIME);
> +}
> +
> +static void exynos5_int_poll_stop(struct busfreq_data_int *data)
> +{
> + cancel_delayed_work_sync(&data->work);
> +}
> +
> +static void exynos5_int_poll(struct work_struct *work)
> +{
> + struct delayed_work *dwork;
> + struct busfreq_data_int *data;
> + int ret;
> +
> + dwork = to_delayed_work(work);
> + data = container_of(dwork, struct busfreq_data_int, work);
> +
> + ret = exynos5_ppmu_get_busy(data->ppmu, PPMU_SET_RIGHT);
> +
> + if (ret >= 0) {
> + data->busy = ret;
> + mutex_lock(&data->devfreq->lock);
> + update_devfreq(data->devfreq);
Again, update_devfreq() is meant for devfreq governors use. Why is the devfreq
driver is doing devfreq governor job? any specific reason? The devfreq device
load monitoring is done by governors.
> + mutex_unlock(&data->devfreq->lock);
> + }
> +
> + schedule_delayed_work(&data->work, EXYNOS5_BUS_INT_POLL_TIME);
> +}
> +
> +static void exynos5_int_exit(struct device *dev)
> +{
> + struct platform_device *pdev = container_of(dev, struct platform_device,
> + dev);
> + struct busfreq_data_int *data = platform_get_drvdata(pdev);
> +
> + devfreq_unregister_opp_notifier(dev, data->devfreq);
> +}
> +
> +static struct devfreq_dev_profile exynos5_devfreq_int_profile = {
> + .initial_freq = 160000,
> + .polling_ms = 0,
why is polling_ms is set to zero? It defeats the purpose of devfreq driver.
> + .target = exynos5_busfreq_int_target,
> + .get_dev_status = exynos5_int_get_dev_status,
> + .exit = exynos5_int_exit,
> +};
> +
> +static int exynos5250_init_int_tables(struct busfreq_data_int *data)
> +{
> + int i, err = 0;
> +
> + for (i = LV_0; i < _LV_END; i++) {
> + err = opp_add(data->dev, exynos5_int_opp_table[i].clk,
> + exynos5_int_opp_table[i].volt);
> + if (err) {
> + dev_err(data->dev, "Cannot add opp entries.\n");
> + return err;
> + }
> + }
> +
> + return 0;
> +}
> +static struct devfreq_simple_ondemand_data exynos5_int_ondemand_data = {
> + .downdifferential = 2,
> + .upthreshold = INT_BUS_SATURATION_RATIO,
> +};
> +
> +static int exynos5_busfreq_int_pm_notifier_event(struct notifier_block *this,
> + unsigned long event, void *ptr)
> +{
> + struct busfreq_data_int *data = container_of(this,
> + struct busfreq_data_int, pm_notifier);
> + struct opp *opp;
> + unsigned long maxfreq = ULONG_MAX;
> + unsigned long freq;
> + unsigned long volt;
> + int err = 0;
> +
> + switch (event) {
> + case PM_SUSPEND_PREPARE:
> + /* Set Fastest and Deactivate DVFS */
> + mutex_lock(&data->lock);
> +
> + data->disabled = true;
> +
> + rcu_read_lock();
> + opp = opp_find_freq_floor(data->dev, &maxfreq);
> + if (IS_ERR(opp)) {
> + rcu_read_unlock();
> + err = PTR_ERR(opp);
> + goto unlock;
> + }
> + freq = opp_get_freq(opp);
> + volt = opp_get_voltage(opp);
> + rcu_read_unlock();
> +
> + err = exynos5_int_setvolt(data, volt);
> + if (err)
> + goto unlock;
> +
> + err = clk_set_rate(data->int_clk, freq * 1000);
> +
> + if (err)
> + goto unlock;
> +
> + data->curr_freq = freq;
> +unlock:
> + mutex_unlock(&data->lock);
> + if (err)
> + return NOTIFY_BAD;
> + return NOTIFY_OK;
> + case PM_POST_RESTORE:
> + case PM_POST_SUSPEND:
> + /* Reactivate */
> + mutex_lock(&data->lock);
> + data->disabled = false;
> + mutex_unlock(&data->lock);
> + return NOTIFY_OK;
> + }
> +
> + return NOTIFY_DONE;
> +}
> +
> +static __devinit int exynos5_busfreq_int_probe(struct platform_device *pdev)
> +{
> + struct busfreq_data_int *data;
> + struct opp *opp;
> + struct device *dev = &pdev->dev;
> + unsigned long initial_freq;
> + unsigned long initial_volt;
> + int err = 0;
> +
> + data = devm_kzalloc(&pdev->dev, sizeof(struct busfreq_data_int),
> + GFP_KERNEL);
> + if (data == NULL) {
> + dev_err(dev, "Cannot allocate memory.\n");
> + return -ENOMEM;
> + }
> +
> + data->pm_notifier.notifier_call = exynos5_busfreq_int_pm_notifier_event;
> + data->dev = dev;
> + mutex_init(&data->lock);
> +
> + err = exynos5250_init_int_tables(data);
> + if (err)
> + goto err_regulator;
> +
> + data->vdd_int = regulator_get(dev, "vdd_int");
> + if (IS_ERR(data->vdd_int)) {
> + dev_err(dev, "Cannot get the regulator \"vdd_int\"\n");
> + err = PTR_ERR(data->vdd_int);
> + goto err_regulator;
> + }
> +
> + data->int_clk = clk_get(dev, "int_clk");
> + if (IS_ERR(data->int_clk)) {
> + dev_err(dev, "Cannot get clock \"int_clk\"\n");
> + err = PTR_ERR(data->int_clk);
> + goto err_clock;
> + }
> +
> + rcu_read_lock();
> + opp = opp_find_freq_floor(dev,
> + &exynos5_devfreq_int_profile.initial_freq);
> + if (IS_ERR(opp)) {
> + rcu_read_unlock();
> + dev_err(dev, "Invalid initial frequency %lu kHz.\n",
> + exynos5_devfreq_int_profile.initial_freq);
> + err = PTR_ERR(opp);
> + goto err_opp_add;
> + }
> + initial_freq = opp_get_freq(opp);
> + initial_volt = opp_get_voltage(opp);
> + rcu_read_unlock();
> + data->curr_freq = initial_freq;
> +
> + err = clk_set_rate(data->int_clk, initial_freq * 1000);
> + if (err) {
> + dev_err(dev, "Failed to set initial frequency\n");
> + goto err_opp_add;
> + }
> +
> + err = exynos5_int_setvolt(data, initial_volt);
> + if (err)
> + goto err_opp_add;
> +
> + platform_set_drvdata(pdev, data);
> +
> + data->ppmu = exynos5_ppmu_get();
> + if (!data->ppmu)
> + goto err_ppmu_get;
> +
> + INIT_DELAYED_WORK(&data->work, exynos5_int_poll);
> + exynos5_int_poll_start(data);
> +
> + data->devfreq = devfreq_add_device(dev, &exynos5_devfreq_int_profile,
> + "simple_ondemand",
> + &exynos5_int_ondemand_data);
> +
> + if (IS_ERR(data->devfreq)) {
> + err = PTR_ERR(data->devfreq);
> + goto err_devfreq_add;
> + }
> +
> + devfreq_register_opp_notifier(dev, data->devfreq);
> +
> + err = register_pm_notifier(&data->pm_notifier);
> + if (err) {
> + dev_err(dev, "Failed to setup pm notifier\n");
> + goto err_devfreq_add;
> + }
> +
> + /* TODO: Add a new QOS class for int/mif bus */
> + pm_qos_add_request(&data->int_req, PM_QOS_NETWORK_THROUGHPUT, -1);
> +
> + return 0;
> +
> +err_devfreq_add:
> + devfreq_remove_device(data->devfreq);
> + exynos5_int_poll_stop(data);
> +err_ppmu_get:
> + platform_set_drvdata(pdev, NULL);
> +err_opp_add:
> + clk_put(data->int_clk);
> +err_clock:
> + regulator_put(data->vdd_int);
> +err_regulator:
> + return err;
> +}
> +
> +static __devexit int exynos5_busfreq_int_remove(struct platform_device *pdev)
> +{
> + struct busfreq_data_int *data = platform_get_drvdata(pdev);
> +
> + pm_qos_remove_request(&data->int_req);
> + unregister_pm_notifier(&data->pm_notifier);
> + devfreq_remove_device(data->devfreq);
> + regulator_put(data->vdd_int);
> + clk_put(data->int_clk);
> + platform_set_drvdata(pdev, NULL);
> +
> + return 0;
> +}
> +
> +#ifdef CONFIG_PM_SLEEP
> +static int exynos5_busfreq_int_suspend(struct device *dev)
> +{
> + struct platform_device *pdev = container_of(dev, struct platform_device,
> + dev);
> + struct busfreq_data_int *data = platform_get_drvdata(pdev);
> +
> + exynos5_int_poll_stop(data);
> + return 0;
> +}
> +
> +static int exynos5_busfreq_int_resume(struct device *dev)
> +{
> + struct platform_device *pdev = container_of(dev, struct platform_device,
> + dev);
> + struct busfreq_data_int *data = platform_get_drvdata(pdev);
> +
> + exynos5_int_poll_start(data);
> + return 0;
> +}
> +#endif
> +
> +static SIMPLE_DEV_PM_OPS(exynos5_busfreq_int_pm, exynos5_busfreq_int_suspend,
> + exynos5_busfreq_int_resume);
> +
> +/* platform device pointer for exynos5 devfreq device. */
> +static struct platform_device *exynos5_devfreq_pdev;
> +
> +static struct platform_driver exynos5_busfreq_int_driver = {
> + .probe = exynos5_busfreq_int_probe,
> + .remove = __devexit_p(exynos5_busfreq_int_remove),
> + .driver = {
> + .name = "exynos5-bus-int",
> + .owner = THIS_MODULE,
> + .pm = &exynos5_busfreq_int_pm,
> + },
> +};
> +
> +static int __init exynos5_busfreq_int_init(void)
> +{
> + int ret;
> +
> + ret = platform_driver_register(&exynos5_busfreq_int_driver);
> + if (ret < 0)
> + goto out;
> +
> + exynos5_devfreq_pdev =
> + platform_device_register_simple("exynos5-bus-int", -1, NULL, 0);
> + if (IS_ERR_OR_NULL(exynos5_devfreq_pdev)) {
> + ret = PTR_ERR(exynos5_devfreq_pdev);
> + goto out1;
> + }
> +
> + return 0;
> +out1:
> + platform_driver_unregister(&exynos5_busfreq_int_driver);
> +out:
> + return ret;
> +}
> +late_initcall(exynos5_busfreq_int_init);
> +
> +static void __exit exynos5_busfreq_int_exit(void)
> +{
> + platform_device_unregister(exynos5_devfreq_pdev);
> + platform_driver_unregister(&exynos5_busfreq_int_driver);
> +}
> +module_exit(exynos5_busfreq_int_exit);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("EXYNOS5 busfreq driver with devfreq framework");
> diff --git a/drivers/devfreq/exynos/exynos5_ppmu.c b/drivers/devfreq/exynos/exynos5_ppmu.c
> new file mode 100644
> index 0000000..0620f24
> --- /dev/null
> +++ b/drivers/devfreq/exynos/exynos5_ppmu.c
> @@ -0,0 +1,412 @@
> +/*
> + * Copyright (c) 2012 Samsung Electronics Co., Ltd.
> + * http://www.samsung.com/
> + *
> + * EXYNOS5 PPMU support
> + * Support for only EXYNOS5250 is present.
> + *
> + * 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.
> + *
> + */
> +
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/kernel.h>
> +#include <linux/debugfs.h>
> +#include <linux/delay.h>
> +#include <linux/hrtimer.h>
> +#include <linux/io.h>
> +#include <linux/of.h>
> +#include <linux/slab.h>
> +#include <linux/exynos_ppmu.h>
> +#include <linux/exynos5_ppmu.h>
> +
> +#include <mach/map.h>
> +
> +#define FIXED_POINT_OFFSET (0x8)
> +#define FIXED_POINT_MASK (0xff)
> +
> +enum exynos5_ppmu_list {
> + PPMU_DDR_C,
> + PPMU_DDR_R1,
> + PPMU_DDR_L,
> + PPMU_RIGHT,
> + PPMU_CPU,
> + PPMU_END,
> +};
> +
> +struct exynos5_ppmu_handle {
> + struct list_head node;
> + struct exynos_ppmu ppmu[PPMU_END];
> +};
> +
> +static DEFINE_SPINLOCK(exynos5_ppmu_lock);
> +static LIST_HEAD(exynos5_ppmu_handle_list);
> +static struct exynos_ppmu ppmu[PPMU_END];
> +
> +static const char *exynos5_ppmu_name[PPMU_END] = {
> + [PPMU_DDR_C] = "DDR_C",
> + [PPMU_DDR_R1] = "DDR_R1",
> + [PPMU_DDR_L] = "DDR_L",
> + [PPMU_RIGHT] = "RIGHT",
> + [PPMU_CPU] = "CPU",
> +};
> +
> +static void exynos5_ppmu_reset(struct exynos_ppmu *ppmu)
> +{
> + unsigned long flags;
> + void __iomem *ppmu_base = ppmu->hw_base;
> +
> + /* Reset the performance and cycle counters */
> + exynos_ppmu_reset(ppmu_base);
> +
> + /* Setup count registers for monitoring read/write transactions */
> + ppmu->event[PPMU_PMNCNT0] = RD_DATA_COUNT;
> + exynos_ppmu_setevent(ppmu_base, PPMU_PMNCNT0,
> + ppmu->event[PPMU_PMNCNT0]);
> + ppmu->event[PPMU_PMCCNT1] = WR_DATA_COUNT;
> + exynos_ppmu_setevent(ppmu_base, PPMU_PMCCNT1,
> + ppmu->event[PPMU_PMCCNT1]);
> + ppmu->event[PPMU_PMNCNT3] = RDWR_DATA_COUNT;
> + exynos_ppmu_setevent(ppmu_base, PPMU_PMNCNT3,
> + ppmu->event[PPMU_PMNCNT3]);
> +
> + local_irq_save(flags);
> + ppmu->reset_time = ktime_get();
> + exynos_ppmu_start(ppmu_base);
> + local_irq_restore(flags);
> +}
> +
> +static void exynos5_ppmu_read(struct exynos_ppmu *ppmu)
> +{
> + unsigned long flags;
> + ktime_t read_time;
> + ktime_t t;
> + int reg, j;
> + void __iomem *ppmu_base = ppmu->hw_base;
> +
> + local_irq_save(flags);
> + read_time = ktime_get();
> + exynos_ppmu_stop(ppmu_base);
> + local_irq_restore(flags);
> +
> + /* Update local data from PPMU */
> + ppmu->ccnt = __raw_readl(ppmu_base + PPMU_CCNT);
> + reg = __raw_readl(ppmu_base + PPMU_FLAG);
> + ppmu->ccnt_overflow = reg & PPMU_CCNT_OVERFLOW;
> +
> + for (j = PPMU_PMNCNT0; j < PPMU_PMNCNT_MAX; j++) {
> + if (ppmu->event[j] == 0)
> + ppmu->count[j] = 0;
> + else
> + ppmu->count[j] = exynos_ppmu_read(ppmu_base, j);
> + }
> + t = ktime_sub(read_time, ppmu->reset_time);
> + ppmu->ns = ktime_to_ns(t);
> +}
> +
> +static void exynos5_ppmu_add(struct exynos_ppmu *to, struct exynos_ppmu *from)
> +{
> + int i, j;
> +
> + for (i = 0; i < PPMU_END; i++) {
> + for (j = PPMU_PMNCNT0; j < PPMU_PMNCNT_MAX; j++)
> + to[i].count[j] += from[i].count[j];
> +
> + to[i].ccnt += from[i].ccnt;
> + if (to[i].ccnt < from[i].ccnt)
> + to[i].ccnt_overflow = true;
> +
> + to[i].ns += from[i].ns;
> +
> + if (from[i].ccnt_overflow)
> + to[i].ccnt_overflow = true;
> + }
> +}
> +
> +static void exynos5_ppmu_handle_clear(struct exynos5_ppmu_handle *handle)
> +{
> + memset(&handle->ppmu, 0, sizeof(struct exynos_ppmu) * PPMU_END);
> +}
> +
> +static void exynos5_ppmu_update(void)
> +{
> + int i;
> + struct exynos5_ppmu_handle *handle;
> +
> + for (i = 0; i < PPMU_END; i++) {
> + exynos5_ppmu_read(&ppmu[i]);
> + exynos5_ppmu_reset(&ppmu[i]);
> + }
> +
> + list_for_each_entry(handle, &exynos5_ppmu_handle_list, node)
> + exynos5_ppmu_add(handle->ppmu, ppmu);
> +}
> +
> +static int exynos5_ppmu_get_filter(enum exynos5_ppmu_sets filter,
> + enum exynos5_ppmu_list *start, enum exynos5_ppmu_list *end)
> +{
> + switch (filter) {
> + case PPMU_SET_DDR:
> + *start = PPMU_DDR_C;
> + *end = PPMU_DDR_L;
> + break;
> + case PPMU_SET_RIGHT:
> + *start = PPMU_RIGHT;
> + *end = PPMU_RIGHT;
> + break;
> + case PPMU_SET_CPU:
> + *start = PPMU_CPU;
> + *end = PPMU_CPU;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +int exynos5_ppmu_get_busy(struct exynos5_ppmu_handle *handle,
> + enum exynos5_ppmu_sets filter)
> +{
> + unsigned long flags;
> + int i, temp, ret, busy = 0;
> + enum exynos5_ppmu_list start;
> + enum exynos5_ppmu_list end;
> +
> + ret = exynos5_ppmu_get_filter(filter, &start, &end);
> + if (ret < 0)
> + return ret;
> +
> + spin_lock_irqsave(&exynos5_ppmu_lock, flags);
> +
> + exynos5_ppmu_update();
> +
> + for (i = start; i <= end; i++) {
> + if (handle->ppmu[i].ccnt_overflow) {
> + busy = -EOVERFLOW;
> + break;
> + }
> + temp = handle->ppmu[i].count[PPMU_PMNCNT3] * 100;
> + if (handle->ppmu[i].ccnt > 0)
> + temp /= handle->ppmu[i].ccnt;
> + if (temp > busy)
> + busy = temp;
> + }
> +
> + exynos5_ppmu_handle_clear(handle);
> +
> + spin_unlock_irqrestore(&exynos5_ppmu_lock, flags);
> +
> + return busy;
> +}
> +
> +static void exynos5_ppmu_put(struct exynos5_ppmu_handle *handle)
> +{
> + unsigned long flags;
> +
> + spin_lock_irqsave(&exynos5_ppmu_lock, flags);
> +
> + list_del(&handle->node);
> +
> + spin_unlock_irqrestore(&exynos5_ppmu_lock, flags);
> +
> + kfree(handle);
> +}
> +
> +struct exynos5_ppmu_handle *exynos5_ppmu_get(void)
> +{
> + struct exynos5_ppmu_handle *handle;
> + unsigned long flags;
> +
> + handle = kzalloc(sizeof(struct exynos5_ppmu_handle), GFP_KERNEL);
> + if (!handle)
> + return NULL;
> +
> + spin_lock_irqsave(&exynos5_ppmu_lock, flags);
> +
> + exynos5_ppmu_update();
> + list_add_tail(&handle->node, &exynos5_ppmu_handle_list);
> +
> + spin_unlock_irqrestore(&exynos5_ppmu_lock, flags);
> +
> + return handle;
> +}
> +
> +static void exynos5_ppmu_debug_compute(struct exynos_ppmu *ppmu,
> + enum ppmu_counter i, u32 *sat, u32 *freq, u32 *bw)
> +{
> + u64 ns = ppmu->ns;
> + u32 busy = ppmu->count[i];
> + u32 total = ppmu->ccnt;
> +
> + u64 s;
> + u64 f;
> + u64 b;
> +
> + s = (u64)busy * 100 * (1 << FIXED_POINT_OFFSET);
> + s += total / 2;
> + do_div(s, total);
> +
> + f = (u64)total * 1000 * (1 << FIXED_POINT_OFFSET);
> + f += ns / 2;
> + f = div64_u64(f, ns);
> +
> + b = (u64)busy * (128 / 8) * 1000 * (1 << FIXED_POINT_OFFSET);
> + b += ns / 2;
> + b = div64_u64(b, ns);
> +
> + *sat = s;
> + *freq = f;
> + *bw = b;
> +}
> +
> +static void exynos5_ppmu_debug_show_one_counter(struct seq_file *s,
> + const char *name, const char *type, struct exynos_ppmu *ppmu,
> + enum ppmu_counter i, u32 *bw_total)
> +{
> + u32 sat;
> + u32 freq;
> + u32 bw;
> +
> + exynos5_ppmu_debug_compute(ppmu, i, &sat, &freq, &bw);
> +
> + seq_printf(s, "%-10s %-10s %4u.%02u MBps %3u.%02u MHz %2u.%02u%%\n",
> + name, type,
> + bw >> FIXED_POINT_OFFSET,
> + (bw & FIXED_POINT_MASK) * 100 / (1 << FIXED_POINT_OFFSET),
> + freq >> FIXED_POINT_OFFSET,
> + (freq & FIXED_POINT_MASK) * 100 / (1 << FIXED_POINT_OFFSET),
> + sat >> FIXED_POINT_OFFSET,
> + (sat & FIXED_POINT_MASK) * 100 / (1 << FIXED_POINT_OFFSET));
> +
> + *bw_total += bw;
> +}
> +
> +static void exynos5_ppmu_debug_show_one(struct seq_file *s,
> + const char *name, struct exynos_ppmu *ppmu,
> + u32 *bw_total)
> +{
> + exynos5_ppmu_debug_show_one_counter(s, name, "read+write",
> + ppmu, PPMU_PMNCNT3, &bw_total[PPMU_PMNCNT3]);
> + exynos5_ppmu_debug_show_one_counter(s, "", "read",
> + ppmu, PPMU_PMNCNT0, &bw_total[PPMU_PMNCNT0]);
> + exynos5_ppmu_debug_show_one_counter(s, "", "write",
> + ppmu, PPMU_PMCCNT1, &bw_total[PPMU_PMCCNT1]);
> +
> +}
> +
> +static int exynos5_ppmu_debug_show(struct seq_file *s, void *d)
> +{
> + int i;
> + u32 bw_total[PPMU_PMNCNT_MAX];
> + struct exynos5_ppmu_handle *handle;
> + unsigned long flags;
> +
> + memset(bw_total, 0, sizeof(bw_total));
> +
> + handle = exynos5_ppmu_get();
> + msleep(100);
> +
> + spin_lock_irqsave(&exynos5_ppmu_lock, flags);
> +
> + exynos5_ppmu_update();
> +
> + for (i = 0; i < PPMU_CPU; i++)
> + exynos5_ppmu_debug_show_one(s, exynos5_ppmu_name[i],
> + &handle->ppmu[i], bw_total);
> +
> + seq_printf(s, "%-10s %-10s %4u.%02u MBps\n", "total", "read+write",
> + bw_total[PPMU_PMNCNT3] >> FIXED_POINT_OFFSET,
> + (bw_total[PPMU_PMNCNT3] & FIXED_POINT_MASK) *
> + 100 / (1 << FIXED_POINT_OFFSET));
> + seq_printf(s, "%-10s %-10s %4u.%02u MBps\n", "", "read",
> + bw_total[PPMU_PMNCNT0] >> FIXED_POINT_OFFSET,
> + (bw_total[PPMU_PMNCNT0] & FIXED_POINT_MASK) *
> + 100 / (1 << FIXED_POINT_OFFSET));
> + seq_printf(s, "%-10s %-10s %4u.%02u MBps\n", "", "write",
> + bw_total[PPMU_PMCCNT1] >> FIXED_POINT_OFFSET,
> + (bw_total[PPMU_PMCCNT1] & FIXED_POINT_MASK) *
> + 100 / (1 << FIXED_POINT_OFFSET));
> +
> + seq_printf(s, "\n");
> +
> + exynos5_ppmu_debug_show_one(s, exynos5_ppmu_name[PPMU_CPU],
> + &ppmu[PPMU_CPU], bw_total);
> +
> + spin_unlock_irqrestore(&exynos5_ppmu_lock, flags);
> +
> + exynos5_ppmu_put(handle);
> +
> + return 0;
> +}
> +
> +static int exynos5_ppmu_debug_open(struct inode *inode, struct file *file)
> +{
> + return single_open(file, exynos5_ppmu_debug_show, inode->i_private);
> +}
> +
> +static const struct file_operations exynos5_ppmu_debug_fops = {
> + .open = exynos5_ppmu_debug_open,
> + .read = seq_read,
> + .llseek = seq_lseek,
> + .release = single_release,
> +};
> +
> +static int __init exynos5_ppmu_debug_init(void)
> +{
> + debugfs_create_file("exynos5_bus", S_IRUGO, NULL, NULL,
> + &exynos5_ppmu_debug_fops);
> + return 0;
> +}
> +late_initcall(exynos5_ppmu_debug_init);
> +
> +static int exynos5_ppmu_probe(struct platform_device *pdev)
> +{
> + struct resource *res;
> + int i;
> +
> + for (i = 0; i < PPMU_END; i++) {
> + /* get the memory region */
> + res = platform_get_resource(pdev, IORESOURCE_MEM, i);
> + if (res == NULL) {
> + dev_err(&pdev->dev, "failed to get memory region resource\n");
> + return -ENOENT;
> + }
> +
> + ppmu[i].hw_base = devm_request_and_ioremap(&pdev->dev, res);
> + if (ppmu->hw_base == NULL) {
> + dev_err(&pdev->dev, "failed to ioremap memory region\n");
> + return -EINVAL;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int __devexit exynos5_ppmu_remove(struct platform_device *pdev)
> +{
> + dev_dbg(&pdev->dev, "%s driver unloaded\n", pdev->name);
> +
> + return 0;
> +}
> +
> +static const struct of_device_id exynos5_ppmu_match[] = {
> + {
> + .compatible = "samsung,exynos5250-ppmu",
> + },
> + {},
> +};
> +
> +static struct platform_driver exynos5_ppmu_driver = {
> + .probe = exynos5_ppmu_probe,
> + .remove = __devexit_p(exynos5_ppmu_remove),
> + .driver = {
> + .name = "exynos5-ppmu",
> + .owner = THIS_MODULE,
> + .of_match_table = of_match_ptr(exynos5_ppmu_match),
> + },
> +};
> +module_platform_driver(exynos5_ppmu_driver);
> diff --git a/drivers/devfreq/exynos/exynos_ppmu.c b/drivers/devfreq/exynos/exynos_ppmu.c
> new file mode 100644
> index 0000000..da373be
> --- /dev/null
> +++ b/drivers/devfreq/exynos/exynos_ppmu.c
> @@ -0,0 +1,56 @@
> +/*
> + * Copyright (c) 2012 Samsung Electronics Co., Ltd.
> + * http://www.samsung.com/
> + *
> + * EXYNOS - PPMU support
> + *
> + * 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.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/types.h>
> +#include <linux/io.h>
> +
> +#include <linux/exynos_ppmu.h>
> +
> +void exynos_ppmu_reset(void __iomem *ppmu_base)
> +{
> + __raw_writel(PPMU_CYCLE_RESET | PPMU_COUNTER_RESET, ppmu_base);
> + __raw_writel(PPMU_ENABLE_CYCLE |
> + PPMU_ENABLE_COUNT0 |
> + PPMU_ENABLE_COUNT1 |
> + PPMU_ENABLE_COUNT2 |
> + PPMU_ENABLE_COUNT3,
> + ppmu_base + PPMU_CNTENS);
> +}
> +
> +void exynos_ppmu_setevent(void __iomem *ppmu_base, unsigned int ch,
> + unsigned int evt)
> +{
> + __raw_writel(evt, ppmu_base + PPMU_BEVTSEL(ch));
> +}
> +
> +void exynos_ppmu_start(void __iomem *ppmu_base)
> +{
> + __raw_writel(PPMU_ENABLE, ppmu_base);
> +}
> +
> +void exynos_ppmu_stop(void __iomem *ppmu_base)
> +{
> + __raw_writel(PPMU_DISABLE, ppmu_base);
> +}
> +
> +unsigned int exynos_ppmu_read(void __iomem *ppmu_base, unsigned int ch)
> +{
> + unsigned int total;
> +
> + if (ch == PPMU_PMNCNT3)
> + total = ((__raw_readl(ppmu_base + PMCNT_OFFSET(ch)) << 8) |
> + __raw_readl(ppmu_base + PMCNT_OFFSET(ch + 1)));
> + else
> + total = __raw_readl(ppmu_base + PMCNT_OFFSET(ch));
> +
> + return total;
> +}
> diff --git a/include/linux/exynos5_ppmu.h b/include/linux/exynos5_ppmu.h
> new file mode 100644
> index 0000000..9f492c1
> --- /dev/null
> +++ b/include/linux/exynos5_ppmu.h
> @@ -0,0 +1,26 @@
> +/*
> + * Copyright (c) 2012 Samsung Electronics Co., Ltd.
> + * http://www.samsung.com/
> + *
> + * EXYNOS5 PPMU header
> + *
> + * 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.
> +*/
> +
> +#ifndef __DEVFREQ_EXYNOS5_PPMU_H
> +#define __DEVFREQ_EXYNOS5_PPMU_H __FILE__
> +
> +enum exynos5_ppmu_sets {
> + PPMU_SET_DDR,
> + PPMU_SET_RIGHT,
> + PPMU_SET_CPU,
> +};
> +
> +struct exynos5_ppmu_handle *exynos5_ppmu_get(void);
> +extern int exynos5_ppmu_get_busy(struct exynos5_ppmu_handle *handle,
> + enum exynos5_ppmu_sets filter);
> +
> +#endif /* __DEVFREQ_EXYNOS5_PPMU_H */
> +
> diff --git a/include/linux/exynos_ppmu.h b/include/linux/exynos_ppmu.h
> new file mode 100644
> index 0000000..b46d31b
> --- /dev/null
> +++ b/include/linux/exynos_ppmu.h
> @@ -0,0 +1,79 @@
> +/*
> + * Copyright (c) 2012 Samsung Electronics Co., Ltd.
> + * http://www.samsung.com/
> + *
> + * EXYNOS PPMU header
> + *
> + * 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.
> +*/
> +
> +#ifndef __DEVFREQ_EXYNOS_PPMU_H
> +#define __DEVFREQ_EXYNOS_PPMU_H __FILE__
> +
> +#include <linux/ktime.h>
> +
> +/* For PPMU Control */
> +#define PPMU_ENABLE BIT(0)
> +#define PPMU_DISABLE 0x0
> +#define PPMU_CYCLE_RESET BIT(1)
> +#define PPMU_COUNTER_RESET BIT(2)
> +
> +#define PPMU_ENABLE_COUNT0 BIT(0)
> +#define PPMU_ENABLE_COUNT1 BIT(1)
> +#define PPMU_ENABLE_COUNT2 BIT(2)
> +#define PPMU_ENABLE_COUNT3 BIT(3)
> +#define PPMU_ENABLE_CYCLE BIT(31)
> +
> +#define PPMU_CNTENS 0x10
> +#define PPMU_FLAG 0x50
> +#define PPMU_CCNT_OVERFLOW BIT(31)
> +#define PPMU_CCNT 0x100
> +
> +#define PPMU_PMCNT0 0x110
> +#define PPMU_PMCNT_OFFSET 0x10
> +#define PMCNT_OFFSET(x) (PPMU_PMCNT0 + (PPMU_PMCNT_OFFSET * x))
> +
> +#define PPMU_BEVT0SEL 0x1000
> +#define PPMU_BEVTSEL_OFFSET 0x100
> +#define PPMU_BEVTSEL(x) (PPMU_BEVT0SEL + (ch * PPMU_BEVTSEL_OFFSET))
> +
> +/* For Event Selection */
> +#define RD_DATA_COUNT 0x5
> +#define WR_DATA_COUNT 0x6
> +#define RDWR_DATA_COUNT 0x7
> +
> +enum ppmu_counter {
> + PPMU_PMNCNT0,
> + PPMU_PMCCNT1,
> + PPMU_PMNCNT2,
> + PPMU_PMNCNT3,
> + PPMU_PMNCNT_MAX,
> +};
> +
> +struct bus_opp_table {
> + unsigned int idx;
> + unsigned long clk;
> + unsigned long volt;
> +};
> +
> +struct exynos_ppmu {
> + void __iomem *hw_base;
> + unsigned int ccnt;
> + unsigned int event[PPMU_PMNCNT_MAX];
> + unsigned int count[PPMU_PMNCNT_MAX];
> + unsigned long long ns;
> + ktime_t reset_time;
> + bool ccnt_overflow;
> + bool count_overflow[PPMU_PMNCNT_MAX];
> +};
> +
> +void exynos_ppmu_reset(void __iomem *ppmu_base);
> +void exynos_ppmu_setevent(void __iomem *ppmu_base, unsigned int ch,
> + unsigned int evt);
> +void exynos_ppmu_start(void __iomem *ppmu_base);
> +void exynos_ppmu_stop(void __iomem *ppmu_base);
> +unsigned int exynos_ppmu_read(void __iomem *ppmu_base, unsigned int ch);
> +#endif /* __DEVFREQ_EXYNOS_PPMU_H */
> +
> --
> 1.7.8.6
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-pm" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
--
Regards,
Rajagopal
^ permalink raw reply
* Re: [PATCH] cpufreq: Simplify __cpufreq_remove_dev()
From: Viresh Kumar @ 2013-01-09 15:39 UTC (permalink / raw)
To: rjw
Cc: robin.randhawa, Steve.Bannister, Liviu.Dudau, linaro-dev, patches,
cpufreq, linux-pm, linux-kernel, shawn.guo, Viresh Kumar
In-Reply-To: <aa10abe640afed8700abf262dbfc9b9c22659b6a.1357729671.git.viresh.kumar@linaro.org>
On 9 January 2013 16:50, Viresh Kumar <viresh.kumar@linaro.org> wrote:
> [Probably need to simplify cpufreq_add_dev() too, but that can be done as next
> step.]
I have tried that too, it is also pushed at:
https://lkml.org/lkml/2012/12/16/5
[Untested for now, will be doing it tomorrow]
From: Viresh Kumar <viresh.kumar@linaro.org>
Date: Wed, 9 Jan 2013 21:02:50 +0530
Subject: [PATCH] cpufreq: Simplify cpufreq_add_dev()
Currently cpufreq_add_dev() firsts allocated policy, calls ->init() and then
checks if this cpu should be already managed or not. And if it already managed,
free its policy.
We can save all this if we somehow know if this cpu is managed or not in
advance. policy->related_cpus contains list of all valid sibling cpus of
policy->cpu. We can check this to know if current cpu is already managed.
Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
---
drivers/cpufreq/cpufreq.c | 150 ++++++++++++++++------------------------------
1 file changed, 52 insertions(+), 98 deletions(-)
diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c
index b8709899..e3f7c7b 100644
--- a/drivers/cpufreq/cpufreq.c
+++ b/drivers/cpufreq/cpufreq.c
@@ -701,92 +701,6 @@ static struct kobj_type ktype_cpufreq = {
.release = cpufreq_sysfs_release,
};
-/*
- * Returns:
- * Negative: Failure
- * 0: Success
- * Positive: When we have a managed CPU and the sysfs got symlinked
- */
-static int cpufreq_add_dev_policy(unsigned int cpu,
- struct cpufreq_policy *policy,
- struct device *dev)
-{
- int ret = 0;
-#ifdef CONFIG_SMP
- unsigned long flags;
- unsigned int j;
-#ifdef CONFIG_HOTPLUG_CPU
- struct cpufreq_governor *gov;
-
- gov = __find_governor(per_cpu(cpufreq_cpu_governor, cpu));
- if (gov) {
- policy->governor = gov;
- pr_debug("Restoring governor %s for cpu %d\n",
- policy->governor->name, cpu);
- }
-#endif
-
- for_each_cpu(j, policy->cpus) {
- struct cpufreq_policy *managed_policy;
-
- if (cpu == j)
- continue;
-
- /* Check for existing affected CPUs.
- * They may not be aware of it due to CPU Hotplug.
- * cpufreq_cpu_put is called when the device is removed
- * in __cpufreq_remove_dev()
- */
- managed_policy = cpufreq_cpu_get(j);
- if (unlikely(managed_policy)) {
-
- /* Set proper policy_cpu */
- unlock_policy_rwsem_write(cpu);
- per_cpu(cpufreq_policy_cpu, cpu) = managed_policy->cpu;
-
- if (lock_policy_rwsem_write(cpu) < 0) {
- /* Should not go through policy unlock path */
- if (cpufreq_driver->exit)
- cpufreq_driver->exit(policy);
- cpufreq_cpu_put(managed_policy);
- return -EBUSY;
- }
-
- __cpufreq_governor(managed_policy, CPUFREQ_GOV_STOP);
-
- spin_lock_irqsave(&cpufreq_driver_lock, flags);
- cpumask_copy(managed_policy->cpus, policy->cpus);
- per_cpu(cpufreq_cpu_data, cpu) = managed_policy;
- spin_unlock_irqrestore(&cpufreq_driver_lock, flags);
-
- __cpufreq_governor(managed_policy, CPUFREQ_GOV_START);
- __cpufreq_governor(managed_policy, CPUFREQ_GOV_LIMITS);
-
- pr_debug("CPU already managed, adding link\n");
- ret = sysfs_create_link(&dev->kobj,
- &managed_policy->kobj,
- "cpufreq");
- if (ret)
- cpufreq_cpu_put(managed_policy);
- /*
- * Success. We only needed to be added to the mask.
- * Call driver->exit() because only the cpu parent of
- * the kobj needed to call init().
- */
- if (cpufreq_driver->exit)
- cpufreq_driver->exit(policy);
-
- if (!ret)
- return 1;
- else
- return ret;
- }
- }
-#endif
- return ret;
-}
-
-
/* symlink affected CPUs */
static int cpufreq_add_dev_symlink(unsigned int cpu,
struct cpufreq_policy *policy)
@@ -891,6 +805,41 @@ err_out_kobj_put:
return ret;
}
+static int cpufreq_add_policy_cpu(unsigned int cpu, unsigned int sibling,
+ struct device *dev)
+{
+ struct cpufreq_policy *policy;
+ int ret = 0;
+ unsigned long flags;
+
+ policy = cpufreq_cpu_get(sibling);
+ WARN_ON(!policy);
+
+ per_cpu(cpufreq_policy_cpu, cpu) = policy->cpu;
+
+ lock_policy_rwsem_write(cpu);
+
+ __cpufreq_governor(policy, CPUFREQ_GOV_STOP);
+
+ spin_lock_irqsave(&cpufreq_driver_lock, flags);
+ cpumask_set_cpu(cpu, policy->cpus);
+ per_cpu(cpufreq_cpu_data, cpu) = policy;
+ spin_unlock_irqrestore(&cpufreq_driver_lock, flags);
+
+ __cpufreq_governor(policy, CPUFREQ_GOV_START);
+ __cpufreq_governor(policy, CPUFREQ_GOV_LIMITS);
+
+ unlock_policy_rwsem_write(cpu);
+
+ ret = sysfs_create_link(&dev->kobj, &policy->kobj, "cpufreq");
+ if (ret) {
+ cpufreq_cpu_put(policy);
+ return ret;
+ }
+
+ return 0;
+}
+
/**
* cpufreq_add_dev - add a CPU device
@@ -903,12 +852,12 @@ err_out_kobj_put:
*/
static int cpufreq_add_dev(struct device *dev, struct subsys_interface *sif)
{
- unsigned int cpu = dev->id;
- int ret = 0, found = 0;
+ unsigned int j, cpu = dev->id;
+ int ret = -ENOMEM, found = 0;
struct cpufreq_policy *policy;
unsigned long flags;
- unsigned int j;
#ifdef CONFIG_HOTPLUG_CPU
+ struct cpufreq_governor *gov;
int sibling;
#endif
@@ -925,6 +874,13 @@ static int cpufreq_add_dev(struct device *dev,
struct subsys_interface *sif)
cpufreq_cpu_put(policy);
return 0;
}
+
+ /* Check if this cpu was hot-unplugged earlier and has siblings */
+ for_each_online_cpu(sibling) {
+ struct cpufreq_policy *cp = per_cpu(cpufreq_cpu_data, sibling);
+ if (cp && cpumask_test_cpu(cpu, cp->related_cpus))
+ return cpufreq_add_policy_cpu(cpu, sibling, dev);
+ }
#endif
if (!try_module_get(cpufreq_driver->owner)) {
@@ -932,7 +888,6 @@ static int cpufreq_add_dev(struct device *dev,
struct subsys_interface *sif)
goto module_out;
}
- ret = -ENOMEM;
policy = kzalloc(sizeof(struct cpufreq_policy), GFP_KERNEL);
if (!policy)
goto nomem_out;
@@ -990,14 +945,14 @@ static int cpufreq_add_dev(struct device *dev,
struct subsys_interface *sif)
blocking_notifier_call_chain(&cpufreq_policy_notifier_list,
CPUFREQ_START, policy);
- ret = cpufreq_add_dev_policy(cpu, policy, dev);
- if (ret) {
- if (ret > 0)
- /* This is a managed cpu, symlink created,
- exit with 0 */
- ret = 0;
- goto err_unlock_policy;
+#ifdef CONFIG_HOTPLUG_CPU
+ gov = __find_governor(per_cpu(cpufreq_cpu_governor, cpu));
+ if (gov) {
+ policy->governor = gov;
+ pr_debug("Restoring governor %s for cpu %d\n",
+ policy->governor->name, cpu);
}
+#endif
ret = cpufreq_add_dev_interface(cpu, policy, dev);
if (ret)
@@ -1011,7 +966,6 @@ static int cpufreq_add_dev(struct device *dev,
struct subsys_interface *sif)
return 0;
-
err_out_unregister:
spin_lock_irqsave(&cpufreq_driver_lock, flags);
for_each_cpu(j, policy->cpus)
^ permalink raw reply related
* Re: [PATCH 7/9] Thermal: Make PER_ZONE values configurable
From: Greg KH @ 2013-01-09 17:00 UTC (permalink / raw)
To: R, Durgadoss
Cc: Zhang, Rui, linux-pm@vger.kernel.org,
linux-kernel@vger.kernel.org, eduardo.valentin@ti.com,
hongbo.zhang@linaro.org, wni@nvidia.com
In-Reply-To: <4D68720C2E767A4AA6A8796D42C8EB59257F01@BGSMSX101.gar.corp.intel.com>
On Wed, Jan 09, 2013 at 09:12:29AM +0000, R, Durgadoss wrote:
> Hi Greg,
>
> > -----Original Message-----
> > From: Greg KH [mailto:gregkh@linuxfoundation.org]
> > Sent: Tuesday, January 08, 2013 12:54 AM
> > To: R, Durgadoss
> > Cc: Zhang, Rui; linux-pm@vger.kernel.org; linux-kernel@vger.kernel.org;
> > eduardo.valentin@ti.com; hongbo.zhang@linaro.org; wni@nvidia.com
> > Subject: Re: [PATCH 7/9] Thermal: Make PER_ZONE values configurable
> >
> > On Mon, Jan 07, 2013 at 12:43:24PM +0530, Durgadoss R wrote:
> > > This patch makes MAX_SENSORS_PER_ZONE and
> > > MAX_CDEVS_PER_ZONE values configurable. The
> > > default value is 1, and range is 1-12.
> >
> > Why would we ever want to change this? Why make this configurable at
> > all, how is a distro supposed to set this value?
> >
> > Shouldn't it be specified from the driver itself?
>
> These are platform level parameters, that can differ for various platforms.
> (Mostly due to board design and thermistor layouts). Stand-alone thermal
> sensor drivers are not (need not be) aware of these values.
Ok, and how does anyone know how to set them properly?
> That's why these values are made configurable.
Pushing work onto other people, without telling them what they need to
do, isn't nice. Please, either make it auto-configurable, or don't make
it configurable at all. As it is, this is useless for a distro, or any
"normal" user, right?
If these are platform specific things, shouldn't they be defined in the
platform data for the hardware?
thanks,
greg k-h
^ permalink raw reply
* Re: [PATCH 3/9] Thermal: Add APIs to bind cdev to new zone structure
From: Greg KH @ 2013-01-09 17:01 UTC (permalink / raw)
To: R, Durgadoss
Cc: Zhang, Rui, linux-pm@vger.kernel.org,
linux-kernel@vger.kernel.org, eduardo.valentin@ti.com,
hongbo.zhang@linaro.org, wni@nvidia.com
In-Reply-To: <4D68720C2E767A4AA6A8796D42C8EB59257F38@BGSMSX101.gar.corp.intel.com>
On Wed, Jan 09, 2013 at 09:21:23AM +0000, R, Durgadoss wrote:
> > -----Original Message-----
> > From: Greg KH [mailto:gregkh@linuxfoundation.org]
> > Sent: Tuesday, January 08, 2013 12:56 AM
> > To: R, Durgadoss
> > Cc: Zhang, Rui; linux-pm@vger.kernel.org; linux-kernel@vger.kernel.org;
> > eduardo.valentin@ti.com; hongbo.zhang@linaro.org; wni@nvidia.com
> > Subject: Re: [PATCH 3/9] Thermal: Add APIs to bind cdev to new zone
> > structure
> >
> > On Mon, Jan 07, 2013 at 12:43:20PM +0530, Durgadoss R wrote:
> > > +struct thermal_cooling_device *get_cdev_by_name(const char *name)
> > > +{
> > > + struct thermal_cooling_device *pos;
> > > + struct thermal_cooling_device *cdev = NULL;
> > > +
> > > + mutex_lock(&cdev_list_lock);
> > > + for_each_cdev(pos) {
> > > + if (!strnicmp(pos->type, name, THERMAL_NAME_LENGTH)) {
> > > + cdev = pos;
> > > + break;
> > > + }
> > > + }
> > > + mutex_unlock(&cdev_list_lock);
> > > + return cdev;
> > > +}
> > > +EXPORT_SYMBOL(get_cdev_by_name);
> >
> > EXPORT_SYMBOL_GPL?
>
> We have all other exports as EXPORT_SYMBOL in this file(thermal_sys.c)
> If _GPL is required, the we will do a single patch changing all of them to
> EXPORT_SYMBOL_GPL.
Fair enough.
> > You also forgot to increment the reference count, which is required for
> > all reference counted objects.
>
> Sorry, I could not get what you are saying here.
You are doing a "get" call, yet you are failing to increment a reference
count on the object. That isn't allowed and can cause bad problems.
Please use proper reference counting for your structures.
greg k-h
^ permalink raw reply
* [PATCH] drivers/cpufreq: Warn user when powernow-k8 tries to fall back to acpi-cpufreq and it is unavailable.
From: Aravind Gopalakrishnan @ 2013-01-10 1:09 UTC (permalink / raw)
To: rjw, cpufreq, linux-pm, linux-kernel; +Cc: Aravind Gopalakrishnan
This patch is in reference to bug#:51741. (https://bugzilla.kernel.org/show_bug.cgi?id=51741)
powernow-k8 falls back to acpi-cpufreq if CPU is not supported. However, it states that acpi-cpufreq
has taken over even if acpi-cpufreq is not compiled in. This patch rewords the warning message to
clarify that the CPU is unsupported and prints a warning message when there is no acpi-cpufreq
present.
Signed-off-by: Aravind Gopalakrishnan <Aravind.Gopalakrishnan@amd.com>
---
drivers/cpufreq/powernow-k8.c | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/drivers/cpufreq/powernow-k8.c b/drivers/cpufreq/powernow-k8.c
index 056faf6..6fa58b4 100644
--- a/drivers/cpufreq/powernow-k8.c
+++ b/drivers/cpufreq/powernow-k8.c
@@ -1256,7 +1256,15 @@ static int __cpuinit powernowk8_init(void)
int rv;
if (static_cpu_has(X86_FEATURE_HW_PSTATE)) {
- pr_warn(PFX "this CPU is not supported anymore, using acpi-cpufreq instead.\n");
+ pr_warn(PFX
+ "this CPU is not supported anymore, use acpi-cpufreq instead"
+ "Look for message from acpi-cpufreq to ensure it is loaded."
+ ".\n");
+#ifndef CONFIG_X86_ACPI_CPUFREQ
+ pr_warn(PFX "acpi-cpufreq is disabled."
+ "Enable it in the config options to get frequency scaling.\n");
+ return -ENODEV;
+#endif
request_module("acpi-cpufreq");
return -ENODEV;
}
--
1.7.10.4
^ permalink raw reply related
* Re: [PATCH] cpufreq: Simplify __cpufreq_remove_dev()
From: Viresh Kumar @ 2013-01-10 6:01 UTC (permalink / raw)
To: rjw
Cc: robin.randhawa, Steve.Bannister, Liviu.Dudau, linaro-dev, patches,
cpufreq, linux-pm, linux-kernel, shawn.guo, Viresh Kumar
In-Reply-To: <CAKohpo=ajxxanDPNpdxiJO8vvcQPxeQA29sZgeRtRg85F4CVGg@mail.gmail.com>
On 9 January 2013 21:09, Viresh Kumar <viresh.kumar@linaro.org> wrote:
> On 9 January 2013 16:50, Viresh Kumar <viresh.kumar@linaro.org> wrote:
>> [Probably need to simplify cpufreq_add_dev() too, but that can be done as next
>> step.]
>
> I have tried that too, it is also pushed at:
>
> https://lkml.org/lkml/2012/12/16/5
>
> [Untested for now, will be doing it tomorrow]
>
> From: Viresh Kumar <viresh.kumar@linaro.org>
> Date: Wed, 9 Jan 2013 21:02:50 +0530
> Subject: [PATCH] cpufreq: Simplify cpufreq_add_dev()
>
> Currently cpufreq_add_dev() firsts allocated policy, calls ->init() and then
> checks if this cpu should be already managed or not. And if it already managed,
> free its policy.
>
> We can save all this if we somehow know if this cpu is managed or not in
> advance. policy->related_cpus contains list of all valid sibling cpus of
> policy->cpu. We can check this to know if current cpu is already managed.
>
> Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
Tested-by: Viresh Kumar <viresh.kumar@linaro.org>
^ permalink raw reply
* Re: [PATCH] cpufreq: Simplify __cpufreq_remove_dev()
From: Viresh Kumar @ 2013-01-10 6:03 UTC (permalink / raw)
To: rjw
Cc: robin.randhawa, Steve.Bannister, Liviu.Dudau, linaro-dev, patches,
cpufreq, linux-pm, linux-kernel, shawn.guo, Viresh Kumar
In-Reply-To: <CAKohpo=ajxxanDPNpdxiJO8vvcQPxeQA29sZgeRtRg85F4CVGg@mail.gmail.com>
On 9 January 2013 21:09, Viresh Kumar <viresh.kumar@linaro.org> wrote:
> I have tried that too, it is also pushed at:
>
> https://lkml.org/lkml/2012/12/16/5
Bad link :(
http://git.linaro.org/gitweb?p=arm/big.LITTLE/mp.git;a=shortlog;h=refs/heads/cpufreq-fixes-v2
^ permalink raw reply
* Re: [PATCH] cpufreq: Simplify __cpufreq_remove_dev()
From: Shawn Guo @ 2013-01-10 7:54 UTC (permalink / raw)
To: Viresh Kumar
Cc: rjw, robin.randhawa, Steve.Bannister, Liviu.Dudau, linaro-dev,
patches, cpufreq, linux-pm, linux-kernel
In-Reply-To: <aa10abe640afed8700abf262dbfc9b9c22659b6a.1357729671.git.viresh.kumar@linaro.org>
On Wed, Jan 09, 2013 at 04:50:44PM +0530, Viresh Kumar wrote:
> @Shawn: I believe your driver don't require that ugly code anymore (Though i
> know there is a situation for that to happen, if we have two cpus, you remove
> second one and then add it back. With this cpufreq_add_dev() would call init()
> first and then try to match if there are any managed_policies present. But the
> issue you pointed out about unregistering the driver would be solved by this
> patch.)
Yes, just played it and it works for me. However, I would have to keep
that little ugly code in my patch to save the dependency on your patch.
Will send a follow-up to clean that up once your patch hits mainline.
Shawn
^ permalink raw reply
* Re: [PATCH] cpufreq: Simplify __cpufreq_remove_dev()
From: Viresh Kumar @ 2013-01-10 7:59 UTC (permalink / raw)
To: Shawn Guo
Cc: rjw, robin.randhawa, Steve.Bannister, Liviu.Dudau, linaro-dev,
patches, cpufreq, linux-pm, linux-kernel
In-Reply-To: <20130110075422.GA11458@S2101-09.ap.freescale.net>
On 10 January 2013 13:24, Shawn Guo <shawn.guo@linaro.org> wrote:
> Yes, just played it and it works for me. However, I would have to keep
> that little ugly code in my patch to save the dependency on your patch.
> Will send a follow-up to clean that up once your patch hits mainline.
Good. Hopefully, patches from both of us would hit 3.9 and Rafael can
apply yours after mine, with this check removed :)
--
viresh
^ permalink raw reply
* Re: [PATCH] cpufreq: Simplify __cpufreq_remove_dev()
From: Viresh Kumar @ 2013-01-10 8:05 UTC (permalink / raw)
To: Shawn Guo
Cc: rjw, robin.randhawa, Steve.Bannister, Liviu.Dudau, linaro-dev,
patches, cpufreq, linux-pm, linux-kernel
In-Reply-To: <20130110075422.GA11458@S2101-09.ap.freescale.net>
On 10 January 2013 13:24, Shawn Guo <shawn.guo@linaro.org> wrote:
> On Wed, Jan 09, 2013 at 04:50:44PM +0530, Viresh Kumar wrote:
>> @Shawn: I believe your driver don't require that ugly code anymore (Though i
>> know there is a situation for that to happen, if we have two cpus, you remove
>> second one and then add it back. With this cpufreq_add_dev() would call init()
>> first and then try to match if there are any managed_policies present. But the
>> issue you pointed out about unregistering the driver would be solved by this
>> patch.)
>
> Yes, just played it and it works for me. However, I would have to keep
> that little ugly code in my patch to save the dependency on your patch.
> Will send a follow-up to clean that up once your patch hits mainline.
Another thing, can i have a tested-by from you for both my patches ? remove and
add dev?
That would help it go quicker.
^ permalink raw reply
* Re: [PATCH] cpufreq: Simplify __cpufreq_remove_dev()
From: Shawn Guo @ 2013-01-10 8:10 UTC (permalink / raw)
To: Viresh Kumar
Cc: rjw, robin.randhawa, Steve.Bannister, Liviu.Dudau, linaro-dev,
patches, cpufreq, linux-pm, linux-kernel
In-Reply-To: <CAKohpone-pXUjuDsP7r73mP+rzSUUb1oWiu5c+NL8R_WsYoJZg@mail.gmail.com>
On 10 January 2013 16:05, Viresh Kumar <viresh.kumar@linaro.org> wrote:
> Another thing, can i have a tested-by from you for both my patches ? remove and
> add dev?
>
For both:
Tested-by: Shawn Guo <shawn.guo@linaro.org>
^ permalink raw reply
* [PATCH v2 1/2] cpufreq: add imx6q-cpufreq driver
From: Shawn Guo @ 2013-01-10 8:34 UTC (permalink / raw)
To: cpufreq, linux-pm, linux-arm-kernel
Cc: Rafael J. Wysocki, Sascha Hauer, Viresh Kumar, Shawn Guo
In-Reply-To: <1357806863-6899-1-git-send-email-shawn.guo@linaro.org>
Add an imx6q-cpufreq for Freescale i.MX6Q SoC to handle the hardware
specific frequency and voltage scaling requirements.
The driver supports module build and is instantiated by the platform
device/driver mechanism, so that it will be instantiated on other
platform, as IMX is built with multiplatform support.
Signed-off-by: Shawn Guo <shawn.guo@linaro.org>
---
drivers/cpufreq/Kconfig.arm | 9 ++
drivers/cpufreq/Makefile | 1 +
drivers/cpufreq/imx6q-cpufreq.c | 296 +++++++++++++++++++++++++++++++++++++++
3 files changed, 306 insertions(+)
create mode 100644 drivers/cpufreq/imx6q-cpufreq.c
diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm
index a0b3661..9e628ba 100644
--- a/drivers/cpufreq/Kconfig.arm
+++ b/drivers/cpufreq/Kconfig.arm
@@ -77,6 +77,15 @@ config ARM_EXYNOS5250_CPUFREQ
This adds the CPUFreq driver for Samsung EXYNOS5250
SoC.
+config ARM_IMX6Q_CPUFREQ
+ tristate "Freescale i.MX6Q cpufreq support"
+ depends on SOC_IMX6Q
+ depends on REGULATOR_ANATOP
+ help
+ This adds cpufreq driver support for Freescale i.MX6Q SOC.
+
+ If in doubt, say N.
+
config ARM_SPEAR_CPUFREQ
bool "SPEAr CPUFreq support"
depends on PLAT_SPEAR
diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile
index 1f254ec0..31699a0 100644
--- a/drivers/cpufreq/Makefile
+++ b/drivers/cpufreq/Makefile
@@ -49,6 +49,7 @@ obj-$(CONFIG_ARM_EXYNOS_CPUFREQ) += exynos-cpufreq.o
obj-$(CONFIG_ARM_EXYNOS4210_CPUFREQ) += exynos4210-cpufreq.o
obj-$(CONFIG_ARM_EXYNOS4X12_CPUFREQ) += exynos4x12-cpufreq.o
obj-$(CONFIG_ARM_EXYNOS5250_CPUFREQ) += exynos5250-cpufreq.o
+obj-$(CONFIG_ARM_IMX6Q_CPUFREQ) += imx6q-cpufreq.o
obj-$(CONFIG_ARM_OMAP2PLUS_CPUFREQ) += omap-cpufreq.o
obj-$(CONFIG_ARM_SPEAR_CPUFREQ) += spear-cpufreq.o
diff --git a/drivers/cpufreq/imx6q-cpufreq.c b/drivers/cpufreq/imx6q-cpufreq.c
new file mode 100644
index 0000000..218d8f1
--- /dev/null
+++ b/drivers/cpufreq/imx6q-cpufreq.c
@@ -0,0 +1,296 @@
+/*
+ * Copyright (C) 2013 Freescale Semiconductor, 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.
+ */
+
+#include <linux/clk.h>
+#include <linux/cpu.h>
+#include <linux/cpufreq.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/opp.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+
+#define PU_SOC_VOLTAGE_NORMAL 1250000
+#define PU_SOC_VOLTAGE_HIGH 1275000
+#define FREQ_1P2_GHZ 1200000000
+
+static struct regulator *arm_reg;
+static struct regulator *pu_reg;
+static struct regulator *soc_reg;
+
+static struct clk *arm_clk;
+static struct clk *pll1_sys_clk;
+static struct clk *pll1_sw_clk;
+static struct clk *step_clk;
+static struct clk *pll2_pfd2_396m_clk;
+
+static struct device *cpu_dev;
+static struct cpufreq_frequency_table *freq_table;
+static unsigned int transition_latency;
+
+static int imx6q_verify_speed(struct cpufreq_policy *policy)
+{
+ return cpufreq_frequency_table_verify(policy, freq_table);
+}
+
+static unsigned int imx6q_get_speed(unsigned int cpu)
+{
+ return clk_get_rate(arm_clk) / 1000;
+}
+
+static int imx6q_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, volt_old;
+ unsigned int index, cpu;
+ int ret;
+
+ ret = cpufreq_frequency_table_target(policy, freq_table, target_freq,
+ relation, &index);
+ if (ret) {
+ dev_err(cpu_dev, "failed to match target frequency %d: %d\n",
+ target_freq, ret);
+ return ret;
+ }
+
+ freqs.new = freq_table[index].frequency;
+ freq_hz = freqs.new * 1000;
+ freqs.old = clk_get_rate(arm_clk) / 1000;
+
+ if (freqs.old == freqs.new)
+ return 0;
+
+ for_each_online_cpu(cpu) {
+ freqs.cpu = cpu;
+ cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
+ }
+
+ opp = opp_find_freq_ceil(cpu_dev, &freq_hz);
+ if (IS_ERR(opp)) {
+ dev_err(cpu_dev, "failed to find OPP for %ld\n", freq_hz);
+ return PTR_ERR(opp);
+ }
+
+ volt = opp_get_voltage(opp);
+ volt_old = regulator_get_voltage(arm_reg);
+
+ dev_dbg(cpu_dev, "%u MHz, %ld mV --> %u MHz, %ld mV\n",
+ freqs.old / 1000, volt_old / 1000,
+ freqs.new / 1000, volt / 1000);
+
+ /* scaling up? scale voltage before frequency */
+ if (freqs.new > freqs.old) {
+ ret = regulator_set_voltage_tol(arm_reg, volt, 0);
+ if (ret) {
+ dev_err(cpu_dev, "failed to scale voltage up: %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * Need to increase vddpu and vddsoc for safety
+ * if we are about to run at 1.2 GHz.
+ */
+ if (freqs.new == FREQ_1P2_GHZ / 1000) {
+ regulator_set_voltage_tol(pu_reg,
+ PU_SOC_VOLTAGE_HIGH, 0);
+ regulator_set_voltage_tol(soc_reg,
+ PU_SOC_VOLTAGE_HIGH, 0);
+ }
+ }
+
+ /*
+ * The setpoints are selected per PLL/PDF frequencies, so we need to
+ * reprogram PLL for frequency scaling. The procedure of reprogramming
+ * PLL1 is as below.
+ *
+ * - Enable pll2_pfd2_396m_clk and reparent pll1_sw_clk to it
+ * - Disable pll1_sys_clk and reprogram it
+ * - Enable pll1_sys_clk and reparent pll1_sw_clk back to it
+ * - Disable pll2_pfd2_396m_clk
+ */
+ clk_prepare_enable(pll2_pfd2_396m_clk);
+ clk_set_parent(step_clk, pll2_pfd2_396m_clk);
+ clk_set_parent(pll1_sw_clk, step_clk);
+ clk_prepare_enable(pll1_sys_clk);
+ if (freq_hz > clk_get_rate(pll2_pfd2_396m_clk)) {
+ clk_disable_unprepare(pll1_sys_clk);
+ clk_set_rate(pll1_sys_clk, freqs.new * 1000);
+ clk_prepare_enable(pll1_sys_clk);
+ clk_set_parent(pll1_sw_clk, pll1_sys_clk);
+ clk_disable_unprepare(pll2_pfd2_396m_clk);
+ } else {
+ /*
+ * Disable pll1_sys_clk if pll2_pfd2_396m_clk is sufficient
+ * to provide the frequency.
+ */
+ clk_disable_unprepare(pll1_sys_clk);
+ }
+
+ /* Ensure the arm clock divider is what we expect */
+ ret = clk_set_rate(arm_clk, freqs.new * 1000);
+ if (ret) {
+ dev_err(cpu_dev, "failed to set clock rate: %d\n", ret);
+ regulator_set_voltage_tol(arm_reg, volt_old, 0);
+ return ret;
+ }
+
+ /* scaling down? scale voltage after frequency */
+ if (freqs.new < freqs.old) {
+ ret = regulator_set_voltage_tol(arm_reg, volt, 0);
+ if (ret)
+ dev_warn(cpu_dev, "failed to scale voltage down: %d\n", ret);
+
+ if (freqs.old == FREQ_1P2_GHZ / 1000) {
+ regulator_set_voltage_tol(pu_reg,
+ PU_SOC_VOLTAGE_NORMAL, 0);
+ regulator_set_voltage_tol(soc_reg,
+ PU_SOC_VOLTAGE_NORMAL, 0);
+ }
+ }
+
+ for_each_online_cpu(cpu) {
+ freqs.cpu = cpu;
+ cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
+ }
+
+ return 0;
+}
+
+static int imx6q_cpufreq_init(struct cpufreq_policy *policy)
+{
+ int ret;
+
+ ret = cpufreq_frequency_table_cpuinfo(policy, freq_table);
+ if (ret) {
+ dev_err(cpu_dev, "invalid frequency table: %d\n", ret);
+ return ret;
+ }
+
+ policy->cpuinfo.transition_latency = transition_latency;
+ policy->cur = clk_get_rate(arm_clk) / 1000;
+ policy->shared_type = CPUFREQ_SHARED_TYPE_ANY;
+ cpumask_setall(policy->cpus);
+ cpufreq_frequency_table_get_attr(freq_table, policy->cpu);
+
+ return 0;
+}
+
+static int imx6q_cpufreq_exit(struct cpufreq_policy *policy)
+{
+ cpufreq_frequency_table_put_attr(policy->cpu);
+ return 0;
+}
+
+static struct freq_attr *imx6q_cpufreq_attr[] = {
+ &cpufreq_freq_attr_scaling_available_freqs,
+ NULL,
+};
+
+static struct cpufreq_driver imx6q_cpufreq_driver = {
+ .verify = imx6q_verify_speed,
+ .target = imx6q_set_target,
+ .get = imx6q_get_speed,
+ .init = imx6q_cpufreq_init,
+ .exit = imx6q_cpufreq_exit,
+ .name = "imx6q-cpufreq",
+ .attr = imx6q_cpufreq_attr,
+};
+
+static int imx6q_cpufreq_probe(struct platform_device *pdev)
+{
+ struct device_node *np;
+ int ret;
+
+ cpu_dev = &pdev->dev;
+
+ np = of_find_node_by_path("/cpus/cpu@0");
+ if (!np) {
+ dev_err(cpu_dev, "failed to find cpu0 node\n");
+ return -ENOENT;
+ }
+
+ cpu_dev->of_node = np;
+
+ arm_clk = devm_clk_get(cpu_dev, "arm");
+ pll1_sys_clk = devm_clk_get(cpu_dev, "pll1_sys");
+ pll1_sw_clk = devm_clk_get(cpu_dev, "pll1_sw");
+ step_clk = devm_clk_get(cpu_dev, "step");
+ pll2_pfd2_396m_clk = devm_clk_get(cpu_dev, "pll2_pfd2_396m");
+ if (IS_ERR(arm_clk) || IS_ERR(pll1_sys_clk) || IS_ERR(pll1_sw_clk) ||
+ IS_ERR(step_clk) || IS_ERR(pll2_pfd2_396m_clk)) {
+ dev_err(cpu_dev, "failed to get clocks\n");
+ ret = -ENOENT;
+ goto put_node;
+ }
+
+ arm_reg = devm_regulator_get(cpu_dev, "arm");
+ pu_reg = devm_regulator_get(cpu_dev, "pu");
+ soc_reg = devm_regulator_get(cpu_dev, "soc");
+ if (!arm_reg || !pu_reg || !soc_reg) {
+ dev_err(cpu_dev, "failed to get regulators\n");
+ ret = -ENOENT;
+ goto put_node;
+ }
+
+ /* We expect an OPP table supplied by platform */
+ ret = opp_get_opp_count(cpu_dev);
+ if (ret < 0) {
+ dev_err(cpu_dev, "no OPP table is found: %d\n", ret);
+ goto put_node;
+ }
+
+ ret = opp_init_cpufreq_table(cpu_dev, &freq_table);
+ if (ret) {
+ dev_err(cpu_dev, "failed to init cpufreq table: %d\n", ret);
+ goto put_node;
+ }
+
+ if (of_property_read_u32(np, "clock-latency", &transition_latency))
+ transition_latency = CPUFREQ_ETERNAL;
+
+ ret = cpufreq_register_driver(&imx6q_cpufreq_driver);
+ if (ret) {
+ dev_err(cpu_dev, "failed register driver: %d\n", ret);
+ goto free_freq_table;
+ }
+
+ of_node_put(np);
+ return 0;
+
+free_freq_table:
+ opp_free_cpufreq_table(cpu_dev, &freq_table);
+put_node:
+ of_node_put(np);
+ return ret;
+}
+
+static int imx6q_cpufreq_remove(struct platform_device *pdev)
+{
+ cpufreq_unregister_driver(&imx6q_cpufreq_driver);
+ opp_free_cpufreq_table(cpu_dev, &freq_table);
+
+ return 0;
+}
+
+static struct platform_driver imx6q_cpufreq_platdrv = {
+ .driver = {
+ .name = "imx6q-cpufreq",
+ .owner = THIS_MODULE,
+ },
+ .probe = imx6q_cpufreq_probe,
+ .remove = imx6q_cpufreq_remove,
+};
+module_platform_driver(imx6q_cpufreq_platdrv);
+
+MODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>");
+MODULE_DESCRIPTION("Freescale i.MX6Q cpufreq driver");
+MODULE_LICENSE("GPL");
--
1.7.9.5
^ permalink raw reply related
* [PATCH v2 0/2] Add imx6q-cpufreq driver support
From: Shawn Guo @ 2013-01-10 8:34 UTC (permalink / raw)
To: cpufreq, linux-pm, linux-arm-kernel
Cc: Rafael J. Wysocki, Sascha Hauer, Viresh Kumar, Shawn Guo
Changes since v1:
- Drop patch "PM / OPP: Export more symbols for module usage", as
there is already one "[PATCH 5/6 v9] power: export opp cpufreq
functions" from Mark Langsdorf.
- Instead of having cpu_dev be the struct device retrieved from
get_cpu_device(), have it be &pdev->dev, so that we can use managed
functions to simplified the cleanup path.
- Use dev_* rather than pr_* for message output
- Fix Typos and others commented by Viresh and Sascha.
Once the first patch gets accepted, I will apply the second one for
going through arm-soc tree.
Shawn Guo (2):
cpufreq: add imx6q-cpufreq driver
ARM: imx: enable imx6q-cpufreq support
arch/arm/boot/dts/imx6q.dtsi | 19 ++-
arch/arm/mach-imx/mach-imx6q.c | 65 +++++++++
drivers/cpufreq/Kconfig.arm | 9 ++
drivers/cpufreq/Makefile | 1 +
drivers/cpufreq/imx6q-cpufreq.c | 296 +++++++++++++++++++++++++++++++++++++++
5 files changed, 384 insertions(+), 6 deletions(-)
create mode 100644 drivers/cpufreq/imx6q-cpufreq.c
--
1.7.9.5
^ permalink raw reply
* [PATCH v2 2/2] ARM: imx: enable imx6q-cpufreq support
From: Shawn Guo @ 2013-01-10 8:34 UTC (permalink / raw)
To: cpufreq, linux-pm, linux-arm-kernel
Cc: Rafael J. Wysocki, Sascha Hauer, Viresh Kumar, Shawn Guo
In-Reply-To: <1357806863-6899-1-git-send-email-shawn.guo@linaro.org>
Update operating-points per hardware document and add support for
1 GHz and 1.2 GHz frequencies.
400 MHz, 800 MHz and 1 GHz should be supported by all i.MX6Q chips,
while 1.2 GHz support needs to know from OTP fuse bit.
Signed-off-by: Shawn Guo <shawn.guo@linaro.org>
---
arch/arm/boot/dts/imx6q.dtsi | 19 ++++++++----
arch/arm/mach-imx/mach-imx6q.c | 65 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 78 insertions(+), 6 deletions(-)
diff --git a/arch/arm/boot/dts/imx6q.dtsi b/arch/arm/boot/dts/imx6q.dtsi
index d6265ca..17c5618 100644
--- a/arch/arm/boot/dts/imx6q.dtsi
+++ b/arch/arm/boot/dts/imx6q.dtsi
@@ -38,12 +38,18 @@
next-level-cache = <&L2>;
operating-points = <
/* kHz uV */
- 792000 1100000
+ 996000 1250000
+ 792000 1150000
396000 950000
- 198000 850000
>;
clock-latency = <61036>; /* two CLK32 periods */
- cpu0-supply = <®_cpu>;
+ clocks = <&clks 104>, <&clks 6>, <&clks 16>,
+ <&clks 17>, <&clks 170>;
+ clock-names = "arm", "pll2_pfd2_396m", "step",
+ "pll1_sw", "pll1_sys";
+ arm-supply = <®_arm>;
+ pu-supply = <®_pu>;
+ soc-supply = <®_soc>;
};
cpu@1 {
@@ -471,7 +477,7 @@
anatop-max-voltage = <2750000>;
};
- reg_cpu: regulator-vddcore@140 {
+ reg_arm: regulator-vddcore@140 {
compatible = "fsl,anatop-regulator";
regulator-name = "cpu";
regulator-min-microvolt = <725000>;
@@ -485,7 +491,7 @@
anatop-max-voltage = <1450000>;
};
- regulator-vddpu@140 {
+ reg_pu: regulator-vddpu@140 {
compatible = "fsl,anatop-regulator";
regulator-name = "vddpu";
regulator-min-microvolt = <725000>;
@@ -499,7 +505,7 @@
anatop-max-voltage = <1450000>;
};
- regulator-vddsoc@140 {
+ reg_soc: regulator-vddsoc@140 {
compatible = "fsl,anatop-regulator";
regulator-name = "vddsoc";
regulator-min-microvolt = <725000>;
@@ -965,6 +971,7 @@
};
ocotp@021bc000 {
+ compatible = "fsl,imx6q-ocotp";
reg = <0x021bc000 0x4000>;
};
diff --git a/arch/arm/mach-imx/mach-imx6q.c b/arch/arm/mach-imx/mach-imx6q.c
index 4eb1b3a..16f9a13 100644
--- a/arch/arm/mach-imx/mach-imx6q.c
+++ b/arch/arm/mach-imx/mach-imx6q.c
@@ -12,6 +12,7 @@
#include <linux/clk.h>
#include <linux/clkdev.h>
+#include <linux/cpu.h>
#include <linux/cpuidle.h>
#include <linux/delay.h>
#include <linux/export.h>
@@ -22,6 +23,7 @@
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
+#include <linux/opp.h>
#include <linux/phy.h>
#include <linux/regmap.h>
#include <linux/micrel_phy.h>
@@ -209,9 +211,72 @@ static struct cpuidle_driver imx6q_cpuidle_driver = {
.state_count = 1,
};
+#define OCOTP_CFG3 0x440
+#define OCOTP_CFG3_SPEED_SHIFT 16
+#define OCOTP_CFG3_SPEED_1P2GHZ 0x3
+
+static void __init imx6q_opp_check_1p2ghz(struct device *cpu_dev)
+{
+ struct device_node *np;
+ void __iomem *base;
+ u32 val;
+
+ np = of_find_compatible_node(NULL, NULL, "fsl,imx6q-ocotp");
+ if (!np) {
+ pr_warn("failed to find ocotp node\n");
+ return;
+ }
+
+ base = of_iomap(np, 0);
+ if (!base) {
+ pr_warn("failed to map ocotp\n");
+ goto put_node;
+ }
+
+ val = readl_relaxed(base + OCOTP_CFG3);
+ val >>= OCOTP_CFG3_SPEED_SHIFT;
+ if ((val & 0x3) == OCOTP_CFG3_SPEED_1P2GHZ)
+ if (opp_add(cpu_dev, 1200000000, 1275000))
+ pr_warn("failed to add 1.2 GHz operating point\n");
+
+put_node:
+ of_node_put(np);
+}
+
+static void __init imx6q_opp_init(struct device *cpu_dev)
+{
+ struct device_node *np;
+
+ np = of_find_node_by_path("/cpus/cpu@0");
+ if (!np) {
+ pr_warn("failed to find cpu0 node\n");
+ return;
+ }
+
+ cpu_dev->of_node = np;
+ if (of_init_opp_table(cpu_dev)) {
+ pr_warn("failed to init OPP table\n");
+ goto put_node;
+ }
+
+ imx6q_opp_check_1p2ghz(cpu_dev);
+
+put_node:
+ of_node_put(np);
+}
+
+struct platform_device imx6q_cpufreq_pdev = {
+ .name = "imx6q-cpufreq",
+};
+
static void __init imx6q_init_late(void)
{
imx_cpuidle_init(&imx6q_cpuidle_driver);
+
+ if (IS_ENABLED(CONFIG_ARM_IMX6Q_CPUFREQ)) {
+ imx6q_opp_init(&imx6q_cpufreq_pdev.dev);
+ platform_device_register(&imx6q_cpufreq_pdev);
+ }
}
static void __init imx6q_map_io(void)
--
1.7.9.5
^ permalink raw reply related
* Re: [PATCH v2 1/2] cpufreq: add imx6q-cpufreq driver
From: Viresh Kumar @ 2013-01-10 8:45 UTC (permalink / raw)
To: Rafael J. Wysocki, Shawn Guo
Cc: cpufreq, linux-pm, linux-arm-kernel, Sascha Hauer
In-Reply-To: <1357806863-6899-2-git-send-email-shawn.guo@linaro.org>
On 10 January 2013 14:04, Shawn Guo <shawn.guo@linaro.org> wrote:
> Add an imx6q-cpufreq for Freescale i.MX6Q SoC to handle the hardware
^
driver
> specific frequency and voltage scaling requirements.
>
> The driver supports module build and is instantiated by the platform
> device/driver mechanism, so that it will be instantiated on other
> platform, as IMX is built with multiplatform support.
>
> Signed-off-by: Shawn Guo <shawn.guo@linaro.org>
> ---
> drivers/cpufreq/Kconfig.arm | 9 ++
> drivers/cpufreq/Makefile | 1 +
> drivers/cpufreq/imx6q-cpufreq.c | 296 +++++++++++++++++++++++++++++++++++++++
> 3 files changed, 306 insertions(+)
> create mode 100644 drivers/cpufreq/imx6q-cpufreq.c
>
> diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm
> index a0b3661..9e628ba 100644
> --- a/drivers/cpufreq/Kconfig.arm
> +++ b/drivers/cpufreq/Kconfig.arm
> @@ -77,6 +77,15 @@ config ARM_EXYNOS5250_CPUFREQ
> This adds the CPUFreq driver for Samsung EXYNOS5250
> SoC.
>
> +config ARM_IMX6Q_CPUFREQ
> + tristate "Freescale i.MX6Q cpufreq support"
> + depends on SOC_IMX6Q
> + depends on REGULATOR_ANATOP
> + help
> + This adds cpufreq driver support for Freescale i.MX6Q SOC.
> +
> + If in doubt, say N.
> +
> config ARM_SPEAR_CPUFREQ
> bool "SPEAr CPUFreq support"
> depends on PLAT_SPEAR
> diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile
> index 1f254ec0..31699a0 100644
> --- a/drivers/cpufreq/Makefile
> +++ b/drivers/cpufreq/Makefile
> @@ -49,6 +49,7 @@ obj-$(CONFIG_ARM_EXYNOS_CPUFREQ) += exynos-cpufreq.o
> obj-$(CONFIG_ARM_EXYNOS4210_CPUFREQ) += exynos4210-cpufreq.o
> obj-$(CONFIG_ARM_EXYNOS4X12_CPUFREQ) += exynos4x12-cpufreq.o
> obj-$(CONFIG_ARM_EXYNOS5250_CPUFREQ) += exynos5250-cpufreq.o
> +obj-$(CONFIG_ARM_IMX6Q_CPUFREQ) += imx6q-cpufreq.o
> obj-$(CONFIG_ARM_OMAP2PLUS_CPUFREQ) += omap-cpufreq.o
> obj-$(CONFIG_ARM_SPEAR_CPUFREQ) += spear-cpufreq.o
>
> diff --git a/drivers/cpufreq/imx6q-cpufreq.c b/drivers/cpufreq/imx6q-cpufreq.c
> new file mode 100644
> index 0000000..218d8f1
> --- /dev/null
> +++ b/drivers/cpufreq/imx6q-cpufreq.c
> @@ -0,0 +1,296 @@
> +/*
> + * Copyright (C) 2013 Freescale Semiconductor, 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.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/cpu.h>
> +#include <linux/cpufreq.h>
> +#include <linux/err.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/opp.h>
> +#include <linux/platform_device.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/slab.h>
> +
> +#define PU_SOC_VOLTAGE_NORMAL 1250000
> +#define PU_SOC_VOLTAGE_HIGH 1275000
> +#define FREQ_1P2_GHZ 1200000000
> +
> +static struct regulator *arm_reg;
> +static struct regulator *pu_reg;
> +static struct regulator *soc_reg;
> +
> +static struct clk *arm_clk;
> +static struct clk *pll1_sys_clk;
> +static struct clk *pll1_sw_clk;
> +static struct clk *step_clk;
> +static struct clk *pll2_pfd2_396m_clk;
> +
> +static struct device *cpu_dev;
> +static struct cpufreq_frequency_table *freq_table;
> +static unsigned int transition_latency;
> +
> +static int imx6q_verify_speed(struct cpufreq_policy *policy)
> +{
> + return cpufreq_frequency_table_verify(policy, freq_table);
> +}
> +
> +static unsigned int imx6q_get_speed(unsigned int cpu)
> +{
> + return clk_get_rate(arm_clk) / 1000;
> +}
> +
> +static int imx6q_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, volt_old;
> + unsigned int index, cpu;
> + int ret;
> +
> + ret = cpufreq_frequency_table_target(policy, freq_table, target_freq,
> + relation, &index);
@Rafael: Why is this function used in our target routines? Isn't the caller
supposed to send a valid freq from freq_table? All governors do it.
> + if (ret) {
> + dev_err(cpu_dev, "failed to match target frequency %d: %d\n",
> + target_freq, ret);
> + return ret;
> + }
> +
> + freqs.new = freq_table[index].frequency;
> + freq_hz = freqs.new * 1000;
> + freqs.old = clk_get_rate(arm_clk) / 1000;
clk_get_rate() can fail.
> +
> + if (freqs.old == freqs.new)
> + return 0;
> +
> + for_each_online_cpu(cpu) {
> + freqs.cpu = cpu;
> + cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
> + }
> +
> + opp = opp_find_freq_ceil(cpu_dev, &freq_hz);
> + if (IS_ERR(opp)) {
> + dev_err(cpu_dev, "failed to find OPP for %ld\n", freq_hz);
> + return PTR_ERR(opp);
> + }
> +
> + volt = opp_get_voltage(opp);
> + volt_old = regulator_get_voltage(arm_reg);
> +
> + dev_dbg(cpu_dev, "%u MHz, %ld mV --> %u MHz, %ld mV\n",
> + freqs.old / 1000, volt_old / 1000,
> + freqs.new / 1000, volt / 1000);
> +
> + /* scaling up? scale voltage before frequency */
> + if (freqs.new > freqs.old) {
> + ret = regulator_set_voltage_tol(arm_reg, volt, 0);
> + if (ret) {
> + dev_err(cpu_dev, "failed to scale voltage up: %d\n", ret);
> + return ret;
> + }
> +
> + /*
> + * Need to increase vddpu and vddsoc for safety
> + * if we are about to run at 1.2 GHz.
> + */
> + if (freqs.new == FREQ_1P2_GHZ / 1000) {
> + regulator_set_voltage_tol(pu_reg,
> + PU_SOC_VOLTAGE_HIGH, 0);
> + regulator_set_voltage_tol(soc_reg,
> + PU_SOC_VOLTAGE_HIGH, 0);
> + }
> + }
> +
> + /*
> + * The setpoints are selected per PLL/PDF frequencies, so we need to
> + * reprogram PLL for frequency scaling. The procedure of reprogramming
> + * PLL1 is as below.
> + *
> + * - Enable pll2_pfd2_396m_clk and reparent pll1_sw_clk to it
> + * - Disable pll1_sys_clk and reprogram it
> + * - Enable pll1_sys_clk and reparent pll1_sw_clk back to it
> + * - Disable pll2_pfd2_396m_clk
> + */
> + clk_prepare_enable(pll2_pfd2_396m_clk);
> + clk_set_parent(step_clk, pll2_pfd2_396m_clk);
> + clk_set_parent(pll1_sw_clk, step_clk);
> + clk_prepare_enable(pll1_sys_clk);
all these fns can fail too.. don't want to check return values?
> + if (freq_hz > clk_get_rate(pll2_pfd2_396m_clk)) {
> + clk_disable_unprepare(pll1_sys_clk);
> + clk_set_rate(pll1_sys_clk, freqs.new * 1000);
> + clk_prepare_enable(pll1_sys_clk);
> + clk_set_parent(pll1_sw_clk, pll1_sys_clk);
> + clk_disable_unprepare(pll2_pfd2_396m_clk);
> + } else {
> + /*
> + * Disable pll1_sys_clk if pll2_pfd2_396m_clk is sufficient
> + * to provide the frequency.
> + */
> + clk_disable_unprepare(pll1_sys_clk);
> + }
> +
> + /* Ensure the arm clock divider is what we expect */
> + ret = clk_set_rate(arm_clk, freqs.new * 1000);
> + if (ret) {
> + dev_err(cpu_dev, "failed to set clock rate: %d\n", ret);
> + regulator_set_voltage_tol(arm_reg, volt_old, 0);
> + return ret;
> + }
> +
> + /* scaling down? scale voltage after frequency */
> + if (freqs.new < freqs.old) {
> + ret = regulator_set_voltage_tol(arm_reg, volt, 0);
> + if (ret)
> + dev_warn(cpu_dev, "failed to scale voltage down: %d\n", ret);
> +
> + if (freqs.old == FREQ_1P2_GHZ / 1000) {
> + regulator_set_voltage_tol(pu_reg,
> + PU_SOC_VOLTAGE_NORMAL, 0);
> + regulator_set_voltage_tol(soc_reg,
> + PU_SOC_VOLTAGE_NORMAL, 0);
> + }
> + }
> +
> + for_each_online_cpu(cpu) {
> + freqs.cpu = cpu;
> + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
> + }
> +
> + return 0;
> +}
> +
> +static int imx6q_cpufreq_init(struct cpufreq_policy *policy)
> +{
> + int ret;
> +
So you finally removed the ugly check :)
Reviewed-by: Viresh Kumar <viresh.kumar@linaro.org>
^ permalink raw reply
* Re: [PATCH] cpufreq: Simplify __cpufreq_remove_dev()
From: Viresh Kumar @ 2013-01-10 9:19 UTC (permalink / raw)
To: rjw
Cc: robin.randhawa, Steve.Bannister, Liviu.Dudau, linaro-dev, patches,
cpufreq, linux-pm, linux-kernel, shawn.guo, Viresh Kumar
In-Reply-To: <aa10abe640afed8700abf262dbfc9b9c22659b6a.1357729671.git.viresh.kumar@linaro.org>
On 9 January 2013 16:50, Viresh Kumar <viresh.kumar@linaro.org> wrote:
> diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c
> +static void update_policy_cpu(struct cpufreq_policy *policy, unsigned int cpu)
> +{
> + cpufreq_frequency_table_update_policy_cpu(old_cpu, cpu);
> + cpufreq_stats_update_policy_cpu(old_cpu, cpu);
This looked a bit ugly to me, as there may be other users too who want
to get this notification and so have below patch for this: pushed in my repo.
Tested on TC2. I have tested my patches well now and the activity is over from
my end now, unless somebody finds something objectionable :)
diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c
index e3f7c7b..8a0b65e0 100644
--- a/drivers/cpufreq/cpufreq.c
+++ b/drivers/cpufreq/cpufreq.c
@@ -990,9 +990,9 @@ module_out:
static void update_policy_cpu(struct cpufreq_policy *policy, unsigned int cpu)
{
- unsigned int old_cpu = policy->cpu;
int j;
+ policy->last_cpu = policy->cpu;
policy->cpu = cpu;
for_each_cpu(j, policy->cpus) {
@@ -1001,8 +1001,9 @@ static void update_policy_cpu(struct
cpufreq_policy *policy, unsigned int cpu)
per_cpu(cpufreq_policy_cpu, j) = cpu;
}
- cpufreq_frequency_table_update_policy_cpu(old_cpu, cpu);
- cpufreq_stats_update_policy_cpu(old_cpu, cpu);
+ cpufreq_frequency_table_update_policy_cpu(policy);
+ blocking_notifier_call_chain(&cpufreq_policy_notifier_list,
+ CPUFREQ_UPDATE_POLICY_CPU, policy);
}
/**
diff --git a/drivers/cpufreq/cpufreq_stats.c b/drivers/cpufreq/cpufreq_stats.c
index b0df77a..2665f0a 100644
--- a/drivers/cpufreq/cpufreq_stats.c
+++ b/drivers/cpufreq/cpufreq_stats.c
@@ -267,17 +267,17 @@ error_get_fail:
return ret;
}
-void cpufreq_stats_update_policy_cpu(unsigned int old_cpu,
- unsigned int new_cpu)
+static void cpufreq_stats_update_policy_cpu(struct cpufreq_policy *policy)
{
- struct cpufreq_stats *stat = per_cpu(cpufreq_stats_table, old_cpu);
-
- pr_debug("Updating stats_table for new_cpu %u from old_cpu %u\n",
- new_cpu, old_cpu);
- per_cpu(cpufreq_stats_table, new_cpu) = per_cpu(cpufreq_stats_table,
- old_cpu);
- per_cpu(cpufreq_stats_table, old_cpu) = NULL;
- stat->cpu = new_cpu;
+ struct cpufreq_stats *stat = per_cpu(cpufreq_stats_table,
+ policy->last_cpu);
+
+ pr_debug("Updating stats_table for new_cpu %u from last_cpu %u\n",
+ policy->cpu, policy->last_cpu);
+ per_cpu(cpufreq_stats_table, policy->cpu) = per_cpu(cpufreq_stats_table,
+ policy->last_cpu);
+ per_cpu(cpufreq_stats_table, policy->last_cpu) = NULL;
+ stat->cpu = policy->cpu;
}
static int cpufreq_stat_notifier_policy(struct notifier_block *nb,
@@ -287,6 +287,12 @@ static int cpufreq_stat_notifier_policy(struct
notifier_block *nb,
struct cpufreq_policy *policy = data;
struct cpufreq_frequency_table *table;
unsigned int cpu = policy->cpu;
+
+ if (val == CPUFREQ_UPDATE_POLICY_CPU) {
+ cpufreq_stats_update_policy_cpu(policy);
+ return 0;
+ }
+
if (val != CPUFREQ_NOTIFY)
return 0;
table = cpufreq_frequency_get_table(cpu);
diff --git a/drivers/cpufreq/freq_table.c b/drivers/cpufreq/freq_table.c
index 2c3b07b..281e3b4 100644
--- a/drivers/cpufreq/freq_table.c
+++ b/drivers/cpufreq/freq_table.c
@@ -225,14 +225,13 @@ void cpufreq_frequency_table_put_attr(unsigned int cpu)
}
EXPORT_SYMBOL_GPL(cpufreq_frequency_table_put_attr);
-void cpufreq_frequency_table_update_policy_cpu(unsigned int old_cpu,
- unsigned int new_cpu)
+void cpufreq_frequency_table_update_policy_cpu(struct cpufreq_policy *policy)
{
- pr_debug("Updating show_table for new_cpu %u from old_cpu %u\n",
- new_cpu, old_cpu);
- per_cpu(cpufreq_show_table, new_cpu) = per_cpu(cpufreq_show_table,
- old_cpu);
- per_cpu(cpufreq_show_table, old_cpu) = NULL;
+ pr_debug("Updating show_table for new_cpu %u from last_cpu %u\n",
+ policy->cpu, policy->last_cpu);
+ per_cpu(cpufreq_show_table, policy->cpu) = per_cpu(cpufreq_show_table,
+ policy->last_cpu);
+ per_cpu(cpufreq_show_table, policy->last_cpu) = NULL;
}
struct cpufreq_frequency_table *cpufreq_frequency_get_table(unsigned int cpu)
diff --git a/include/linux/cpufreq.h b/include/linux/cpufreq.h
index af50dbd..7a3192a 100644
--- a/include/linux/cpufreq.h
+++ b/include/linux/cpufreq.h
@@ -90,7 +90,9 @@ struct cpufreq_policy {
cpumask_var_t related_cpus; /* CPUs with any coordination */
unsigned int shared_type; /* ANY or ALL affected CPUs
should set cpufreq */
- unsigned int cpu; /* cpu nr of registered CPU */
+ unsigned int cpu; /* cpu nr of CPU managing this policy */
+ unsigned int last_cpu; /* cpu nr of previous CPU that managed
+ * this policy */
struct cpufreq_cpuinfo cpuinfo;/* see above */
unsigned int min; /* in kHz */
@@ -109,10 +111,11 @@ struct cpufreq_policy {
struct completion kobj_unregister;
};
-#define CPUFREQ_ADJUST (0)
-#define CPUFREQ_INCOMPATIBLE (1)
-#define CPUFREQ_NOTIFY (2)
-#define CPUFREQ_START (3)
+#define CPUFREQ_ADJUST (0)
+#define CPUFREQ_INCOMPATIBLE (1)
+#define CPUFREQ_NOTIFY (2)
+#define CPUFREQ_START (3)
+#define CPUFREQ_UPDATE_POLICY_CPU (4)
#define CPUFREQ_SHARED_TYPE_NONE (0) /* None */
#define CPUFREQ_SHARED_TYPE_HW (1) /* HW does needed coordination */
@@ -405,12 +408,8 @@ extern struct freq_attr
cpufreq_freq_attr_scaling_available_freqs;
void cpufreq_frequency_table_get_attr(struct cpufreq_frequency_table *table,
unsigned int cpu);
-void cpufreq_frequency_table_update_policy_cpu(unsigned int old_cpu,
- unsigned int new_cpu);
+void cpufreq_frequency_table_update_policy_cpu(struct cpufreq_policy *policy);
void cpufreq_frequency_table_put_attr(unsigned int cpu);
-void cpufreq_stats_update_policy_cpu(unsigned int old_cpu,
- unsigned int new_cpu);
-
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox