From 4860c4569d35b63e189bb23149a42b9578f6a510 Mon Sep 17 00:00:00 2001 From: Cho KyongHo Date: Sun, 23 Apr 2017 17:19:51 +0900 Subject: [PATCH] [COMMON] g2d: add scheduling to HW 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 Signed-off-by: Cho KyongHo --- drivers/gpu/exynos/g2d/Makefile | 2 +- drivers/gpu/exynos/g2d/g2d_drv.c | 72 ++++++++++++++++++- drivers/gpu/exynos/g2d/g2d_regs.c | 86 +++++++++++++++++++++++ drivers/gpu/exynos/g2d/g2d_regs.h | 57 ++++++++++++++- drivers/gpu/exynos/g2d/g2d_task.c | 111 ++++++++++++++++++++++++++++-- drivers/gpu/exynos/g2d/g2d_task.h | 27 ++++++++ 6 files changed, 348 insertions(+), 7 deletions(-) create mode 100644 drivers/gpu/exynos/g2d/g2d_regs.c diff --git a/drivers/gpu/exynos/g2d/Makefile b/drivers/gpu/exynos/g2d/Makefile index d47295bd8c3d..9eb028b9b8ab 100644 --- a/drivers/gpu/exynos/g2d/Makefile +++ b/drivers/gpu/exynos/g2d/Makefile @@ -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 diff --git a/drivers/gpu/exynos/g2d/g2d_drv.c b/drivers/gpu/exynos/g2d/g2d_drv.c index 8112b158bc42..41a2496f084d 100644 --- a/drivers/gpu/exynos/g2d/g2d_drv.c +++ b/drivers/gpu/exynos/g2d/g2d_drv.c @@ -32,11 +32,60 @@ 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 index 000000000000..504fc20d2ea2 --- /dev/null +++ b/drivers/gpu/exynos/g2d/g2d_regs.c @@ -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 +#include + +#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; +} diff --git a/drivers/gpu/exynos/g2d/g2d_regs.h b/drivers/gpu/exynos/g2d/g2d_regs.h index 869f7974ea9e..43d62c280894 100644 --- a/drivers/gpu/exynos/g2d/g2d_regs.h +++ b/drivers/gpu/exynos/g2d/g2d_regs.h @@ -71,7 +71,62 @@ /* 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__ */ diff --git a/drivers/gpu/exynos/g2d/g2d_task.c b/drivers/gpu/exynos/g2d/g2d_task.c index 3173f175292d..cce2d86c3faf 100644 --- a/drivers/gpu/exynos/g2d/g2d_task.c +++ b/drivers/gpu/exynos/g2d/g2d_task.c @@ -21,6 +21,76 @@ #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); +} diff --git a/drivers/gpu/exynos/g2d/g2d_task.h b/drivers/gpu/exynos/g2d/g2d_task.h index 60773c0fbc9f..6fb09569485e 100644 --- a/drivers/gpu/exynos/g2d/g2d_task.h +++ b/drivers/gpu/exynos/g2d/g2d_task.h @@ -18,6 +18,7 @@ #ifndef __EXYNOS_G2D_TASK_H__ #define __EXYNOS_G2D_TASK_H__ +#include #include #include @@ -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__*/ -- 2.20.1