]> rtime.felk.cvut.cz Git - sojka/nv-tegra/linux-3.10.git/commitdiff
ARM: tegra: power: Add SiMon grading WDT
authorAlex Frid <afrid@nvidia.com>
Wed, 26 Mar 2014 05:19:01 +0000 (22:19 -0700)
committerYu-Huan Hsu <yhsu@nvidia.com>
Fri, 28 Mar 2014 01:17:23 +0000 (18:17 -0700)
Added SiMon grading watch-dog timer for each domain. It is running
while domain grade is above zero. If WDT expires domain grade is reset
to default zero.

Since it is possible to modify grade concurrently by grading and WDT
callbacks, added domain grade lock to serialize grade updates in both
callbacks as well as in debugfs grade write operation.

Bug 1343366

Change-Id: Id907878c478b5ac581905a04a600c37505721dae
Signed-off-by: Alex Frid <afrid@nvidia.com>
Reviewed-on: http://git-master/r/387442
Reviewed-by: Automatic_Commit_Validation_User
Reviewed-by: Yu-Huan Hsu <yhsu@nvidia.com>
arch/arm/mach-tegra/tegra_simon.c
arch/arm/mach-tegra/tegra_simon.h

index 701950fbccdb39013659ba250663a3ebf1fa9017..7593ab064aa67727224018c3aac1870a15102b4e 100644 (file)
@@ -38,6 +38,7 @@ static RAW_NOTIFIER_HEAD(simon_nh);
 static void tegra_simon_grade_notify(struct work_struct *work);
 
 static u32 grading_sec = TEGRA_SIMON_GRADING_INTERVAL_SEC;
+static u32 timeout_sec = TEGRA_SIMON_GRADING_TIMEOUT_SEC;
 
 static struct tegra_simon_grader simon_graders[TEGRA_SIMON_DOMAIN_NUM] = {
        [TEGRA_SIMON_DOMAIN_CPU] = {
@@ -60,6 +61,52 @@ static void settle_delay(struct tegra_simon_grader *grader)
                usleep_range(us, us + 100);
 }
 
+static inline void mod_wdt_on_grade(struct tegra_simon_grader *grader)
+{
+       if (grader->grade) {
+               /* restart WDT while at high grade */
+               struct timespec ts = {timeout_sec, 0};
+               mod_timer(&grader->grade_wdt,
+                         jiffies + timespec_to_jiffies(&ts));
+       }
+}
+
+static void tegra_simon_reset_grade(unsigned long data)
+{
+       unsigned long flags;
+       struct tegra_simon_grader *grader = (struct tegra_simon_grader *)data;
+
+       pr_info("%s: %s grade = 0\n", __func__, grader->domain_name);
+
+       spin_lock_irqsave(&grader->grade_lock, flags);
+       grader->grade = 0;
+       spin_unlock_irqrestore(&grader->grade_lock, flags);
+
+       schedule_work(&grader->grade_update_work);
+}
+
+static void tegra_simon_grade_set(struct tegra_simon_grader *grader,
+                                 int grade, bool restart)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&grader->grade_lock, flags);
+
+       /* once low grade is detected, stop grading (unless restart request) */
+       grader->stop_grading = !grade && !restart;
+
+       if (grader->grade == grade) {
+               mod_wdt_on_grade(grader);
+               spin_unlock_irqrestore(&grader->grade_lock, flags);
+               return;
+       }
+       grader->grade = grade;
+       mod_wdt_on_grade(grader);
+       spin_unlock_irqrestore(&grader->grade_lock, flags);
+
+       schedule_work(&grader->grade_update_work);
+}
+
 /*
  * GPU grading is implemented within vdd_gpu post-change notification chain that
  * guarantees constant voltage during grading. First grading after boot can be
@@ -111,13 +158,7 @@ static int tegra_simon_gpu_grading_cb(
        }
 
        grader->last_grading = now;
-       if (grader->grade != grade) {
-               /* once return to low grade, stay until next boot */
-               if (grade == 0)
-                       grader->stop_grading = true;
-               set_mb(grader->grade, grade);
-               schedule_work(&grader->grade_update_work);
-       }
+       tegra_simon_grade_set(grader, grade, false);
        pr_info("%s: graded %s: v = %d, t = %lu, grade = %d\n",
                __func__, grader->domain_name, mv, t, grade);
        return NOTIFY_OK;
