[COMMON] g2d: add scheduling to HW
authorCho KyongHo <pullip.cho@samsung.com>
Sun, 23 Apr 2017 08:19:51 +0000 (17:19 +0900)
committerSeungchul Kim <sc377.kim@samsung.com>
Mon, 28 May 2018 05:27:12 +0000 (14:27 +0900)
This patch implements pushing a task to H/W and interrupt handler.
Thanks to Job Manager of G2D, it is not required to find a pending
task in tasks_prepared list because a task ready to be scheduled to
H/W is pushed to H/W.

Change-Id: I6cf242009cea87d73d3d715e7312a22dde9062af
Signed-off-by: hyesoo.yu <hyesoo.yu@samsung.com>
Signed-off-by: Cho KyongHo <pullip.cho@samsung.com>
drivers/gpu/exynos/g2d/Makefile
drivers/gpu/exynos/g2d/g2d_drv.c
drivers/gpu/exynos/g2d/g2d_regs.c [new file with mode: 0644]
drivers/gpu/exynos/g2d/g2d_regs.h
drivers/gpu/exynos/g2d/g2d_task.c
drivers/gpu/exynos/g2d/g2d_task.h

index d47295bd8c3d807322e0a746073d3310088d89eb..9eb028b9b8abae894ef709a6cc9bbabdaa33b8af 100644 (file)
@@ -1 +1 @@
-obj-$(CONFIG_EXYNOS_GRAPHICS_G2D) += g2d_drv.o g2d_task.o
+obj-$(CONFIG_EXYNOS_GRAPHICS_G2D) += g2d_drv.o g2d_task.o g2d_regs.o
index 8112b158bc42cb476fbffdf18ecef0ac4c677c48..41a2496f084d734b30d2e72f02b97686bac0965c 100644 (file)
 
 int g2d_device_run(struct g2d_device *g2d_dev, struct g2d_task *task)
 {
+       g2d_hw_push_task(g2d_dev, task);
+
        return 0;
 }
 
 static irqreturn_t g2d_irq_handler(int irq, void *priv)
 {
+       struct g2d_device *g2d_dev = priv;
+       unsigned int id;
+       u32 intflags, errstatus;
+
+       spin_lock(&g2d_dev->lock_task);
+
+       intflags = g2d_hw_finished_job_ids(g2d_dev);
+       if (intflags != 0) {
+               for (id = 0; id < G2D_MAX_JOBS; id++) {
+                       if ((intflags & (1 << id)) == 0)
+                               continue;
+
+                       g2d_finish_task_with_id(g2d_dev, id, true);
+               }
+
+               g2d_hw_clear_job_ids(g2d_dev, intflags);
+       }
+
+       errstatus = g2d_hw_errint_status(g2d_dev);
+       if (errstatus != 0) {
+               int job_id = g2d_hw_get_current_task(g2d_dev);
+               struct g2d_task *task =
+                               g2d_get_active_task_from_id(g2d_dev, job_id);
+
+               if (job_id < 0) {
+                       dev_err(g2d_dev->dev, "No task is running in HW\n");
+               } else if (task == NULL) {
+                       dev_err(g2d_dev->dev,
+                               "%s: Current job %d in HW is not active\n",
+                               __func__, job_id);
+               } else {
+                       dev_err(g2d_dev->dev,
+                               "%s: Error occurred during running job %d\n",
+                               __func__, job_id);
+
+                       g2d_dump_task(g2d_dev, job_id);
+               }
+
+               g2d_flush_all_tasks(g2d_dev);
+
+               g2d_hw_global_reset(g2d_dev);
+
+               g2d_hw_clear_int(g2d_dev, errstatus);
+       }
+
+       spin_unlock(&g2d_dev->lock_task);
+
        return IRQ_HANDLED;
 }
 
@@ -55,7 +104,7 @@ static __u32 get_hw_version(struct g2d_device *g2d_dev, __u32 *version)
                dev_err(g2d_dev->dev, "Failed to enable clock (%d)\n", ret);
        } else {
                *version = readl_relaxed(g2d_dev->reg + G2D_VERSION_INFO_REG);
-               clk_disable_unprepare(g2d_dev->clock);
+               clk_disable(g2d_dev->clock);
        }
 
        pm_runtime_put(g2d_dev->dev);
@@ -231,6 +280,26 @@ static int g2d_remove(struct platform_device *pdev)
        return 0;
 }
 
