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>
-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
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;
}
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);
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",
.driver = {
.name = MODULE_NAME,
.owner = THIS_MODULE,
+ .pm = &g2d_pm_ops,
.of_match_table = of_match_ptr(of_g2d_match),
}
};
--- /dev/null
+/*
+ * 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;
+}
/* 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__ */
#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;
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);
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)
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);
+}
#ifndef __EXYNOS_G2D_TASK_H__
#define __EXYNOS_G2D_TASK_H__
+#include <linux/ktime.h>
#include <linux/dma-buf.h>
#include <linux/workqueue.h>
unsigned int cmd_count;
unsigned int priority;
+ ktime_t ktime_begin;
+ ktime_t ktime_end;
struct work_struct work;
struct completion completion;
(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;
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);
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__*/