| /* |
| * 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 <linux/property.h> |
| |
| #include <asm/cacheflush.h> |
| |
| #include "g2d.h" |
| #include "g2d_regs.h" |
| #include "g2d_task.h" |
| #include "g2d_uapi.h" |
| #include "g2d_debug.h" |
| #include "g2d_secure.h" |
| |
| static void g2d_hw_push_task_by_smc(struct g2d_device *g2d_dev, |
| struct g2d_task *task) |
| { |
| int i; |
| |
| task->sec.secure_layer_mask = 0; |
| |
| for (i = 0; i < task->num_source; i++) |
| if (!!(task->source[i].flags & G2D_LAYERFLAG_SECURE)) |
| task->sec.secure_layer_mask |= 1 << i; |
| |
| if (!!(task->target.flags & G2D_LAYERFLAG_SECURE) || |
| !!(task->sec.secure_layer_mask)) |
| task->sec.secure_layer_mask |= 1 << 24; |
| |
| __flush_dcache_area(&task->sec, sizeof(task->sec)); |
| __flush_dcache_area(page_address(task->cmd_page), G2D_CMD_LIST_SIZE); |
| if (exynos_smc(SMC_DRM_G2D_CMD_DATA, virt_to_phys(&task->sec), 0, 0)) { |
| perrfndev(g2d_dev, "Failed to push %d %d %d %d", |
| task->sec.cmd_count, task->sec.priority, |
| task->sec.job_id, task->sec.secure_layer_mask); |
| |
| g2d_dump_info(g2d_dev, task); |
| BUG(); |
| } |
| } |
| |
| void g2d_hw_push_task(struct g2d_device *g2d_dev, struct g2d_task *task) |
| { |
| bool self_prot = g2d_dev->caps & G2D_DEVICE_CAPS_SELF_PROTECTION; |
| u32 state = g2d_hw_get_job_state(g2d_dev, task->sec.job_id); |
| |
| if (state != G2D_JOB_STATE_DONE) |
| perrfndev(g2d_dev, "Unexpected state %#x of JOB %d", |
| state, task->sec.job_id); |
| |
| if (IS_ENABLED(CONFIG_EXYNOS_CONTENT_PATH_PROTECTION)) { |
| unsigned int i; |
| |
| if (!self_prot) { |
| g2d_hw_push_task_by_smc(g2d_dev, task); |
| return; |
| } |
| |
| state = 0; |
| |
| for (i = 0; i < task->num_source; i++) |
| if (task->source[i].flags & G2D_LAYERFLAG_SECURE) |
| state |= 1 << i; |
| |
| if ((task->target.flags & G2D_LAYERFLAG_SECURE) || state) |
| state |= 1 << 24; |
| |
| writel_relaxed(state, |
| g2d_dev->reg + |
| G2D_JOBn_LAYER_SECURE_REG(task->sec.job_id)); |
| } |
| |
| if (device_get_dma_attr(g2d_dev->dev) != DEV_DMA_COHERENT) |
| __flush_dcache_area(page_address(task->cmd_page), |
| G2D_CMD_LIST_SIZE); |
| |
| writel_relaxed(G2D_JOB_HEADER_DATA(task->sec.priority, |
| task->sec.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->sec.cmd_count, g2d_dev->reg + G2D_JOB_SFRNUM_REG); |
| writel_relaxed(1 << task->sec.job_id, |
| g2d_dev->reg + G2D_JOB_INT_ID_REG); |
| writel(G2D_JOBPUSH_INT_ENABLE, g2d_dev->reg + G2D_JOB_PUSH_REG); |
| } |
| |
| bool g2d_hw_stuck_state(struct g2d_device *g2d_dev) |
| { |
| int i, val; |
| int retry_count = 10; |
| bool is_idle = true; |
| bool is_running = false; |
| |
| while (retry_count-- > 0) { |
| for (i = 0; i < G2D_MAX_JOBS; i++) { |
| val = readl_relaxed( |
| g2d_dev->reg + G2D_JOB_IDn_STATE_REG(i)); |
| |
| val &= G2D_JOB_STATE_MASK; |
| |
| if (val != G2D_JOB_STATE_DONE) |
| is_idle = false; |
| |
| if (val == G2D_JOB_STATE_RUNNING) |
| is_running = true; |
| } |
| |
| if (is_idle || is_running) |
| return false; |
| |
| is_idle = true; |
| } |
| |
| return true; |
| } |
| |
| 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)) |
| perrdev(g2d_dev, "G2D ERROR INTERRUPT: %s", |
| error_desc[idx]); |
| } |
| |
| perrdev(g2d_dev, "G2D FIFO STATUS: %#x", g2d_hw_fifo_status(g2d_dev)); |
| |
| return status; |
| } |
| |
| /* |
| * This is called when H/W is judged to be in operation, |
| * for example, when a sysmmu fault and an error interrupt occurs. |
| * If there is no task in progress, the status of all task on H/W |
| * is taken and print for debugging |
| */ |
| 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; |
| } |
| |
| for (i = 0; i < G2D_MAX_JOBS; i++) { |
| val = readl_relaxed(g2d_dev->reg + G2D_JOB_IDn_STATE_REG(i)); |
| perrdev(g2d_dev, "G2D TASK[%03d] STATE : %d", i, val); |
| } |
| |
| return -1; |
| } |
| |
| void g2d_hw_kill_task(struct g2d_device *g2d_dev, unsigned int job_id) |
| { |
| int retry_count = 120; |
| |
| writel((0 << 4) | job_id, g2d_dev->reg + G2D_JOB_KILL_REG); |
| |
| while (retry_count-- > 0) { |
| if (!(readl(g2d_dev->reg + G2D_JOB_PUSHKILL_STATE_REG) & 0x2)) { |
| perrdev(g2d_dev, "Killed JOB %d", job_id); |
| return; |
| } |
| } |
| |
| perrdev(g2d_dev, "Failed to kill job %d", job_id); |
| } |