+#ifdef CONFIG_PM
+static int g2d_runtime_resume(struct device *dev)
+{
+       return 0;
+}
+
+static int g2d_runtime_suspend(struct device *dev)
+{
+       struct g2d_device *g2d_dev = dev_get_drvdata(dev);
+
+       clk_unprepare(g2d_dev->clock);
+
+       return 0;
+}
+#endif
+
+static const struct dev_pm_ops g2d_pm_ops = {
+       SET_RUNTIME_PM_OPS(NULL, g2d_runtime_resume, g2d_runtime_suspend)
+};
+
 static const struct of_device_id of_g2d_match[] = {
        {
                .compatible = "samsung,exynos9810-g2d",
@@ -244,6 +313,7 @@ static struct platform_driver g2d_driver = {
        .driver = {
                .name   = MODULE_NAME,
                .owner  = THIS_MODULE,
+               .pm     = &g2d_pm_ops,
                .of_match_table = of_match_ptr(of_g2d_match),
        }
 };
diff --git a/drivers/gpu/exynos/g2d/g2d_regs.c b/drivers/gpu/exynos/g2d/g2d_regs.c
new file mode 100644 (file)
index 0000000..504fc20
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * linux/drivers/gpu/exynos/g2d/g2d_regs.c
+ *
+ * Copyright (C) 2017 Samsung Electronics Co., Ltd.
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/io.h>
+
+#include "g2d.h"
+#include "g2d_regs.h"
+#include "g2d_task.h"
+
+void g2d_hw_push_task(struct g2d_device *g2d_dev, struct g2d_task *task)
+{
+       u32 state = g2d_hw_get_job_state(g2d_dev, task->job_id);
+
+       if (state != G2D_JOB_STATE_DONE)
+               dev_err(g2d_dev->dev, "%s: Unexpected state %#x of JOB %d\n",
+                       __func__, state, task->job_id);
+
+       writel_relaxed(G2D_JOB_HEADER_DATA(task->priority, task->job_id),
+                       g2d_dev->reg + G2D_JOB_HEADER_REG);
+
+       writel_relaxed(G2D_ERR_INT_ENABLE, g2d_dev->reg + G2D_INTEN_REG);
+
+       writel_relaxed(task->cmd_addr, g2d_dev->reg + G2D_JOB_BASEADDR_REG);
+       writel_relaxed(task->cmd_count, g2d_dev->reg + G2D_JOB_SFRNUM_REG);
+       writel_relaxed(1 << task->job_id, g2d_dev->reg + G2D_JOB_INT_ID_REG);
+       writel(G2D_JOBPUSH_INT_ENABLE, g2d_dev->reg + G2D_JOB_PUSH_REG);
+}
+
+static const char *error_desc[3] = {
+       "AFBC Stuck",
+       "No read response",
+       "No write response",
+};
+
+u32 g2d_hw_errint_status(struct g2d_device *g2d_dev)
+{
+       u32 status = readl_relaxed(g2d_dev->reg + G2D_INTC_PEND_REG);
+       u32 errstatus = status;
+       int idx;
+
+       /* IRQPEND_SCF should not be set because we don't use ABP mode */
+       BUG_ON((status & 0x1) == 1);
+
+       errstatus >>= 16;
+       errstatus &= 0x7;
+
+       if (errstatus == 0)
+               return 0;
+
+       for (idx = 0; idx < 3; idx++) {
+               if (errstatus & (1 << idx))
+                       dev_err(g2d_dev->dev,
+                               "G2D ERROR INTERRUPT: %s\n", error_desc[idx]);
+       }
+
+       dev_err(g2d_dev->dev, "G2D FIFO STATUS: %#x\n",
+               g2d_hw_fifo_status(g2d_dev));
+
+       return status;
+}
+
+int g2d_hw_get_current_task(struct g2d_device *g2d_dev)
+{
+       int i, val;
+
+       for (i = 0; i < G2D_MAX_JOBS; i++) {
+               val = readl_relaxed(g2d_dev->reg + G2D_JOB_IDn_STATE_REG(i));
+               if ((val & G2D_JOB_STATE_MASK) == G2D_JOB_STATE_RUNNING)
+                       return i;
+       }
+
+       return -1;
+}
index 869f7974ea9ec96285535bfe458734f4ce0a4f63..43d62c2808942ed24c5b52123f04a65840c1dc26 100644 (file)
 
 /* Fields of G2D_SOFT_RESET_REG */
 #define G2D_SFR_CLEAR                          (1 << 2)
-#define G2D_GLOBAL_RESET                       (1 << 1)
+#define G2D_GLOBAL_RESET                       (3 << 0)
 #define G2D_SOFT_RESET                         (1 << 0)
 
+void g2d_hw_push_task(struct g2d_device *g2d_dev, struct g2d_task *task);
+int g2d_hw_get_current_task(struct g2d_device *g2d_dev);
+
+static inline u32 g2d_hw_finished_job_ids(struct g2d_device *g2d_dev)
+{
+       return readl_relaxed(g2d_dev->reg + G2D_JOB_INT_ID_REG);
+}
+
+static inline void g2d_hw_clear_job_ids(struct g2d_device *g2d_dev, u32 val)
+{
+       writel_relaxed(val, g2d_dev->reg + G2D_JOB_INT_ID_REG);
+}
+
+static inline u32 g2d_hw_get_job_state(struct g2d_device *g2d_dev,
+                                      unsigned int job_id)
+{
+       return readl(g2d_dev->reg + G2D_JOB_IDn_STATE_REG(job_id));
+}
+
+u32 g2d_hw_errint_status(struct g2d_device *g2d_dev);
+
+static inline u32 g2d_hw_fifo_status(struct g2d_device *g2d_dev)
+{
+       return readl(g2d_dev->reg + G2D_FIFO_STAT_REG);
+}
+
+static inline bool g2d_hw_fifo_idle(struct g2d_device *g2d_dev)
+{
+       int retry_count = 120;
+
+       while (retry_count-- > 0) {
+               if ((g2d_hw_fifo_status(g2d_dev) & 1) == 1)
+                       return true;
+       }
+
+       return false;
+}
+
+static inline void g2d_hw_clear_int(struct g2d_device *g2d_dev, u32 flags)
+{
+       writel_relaxed(flags, g2d_dev->reg + G2D_INTC_PEND_REG);
+}
+
+static inline bool g2d_hw_core_reset(struct g2d_device *g2d_dev)
+{
+       writel(G2D_SOFT_RESET, g2d_dev->reg + G2D_SOFT_RESET_REG);
+
+       return g2d_hw_fifo_idle(g2d_dev);
+}
+
+static inline void g2d_hw_global_reset(struct g2d_device *g2d_dev)
+{
+       writel(G2D_GLOBAL_RESET, g2d_dev->reg + G2D_SOFT_RESET_REG);
+}
+
 #endif /* __G2D_REGS_H__ */
index 3173f175292d1539cd2b2b6ecfa86dbe1a9e7b48..cce2d86c3faf9e6728eec524343fdcc1c199e5df 100644 (file)
 #include "g2d.h"
 #include "g2d_task.h"
 
+struct g2d_task *g2d_get_active_task_from_id(struct g2d_device *g2d_dev,
+                                            unsigned int id)
+{
+       struct g2d_task *task;
+
+       list_for_each_entry(task, &g2d_dev->tasks_active, node) {
+               if (task->job_id == id)
+                       return task;
+       }
+
+       dev_err(g2d_dev->dev,
+               "%s: No active task entry is found for ID %d\n", __func__, id);
+
+       return NULL;
+}
+
+static void __g2d_finish_task(struct g2d_task *task, bool success)
+{
+       change_task_state_finished(task);
+       if (!success)
+               mark_task_state_error(task);
+
+       complete_all(&task->completion);
+}
+
+static void g2d_finish_task(struct g2d_device *g2d_dev,
+                           struct g2d_task *task, bool success)
+{
+       list_del_init(&task->node);
+
+       clk_disable(g2d_dev->clock);
+
+       pm_runtime_put(g2d_dev->dev);
+
+       __g2d_finish_task(task, success);
+}
+
+void g2d_finish_task_with_id(struct g2d_device *g2d_dev,
+                            unsigned int job_id, bool success)
+{
+       struct g2d_task *task = NULL;
+
+       task = g2d_get_active_task_from_id(g2d_dev, job_id);
+       if (!task)
+               return;
+
+       task->ktime_end = ktime_get();
+
+       g2d_finish_task(g2d_dev, task, success);
+}
+
+void g2d_flush_all_tasks(struct g2d_device *g2d_dev)
+{
+       struct g2d_task *task;
+
+       dev_err(g2d_dev->dev, "%s: Flushing all active tasks\n", __func__);
+
+       while (!list_empty(&g2d_dev->tasks_active)) {
+               task = list_first_entry(&g2d_dev->tasks_active,
+                                       struct g2d_task, node);
+
+               dev_err(g2d_dev->dev, "%s: Flushed task of ID %d\n",
+                       __func__, task->job_id);
+
+               mark_task_state_killed(task);
+
+               g2d_finish_task(g2d_dev, task, false);
+       }
+}
+
 static void g2d_schedule_task(struct g2d_task *task)
 {
        struct g2d_device *g2d_dev = task->g2d_dev;
@@ -38,8 +108,13 @@ static void g2d_schedule_task(struct g2d_task *task)
        ret = pm_runtime_get_sync(g2d_dev->dev);
        if (ret < 0) {
                dev_err(g2d_dev->dev, "Failed to enable power (%d)\n", ret);
-               /* TODO: cancel task */
-               return;
+               goto err_pm;
+       }
+
+       ret = clk_prepare_enable(g2d_dev->clock);
+       if (ret < 0) {
+               dev_err(g2d_dev->dev, "Failed to enable clock (%d)\n", ret);
+               goto err_clk;
        }
 
        spin_lock_irqsave(&g2d_dev->lock_task, flags);
@@ -48,17 +123,28 @@ static void g2d_schedule_task(struct g2d_task *task)
 
        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) {
-               pm_runtime_put(g2d_dev->dev);
-               /* TODO: cancel task */
+               list_del_init(&task->node);
+               spin_unlock_irqrestore(&g2d_dev->lock_task, flags);
+               goto err_run;
        }
 
        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:
+       __g2d_finish_task(task, false);
 }
 
 static void g2d_task_schedule_work(struct work_struct *work)
@@ -210,3 +296,20 @@ int g2d_create_tasks(struct g2d_device *g2d_dev)
 
        return 0;
 }