@@ -129,6 +170,9 @@ static int __init tegra_simon_init_gpu(void)
        struct tegra_simon_grader *grader =
                &simon_graders[TEGRA_SIMON_DOMAIN_GPU];
 
+       spin_lock_init(&grader->grade_lock);
+       setup_timer(&grader->grade_wdt, tegra_simon_reset_grade,
+                   (unsigned long)grader);
        INIT_WORK(&grader->grade_update_work, tegra_simon_grade_notify);
 
        grader->tzd = thermal_zone_device_find_by_name("GPU-therm");
@@ -225,13 +269,7 @@ static int tegra_simon_cpu_grading_cb(
        tegra_cl_dvfs_clamp_at_vmin(cld, false);
 
        grader->last_grading = now;
-       if (grader->grade != grade) {
-               /* once return to low grade, stay until next boot */
-               if (grade == 0)
-                       grader->stop_grading = true;
-               set_mb(grader->grade, grade);
-               schedule_work(&grader->grade_update_work);
-       }
+       tegra_simon_grade_set(grader, grade, false);
        pr_info("%s: graded %s: v = %d, t = %lu, grade = %d\n",
                __func__, grader->domain_name, mv, t, grade);
        return NOTIFY_OK;
@@ -244,6 +282,9 @@ static int __init tegra_simon_init_cpu(void)
        struct clk *c;
        int r;
 
+       spin_lock_init(&grader->grade_lock);
+       setup_timer(&grader->grade_wdt, tegra_simon_reset_grade,
+                   (unsigned long)grader);
        INIT_WORK(&grader->grade_update_work, tegra_simon_grade_notify);
 
        grader->tzd = thermal_zone_device_find_by_name("CPU-therm");
@@ -369,15 +410,18 @@ static int grade_get(void *data, u64 *val)
 }
 static int grade_set(void *data, u64 val)
 {
+       int grade = (int)val;
        struct tegra_simon_grader *grader = data;
 
        if (grader->domain >= TEGRA_SIMON_DOMAIN_NUM)
                return -EINVAL;
 
-       if (grader->grade != (int)val) {
+       if (!grader->desc && (grader->grade != grade)) {
                grader->stop_grading = false;
-               grader->grade = (int)val;
+               grader->grade = grade;
                grade_notify(grader);
+       } else if (grader->desc) {
+               tegra_simon_grade_set(grader, grade, true);
        }
        return 0;
 }
@@ -417,6 +461,10 @@ static int __init simon_debugfs_init(void)
                                &grading_sec))
                goto err_out;
 
+       if (!debugfs_create_u32("timeout_sec", S_IWUSR | S_IRUGO, dir,
+                               &timeout_sec))
+               goto err_out;
+
        for (i = 0; i < TEGRA_SIMON_DOMAIN_NUM; i++) {
                grader = &simon_graders[i];
 
index eca53c0cb1cec80fc56a6e65f9ed82acaf2f0224..0ca2bb680c24fa27266bb28c1e6e6b1893bbacb0 100644 (file)
@@ -27,6 +27,7 @@ enum tegra_simon_domain {
 };
 
 #define TEGRA_SIMON_GRADING_INTERVAL_SEC       5000000
+#define TEGRA_SIMON_GRADING_TIMEOUT_SEC                100000000
 
 struct tegra_simon_grader_desc {
        enum tegra_simon_domain         domain;
@@ -40,9 +41,13 @@ struct tegra_simon_grader_desc {
 struct tegra_simon_grader {
        enum tegra_simon_domain         domain;
        const char                      *domain_name;
+
+       spinlock_t                      grade_lock;
+       struct timer_list               grade_wdt;
        ktime_t                         last_grading;
        bool                            stop_grading;
        int                             grade;
+
        struct work_struct              grade_update_work;
        struct notifier_block           grading_condition_nb;
        struct thermal_zone_device      *tzd;