[COMMON] g2d: add support for suspend and shutdown
authorCho KyongHo <pullip.cho@samsung.com>
Mon, 24 Apr 2017 02:05:23 +0000 (11:05 +0900)
committerSeungchul Kim <sc377.kim@samsung.com>
Mon, 28 May 2018 05:27:14 +0000 (14:27 +0900)
When the system is going to be suspeded to RAM, all the drivers should
complete their active tasks and freeze their working. G2D driver
registers pm notifiers to get informed about the time of suspend a bit
earlier than .suspend() callback function called by dpm. Likewise, it
makes the driver know the time of wake-up later than the invokation of
.resume() callback function. It is important for the driver because
RPM is detached before .suspend() is called and attached after
.resume().
Before suspend, G2D driver waits completion of all jobs running in H/W
and blocks all further jobs not to be pushed to H/W. The blocked jobs
are subsequently scheduled after wake-up.
The behavior G2D driver for suspend is also applied to shutdown except
the blocked jobs are never scheduled any more.

Change-Id: I2ed215219a2d02dfee507c731d4ed12ac4604eaa
Signed-off-by: hyesoo.yu <hyesoo.yu@samsung.com>
Signed-off-by: Cho KyongHo <pullip.cho@samsung.com>
drivers/gpu/exynos/g2d/g2d.h
drivers/gpu/exynos/g2d/g2d_drv.c
drivers/gpu/exynos/g2d/g2d_task.c
drivers/gpu/exynos/g2d/g2d_task.h

index 38bd33b23e7d24a5ca8644a0181a34bcdca1f0d2..d9868860cad5455086dba365f9ee954a8245220a 100644 (file)
 
 struct g2d_task; /* defined in g2d_task.h */
 
+/*
+ * G2D_DEVICE_STATE_SUSPEND should be treated under g2d_dev->lock_task held
+ * because it should be consistent with the state of all tasks attached to
+ * g2d_dev->tasks_active.
+ */
+#define G2D_DEVICE_STATE_SUSPEND       1
+#define G2D_DEVICE_STATE_IOVMM_DISABLED        2
+
 struct g2d_device {
+       unsigned long           state;
+
        struct miscdevice       misc;
        struct device           *dev;
        struct clk              *clock;
@@ -33,6 +43,9 @@ struct g2d_device {
        struct list_head        tasks_prepared;
        struct list_head        tasks_active;
        struct workqueue_struct *schedule_workq;
+
+       struct notifier_block   pm_notifier;
+       wait_queue_head_t       freeze_wait;
 };
 
 struct g2d_context {
index 99325b88dac23fb4cf86acab6ff261f78c6e0e7a..360529a0e8c57f0857afb0026db3532ad5807958 100644 (file)
@@ -20,6 +20,7 @@
 #include <linux/exynos_iovmm.h>
 #include <linux/interrupt.h>
 #include <linux/slab.h>
+#include <linux/suspend.h>
 #include <linux/module.h>
 #include <linux/slab.h>
 
@@ -143,6 +144,8 @@ static irqreturn_t g2d_irq_handler(int irq, void *priv)
 
        spin_unlock(&g2d_dev->lock_task);
 
+       wake_up(&g2d_dev->freeze_wait);
+
        return IRQ_HANDLED;
 }
 
@@ -233,6 +236,26 @@ static const struct file_operations g2d_fops = {
 #endif
 };
 
+static int g2d_notifier_event(struct notifier_block *this,
+                             unsigned long event, void *ptr)
+{
+       struct g2d_device *g2d_dev;
+
+       g2d_dev = container_of(this, struct g2d_device, pm_notifier);
+
+       switch (event) {
+       case PM_SUSPEND_PREPARE:
+               g2d_prepare_suspend(g2d_dev);
+               break;
+
+       case PM_POST_SUSPEND:
+               g2d_suspend_finish(g2d_dev);
+               break;
+       }
+
+       return NOTIFY_OK;
+}
+
 static int g2d_probe(struct platform_device *pdev)
 {
        struct g2d_device *g2d_dev;
@@ -308,9 +331,18 @@ static int g2d_probe(struct platform_device *pdev)
                goto err_task;
        }
 
+       init_waitqueue_head(&g2d_dev->freeze_wait);
+
+       g2d_dev->pm_notifier.notifier_call = &g2d_notifier_event;
+       ret = register_pm_notifier(&g2d_dev->pm_notifier);
+       if (ret)
+               goto err_pm;
+
        dev_info(&pdev->dev, "Probed FIMG2D version %#010x\n", version);
 
        return 0;
+err_pm:
+       g2d_destroy_tasks(g2d_dev);
 err_task:
        misc_deregister(&g2d_dev->misc);
 err:
@@ -322,18 +354,30 @@ err:
        return ret;
 }
 