+
+void g2d_dump_task(struct g2d_device *g2d_dev, unsigned int job_id)
+{
+       struct g2d_task *task;
+       unsigned long flags;
+
+       spin_lock_irqsave(&g2d_dev->lock_task, flags);
+
+       list_for_each_entry(task, &g2d_dev->tasks_active, node) {
+               if (task->job_id == job_id)
+                       break;
+       }
+
+       /* TODO: more dump task */
+
+       spin_unlock_irqrestore(&g2d_dev->lock_task, flags);
+}
index 60773c0fbc9faddbf948d72dd4c1f81cb4413272..6fb09569485e462084011b618995e73524a5941a 100644 (file)
@@ -18,6 +18,7 @@
 #ifndef __EXYNOS_G2D_TASK_H__
 #define __EXYNOS_G2D_TASK_H__
 
+#include <linux/ktime.h>
 #include <linux/dma-buf.h>
 #include <linux/workqueue.h>
 
@@ -84,6 +85,8 @@ struct g2d_task {
        unsigned int            cmd_count;
 
        unsigned int            priority;
+       ktime_t                 ktime_begin;
+       ktime_t                 ktime_end;
 
        struct work_struct      work;
        struct completion       completion;
@@ -100,6 +103,23 @@ struct g2d_task {
        (task)->state &= ~G2D_TASKSTATE_UNPREPARED;     \
 } while (0)
 
+#define change_task_state_finished(task) do {          \
+       (task)->state &= ~(G2D_TASKSTATE_ACTIVE |       \
+                          G2D_TASKSTATE_KILLED |       \
+                          G2D_TASKSTATE_TIMEOUT);      \
+       (task)->state |= G2D_TASKSTATE_PROCESSED;       \
+} while (0)
+
+static inline void mark_task_state_error(struct g2d_task *task)
+{
+       task->state |= G2D_TASKSTATE_ERROR;
+}
+
+static inline void mark_task_state_killed(struct g2d_task *task)
+{
+       task->state |= G2D_TASKSTATE_KILLED;
+}
+
 static inline void init_task_state(struct g2d_task *task)
 {
        task->state = G2D_TASKSTATE_UNPREPARED;
@@ -110,6 +130,8 @@ static inline void clear_task_state(struct g2d_task *task)
        task->state = 0;
 }
 
+struct g2d_task *g2d_get_active_task_from_id(struct g2d_device *g2d_dev,
+                                            unsigned int id);
 void g2d_destroy_tasks(struct g2d_device *g2d_dev);
 int g2d_create_tasks(struct g2d_device *g2d_dev);
 
@@ -117,5 +139,10 @@ struct g2d_task *g2d_get_free_task(struct g2d_device *g2d_dev);
 void g2d_put_free_task(struct g2d_device *g2d_dev, struct g2d_task *task);
 
 void g2d_start_task(struct g2d_task *task);
+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_dump_task(struct g2d_device *g2d_dev, unsigned int job_id);
 
 #endif /*__EXYNOS_G2D_TASK_H__*/