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;
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 {
#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>
spin_unlock(&g2d_dev->lock_task);
+ wake_up(&g2d_dev->freeze_wait);
+
return IRQ_HANDLED;
}
#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;
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:
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;
}
static struct platform_driver g2d_driver = {
.probe = g2d_probe,
.remove = g2d_remove,
+ .shutdown = g2d_shutdown,
.driver = {
.name = MODULE_NAME,
.owner = THIS_MODULE,
}
}
+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;
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:
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__*/