+static void g2d_shutdown(struct platform_device *pdev)
+{
+       struct g2d_device *g2d_dev = platform_get_drvdata(pdev);
+
+       g2d_prepare_suspend(g2d_dev);
+
+       wait_event(g2d_dev->freeze_wait, list_empty(&g2d_dev->tasks_active));
+
+       if (test_and_set_bit(G2D_DEVICE_STATE_IOVMM_DISABLED, &g2d_dev->state))
+               iovmm_deactivate(g2d_dev->dev);
+}
+
 static int g2d_remove(struct platform_device *pdev)
 {
        struct g2d_device *g2d_dev = platform_get_drvdata(pdev);
 
+       g2d_shutdown(pdev);
+
        g2d_destroy_tasks(g2d_dev);
 
        misc_deregister(&g2d_dev->misc);
 
        pm_runtime_disable(&pdev->dev);
 
-       iovmm_deactivate(g2d_dev->dev);
-
        return 0;
 }
 
@@ -367,6 +411,7 @@ static const struct of_device_id of_g2d_match[] = {
 static struct platform_driver g2d_driver = {
        .probe          = g2d_probe,
        .remove         = g2d_remove,
+       .shutdown       = g2d_shutdown,
        .driver = {
                .name   = MODULE_NAME,
                .owner  = THIS_MODULE,
index 6fb3462b8263b94ec914b0af262e41802d0baa65..8f51fbecab9e7f11a22df8b2f7ce7a5599319dcf 100644 (file)
@@ -99,6 +99,51 @@ void g2d_flush_all_tasks(struct g2d_device *g2d_dev)
        }
 }
 
+static void g2d_execute_task(struct g2d_device *g2d_dev, struct g2d_task *task)
+{
+       list_move_tail(&task->node, &g2d_dev->tasks_active);
+       change_task_state_active(task);
+
+       task->ktime_begin = ktime_get();
+
+       mod_timer(&task->hw_timer,
+                 jiffies + msecs_to_jiffies(G2D_HW_TIMEOUT_MSEC));
+       /*
+        * g2d_device_run() is not reentrant while g2d_schedule() is
+        * reentrant g2d_device_run() should be protected with
+        * g2d_dev->lock_task from race.
+        */
+       if (g2d_device_run(g2d_dev, task) < 0)
+               g2d_finish_task(g2d_dev, task, false);
+}
+
+void g2d_prepare_suspend(struct g2d_device *g2d_dev)
+{
+       spin_lock_irq(&g2d_dev->lock_task);
+       set_bit(G2D_DEVICE_STATE_SUSPEND, &g2d_dev->state);
+       spin_unlock_irq(&g2d_dev->lock_task);
+
+       wait_event(g2d_dev->freeze_wait, list_empty(&g2d_dev->tasks_active));
+}
+
+void g2d_suspend_finish(struct g2d_device *g2d_dev)
+{
+       struct g2d_task *task;
+
+       spin_lock_irq(&g2d_dev->lock_task);
+
+       clear_bit(G2D_DEVICE_STATE_SUSPEND, &g2d_dev->state);
+
+       while (!list_empty(&g2d_dev->tasks_prepared)) {
+
+               task = list_first_entry(&g2d_dev->tasks_prepared,
+                                       struct g2d_task, node);
+               g2d_execute_task(g2d_dev, task);
+       }
+
+       spin_unlock_irq(&g2d_dev->lock_task);
+}
+
 static void g2d_schedule_task(struct g2d_task *task)
 {
        struct g2d_device *g2d_dev = task->g2d_dev;
@@ -127,31 +172,16 @@ static void g2d_schedule_task(struct g2d_task *task)
 
        spin_lock_irqsave(&g2d_dev->lock_task, flags);
 
-       list_add_tail(&task->node, &g2d_dev->tasks_active);
-
+       list_add_tail(&task->node, &g2d_dev->tasks_prepared);
        change_task_state_prepared(task);
-       change_task_state_active(task);
-
-       task->ktime_begin = ktime_get();
 
-       /*
-        * g2d_device_run() is not reentrant while g2d_schedule() is
-        * reentrant g2d_device_run() should be protected with
-        * g2d_dev->lock_task from race.
-        */
-       if (g2d_device_run(g2d_dev, task) < 0) {
-               list_del_init(&task->node);
-               spin_unlock_irqrestore(&g2d_dev->lock_task, flags);
-               goto err_run;
-       }
+       if (!!(g2d_dev->state & (1 << G2D_DEVICE_STATE_SUSPEND)))
+               return;
 
-       mod_timer(&task->hw_timer,
-                 jiffies + msecs_to_jiffies(G2D_HW_TIMEOUT_MSEC));
+       g2d_execute_task(g2d_dev, task);
 
        spin_unlock_irqrestore(&g2d_dev->lock_task, flags);
        return;
-err_run:
-       clk_disable(g2d_dev->clock);
 err_clk:
        pm_runtime_put(g2d_dev->dev);
 err_pm:
index b65459f7f00bcfb9ca1aeca8b7f5078fc7682dbf..c8fad74afd3d622bd5561661454a567bf9e8f1cc 100644 (file)
@@ -150,6 +150,9 @@ void g2d_finish_task_with_id(struct g2d_device *g2d_dev,
                             unsigned int job_id, bool success);
 void g2d_flush_all_tasks(struct g2d_device *g2d_dev);
 
+void g2d_prepare_suspend(struct g2d_device *g2d_dev);
+void g2d_suspend_finish(struct g2d_device *g2d_dev);
+
 void g2d_dump_task(struct g2d_device *g2d_dev, unsigned int job_id);
 
 #endif /*__EXYNOS_G2D_TASK_H__*/