Re: [PATCH 4/5] [CPUFREQ] EXYNOS4210: Add DVS lock feature for other driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Wed, Nov 2, 2011 at 9:43 PM, Kukjin Kim <kgene.kim@xxxxxxxxxxx> wrote:
> From: Jongpill Lee <boyko.lee@xxxxxxxxxxx>
>
> This patch adds DVS lock feature for other driver and pm/
> reboot notifier to enhance stability.
>
> Signed-off-by: Jongpill Lee <boyko.lee@xxxxxxxxxxx>
> Signed-off-by: SangWook Ju <sw.ju@xxxxxxxxxxx>
> Signed-off-by: Jonghwan Choi <jhbird.choi@xxxxxxxxxxx>
> Signed-off-by: Jaecheol Lee <jc.lee@xxxxxxxxxxx>
> ---
>  arch/arm/mach-exynos4/include/mach/cpufreq.h |   39 ++++++
>  drivers/cpufreq/exynos4210-cpufreq.c         |  174
> +++++++++++++++++++++++++-
>  2 files changed, 207 insertions(+), 6 deletions(-)
>  create mode 100644 arch/arm/mach-exynos4/include/mach/cpufreq.h
>
> diff --git a/arch/arm/mach-exynos4/include/mach/cpufreq.h
> b/arch/arm/mach-exynos4/include/mach/cpufreq.h
> new file mode 100644
> index 0000000..7e00931
> --- /dev/null
> +++ b/arch/arm/mach-exynos4/include/mach/cpufreq.h
> @@ -0,0 +1,39 @@
> +/* linux/arch/arm/mach-exynos4/include/mach/cpufreq.h
> + *
> + * Copyright (c) 2011 Samsung Electronics Co., Ltd.
> + *             http://www.samsung.com
> + *
> + * EXYNOS4 - CPUFreq 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.
> + */
> +
> +/*
> + * CPU frequency level index for using cpufreq lock API
> + * This should be same with cpufreq_frequency_table
> + */
> +enum cpufreq_level_request {
> +       CPU_L0,         /* 1200MHz */
> +       CPU_L1,         /* 1000MHz */
> +       CPU_L2,         /* 800MHz */
> +       CPU_L3,         /* 500MHz */
> +       CPU_L4,         /* 200MHz */
> +       CPU_LEVEL_END,
> +};
> +
> +enum cpufreq_lock_ID {
> +       DVFS_LOCK_ID_G2D,       /* G2D */
> +       DVFS_LOCK_ID_TV,        /* TV */
> +       DVFS_LOCK_ID_MFC,       /* MFC */
> +       DVFS_LOCK_ID_USB,       /* USB */
> +       DVFS_LOCK_ID_CAM,       /* CAM */
> +       DVFS_LOCK_ID_PM,        /* PM */
> +       DVFS_LOCK_ID_USER,      /* USER */
> +       DVFS_LOCK_ID_END,
> +};
> +
> +int exynos4_cpufreq_lock(unsigned int nId,
> +                       enum cpufreq_level_request cpufreq_level);
> +void exynos4_cpufreq_lock_free(unsigned int nId);
> diff --git a/drivers/cpufreq/exynos4210-cpufreq.c
> b/drivers/cpufreq/exynos4210-cpufreq.c
> index 246f9e2..30e1949 100644
> --- a/drivers/cpufreq/exynos4210-cpufreq.c
> +++ b/drivers/cpufreq/exynos4210-cpufreq.c
> @@ -17,14 +17,21 @@
>  #include <linux/slab.h>
>  #include <linux/regulator/consumer.h>
>  #include <linux/cpufreq.h>
> +#include <linux/suspend.h>
> +#include <linux/reboot.h>
>
>  #include <mach/map.h>
>  #include <mach/regs-clock.h>
>  #include <mach/regs-mem.h>
> +#include <mach/cpufreq.h>
>
>  #include <plat/clock.h>
>  #include <plat/pm.h>
>
> +static bool exynos4_cpufreq_init_done;
> +static DEFINE_MUTEX(set_freq_lock);
> +static DEFINE_MUTEX(set_cpu_freq_lock);
> +
>  static struct clk *cpu_clk;
>  static struct clk *moutcore;
>  static struct clk *mout_mpll;
> @@ -53,6 +60,12 @@ static struct cpufreq_frequency_table
> exynos4_freq_table[] = {
>        {0, CPUFREQ_TABLE_END},
>  };
>
> +/* This defines are for cpufreq lock */
> +#define CPUFREQ_MIN_LEVEL      (CPUFREQ_LEVEL_END - 1)
> +unsigned int cpufreq_lock_id;
> +unsigned int cpufreq_lock_val[DVFS_LOCK_ID_END];
> +unsigned int cpufreq_lock_level = CPUFREQ_MIN_LEVEL;
> +
>  static unsigned int clkdiv_cpu0[CPUFREQ_LEVEL_END][7] = {
>        /*
>         * Clock divider value for following
> @@ -272,22 +285,31 @@ static int exynos4_target(struct cpufreq_policy
> *policy,
>  {
>        unsigned int index, old_index;
>        unsigned int arm_volt;
> +       int ret = 0;
> +
> +       mutex_lock(&set_freq_lock);
>
>        freqs.old = exynos4_getspeed(policy->cpu);
>
>        if (cpufreq_frequency_table_target(policy, exynos4_freq_table,
> -                                          freqs.old, relation, &old_index))
> -               return -EINVAL;
> +                                          freqs.old, relation, &old_index))
> {
> +               ret = -EINVAL;
> +               goto out;
> +       }
>
>        if (cpufreq_frequency_table_target(policy, exynos4_freq_table,
> -                                          target_freq, relation, &index))
> -               return -EINVAL;
> +                                          target_freq, relation, &index)) {
> +               ret = -EINVAL;
> +               goto out;
> +       }
>
>        freqs.new = exynos4_freq_table[index].frequency;
>        freqs.cpu = policy->cpu;
>
> -       if (freqs.new == freqs.old)
> -               return 0;
> +       if (freqs.new == freqs.old) {
> +               ret = -EINVAL;
> +               goto out;
> +       }
>
>        /* get the voltage value */
>        arm_volt = exynos4_volt_table[index].arm_volt;
> @@ -311,8 +333,98 @@ static int exynos4_target(struct cpufreq_policy
> *policy,
>                regulator_set_voltage(arm_regulator, arm_volt, arm_volt);
>        }
>
> +out:
> +       mutex_unlock(&set_freq_lock);
> +
> +       return ret;
> +}
> +
> +atomic_t exynos4_cpufreq_lock_count;
> +
> +int exynos4_cpufreq_lock(unsigned int id,
> +               enum cpufreq_level_request cpufreq_level)
> +{
> +       int i, old_idx = 0;
> +       unsigned int freq_old, freq_new, arm_volt;
> +
> +       if (!exynos4_cpufreq_init_done)
> +               return 0;
> +
> +       if (cpufreq_lock_id & (1 << id)) {
> +               printk(KERN_ERR "%s:Device [%d] already locked cpufreq\n",
> +                               __func__,  id);
> +               return 0;
> +       }
> +       mutex_lock(&set_cpu_freq_lock);
> +       cpufreq_lock_id |= (1 << id);
> +       cpufreq_lock_val[id] = cpufreq_level;
> +
> +       /* If the requested cpufreq is higher than current min frequency */
> +       if (cpufreq_level < cpufreq_lock_level)
> +               cpufreq_lock_level = cpufreq_level;
> +
> +       mutex_unlock(&set_cpu_freq_lock);
> +
> +       /*
> +        * If current frequency is lower than requested freq,
> +        * it needs to update
> +        */
> +       mutex_lock(&set_freq_lock);
> +       freq_old = exynos4_getspeed(0);
> +       freq_new = exynos4_freq_table[cpufreq_level].frequency;
> +       if (freq_old < freq_new) {
> +               /* Find out current level index */
> +               for (i = 0 ; i < CPUFREQ_LEVEL_END ; i++) {
> +                       if (freq_old == exynos4_freq_table[i].frequency) {
> +                               old_idx = exynos4_freq_table[i].index;
> +                               break;
> +                       } else if (i == (CPUFREQ_LEVEL_END - 1)) {
> +                               printk(KERN_ERR "%s: Level not found\n",
> +                                               __func__);
> +                               mutex_unlock(&set_freq_lock);
> +                               return -EINVAL;
> +                       } else {
> +                               continue;
> +                       }
> +               }
> +               freqs.old = freq_old;
> +               freqs.new = freq_new;
> +               cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
> +
> +               /* get the voltage value */
> +               arm_volt = exynos4_volt_table[cpufreq_level].arm_volt;
> +               regulator_set_voltage(arm_regulator, arm_volt,
> +                               arm_volt);
> +
> +               exynos4_set_frequency(old_idx, cpufreq_level);
> +               cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
> +       }
> +       mutex_unlock(&set_freq_lock);
> +
>        return 0;
>  }
> +EXPORT_SYMBOL_GPL(exynos4_cpufreq_lock);
> +
> +void exynos4_cpufreq_lock_free(unsigned int id)
> +{
> +       int i;
> +
> +       if (!exynos4_cpufreq_init_done)
> +               return;
> +
> +       mutex_lock(&set_cpu_freq_lock);
> +       cpufreq_lock_id &= ~(1 << id);
> +       cpufreq_lock_val[id] = CPUFREQ_MIN_LEVEL;
> +       cpufreq_lock_level = CPUFREQ_MIN_LEVEL;
> +       if (cpufreq_lock_id) {
> +               for (i = 0; i < DVFS_LOCK_ID_END; i++) {
> +                       if (cpufreq_lock_val[i] < cpufreq_lock_level)
> +                               cpufreq_lock_level = cpufreq_lock_val[i];
> +               }
> +       }
> +       mutex_unlock(&set_cpu_freq_lock);
> +}
> +EXPORT_SYMBOL_GPL(exynos4_cpufreq_lock_free);
>
>  #ifdef CONFIG_PM
>  static int exynos4_cpufreq_suspend(struct cpufreq_policy *policy)
> @@ -326,6 +438,31 @@ static int exynos4_cpufreq_resume(struct cpufreq_policy
> *policy)
>  }
>  #endif
>
> +static int exynos4_cpufreq_notifier_event(struct notifier_block *this,
> +               unsigned long event, void *ptr)
> +{
> +       int ret;
> +
> +       switch (event) {
> +       case PM_SUSPEND_PREPARE:
> +               ret = exynos4_cpufreq_lock(DVFS_LOCK_ID_PM, CPU_L0);
> +               if (ret < 0)
> +                       return NOTIFY_BAD;
> +               pr_debug("PM_SUSPEND_PREPARE for CPUFREQ\n");
> +               return NOTIFY_OK;
> +       case PM_POST_RESTORE:
> +       case PM_POST_SUSPEND:
> +               pr_debug("PM_POST_SUSPEND for CPUFREQ\n");
> +               exynos4_cpufreq_lock_free(DVFS_LOCK_ID_PM);
> +               return NOTIFY_OK;
> +       }
> +       return NOTIFY_DONE;
> +}
> +
> +static struct notifier_block exynos4_cpufreq_notifier = {
> +       .notifier_call = exynos4_cpufreq_notifier_event,
> +};
> +
>  static int exynos4_cpufreq_cpu_init(struct cpufreq_policy *policy)
>  {
>        policy->cur = policy->min = policy->max =
> exynos4_getspeed(policy->cpu);
> @@ -351,6 +488,23 @@ static int exynos4_cpufreq_cpu_init(struct
> cpufreq_policy *policy)
>        return cpufreq_frequency_table_cpuinfo(policy, exynos4_freq_table);
>  }
>
> +static int exynos4_cpufreq_reboot_notifier_call(struct notifier_block
> *this,
> +               unsigned long code, void *_cmd)
> +{
> +       int ret;
> +
> +       ret = exynos4_cpufreq_lock(DVFS_LOCK_ID_PM, CPU_L0);
> +       if (ret < 0)
> +               return NOTIFY_BAD;
> +
> +       printk(KERN_INFO "REBOOT Notifier for CPUFREQ\n");
> +       return NOTIFY_DONE;
> +}
> +
> +static struct notifier_block exynos4_cpufreq_reboot_notifier = {
> +       .notifier_call = exynos4_cpufreq_reboot_notifier_call,
> +};
> +
>  static struct cpufreq_driver exynos4_driver = {
>        .flags          = CPUFREQ_STICKY,
>        .verify         = exynos4_verify_speed,
> @@ -391,6 +545,11 @@ static int __init exynos4_cpufreq_init(void)
>                goto err_vdd_arm;
>        }
>
> +       register_pm_notifier(&exynos4_cpufreq_notifier);
> +       register_reboot_notifier(&exynos4_cpufreq_reboot_notifier);
> +
> +       exynos4_cpufreq_init_done = true;
> +
>        tmp = __raw_readl(S5P_CLKDIV_CPU);
>
>        for (i = L0; i <  CPUFREQ_LEVEL_END; i++) {
> @@ -420,6 +579,9 @@ static int __init exynos4_cpufreq_init(void)
>
>        return 0;
>  err_cpufreq:
> +       unregister_reboot_notifier(&exynos4_cpufreq_reboot_notifier);
> +       unregister_pm_notifier(&exynos4_cpufreq_notifier);
> +
>        if (!IS_ERR(arm_regulator))
>                regulator_put(arm_regulator);
>
> --
> 1.7.1
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-samsung-soc" in
> the body of a message to majordomo@xxxxxxxxxxxxxxx
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>

Except for the case of PM (suspend/reset), it appears that this
feature is something that is meant to be supported by qos
(<linux/pm_qos.h>).
Couldn't we simply use the QoS feature to support locking frequency
above some specific levels? With QoS, I guess the implementation
becomes more general and straightforward.
Although we will probably need to use "CPU_DMA_LATENCY" for this case,
I think it isn't awefully difficult to use DMA-Latency as the metric.

One concern about this is that we are hereby enforcing all the related
device drivers stuck with Exynos4210 only unless others also use
"cpufreq_lock_ID" and its friends.

Cheers!
MyungJoo.

-- 
MyungJoo Ham, Ph.D.
Mobile Software Platform Lab, DMC Business, Samsung Electronics
--
To unsubscribe from this list: send the line "unsubscribe cpufreq" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Kernel Devel]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite Forum]     [Linux SCSI]

  Powered by Linux