162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2012 Samsung Electronics Co.Ltd 462306a36Sopenharmony_ci * Authors: Joonyoung Shim <jy0922.shim@samsung.com> 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include <linux/refcount.h> 862306a36Sopenharmony_ci#include <linux/clk.h> 962306a36Sopenharmony_ci#include <linux/component.h> 1062306a36Sopenharmony_ci#include <linux/delay.h> 1162306a36Sopenharmony_ci#include <linux/dma-mapping.h> 1262306a36Sopenharmony_ci#include <linux/err.h> 1362306a36Sopenharmony_ci#include <linux/interrupt.h> 1462306a36Sopenharmony_ci#include <linux/io.h> 1562306a36Sopenharmony_ci#include <linux/kernel.h> 1662306a36Sopenharmony_ci#include <linux/of.h> 1762306a36Sopenharmony_ci#include <linux/platform_device.h> 1862306a36Sopenharmony_ci#include <linux/pm_runtime.h> 1962306a36Sopenharmony_ci#include <linux/slab.h> 2062306a36Sopenharmony_ci#include <linux/uaccess.h> 2162306a36Sopenharmony_ci#include <linux/workqueue.h> 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci#include <drm/drm_file.h> 2462306a36Sopenharmony_ci#include <drm/exynos_drm.h> 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci#include "exynos_drm_drv.h" 2762306a36Sopenharmony_ci#include "exynos_drm_g2d.h" 2862306a36Sopenharmony_ci#include "exynos_drm_gem.h" 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci#define G2D_HW_MAJOR_VER 4 3162306a36Sopenharmony_ci#define G2D_HW_MINOR_VER 1 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci/* vaild register range set from user: 0x0104 ~ 0x0880 */ 3462306a36Sopenharmony_ci#define G2D_VALID_START 0x0104 3562306a36Sopenharmony_ci#define G2D_VALID_END 0x0880 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci/* general registers */ 3862306a36Sopenharmony_ci#define G2D_SOFT_RESET 0x0000 3962306a36Sopenharmony_ci#define G2D_INTEN 0x0004 4062306a36Sopenharmony_ci#define G2D_INTC_PEND 0x000C 4162306a36Sopenharmony_ci#define G2D_DMA_SFR_BASE_ADDR 0x0080 4262306a36Sopenharmony_ci#define G2D_DMA_COMMAND 0x0084 4362306a36Sopenharmony_ci#define G2D_DMA_STATUS 0x008C 4462306a36Sopenharmony_ci#define G2D_DMA_HOLD_CMD 0x0090 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci/* command registers */ 4762306a36Sopenharmony_ci#define G2D_BITBLT_START 0x0100 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci/* registers for base address */ 5062306a36Sopenharmony_ci#define G2D_SRC_BASE_ADDR 0x0304 5162306a36Sopenharmony_ci#define G2D_SRC_STRIDE 0x0308 5262306a36Sopenharmony_ci#define G2D_SRC_COLOR_MODE 0x030C 5362306a36Sopenharmony_ci#define G2D_SRC_LEFT_TOP 0x0310 5462306a36Sopenharmony_ci#define G2D_SRC_RIGHT_BOTTOM 0x0314 5562306a36Sopenharmony_ci#define G2D_SRC_PLANE2_BASE_ADDR 0x0318 5662306a36Sopenharmony_ci#define G2D_DST_BASE_ADDR 0x0404 5762306a36Sopenharmony_ci#define G2D_DST_STRIDE 0x0408 5862306a36Sopenharmony_ci#define G2D_DST_COLOR_MODE 0x040C 5962306a36Sopenharmony_ci#define G2D_DST_LEFT_TOP 0x0410 6062306a36Sopenharmony_ci#define G2D_DST_RIGHT_BOTTOM 0x0414 6162306a36Sopenharmony_ci#define G2D_DST_PLANE2_BASE_ADDR 0x0418 6262306a36Sopenharmony_ci#define G2D_PAT_BASE_ADDR 0x0500 6362306a36Sopenharmony_ci#define G2D_MSK_BASE_ADDR 0x0520 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci/* G2D_SOFT_RESET */ 6662306a36Sopenharmony_ci#define G2D_SFRCLEAR (1 << 1) 6762306a36Sopenharmony_ci#define G2D_R (1 << 0) 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci/* G2D_INTEN */ 7062306a36Sopenharmony_ci#define G2D_INTEN_ACF (1 << 3) 7162306a36Sopenharmony_ci#define G2D_INTEN_UCF (1 << 2) 7262306a36Sopenharmony_ci#define G2D_INTEN_GCF (1 << 1) 7362306a36Sopenharmony_ci#define G2D_INTEN_SCF (1 << 0) 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci/* G2D_INTC_PEND */ 7662306a36Sopenharmony_ci#define G2D_INTP_ACMD_FIN (1 << 3) 7762306a36Sopenharmony_ci#define G2D_INTP_UCMD_FIN (1 << 2) 7862306a36Sopenharmony_ci#define G2D_INTP_GCMD_FIN (1 << 1) 7962306a36Sopenharmony_ci#define G2D_INTP_SCMD_FIN (1 << 0) 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci/* G2D_DMA_COMMAND */ 8262306a36Sopenharmony_ci#define G2D_DMA_HALT (1 << 2) 8362306a36Sopenharmony_ci#define G2D_DMA_CONTINUE (1 << 1) 8462306a36Sopenharmony_ci#define G2D_DMA_START (1 << 0) 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci/* G2D_DMA_STATUS */ 8762306a36Sopenharmony_ci#define G2D_DMA_LIST_DONE_COUNT (0xFF << 17) 8862306a36Sopenharmony_ci#define G2D_DMA_BITBLT_DONE_COUNT (0xFFFF << 1) 8962306a36Sopenharmony_ci#define G2D_DMA_DONE (1 << 0) 9062306a36Sopenharmony_ci#define G2D_DMA_LIST_DONE_COUNT_OFFSET 17 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci/* G2D_DMA_HOLD_CMD */ 9362306a36Sopenharmony_ci#define G2D_USER_HOLD (1 << 2) 9462306a36Sopenharmony_ci#define G2D_LIST_HOLD (1 << 1) 9562306a36Sopenharmony_ci#define G2D_BITBLT_HOLD (1 << 0) 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci/* G2D_BITBLT_START */ 9862306a36Sopenharmony_ci#define G2D_START_CASESEL (1 << 2) 9962306a36Sopenharmony_ci#define G2D_START_NHOLT (1 << 1) 10062306a36Sopenharmony_ci#define G2D_START_BITBLT (1 << 0) 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci/* buffer color format */ 10362306a36Sopenharmony_ci#define G2D_FMT_XRGB8888 0 10462306a36Sopenharmony_ci#define G2D_FMT_ARGB8888 1 10562306a36Sopenharmony_ci#define G2D_FMT_RGB565 2 10662306a36Sopenharmony_ci#define G2D_FMT_XRGB1555 3 10762306a36Sopenharmony_ci#define G2D_FMT_ARGB1555 4 10862306a36Sopenharmony_ci#define G2D_FMT_XRGB4444 5 10962306a36Sopenharmony_ci#define G2D_FMT_ARGB4444 6 11062306a36Sopenharmony_ci#define G2D_FMT_PACKED_RGB888 7 11162306a36Sopenharmony_ci#define G2D_FMT_A8 11 11262306a36Sopenharmony_ci#define G2D_FMT_L8 12 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci/* buffer valid length */ 11562306a36Sopenharmony_ci#define G2D_LEN_MIN 1 11662306a36Sopenharmony_ci#define G2D_LEN_MAX 8000 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci#define G2D_CMDLIST_SIZE (PAGE_SIZE / 4) 11962306a36Sopenharmony_ci#define G2D_CMDLIST_NUM 64 12062306a36Sopenharmony_ci#define G2D_CMDLIST_POOL_SIZE (G2D_CMDLIST_SIZE * G2D_CMDLIST_NUM) 12162306a36Sopenharmony_ci#define G2D_CMDLIST_DATA_NUM (G2D_CMDLIST_SIZE / sizeof(u32) - 2) 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci/* maximum buffer pool size of userptr is 64MB as default */ 12462306a36Sopenharmony_ci#define MAX_POOL (64 * 1024 * 1024) 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_cienum { 12762306a36Sopenharmony_ci BUF_TYPE_GEM = 1, 12862306a36Sopenharmony_ci BUF_TYPE_USERPTR, 12962306a36Sopenharmony_ci}; 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_cienum g2d_reg_type { 13262306a36Sopenharmony_ci REG_TYPE_NONE = -1, 13362306a36Sopenharmony_ci REG_TYPE_SRC, 13462306a36Sopenharmony_ci REG_TYPE_SRC_PLANE2, 13562306a36Sopenharmony_ci REG_TYPE_DST, 13662306a36Sopenharmony_ci REG_TYPE_DST_PLANE2, 13762306a36Sopenharmony_ci REG_TYPE_PAT, 13862306a36Sopenharmony_ci REG_TYPE_MSK, 13962306a36Sopenharmony_ci MAX_REG_TYPE_NR 14062306a36Sopenharmony_ci}; 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_cienum g2d_flag_bits { 14362306a36Sopenharmony_ci /* 14462306a36Sopenharmony_ci * If set, suspends the runqueue worker after the currently 14562306a36Sopenharmony_ci * processed node is finished. 14662306a36Sopenharmony_ci */ 14762306a36Sopenharmony_ci G2D_BIT_SUSPEND_RUNQUEUE, 14862306a36Sopenharmony_ci /* 14962306a36Sopenharmony_ci * If set, indicates that the engine is currently busy. 15062306a36Sopenharmony_ci */ 15162306a36Sopenharmony_ci G2D_BIT_ENGINE_BUSY, 15262306a36Sopenharmony_ci}; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci/* cmdlist data structure */ 15562306a36Sopenharmony_cistruct g2d_cmdlist { 15662306a36Sopenharmony_ci u32 head; 15762306a36Sopenharmony_ci unsigned long data[G2D_CMDLIST_DATA_NUM]; 15862306a36Sopenharmony_ci u32 last; /* last data offset */ 15962306a36Sopenharmony_ci}; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci/* 16262306a36Sopenharmony_ci * A structure of buffer description 16362306a36Sopenharmony_ci * 16462306a36Sopenharmony_ci * @format: color format 16562306a36Sopenharmony_ci * @stride: buffer stride/pitch in bytes 16662306a36Sopenharmony_ci * @left_x: the x coordinates of left top corner 16762306a36Sopenharmony_ci * @top_y: the y coordinates of left top corner 16862306a36Sopenharmony_ci * @right_x: the x coordinates of right bottom corner 16962306a36Sopenharmony_ci * @bottom_y: the y coordinates of right bottom corner 17062306a36Sopenharmony_ci * 17162306a36Sopenharmony_ci */ 17262306a36Sopenharmony_cistruct g2d_buf_desc { 17362306a36Sopenharmony_ci unsigned int format; 17462306a36Sopenharmony_ci unsigned int stride; 17562306a36Sopenharmony_ci unsigned int left_x; 17662306a36Sopenharmony_ci unsigned int top_y; 17762306a36Sopenharmony_ci unsigned int right_x; 17862306a36Sopenharmony_ci unsigned int bottom_y; 17962306a36Sopenharmony_ci}; 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci/* 18262306a36Sopenharmony_ci * A structure of buffer information 18362306a36Sopenharmony_ci * 18462306a36Sopenharmony_ci * @map_nr: manages the number of mapped buffers 18562306a36Sopenharmony_ci * @reg_types: stores regitster type in the order of requested command 18662306a36Sopenharmony_ci * @handles: stores buffer handle in its reg_type position 18762306a36Sopenharmony_ci * @types: stores buffer type in its reg_type position 18862306a36Sopenharmony_ci * @descs: stores buffer description in its reg_type position 18962306a36Sopenharmony_ci * 19062306a36Sopenharmony_ci */ 19162306a36Sopenharmony_cistruct g2d_buf_info { 19262306a36Sopenharmony_ci unsigned int map_nr; 19362306a36Sopenharmony_ci enum g2d_reg_type reg_types[MAX_REG_TYPE_NR]; 19462306a36Sopenharmony_ci void *obj[MAX_REG_TYPE_NR]; 19562306a36Sopenharmony_ci unsigned int types[MAX_REG_TYPE_NR]; 19662306a36Sopenharmony_ci struct g2d_buf_desc descs[MAX_REG_TYPE_NR]; 19762306a36Sopenharmony_ci}; 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_cistruct drm_exynos_pending_g2d_event { 20062306a36Sopenharmony_ci struct drm_pending_event base; 20162306a36Sopenharmony_ci struct drm_exynos_g2d_event event; 20262306a36Sopenharmony_ci}; 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_cistruct g2d_cmdlist_userptr { 20562306a36Sopenharmony_ci struct list_head list; 20662306a36Sopenharmony_ci dma_addr_t dma_addr; 20762306a36Sopenharmony_ci unsigned long userptr; 20862306a36Sopenharmony_ci unsigned long size; 20962306a36Sopenharmony_ci struct page **pages; 21062306a36Sopenharmony_ci unsigned int npages; 21162306a36Sopenharmony_ci struct sg_table *sgt; 21262306a36Sopenharmony_ci refcount_t refcount; 21362306a36Sopenharmony_ci bool in_pool; 21462306a36Sopenharmony_ci bool out_of_list; 21562306a36Sopenharmony_ci}; 21662306a36Sopenharmony_cistruct g2d_cmdlist_node { 21762306a36Sopenharmony_ci struct list_head list; 21862306a36Sopenharmony_ci struct g2d_cmdlist *cmdlist; 21962306a36Sopenharmony_ci dma_addr_t dma_addr; 22062306a36Sopenharmony_ci struct g2d_buf_info buf_info; 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci struct drm_exynos_pending_g2d_event *event; 22362306a36Sopenharmony_ci}; 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_cistruct g2d_runqueue_node { 22662306a36Sopenharmony_ci struct list_head list; 22762306a36Sopenharmony_ci struct list_head run_cmdlist; 22862306a36Sopenharmony_ci struct list_head event_list; 22962306a36Sopenharmony_ci struct drm_file *filp; 23062306a36Sopenharmony_ci pid_t pid; 23162306a36Sopenharmony_ci struct completion complete; 23262306a36Sopenharmony_ci int async; 23362306a36Sopenharmony_ci}; 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_cistruct g2d_data { 23662306a36Sopenharmony_ci struct device *dev; 23762306a36Sopenharmony_ci void *dma_priv; 23862306a36Sopenharmony_ci struct clk *gate_clk; 23962306a36Sopenharmony_ci void __iomem *regs; 24062306a36Sopenharmony_ci int irq; 24162306a36Sopenharmony_ci struct workqueue_struct *g2d_workq; 24262306a36Sopenharmony_ci struct work_struct runqueue_work; 24362306a36Sopenharmony_ci struct drm_device *drm_dev; 24462306a36Sopenharmony_ci unsigned long flags; 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ci /* cmdlist */ 24762306a36Sopenharmony_ci struct g2d_cmdlist_node *cmdlist_node; 24862306a36Sopenharmony_ci struct list_head free_cmdlist; 24962306a36Sopenharmony_ci struct mutex cmdlist_mutex; 25062306a36Sopenharmony_ci dma_addr_t cmdlist_pool; 25162306a36Sopenharmony_ci void *cmdlist_pool_virt; 25262306a36Sopenharmony_ci unsigned long cmdlist_dma_attrs; 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci /* runqueue*/ 25562306a36Sopenharmony_ci struct g2d_runqueue_node *runqueue_node; 25662306a36Sopenharmony_ci struct list_head runqueue; 25762306a36Sopenharmony_ci struct mutex runqueue_mutex; 25862306a36Sopenharmony_ci struct kmem_cache *runqueue_slab; 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci unsigned long current_pool; 26162306a36Sopenharmony_ci unsigned long max_pool; 26262306a36Sopenharmony_ci}; 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_cistatic inline void g2d_hw_reset(struct g2d_data *g2d) 26562306a36Sopenharmony_ci{ 26662306a36Sopenharmony_ci writel(G2D_R | G2D_SFRCLEAR, g2d->regs + G2D_SOFT_RESET); 26762306a36Sopenharmony_ci clear_bit(G2D_BIT_ENGINE_BUSY, &g2d->flags); 26862306a36Sopenharmony_ci} 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_cistatic int g2d_init_cmdlist(struct g2d_data *g2d) 27162306a36Sopenharmony_ci{ 27262306a36Sopenharmony_ci struct device *dev = g2d->dev; 27362306a36Sopenharmony_ci struct g2d_cmdlist_node *node; 27462306a36Sopenharmony_ci int nr; 27562306a36Sopenharmony_ci int ret; 27662306a36Sopenharmony_ci struct g2d_buf_info *buf_info; 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci g2d->cmdlist_dma_attrs = DMA_ATTR_WRITE_COMBINE; 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci g2d->cmdlist_pool_virt = dma_alloc_attrs(to_dma_dev(g2d->drm_dev), 28162306a36Sopenharmony_ci G2D_CMDLIST_POOL_SIZE, 28262306a36Sopenharmony_ci &g2d->cmdlist_pool, GFP_KERNEL, 28362306a36Sopenharmony_ci g2d->cmdlist_dma_attrs); 28462306a36Sopenharmony_ci if (!g2d->cmdlist_pool_virt) { 28562306a36Sopenharmony_ci dev_err(dev, "failed to allocate dma memory\n"); 28662306a36Sopenharmony_ci return -ENOMEM; 28762306a36Sopenharmony_ci } 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_ci node = kcalloc(G2D_CMDLIST_NUM, sizeof(*node), GFP_KERNEL); 29062306a36Sopenharmony_ci if (!node) { 29162306a36Sopenharmony_ci ret = -ENOMEM; 29262306a36Sopenharmony_ci goto err; 29362306a36Sopenharmony_ci } 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_ci for (nr = 0; nr < G2D_CMDLIST_NUM; nr++) { 29662306a36Sopenharmony_ci unsigned int i; 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_ci node[nr].cmdlist = 29962306a36Sopenharmony_ci g2d->cmdlist_pool_virt + nr * G2D_CMDLIST_SIZE; 30062306a36Sopenharmony_ci node[nr].dma_addr = 30162306a36Sopenharmony_ci g2d->cmdlist_pool + nr * G2D_CMDLIST_SIZE; 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ci buf_info = &node[nr].buf_info; 30462306a36Sopenharmony_ci for (i = 0; i < MAX_REG_TYPE_NR; i++) 30562306a36Sopenharmony_ci buf_info->reg_types[i] = REG_TYPE_NONE; 30662306a36Sopenharmony_ci 30762306a36Sopenharmony_ci list_add_tail(&node[nr].list, &g2d->free_cmdlist); 30862306a36Sopenharmony_ci } 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_ci return 0; 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_cierr: 31362306a36Sopenharmony_ci dma_free_attrs(to_dma_dev(g2d->drm_dev), G2D_CMDLIST_POOL_SIZE, 31462306a36Sopenharmony_ci g2d->cmdlist_pool_virt, 31562306a36Sopenharmony_ci g2d->cmdlist_pool, g2d->cmdlist_dma_attrs); 31662306a36Sopenharmony_ci return ret; 31762306a36Sopenharmony_ci} 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_cistatic void g2d_fini_cmdlist(struct g2d_data *g2d) 32062306a36Sopenharmony_ci{ 32162306a36Sopenharmony_ci kfree(g2d->cmdlist_node); 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_ci if (g2d->cmdlist_pool_virt && g2d->cmdlist_pool) { 32462306a36Sopenharmony_ci dma_free_attrs(to_dma_dev(g2d->drm_dev), 32562306a36Sopenharmony_ci G2D_CMDLIST_POOL_SIZE, 32662306a36Sopenharmony_ci g2d->cmdlist_pool_virt, 32762306a36Sopenharmony_ci g2d->cmdlist_pool, g2d->cmdlist_dma_attrs); 32862306a36Sopenharmony_ci } 32962306a36Sopenharmony_ci} 33062306a36Sopenharmony_ci 33162306a36Sopenharmony_cistatic struct g2d_cmdlist_node *g2d_get_cmdlist(struct g2d_data *g2d) 33262306a36Sopenharmony_ci{ 33362306a36Sopenharmony_ci struct device *dev = g2d->dev; 33462306a36Sopenharmony_ci struct g2d_cmdlist_node *node; 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_ci mutex_lock(&g2d->cmdlist_mutex); 33762306a36Sopenharmony_ci if (list_empty(&g2d->free_cmdlist)) { 33862306a36Sopenharmony_ci dev_err(dev, "there is no free cmdlist\n"); 33962306a36Sopenharmony_ci mutex_unlock(&g2d->cmdlist_mutex); 34062306a36Sopenharmony_ci return NULL; 34162306a36Sopenharmony_ci } 34262306a36Sopenharmony_ci 34362306a36Sopenharmony_ci node = list_first_entry(&g2d->free_cmdlist, struct g2d_cmdlist_node, 34462306a36Sopenharmony_ci list); 34562306a36Sopenharmony_ci list_del_init(&node->list); 34662306a36Sopenharmony_ci mutex_unlock(&g2d->cmdlist_mutex); 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_ci return node; 34962306a36Sopenharmony_ci} 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_cistatic void g2d_put_cmdlist(struct g2d_data *g2d, struct g2d_cmdlist_node *node) 35262306a36Sopenharmony_ci{ 35362306a36Sopenharmony_ci mutex_lock(&g2d->cmdlist_mutex); 35462306a36Sopenharmony_ci list_move_tail(&node->list, &g2d->free_cmdlist); 35562306a36Sopenharmony_ci mutex_unlock(&g2d->cmdlist_mutex); 35662306a36Sopenharmony_ci} 35762306a36Sopenharmony_ci 35862306a36Sopenharmony_cistatic void g2d_add_cmdlist_to_inuse(struct drm_exynos_file_private *file_priv, 35962306a36Sopenharmony_ci struct g2d_cmdlist_node *node) 36062306a36Sopenharmony_ci{ 36162306a36Sopenharmony_ci struct g2d_cmdlist_node *lnode; 36262306a36Sopenharmony_ci 36362306a36Sopenharmony_ci if (list_empty(&file_priv->inuse_cmdlist)) 36462306a36Sopenharmony_ci goto add_to_list; 36562306a36Sopenharmony_ci 36662306a36Sopenharmony_ci /* this links to base address of new cmdlist */ 36762306a36Sopenharmony_ci lnode = list_entry(file_priv->inuse_cmdlist.prev, 36862306a36Sopenharmony_ci struct g2d_cmdlist_node, list); 36962306a36Sopenharmony_ci lnode->cmdlist->data[lnode->cmdlist->last] = node->dma_addr; 37062306a36Sopenharmony_ci 37162306a36Sopenharmony_ciadd_to_list: 37262306a36Sopenharmony_ci list_add_tail(&node->list, &file_priv->inuse_cmdlist); 37362306a36Sopenharmony_ci 37462306a36Sopenharmony_ci if (node->event) 37562306a36Sopenharmony_ci list_add_tail(&node->event->base.link, &file_priv->event_list); 37662306a36Sopenharmony_ci} 37762306a36Sopenharmony_ci 37862306a36Sopenharmony_cistatic void g2d_userptr_put_dma_addr(struct g2d_data *g2d, 37962306a36Sopenharmony_ci void *obj, 38062306a36Sopenharmony_ci bool force) 38162306a36Sopenharmony_ci{ 38262306a36Sopenharmony_ci struct g2d_cmdlist_userptr *g2d_userptr = obj; 38362306a36Sopenharmony_ci 38462306a36Sopenharmony_ci if (!obj) 38562306a36Sopenharmony_ci return; 38662306a36Sopenharmony_ci 38762306a36Sopenharmony_ci if (force) 38862306a36Sopenharmony_ci goto out; 38962306a36Sopenharmony_ci 39062306a36Sopenharmony_ci refcount_dec(&g2d_userptr->refcount); 39162306a36Sopenharmony_ci 39262306a36Sopenharmony_ci if (refcount_read(&g2d_userptr->refcount) > 0) 39362306a36Sopenharmony_ci return; 39462306a36Sopenharmony_ci 39562306a36Sopenharmony_ci if (g2d_userptr->in_pool) 39662306a36Sopenharmony_ci return; 39762306a36Sopenharmony_ci 39862306a36Sopenharmony_ciout: 39962306a36Sopenharmony_ci dma_unmap_sgtable(to_dma_dev(g2d->drm_dev), g2d_userptr->sgt, 40062306a36Sopenharmony_ci DMA_BIDIRECTIONAL, 0); 40162306a36Sopenharmony_ci 40262306a36Sopenharmony_ci unpin_user_pages_dirty_lock(g2d_userptr->pages, g2d_userptr->npages, 40362306a36Sopenharmony_ci true); 40462306a36Sopenharmony_ci kvfree(g2d_userptr->pages); 40562306a36Sopenharmony_ci 40662306a36Sopenharmony_ci if (!g2d_userptr->out_of_list) 40762306a36Sopenharmony_ci list_del_init(&g2d_userptr->list); 40862306a36Sopenharmony_ci 40962306a36Sopenharmony_ci sg_free_table(g2d_userptr->sgt); 41062306a36Sopenharmony_ci kfree(g2d_userptr->sgt); 41162306a36Sopenharmony_ci kfree(g2d_userptr); 41262306a36Sopenharmony_ci} 41362306a36Sopenharmony_ci 41462306a36Sopenharmony_cistatic dma_addr_t *g2d_userptr_get_dma_addr(struct g2d_data *g2d, 41562306a36Sopenharmony_ci unsigned long userptr, 41662306a36Sopenharmony_ci unsigned long size, 41762306a36Sopenharmony_ci struct drm_file *filp, 41862306a36Sopenharmony_ci void **obj) 41962306a36Sopenharmony_ci{ 42062306a36Sopenharmony_ci struct drm_exynos_file_private *file_priv = filp->driver_priv; 42162306a36Sopenharmony_ci struct g2d_cmdlist_userptr *g2d_userptr; 42262306a36Sopenharmony_ci struct sg_table *sgt; 42362306a36Sopenharmony_ci unsigned long start, end; 42462306a36Sopenharmony_ci unsigned int npages, offset; 42562306a36Sopenharmony_ci int ret; 42662306a36Sopenharmony_ci 42762306a36Sopenharmony_ci if (!size) { 42862306a36Sopenharmony_ci DRM_DEV_ERROR(g2d->dev, "invalid userptr size.\n"); 42962306a36Sopenharmony_ci return ERR_PTR(-EINVAL); 43062306a36Sopenharmony_ci } 43162306a36Sopenharmony_ci 43262306a36Sopenharmony_ci /* check if userptr already exists in userptr_list. */ 43362306a36Sopenharmony_ci list_for_each_entry(g2d_userptr, &file_priv->userptr_list, list) { 43462306a36Sopenharmony_ci if (g2d_userptr->userptr == userptr) { 43562306a36Sopenharmony_ci /* 43662306a36Sopenharmony_ci * also check size because there could be same address 43762306a36Sopenharmony_ci * and different size. 43862306a36Sopenharmony_ci */ 43962306a36Sopenharmony_ci if (g2d_userptr->size == size) { 44062306a36Sopenharmony_ci refcount_inc(&g2d_userptr->refcount); 44162306a36Sopenharmony_ci *obj = g2d_userptr; 44262306a36Sopenharmony_ci 44362306a36Sopenharmony_ci return &g2d_userptr->dma_addr; 44462306a36Sopenharmony_ci } 44562306a36Sopenharmony_ci 44662306a36Sopenharmony_ci /* 44762306a36Sopenharmony_ci * at this moment, maybe g2d dma is accessing this 44862306a36Sopenharmony_ci * g2d_userptr memory region so just remove this 44962306a36Sopenharmony_ci * g2d_userptr object from userptr_list not to be 45062306a36Sopenharmony_ci * referred again and also except it the userptr 45162306a36Sopenharmony_ci * pool to be released after the dma access completion. 45262306a36Sopenharmony_ci */ 45362306a36Sopenharmony_ci g2d_userptr->out_of_list = true; 45462306a36Sopenharmony_ci g2d_userptr->in_pool = false; 45562306a36Sopenharmony_ci list_del_init(&g2d_userptr->list); 45662306a36Sopenharmony_ci 45762306a36Sopenharmony_ci break; 45862306a36Sopenharmony_ci } 45962306a36Sopenharmony_ci } 46062306a36Sopenharmony_ci 46162306a36Sopenharmony_ci g2d_userptr = kzalloc(sizeof(*g2d_userptr), GFP_KERNEL); 46262306a36Sopenharmony_ci if (!g2d_userptr) 46362306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 46462306a36Sopenharmony_ci 46562306a36Sopenharmony_ci refcount_set(&g2d_userptr->refcount, 1); 46662306a36Sopenharmony_ci g2d_userptr->size = size; 46762306a36Sopenharmony_ci 46862306a36Sopenharmony_ci start = userptr & PAGE_MASK; 46962306a36Sopenharmony_ci offset = userptr & ~PAGE_MASK; 47062306a36Sopenharmony_ci end = PAGE_ALIGN(userptr + size); 47162306a36Sopenharmony_ci npages = (end - start) >> PAGE_SHIFT; 47262306a36Sopenharmony_ci g2d_userptr->pages = kvmalloc_array(npages, sizeof(*g2d_userptr->pages), 47362306a36Sopenharmony_ci GFP_KERNEL); 47462306a36Sopenharmony_ci if (!g2d_userptr->pages) { 47562306a36Sopenharmony_ci ret = -ENOMEM; 47662306a36Sopenharmony_ci goto err_free; 47762306a36Sopenharmony_ci } 47862306a36Sopenharmony_ci 47962306a36Sopenharmony_ci ret = pin_user_pages_fast(start, npages, 48062306a36Sopenharmony_ci FOLL_WRITE | FOLL_LONGTERM, 48162306a36Sopenharmony_ci g2d_userptr->pages); 48262306a36Sopenharmony_ci if (ret != npages) { 48362306a36Sopenharmony_ci DRM_DEV_ERROR(g2d->dev, 48462306a36Sopenharmony_ci "failed to get user pages from userptr.\n"); 48562306a36Sopenharmony_ci if (ret < 0) 48662306a36Sopenharmony_ci goto err_destroy_pages; 48762306a36Sopenharmony_ci npages = ret; 48862306a36Sopenharmony_ci ret = -EFAULT; 48962306a36Sopenharmony_ci goto err_unpin_pages; 49062306a36Sopenharmony_ci } 49162306a36Sopenharmony_ci g2d_userptr->npages = npages; 49262306a36Sopenharmony_ci 49362306a36Sopenharmony_ci sgt = kzalloc(sizeof(*sgt), GFP_KERNEL); 49462306a36Sopenharmony_ci if (!sgt) { 49562306a36Sopenharmony_ci ret = -ENOMEM; 49662306a36Sopenharmony_ci goto err_unpin_pages; 49762306a36Sopenharmony_ci } 49862306a36Sopenharmony_ci 49962306a36Sopenharmony_ci ret = sg_alloc_table_from_pages(sgt, 50062306a36Sopenharmony_ci g2d_userptr->pages, 50162306a36Sopenharmony_ci npages, offset, size, GFP_KERNEL); 50262306a36Sopenharmony_ci if (ret < 0) { 50362306a36Sopenharmony_ci DRM_DEV_ERROR(g2d->dev, "failed to get sgt from pages.\n"); 50462306a36Sopenharmony_ci goto err_free_sgt; 50562306a36Sopenharmony_ci } 50662306a36Sopenharmony_ci 50762306a36Sopenharmony_ci g2d_userptr->sgt = sgt; 50862306a36Sopenharmony_ci 50962306a36Sopenharmony_ci ret = dma_map_sgtable(to_dma_dev(g2d->drm_dev), sgt, 51062306a36Sopenharmony_ci DMA_BIDIRECTIONAL, 0); 51162306a36Sopenharmony_ci if (ret) { 51262306a36Sopenharmony_ci DRM_DEV_ERROR(g2d->dev, "failed to map sgt with dma region.\n"); 51362306a36Sopenharmony_ci goto err_sg_free_table; 51462306a36Sopenharmony_ci } 51562306a36Sopenharmony_ci 51662306a36Sopenharmony_ci g2d_userptr->dma_addr = sgt->sgl[0].dma_address; 51762306a36Sopenharmony_ci g2d_userptr->userptr = userptr; 51862306a36Sopenharmony_ci 51962306a36Sopenharmony_ci list_add_tail(&g2d_userptr->list, &file_priv->userptr_list); 52062306a36Sopenharmony_ci 52162306a36Sopenharmony_ci if (g2d->current_pool + (npages << PAGE_SHIFT) < g2d->max_pool) { 52262306a36Sopenharmony_ci g2d->current_pool += npages << PAGE_SHIFT; 52362306a36Sopenharmony_ci g2d_userptr->in_pool = true; 52462306a36Sopenharmony_ci } 52562306a36Sopenharmony_ci 52662306a36Sopenharmony_ci *obj = g2d_userptr; 52762306a36Sopenharmony_ci 52862306a36Sopenharmony_ci return &g2d_userptr->dma_addr; 52962306a36Sopenharmony_ci 53062306a36Sopenharmony_cierr_sg_free_table: 53162306a36Sopenharmony_ci sg_free_table(sgt); 53262306a36Sopenharmony_ci 53362306a36Sopenharmony_cierr_free_sgt: 53462306a36Sopenharmony_ci kfree(sgt); 53562306a36Sopenharmony_ci 53662306a36Sopenharmony_cierr_unpin_pages: 53762306a36Sopenharmony_ci unpin_user_pages(g2d_userptr->pages, npages); 53862306a36Sopenharmony_ci 53962306a36Sopenharmony_cierr_destroy_pages: 54062306a36Sopenharmony_ci kvfree(g2d_userptr->pages); 54162306a36Sopenharmony_ci 54262306a36Sopenharmony_cierr_free: 54362306a36Sopenharmony_ci kfree(g2d_userptr); 54462306a36Sopenharmony_ci 54562306a36Sopenharmony_ci return ERR_PTR(ret); 54662306a36Sopenharmony_ci} 54762306a36Sopenharmony_ci 54862306a36Sopenharmony_cistatic void g2d_userptr_free_all(struct g2d_data *g2d, struct drm_file *filp) 54962306a36Sopenharmony_ci{ 55062306a36Sopenharmony_ci struct drm_exynos_file_private *file_priv = filp->driver_priv; 55162306a36Sopenharmony_ci struct g2d_cmdlist_userptr *g2d_userptr, *n; 55262306a36Sopenharmony_ci 55362306a36Sopenharmony_ci list_for_each_entry_safe(g2d_userptr, n, &file_priv->userptr_list, list) 55462306a36Sopenharmony_ci if (g2d_userptr->in_pool) 55562306a36Sopenharmony_ci g2d_userptr_put_dma_addr(g2d, g2d_userptr, true); 55662306a36Sopenharmony_ci 55762306a36Sopenharmony_ci g2d->current_pool = 0; 55862306a36Sopenharmony_ci} 55962306a36Sopenharmony_ci 56062306a36Sopenharmony_cistatic enum g2d_reg_type g2d_get_reg_type(struct g2d_data *g2d, int reg_offset) 56162306a36Sopenharmony_ci{ 56262306a36Sopenharmony_ci enum g2d_reg_type reg_type; 56362306a36Sopenharmony_ci 56462306a36Sopenharmony_ci switch (reg_offset) { 56562306a36Sopenharmony_ci case G2D_SRC_BASE_ADDR: 56662306a36Sopenharmony_ci case G2D_SRC_STRIDE: 56762306a36Sopenharmony_ci case G2D_SRC_COLOR_MODE: 56862306a36Sopenharmony_ci case G2D_SRC_LEFT_TOP: 56962306a36Sopenharmony_ci case G2D_SRC_RIGHT_BOTTOM: 57062306a36Sopenharmony_ci reg_type = REG_TYPE_SRC; 57162306a36Sopenharmony_ci break; 57262306a36Sopenharmony_ci case G2D_SRC_PLANE2_BASE_ADDR: 57362306a36Sopenharmony_ci reg_type = REG_TYPE_SRC_PLANE2; 57462306a36Sopenharmony_ci break; 57562306a36Sopenharmony_ci case G2D_DST_BASE_ADDR: 57662306a36Sopenharmony_ci case G2D_DST_STRIDE: 57762306a36Sopenharmony_ci case G2D_DST_COLOR_MODE: 57862306a36Sopenharmony_ci case G2D_DST_LEFT_TOP: 57962306a36Sopenharmony_ci case G2D_DST_RIGHT_BOTTOM: 58062306a36Sopenharmony_ci reg_type = REG_TYPE_DST; 58162306a36Sopenharmony_ci break; 58262306a36Sopenharmony_ci case G2D_DST_PLANE2_BASE_ADDR: 58362306a36Sopenharmony_ci reg_type = REG_TYPE_DST_PLANE2; 58462306a36Sopenharmony_ci break; 58562306a36Sopenharmony_ci case G2D_PAT_BASE_ADDR: 58662306a36Sopenharmony_ci reg_type = REG_TYPE_PAT; 58762306a36Sopenharmony_ci break; 58862306a36Sopenharmony_ci case G2D_MSK_BASE_ADDR: 58962306a36Sopenharmony_ci reg_type = REG_TYPE_MSK; 59062306a36Sopenharmony_ci break; 59162306a36Sopenharmony_ci default: 59262306a36Sopenharmony_ci reg_type = REG_TYPE_NONE; 59362306a36Sopenharmony_ci DRM_DEV_ERROR(g2d->dev, "Unknown register offset![%d]\n", 59462306a36Sopenharmony_ci reg_offset); 59562306a36Sopenharmony_ci break; 59662306a36Sopenharmony_ci } 59762306a36Sopenharmony_ci 59862306a36Sopenharmony_ci return reg_type; 59962306a36Sopenharmony_ci} 60062306a36Sopenharmony_ci 60162306a36Sopenharmony_cistatic unsigned long g2d_get_buf_bpp(unsigned int format) 60262306a36Sopenharmony_ci{ 60362306a36Sopenharmony_ci unsigned long bpp; 60462306a36Sopenharmony_ci 60562306a36Sopenharmony_ci switch (format) { 60662306a36Sopenharmony_ci case G2D_FMT_XRGB8888: 60762306a36Sopenharmony_ci case G2D_FMT_ARGB8888: 60862306a36Sopenharmony_ci bpp = 4; 60962306a36Sopenharmony_ci break; 61062306a36Sopenharmony_ci case G2D_FMT_RGB565: 61162306a36Sopenharmony_ci case G2D_FMT_XRGB1555: 61262306a36Sopenharmony_ci case G2D_FMT_ARGB1555: 61362306a36Sopenharmony_ci case G2D_FMT_XRGB4444: 61462306a36Sopenharmony_ci case G2D_FMT_ARGB4444: 61562306a36Sopenharmony_ci bpp = 2; 61662306a36Sopenharmony_ci break; 61762306a36Sopenharmony_ci case G2D_FMT_PACKED_RGB888: 61862306a36Sopenharmony_ci bpp = 3; 61962306a36Sopenharmony_ci break; 62062306a36Sopenharmony_ci default: 62162306a36Sopenharmony_ci bpp = 1; 62262306a36Sopenharmony_ci break; 62362306a36Sopenharmony_ci } 62462306a36Sopenharmony_ci 62562306a36Sopenharmony_ci return bpp; 62662306a36Sopenharmony_ci} 62762306a36Sopenharmony_ci 62862306a36Sopenharmony_cistatic bool g2d_check_buf_desc_is_valid(struct g2d_data *g2d, 62962306a36Sopenharmony_ci struct g2d_buf_desc *buf_desc, 63062306a36Sopenharmony_ci enum g2d_reg_type reg_type, 63162306a36Sopenharmony_ci unsigned long size) 63262306a36Sopenharmony_ci{ 63362306a36Sopenharmony_ci int width, height; 63462306a36Sopenharmony_ci unsigned long bpp, last_pos; 63562306a36Sopenharmony_ci 63662306a36Sopenharmony_ci /* 63762306a36Sopenharmony_ci * check source and destination buffers only. 63862306a36Sopenharmony_ci * so the others are always valid. 63962306a36Sopenharmony_ci */ 64062306a36Sopenharmony_ci if (reg_type != REG_TYPE_SRC && reg_type != REG_TYPE_DST) 64162306a36Sopenharmony_ci return true; 64262306a36Sopenharmony_ci 64362306a36Sopenharmony_ci /* This check also makes sure that right_x > left_x. */ 64462306a36Sopenharmony_ci width = (int)buf_desc->right_x - (int)buf_desc->left_x; 64562306a36Sopenharmony_ci if (width < G2D_LEN_MIN || width > G2D_LEN_MAX) { 64662306a36Sopenharmony_ci DRM_DEV_ERROR(g2d->dev, "width[%d] is out of range!\n", width); 64762306a36Sopenharmony_ci return false; 64862306a36Sopenharmony_ci } 64962306a36Sopenharmony_ci 65062306a36Sopenharmony_ci /* This check also makes sure that bottom_y > top_y. */ 65162306a36Sopenharmony_ci height = (int)buf_desc->bottom_y - (int)buf_desc->top_y; 65262306a36Sopenharmony_ci if (height < G2D_LEN_MIN || height > G2D_LEN_MAX) { 65362306a36Sopenharmony_ci DRM_DEV_ERROR(g2d->dev, 65462306a36Sopenharmony_ci "height[%d] is out of range!\n", height); 65562306a36Sopenharmony_ci return false; 65662306a36Sopenharmony_ci } 65762306a36Sopenharmony_ci 65862306a36Sopenharmony_ci bpp = g2d_get_buf_bpp(buf_desc->format); 65962306a36Sopenharmony_ci 66062306a36Sopenharmony_ci /* Compute the position of the last byte that the engine accesses. */ 66162306a36Sopenharmony_ci last_pos = ((unsigned long)buf_desc->bottom_y - 1) * 66262306a36Sopenharmony_ci (unsigned long)buf_desc->stride + 66362306a36Sopenharmony_ci (unsigned long)buf_desc->right_x * bpp - 1; 66462306a36Sopenharmony_ci 66562306a36Sopenharmony_ci /* 66662306a36Sopenharmony_ci * Since right_x > left_x and bottom_y > top_y we already know 66762306a36Sopenharmony_ci * that the first_pos < last_pos (first_pos being the position 66862306a36Sopenharmony_ci * of the first byte the engine accesses), it just remains to 66962306a36Sopenharmony_ci * check if last_pos is smaller then the buffer size. 67062306a36Sopenharmony_ci */ 67162306a36Sopenharmony_ci 67262306a36Sopenharmony_ci if (last_pos >= size) { 67362306a36Sopenharmony_ci DRM_DEV_ERROR(g2d->dev, "last engine access position [%lu] " 67462306a36Sopenharmony_ci "is out of range [%lu]!\n", last_pos, size); 67562306a36Sopenharmony_ci return false; 67662306a36Sopenharmony_ci } 67762306a36Sopenharmony_ci 67862306a36Sopenharmony_ci return true; 67962306a36Sopenharmony_ci} 68062306a36Sopenharmony_ci 68162306a36Sopenharmony_cistatic int g2d_map_cmdlist_gem(struct g2d_data *g2d, 68262306a36Sopenharmony_ci struct g2d_cmdlist_node *node, 68362306a36Sopenharmony_ci struct drm_device *drm_dev, 68462306a36Sopenharmony_ci struct drm_file *file) 68562306a36Sopenharmony_ci{ 68662306a36Sopenharmony_ci struct g2d_cmdlist *cmdlist = node->cmdlist; 68762306a36Sopenharmony_ci struct g2d_buf_info *buf_info = &node->buf_info; 68862306a36Sopenharmony_ci int offset; 68962306a36Sopenharmony_ci int ret; 69062306a36Sopenharmony_ci int i; 69162306a36Sopenharmony_ci 69262306a36Sopenharmony_ci for (i = 0; i < buf_info->map_nr; i++) { 69362306a36Sopenharmony_ci struct g2d_buf_desc *buf_desc; 69462306a36Sopenharmony_ci enum g2d_reg_type reg_type; 69562306a36Sopenharmony_ci int reg_pos; 69662306a36Sopenharmony_ci unsigned long handle; 69762306a36Sopenharmony_ci dma_addr_t *addr; 69862306a36Sopenharmony_ci 69962306a36Sopenharmony_ci reg_pos = cmdlist->last - 2 * (i + 1); 70062306a36Sopenharmony_ci 70162306a36Sopenharmony_ci offset = cmdlist->data[reg_pos]; 70262306a36Sopenharmony_ci handle = cmdlist->data[reg_pos + 1]; 70362306a36Sopenharmony_ci 70462306a36Sopenharmony_ci reg_type = g2d_get_reg_type(g2d, offset); 70562306a36Sopenharmony_ci if (reg_type == REG_TYPE_NONE) { 70662306a36Sopenharmony_ci ret = -EFAULT; 70762306a36Sopenharmony_ci goto err; 70862306a36Sopenharmony_ci } 70962306a36Sopenharmony_ci 71062306a36Sopenharmony_ci buf_desc = &buf_info->descs[reg_type]; 71162306a36Sopenharmony_ci 71262306a36Sopenharmony_ci if (buf_info->types[reg_type] == BUF_TYPE_GEM) { 71362306a36Sopenharmony_ci struct exynos_drm_gem *exynos_gem; 71462306a36Sopenharmony_ci 71562306a36Sopenharmony_ci exynos_gem = exynos_drm_gem_get(file, handle); 71662306a36Sopenharmony_ci if (!exynos_gem) { 71762306a36Sopenharmony_ci ret = -EFAULT; 71862306a36Sopenharmony_ci goto err; 71962306a36Sopenharmony_ci } 72062306a36Sopenharmony_ci 72162306a36Sopenharmony_ci if (!g2d_check_buf_desc_is_valid(g2d, buf_desc, 72262306a36Sopenharmony_ci reg_type, exynos_gem->size)) { 72362306a36Sopenharmony_ci exynos_drm_gem_put(exynos_gem); 72462306a36Sopenharmony_ci ret = -EFAULT; 72562306a36Sopenharmony_ci goto err; 72662306a36Sopenharmony_ci } 72762306a36Sopenharmony_ci 72862306a36Sopenharmony_ci addr = &exynos_gem->dma_addr; 72962306a36Sopenharmony_ci buf_info->obj[reg_type] = exynos_gem; 73062306a36Sopenharmony_ci } else { 73162306a36Sopenharmony_ci struct drm_exynos_g2d_userptr g2d_userptr; 73262306a36Sopenharmony_ci 73362306a36Sopenharmony_ci if (copy_from_user(&g2d_userptr, (void __user *)handle, 73462306a36Sopenharmony_ci sizeof(struct drm_exynos_g2d_userptr))) { 73562306a36Sopenharmony_ci ret = -EFAULT; 73662306a36Sopenharmony_ci goto err; 73762306a36Sopenharmony_ci } 73862306a36Sopenharmony_ci 73962306a36Sopenharmony_ci if (!g2d_check_buf_desc_is_valid(g2d, buf_desc, 74062306a36Sopenharmony_ci reg_type, 74162306a36Sopenharmony_ci g2d_userptr.size)) { 74262306a36Sopenharmony_ci ret = -EFAULT; 74362306a36Sopenharmony_ci goto err; 74462306a36Sopenharmony_ci } 74562306a36Sopenharmony_ci 74662306a36Sopenharmony_ci addr = g2d_userptr_get_dma_addr(g2d, 74762306a36Sopenharmony_ci g2d_userptr.userptr, 74862306a36Sopenharmony_ci g2d_userptr.size, 74962306a36Sopenharmony_ci file, 75062306a36Sopenharmony_ci &buf_info->obj[reg_type]); 75162306a36Sopenharmony_ci if (IS_ERR(addr)) { 75262306a36Sopenharmony_ci ret = -EFAULT; 75362306a36Sopenharmony_ci goto err; 75462306a36Sopenharmony_ci } 75562306a36Sopenharmony_ci } 75662306a36Sopenharmony_ci 75762306a36Sopenharmony_ci cmdlist->data[reg_pos + 1] = *addr; 75862306a36Sopenharmony_ci buf_info->reg_types[i] = reg_type; 75962306a36Sopenharmony_ci } 76062306a36Sopenharmony_ci 76162306a36Sopenharmony_ci return 0; 76262306a36Sopenharmony_ci 76362306a36Sopenharmony_cierr: 76462306a36Sopenharmony_ci buf_info->map_nr = i; 76562306a36Sopenharmony_ci return ret; 76662306a36Sopenharmony_ci} 76762306a36Sopenharmony_ci 76862306a36Sopenharmony_cistatic void g2d_unmap_cmdlist_gem(struct g2d_data *g2d, 76962306a36Sopenharmony_ci struct g2d_cmdlist_node *node, 77062306a36Sopenharmony_ci struct drm_file *filp) 77162306a36Sopenharmony_ci{ 77262306a36Sopenharmony_ci struct g2d_buf_info *buf_info = &node->buf_info; 77362306a36Sopenharmony_ci int i; 77462306a36Sopenharmony_ci 77562306a36Sopenharmony_ci for (i = 0; i < buf_info->map_nr; i++) { 77662306a36Sopenharmony_ci struct g2d_buf_desc *buf_desc; 77762306a36Sopenharmony_ci enum g2d_reg_type reg_type; 77862306a36Sopenharmony_ci void *obj; 77962306a36Sopenharmony_ci 78062306a36Sopenharmony_ci reg_type = buf_info->reg_types[i]; 78162306a36Sopenharmony_ci 78262306a36Sopenharmony_ci buf_desc = &buf_info->descs[reg_type]; 78362306a36Sopenharmony_ci obj = buf_info->obj[reg_type]; 78462306a36Sopenharmony_ci 78562306a36Sopenharmony_ci if (buf_info->types[reg_type] == BUF_TYPE_GEM) 78662306a36Sopenharmony_ci exynos_drm_gem_put(obj); 78762306a36Sopenharmony_ci else 78862306a36Sopenharmony_ci g2d_userptr_put_dma_addr(g2d, obj, false); 78962306a36Sopenharmony_ci 79062306a36Sopenharmony_ci buf_info->reg_types[i] = REG_TYPE_NONE; 79162306a36Sopenharmony_ci buf_info->obj[reg_type] = NULL; 79262306a36Sopenharmony_ci buf_info->types[reg_type] = 0; 79362306a36Sopenharmony_ci memset(buf_desc, 0x00, sizeof(*buf_desc)); 79462306a36Sopenharmony_ci } 79562306a36Sopenharmony_ci 79662306a36Sopenharmony_ci buf_info->map_nr = 0; 79762306a36Sopenharmony_ci} 79862306a36Sopenharmony_ci 79962306a36Sopenharmony_cistatic void g2d_dma_start(struct g2d_data *g2d, 80062306a36Sopenharmony_ci struct g2d_runqueue_node *runqueue_node) 80162306a36Sopenharmony_ci{ 80262306a36Sopenharmony_ci struct g2d_cmdlist_node *node = 80362306a36Sopenharmony_ci list_first_entry(&runqueue_node->run_cmdlist, 80462306a36Sopenharmony_ci struct g2d_cmdlist_node, list); 80562306a36Sopenharmony_ci 80662306a36Sopenharmony_ci set_bit(G2D_BIT_ENGINE_BUSY, &g2d->flags); 80762306a36Sopenharmony_ci writel_relaxed(node->dma_addr, g2d->regs + G2D_DMA_SFR_BASE_ADDR); 80862306a36Sopenharmony_ci writel_relaxed(G2D_DMA_START, g2d->regs + G2D_DMA_COMMAND); 80962306a36Sopenharmony_ci} 81062306a36Sopenharmony_ci 81162306a36Sopenharmony_cistatic struct g2d_runqueue_node *g2d_get_runqueue_node(struct g2d_data *g2d) 81262306a36Sopenharmony_ci{ 81362306a36Sopenharmony_ci struct g2d_runqueue_node *runqueue_node; 81462306a36Sopenharmony_ci 81562306a36Sopenharmony_ci if (list_empty(&g2d->runqueue)) 81662306a36Sopenharmony_ci return NULL; 81762306a36Sopenharmony_ci 81862306a36Sopenharmony_ci runqueue_node = list_first_entry(&g2d->runqueue, 81962306a36Sopenharmony_ci struct g2d_runqueue_node, list); 82062306a36Sopenharmony_ci list_del_init(&runqueue_node->list); 82162306a36Sopenharmony_ci return runqueue_node; 82262306a36Sopenharmony_ci} 82362306a36Sopenharmony_ci 82462306a36Sopenharmony_cistatic void g2d_free_runqueue_node(struct g2d_data *g2d, 82562306a36Sopenharmony_ci struct g2d_runqueue_node *runqueue_node) 82662306a36Sopenharmony_ci{ 82762306a36Sopenharmony_ci struct g2d_cmdlist_node *node; 82862306a36Sopenharmony_ci 82962306a36Sopenharmony_ci mutex_lock(&g2d->cmdlist_mutex); 83062306a36Sopenharmony_ci /* 83162306a36Sopenharmony_ci * commands in run_cmdlist have been completed so unmap all gem 83262306a36Sopenharmony_ci * objects in each command node so that they are unreferenced. 83362306a36Sopenharmony_ci */ 83462306a36Sopenharmony_ci list_for_each_entry(node, &runqueue_node->run_cmdlist, list) 83562306a36Sopenharmony_ci g2d_unmap_cmdlist_gem(g2d, node, runqueue_node->filp); 83662306a36Sopenharmony_ci list_splice_tail_init(&runqueue_node->run_cmdlist, &g2d->free_cmdlist); 83762306a36Sopenharmony_ci mutex_unlock(&g2d->cmdlist_mutex); 83862306a36Sopenharmony_ci 83962306a36Sopenharmony_ci kmem_cache_free(g2d->runqueue_slab, runqueue_node); 84062306a36Sopenharmony_ci} 84162306a36Sopenharmony_ci 84262306a36Sopenharmony_ci/** 84362306a36Sopenharmony_ci * g2d_remove_runqueue_nodes - remove items from the list of runqueue nodes 84462306a36Sopenharmony_ci * @g2d: G2D state object 84562306a36Sopenharmony_ci * @file: if not zero, only remove items with this DRM file 84662306a36Sopenharmony_ci * 84762306a36Sopenharmony_ci * Has to be called under runqueue lock. 84862306a36Sopenharmony_ci */ 84962306a36Sopenharmony_cistatic void g2d_remove_runqueue_nodes(struct g2d_data *g2d, struct drm_file *file) 85062306a36Sopenharmony_ci{ 85162306a36Sopenharmony_ci struct g2d_runqueue_node *node, *n; 85262306a36Sopenharmony_ci 85362306a36Sopenharmony_ci if (list_empty(&g2d->runqueue)) 85462306a36Sopenharmony_ci return; 85562306a36Sopenharmony_ci 85662306a36Sopenharmony_ci list_for_each_entry_safe(node, n, &g2d->runqueue, list) { 85762306a36Sopenharmony_ci if (file && node->filp != file) 85862306a36Sopenharmony_ci continue; 85962306a36Sopenharmony_ci 86062306a36Sopenharmony_ci list_del_init(&node->list); 86162306a36Sopenharmony_ci g2d_free_runqueue_node(g2d, node); 86262306a36Sopenharmony_ci } 86362306a36Sopenharmony_ci} 86462306a36Sopenharmony_ci 86562306a36Sopenharmony_cistatic void g2d_runqueue_worker(struct work_struct *work) 86662306a36Sopenharmony_ci{ 86762306a36Sopenharmony_ci struct g2d_data *g2d = container_of(work, struct g2d_data, 86862306a36Sopenharmony_ci runqueue_work); 86962306a36Sopenharmony_ci struct g2d_runqueue_node *runqueue_node; 87062306a36Sopenharmony_ci 87162306a36Sopenharmony_ci /* 87262306a36Sopenharmony_ci * The engine is busy and the completion of the current node is going 87362306a36Sopenharmony_ci * to poke the runqueue worker, so nothing to do here. 87462306a36Sopenharmony_ci */ 87562306a36Sopenharmony_ci if (test_bit(G2D_BIT_ENGINE_BUSY, &g2d->flags)) 87662306a36Sopenharmony_ci return; 87762306a36Sopenharmony_ci 87862306a36Sopenharmony_ci mutex_lock(&g2d->runqueue_mutex); 87962306a36Sopenharmony_ci 88062306a36Sopenharmony_ci runqueue_node = g2d->runqueue_node; 88162306a36Sopenharmony_ci g2d->runqueue_node = NULL; 88262306a36Sopenharmony_ci 88362306a36Sopenharmony_ci if (runqueue_node) { 88462306a36Sopenharmony_ci pm_runtime_mark_last_busy(g2d->dev); 88562306a36Sopenharmony_ci pm_runtime_put_autosuspend(g2d->dev); 88662306a36Sopenharmony_ci 88762306a36Sopenharmony_ci complete(&runqueue_node->complete); 88862306a36Sopenharmony_ci if (runqueue_node->async) 88962306a36Sopenharmony_ci g2d_free_runqueue_node(g2d, runqueue_node); 89062306a36Sopenharmony_ci } 89162306a36Sopenharmony_ci 89262306a36Sopenharmony_ci if (!test_bit(G2D_BIT_SUSPEND_RUNQUEUE, &g2d->flags)) { 89362306a36Sopenharmony_ci g2d->runqueue_node = g2d_get_runqueue_node(g2d); 89462306a36Sopenharmony_ci 89562306a36Sopenharmony_ci if (g2d->runqueue_node) { 89662306a36Sopenharmony_ci int ret; 89762306a36Sopenharmony_ci 89862306a36Sopenharmony_ci ret = pm_runtime_resume_and_get(g2d->dev); 89962306a36Sopenharmony_ci if (ret < 0) { 90062306a36Sopenharmony_ci dev_err(g2d->dev, "failed to enable G2D device.\n"); 90162306a36Sopenharmony_ci goto out; 90262306a36Sopenharmony_ci } 90362306a36Sopenharmony_ci 90462306a36Sopenharmony_ci g2d_dma_start(g2d, g2d->runqueue_node); 90562306a36Sopenharmony_ci } 90662306a36Sopenharmony_ci } 90762306a36Sopenharmony_ci 90862306a36Sopenharmony_ciout: 90962306a36Sopenharmony_ci mutex_unlock(&g2d->runqueue_mutex); 91062306a36Sopenharmony_ci} 91162306a36Sopenharmony_ci 91262306a36Sopenharmony_cistatic void g2d_finish_event(struct g2d_data *g2d, u32 cmdlist_no) 91362306a36Sopenharmony_ci{ 91462306a36Sopenharmony_ci struct drm_device *drm_dev = g2d->drm_dev; 91562306a36Sopenharmony_ci struct g2d_runqueue_node *runqueue_node = g2d->runqueue_node; 91662306a36Sopenharmony_ci struct drm_exynos_pending_g2d_event *e; 91762306a36Sopenharmony_ci struct timespec64 now; 91862306a36Sopenharmony_ci 91962306a36Sopenharmony_ci if (list_empty(&runqueue_node->event_list)) 92062306a36Sopenharmony_ci return; 92162306a36Sopenharmony_ci 92262306a36Sopenharmony_ci e = list_first_entry(&runqueue_node->event_list, 92362306a36Sopenharmony_ci struct drm_exynos_pending_g2d_event, base.link); 92462306a36Sopenharmony_ci 92562306a36Sopenharmony_ci ktime_get_ts64(&now); 92662306a36Sopenharmony_ci e->event.tv_sec = now.tv_sec; 92762306a36Sopenharmony_ci e->event.tv_usec = now.tv_nsec / NSEC_PER_USEC; 92862306a36Sopenharmony_ci e->event.cmdlist_no = cmdlist_no; 92962306a36Sopenharmony_ci 93062306a36Sopenharmony_ci drm_send_event(drm_dev, &e->base); 93162306a36Sopenharmony_ci} 93262306a36Sopenharmony_ci 93362306a36Sopenharmony_cistatic irqreturn_t g2d_irq_handler(int irq, void *dev_id) 93462306a36Sopenharmony_ci{ 93562306a36Sopenharmony_ci struct g2d_data *g2d = dev_id; 93662306a36Sopenharmony_ci u32 pending; 93762306a36Sopenharmony_ci 93862306a36Sopenharmony_ci pending = readl_relaxed(g2d->regs + G2D_INTC_PEND); 93962306a36Sopenharmony_ci if (pending) 94062306a36Sopenharmony_ci writel_relaxed(pending, g2d->regs + G2D_INTC_PEND); 94162306a36Sopenharmony_ci 94262306a36Sopenharmony_ci if (pending & G2D_INTP_GCMD_FIN) { 94362306a36Sopenharmony_ci u32 cmdlist_no = readl_relaxed(g2d->regs + G2D_DMA_STATUS); 94462306a36Sopenharmony_ci 94562306a36Sopenharmony_ci cmdlist_no = (cmdlist_no & G2D_DMA_LIST_DONE_COUNT) >> 94662306a36Sopenharmony_ci G2D_DMA_LIST_DONE_COUNT_OFFSET; 94762306a36Sopenharmony_ci 94862306a36Sopenharmony_ci g2d_finish_event(g2d, cmdlist_no); 94962306a36Sopenharmony_ci 95062306a36Sopenharmony_ci writel_relaxed(0, g2d->regs + G2D_DMA_HOLD_CMD); 95162306a36Sopenharmony_ci if (!(pending & G2D_INTP_ACMD_FIN)) { 95262306a36Sopenharmony_ci writel_relaxed(G2D_DMA_CONTINUE, 95362306a36Sopenharmony_ci g2d->regs + G2D_DMA_COMMAND); 95462306a36Sopenharmony_ci } 95562306a36Sopenharmony_ci } 95662306a36Sopenharmony_ci 95762306a36Sopenharmony_ci if (pending & G2D_INTP_ACMD_FIN) { 95862306a36Sopenharmony_ci clear_bit(G2D_BIT_ENGINE_BUSY, &g2d->flags); 95962306a36Sopenharmony_ci queue_work(g2d->g2d_workq, &g2d->runqueue_work); 96062306a36Sopenharmony_ci } 96162306a36Sopenharmony_ci 96262306a36Sopenharmony_ci return IRQ_HANDLED; 96362306a36Sopenharmony_ci} 96462306a36Sopenharmony_ci 96562306a36Sopenharmony_ci/** 96662306a36Sopenharmony_ci * g2d_wait_finish - wait for the G2D engine to finish the current runqueue node 96762306a36Sopenharmony_ci * @g2d: G2D state object 96862306a36Sopenharmony_ci * @file: if not zero, only wait if the current runqueue node belongs 96962306a36Sopenharmony_ci * to the DRM file 97062306a36Sopenharmony_ci * 97162306a36Sopenharmony_ci * Should the engine not become idle after a 100ms timeout, a hardware 97262306a36Sopenharmony_ci * reset is issued. 97362306a36Sopenharmony_ci */ 97462306a36Sopenharmony_cistatic void g2d_wait_finish(struct g2d_data *g2d, struct drm_file *file) 97562306a36Sopenharmony_ci{ 97662306a36Sopenharmony_ci struct device *dev = g2d->dev; 97762306a36Sopenharmony_ci 97862306a36Sopenharmony_ci struct g2d_runqueue_node *runqueue_node = NULL; 97962306a36Sopenharmony_ci unsigned int tries = 10; 98062306a36Sopenharmony_ci 98162306a36Sopenharmony_ci mutex_lock(&g2d->runqueue_mutex); 98262306a36Sopenharmony_ci 98362306a36Sopenharmony_ci /* If no node is currently processed, we have nothing to do. */ 98462306a36Sopenharmony_ci if (!g2d->runqueue_node) 98562306a36Sopenharmony_ci goto out; 98662306a36Sopenharmony_ci 98762306a36Sopenharmony_ci runqueue_node = g2d->runqueue_node; 98862306a36Sopenharmony_ci 98962306a36Sopenharmony_ci /* Check if the currently processed item belongs to us. */ 99062306a36Sopenharmony_ci if (file && runqueue_node->filp != file) 99162306a36Sopenharmony_ci goto out; 99262306a36Sopenharmony_ci 99362306a36Sopenharmony_ci mutex_unlock(&g2d->runqueue_mutex); 99462306a36Sopenharmony_ci 99562306a36Sopenharmony_ci /* Wait for the G2D engine to finish. */ 99662306a36Sopenharmony_ci while (tries-- && (g2d->runqueue_node == runqueue_node)) 99762306a36Sopenharmony_ci mdelay(10); 99862306a36Sopenharmony_ci 99962306a36Sopenharmony_ci mutex_lock(&g2d->runqueue_mutex); 100062306a36Sopenharmony_ci 100162306a36Sopenharmony_ci if (g2d->runqueue_node != runqueue_node) 100262306a36Sopenharmony_ci goto out; 100362306a36Sopenharmony_ci 100462306a36Sopenharmony_ci dev_err(dev, "wait timed out, resetting engine...\n"); 100562306a36Sopenharmony_ci g2d_hw_reset(g2d); 100662306a36Sopenharmony_ci 100762306a36Sopenharmony_ci /* 100862306a36Sopenharmony_ci * After the hardware reset of the engine we are going to loose 100962306a36Sopenharmony_ci * the IRQ which triggers the PM runtime put(). 101062306a36Sopenharmony_ci * So do this manually here. 101162306a36Sopenharmony_ci */ 101262306a36Sopenharmony_ci pm_runtime_mark_last_busy(dev); 101362306a36Sopenharmony_ci pm_runtime_put_autosuspend(dev); 101462306a36Sopenharmony_ci 101562306a36Sopenharmony_ci complete(&runqueue_node->complete); 101662306a36Sopenharmony_ci if (runqueue_node->async) 101762306a36Sopenharmony_ci g2d_free_runqueue_node(g2d, runqueue_node); 101862306a36Sopenharmony_ci 101962306a36Sopenharmony_ciout: 102062306a36Sopenharmony_ci mutex_unlock(&g2d->runqueue_mutex); 102162306a36Sopenharmony_ci} 102262306a36Sopenharmony_ci 102362306a36Sopenharmony_cistatic int g2d_check_reg_offset(struct g2d_data *g2d, 102462306a36Sopenharmony_ci struct g2d_cmdlist_node *node, 102562306a36Sopenharmony_ci int nr, bool for_addr) 102662306a36Sopenharmony_ci{ 102762306a36Sopenharmony_ci struct g2d_cmdlist *cmdlist = node->cmdlist; 102862306a36Sopenharmony_ci int reg_offset; 102962306a36Sopenharmony_ci int index; 103062306a36Sopenharmony_ci int i; 103162306a36Sopenharmony_ci 103262306a36Sopenharmony_ci for (i = 0; i < nr; i++) { 103362306a36Sopenharmony_ci struct g2d_buf_info *buf_info = &node->buf_info; 103462306a36Sopenharmony_ci struct g2d_buf_desc *buf_desc; 103562306a36Sopenharmony_ci enum g2d_reg_type reg_type; 103662306a36Sopenharmony_ci unsigned long value; 103762306a36Sopenharmony_ci 103862306a36Sopenharmony_ci index = cmdlist->last - 2 * (i + 1); 103962306a36Sopenharmony_ci 104062306a36Sopenharmony_ci reg_offset = cmdlist->data[index] & ~0xfffff000; 104162306a36Sopenharmony_ci if (reg_offset < G2D_VALID_START || reg_offset > G2D_VALID_END) 104262306a36Sopenharmony_ci goto err; 104362306a36Sopenharmony_ci if (reg_offset % 4) 104462306a36Sopenharmony_ci goto err; 104562306a36Sopenharmony_ci 104662306a36Sopenharmony_ci switch (reg_offset) { 104762306a36Sopenharmony_ci case G2D_SRC_BASE_ADDR: 104862306a36Sopenharmony_ci case G2D_SRC_PLANE2_BASE_ADDR: 104962306a36Sopenharmony_ci case G2D_DST_BASE_ADDR: 105062306a36Sopenharmony_ci case G2D_DST_PLANE2_BASE_ADDR: 105162306a36Sopenharmony_ci case G2D_PAT_BASE_ADDR: 105262306a36Sopenharmony_ci case G2D_MSK_BASE_ADDR: 105362306a36Sopenharmony_ci if (!for_addr) 105462306a36Sopenharmony_ci goto err; 105562306a36Sopenharmony_ci 105662306a36Sopenharmony_ci reg_type = g2d_get_reg_type(g2d, reg_offset); 105762306a36Sopenharmony_ci 105862306a36Sopenharmony_ci /* check userptr buffer type. */ 105962306a36Sopenharmony_ci if ((cmdlist->data[index] & ~0x7fffffff) >> 31) { 106062306a36Sopenharmony_ci buf_info->types[reg_type] = BUF_TYPE_USERPTR; 106162306a36Sopenharmony_ci cmdlist->data[index] &= ~G2D_BUF_USERPTR; 106262306a36Sopenharmony_ci } else 106362306a36Sopenharmony_ci buf_info->types[reg_type] = BUF_TYPE_GEM; 106462306a36Sopenharmony_ci break; 106562306a36Sopenharmony_ci case G2D_SRC_STRIDE: 106662306a36Sopenharmony_ci case G2D_DST_STRIDE: 106762306a36Sopenharmony_ci if (for_addr) 106862306a36Sopenharmony_ci goto err; 106962306a36Sopenharmony_ci 107062306a36Sopenharmony_ci reg_type = g2d_get_reg_type(g2d, reg_offset); 107162306a36Sopenharmony_ci 107262306a36Sopenharmony_ci buf_desc = &buf_info->descs[reg_type]; 107362306a36Sopenharmony_ci buf_desc->stride = cmdlist->data[index + 1]; 107462306a36Sopenharmony_ci break; 107562306a36Sopenharmony_ci case G2D_SRC_COLOR_MODE: 107662306a36Sopenharmony_ci case G2D_DST_COLOR_MODE: 107762306a36Sopenharmony_ci if (for_addr) 107862306a36Sopenharmony_ci goto err; 107962306a36Sopenharmony_ci 108062306a36Sopenharmony_ci reg_type = g2d_get_reg_type(g2d, reg_offset); 108162306a36Sopenharmony_ci 108262306a36Sopenharmony_ci buf_desc = &buf_info->descs[reg_type]; 108362306a36Sopenharmony_ci value = cmdlist->data[index + 1]; 108462306a36Sopenharmony_ci 108562306a36Sopenharmony_ci buf_desc->format = value & 0xf; 108662306a36Sopenharmony_ci break; 108762306a36Sopenharmony_ci case G2D_SRC_LEFT_TOP: 108862306a36Sopenharmony_ci case G2D_DST_LEFT_TOP: 108962306a36Sopenharmony_ci if (for_addr) 109062306a36Sopenharmony_ci goto err; 109162306a36Sopenharmony_ci 109262306a36Sopenharmony_ci reg_type = g2d_get_reg_type(g2d, reg_offset); 109362306a36Sopenharmony_ci 109462306a36Sopenharmony_ci buf_desc = &buf_info->descs[reg_type]; 109562306a36Sopenharmony_ci value = cmdlist->data[index + 1]; 109662306a36Sopenharmony_ci 109762306a36Sopenharmony_ci buf_desc->left_x = value & 0x1fff; 109862306a36Sopenharmony_ci buf_desc->top_y = (value & 0x1fff0000) >> 16; 109962306a36Sopenharmony_ci break; 110062306a36Sopenharmony_ci case G2D_SRC_RIGHT_BOTTOM: 110162306a36Sopenharmony_ci case G2D_DST_RIGHT_BOTTOM: 110262306a36Sopenharmony_ci if (for_addr) 110362306a36Sopenharmony_ci goto err; 110462306a36Sopenharmony_ci 110562306a36Sopenharmony_ci reg_type = g2d_get_reg_type(g2d, reg_offset); 110662306a36Sopenharmony_ci 110762306a36Sopenharmony_ci buf_desc = &buf_info->descs[reg_type]; 110862306a36Sopenharmony_ci value = cmdlist->data[index + 1]; 110962306a36Sopenharmony_ci 111062306a36Sopenharmony_ci buf_desc->right_x = value & 0x1fff; 111162306a36Sopenharmony_ci buf_desc->bottom_y = (value & 0x1fff0000) >> 16; 111262306a36Sopenharmony_ci break; 111362306a36Sopenharmony_ci default: 111462306a36Sopenharmony_ci if (for_addr) 111562306a36Sopenharmony_ci goto err; 111662306a36Sopenharmony_ci break; 111762306a36Sopenharmony_ci } 111862306a36Sopenharmony_ci } 111962306a36Sopenharmony_ci 112062306a36Sopenharmony_ci return 0; 112162306a36Sopenharmony_ci 112262306a36Sopenharmony_cierr: 112362306a36Sopenharmony_ci dev_err(g2d->dev, "Bad register offset: 0x%lx\n", cmdlist->data[index]); 112462306a36Sopenharmony_ci return -EINVAL; 112562306a36Sopenharmony_ci} 112662306a36Sopenharmony_ci 112762306a36Sopenharmony_ci/* ioctl functions */ 112862306a36Sopenharmony_ciint exynos_g2d_get_ver_ioctl(struct drm_device *drm_dev, void *data, 112962306a36Sopenharmony_ci struct drm_file *file) 113062306a36Sopenharmony_ci{ 113162306a36Sopenharmony_ci struct drm_exynos_g2d_get_ver *ver = data; 113262306a36Sopenharmony_ci 113362306a36Sopenharmony_ci ver->major = G2D_HW_MAJOR_VER; 113462306a36Sopenharmony_ci ver->minor = G2D_HW_MINOR_VER; 113562306a36Sopenharmony_ci 113662306a36Sopenharmony_ci return 0; 113762306a36Sopenharmony_ci} 113862306a36Sopenharmony_ci 113962306a36Sopenharmony_ciint exynos_g2d_set_cmdlist_ioctl(struct drm_device *drm_dev, void *data, 114062306a36Sopenharmony_ci struct drm_file *file) 114162306a36Sopenharmony_ci{ 114262306a36Sopenharmony_ci struct drm_exynos_file_private *file_priv = file->driver_priv; 114362306a36Sopenharmony_ci struct exynos_drm_private *priv = drm_dev->dev_private; 114462306a36Sopenharmony_ci struct g2d_data *g2d = dev_get_drvdata(priv->g2d_dev); 114562306a36Sopenharmony_ci struct drm_exynos_g2d_set_cmdlist *req = data; 114662306a36Sopenharmony_ci struct drm_exynos_g2d_cmd *cmd; 114762306a36Sopenharmony_ci struct drm_exynos_pending_g2d_event *e; 114862306a36Sopenharmony_ci struct g2d_cmdlist_node *node; 114962306a36Sopenharmony_ci struct g2d_cmdlist *cmdlist; 115062306a36Sopenharmony_ci int size; 115162306a36Sopenharmony_ci int ret; 115262306a36Sopenharmony_ci 115362306a36Sopenharmony_ci node = g2d_get_cmdlist(g2d); 115462306a36Sopenharmony_ci if (!node) 115562306a36Sopenharmony_ci return -ENOMEM; 115662306a36Sopenharmony_ci 115762306a36Sopenharmony_ci /* 115862306a36Sopenharmony_ci * To avoid an integer overflow for the later size computations, we 115962306a36Sopenharmony_ci * enforce a maximum number of submitted commands here. This limit is 116062306a36Sopenharmony_ci * sufficient for all conceivable usage cases of the G2D. 116162306a36Sopenharmony_ci */ 116262306a36Sopenharmony_ci if (req->cmd_nr > G2D_CMDLIST_DATA_NUM || 116362306a36Sopenharmony_ci req->cmd_buf_nr > G2D_CMDLIST_DATA_NUM) { 116462306a36Sopenharmony_ci dev_err(g2d->dev, "number of submitted G2D commands exceeds limit\n"); 116562306a36Sopenharmony_ci return -EINVAL; 116662306a36Sopenharmony_ci } 116762306a36Sopenharmony_ci 116862306a36Sopenharmony_ci node->event = NULL; 116962306a36Sopenharmony_ci 117062306a36Sopenharmony_ci if (req->event_type != G2D_EVENT_NOT) { 117162306a36Sopenharmony_ci e = kzalloc(sizeof(*node->event), GFP_KERNEL); 117262306a36Sopenharmony_ci if (!e) { 117362306a36Sopenharmony_ci ret = -ENOMEM; 117462306a36Sopenharmony_ci goto err; 117562306a36Sopenharmony_ci } 117662306a36Sopenharmony_ci 117762306a36Sopenharmony_ci e->event.base.type = DRM_EXYNOS_G2D_EVENT; 117862306a36Sopenharmony_ci e->event.base.length = sizeof(e->event); 117962306a36Sopenharmony_ci e->event.user_data = req->user_data; 118062306a36Sopenharmony_ci 118162306a36Sopenharmony_ci ret = drm_event_reserve_init(drm_dev, file, &e->base, &e->event.base); 118262306a36Sopenharmony_ci if (ret) { 118362306a36Sopenharmony_ci kfree(e); 118462306a36Sopenharmony_ci goto err; 118562306a36Sopenharmony_ci } 118662306a36Sopenharmony_ci 118762306a36Sopenharmony_ci node->event = e; 118862306a36Sopenharmony_ci } 118962306a36Sopenharmony_ci 119062306a36Sopenharmony_ci cmdlist = node->cmdlist; 119162306a36Sopenharmony_ci 119262306a36Sopenharmony_ci cmdlist->last = 0; 119362306a36Sopenharmony_ci 119462306a36Sopenharmony_ci /* 119562306a36Sopenharmony_ci * If don't clear SFR registers, the cmdlist is affected by register 119662306a36Sopenharmony_ci * values of previous cmdlist. G2D hw executes SFR clear command and 119762306a36Sopenharmony_ci * a next command at the same time then the next command is ignored and 119862306a36Sopenharmony_ci * is executed rightly from next next command, so needs a dummy command 119962306a36Sopenharmony_ci * to next command of SFR clear command. 120062306a36Sopenharmony_ci */ 120162306a36Sopenharmony_ci cmdlist->data[cmdlist->last++] = G2D_SOFT_RESET; 120262306a36Sopenharmony_ci cmdlist->data[cmdlist->last++] = G2D_SFRCLEAR; 120362306a36Sopenharmony_ci cmdlist->data[cmdlist->last++] = G2D_SRC_BASE_ADDR; 120462306a36Sopenharmony_ci cmdlist->data[cmdlist->last++] = 0; 120562306a36Sopenharmony_ci 120662306a36Sopenharmony_ci /* 120762306a36Sopenharmony_ci * 'LIST_HOLD' command should be set to the DMA_HOLD_CMD_REG 120862306a36Sopenharmony_ci * and GCF bit should be set to INTEN register if user wants 120962306a36Sopenharmony_ci * G2D interrupt event once current command list execution is 121062306a36Sopenharmony_ci * finished. 121162306a36Sopenharmony_ci * Otherwise only ACF bit should be set to INTEN register so 121262306a36Sopenharmony_ci * that one interrupt is occurred after all command lists 121362306a36Sopenharmony_ci * have been completed. 121462306a36Sopenharmony_ci */ 121562306a36Sopenharmony_ci if (node->event) { 121662306a36Sopenharmony_ci cmdlist->data[cmdlist->last++] = G2D_INTEN; 121762306a36Sopenharmony_ci cmdlist->data[cmdlist->last++] = G2D_INTEN_ACF | G2D_INTEN_GCF; 121862306a36Sopenharmony_ci cmdlist->data[cmdlist->last++] = G2D_DMA_HOLD_CMD; 121962306a36Sopenharmony_ci cmdlist->data[cmdlist->last++] = G2D_LIST_HOLD; 122062306a36Sopenharmony_ci } else { 122162306a36Sopenharmony_ci cmdlist->data[cmdlist->last++] = G2D_INTEN; 122262306a36Sopenharmony_ci cmdlist->data[cmdlist->last++] = G2D_INTEN_ACF; 122362306a36Sopenharmony_ci } 122462306a36Sopenharmony_ci 122562306a36Sopenharmony_ci /* 122662306a36Sopenharmony_ci * Check the size of cmdlist. The 2 that is added last comes from 122762306a36Sopenharmony_ci * the implicit G2D_BITBLT_START that is appended once we have 122862306a36Sopenharmony_ci * checked all the submitted commands. 122962306a36Sopenharmony_ci */ 123062306a36Sopenharmony_ci size = cmdlist->last + req->cmd_nr * 2 + req->cmd_buf_nr * 2 + 2; 123162306a36Sopenharmony_ci if (size > G2D_CMDLIST_DATA_NUM) { 123262306a36Sopenharmony_ci dev_err(g2d->dev, "cmdlist size is too big\n"); 123362306a36Sopenharmony_ci ret = -EINVAL; 123462306a36Sopenharmony_ci goto err_free_event; 123562306a36Sopenharmony_ci } 123662306a36Sopenharmony_ci 123762306a36Sopenharmony_ci cmd = (struct drm_exynos_g2d_cmd *)(unsigned long)req->cmd; 123862306a36Sopenharmony_ci 123962306a36Sopenharmony_ci if (copy_from_user(cmdlist->data + cmdlist->last, 124062306a36Sopenharmony_ci (void __user *)cmd, 124162306a36Sopenharmony_ci sizeof(*cmd) * req->cmd_nr)) { 124262306a36Sopenharmony_ci ret = -EFAULT; 124362306a36Sopenharmony_ci goto err_free_event; 124462306a36Sopenharmony_ci } 124562306a36Sopenharmony_ci cmdlist->last += req->cmd_nr * 2; 124662306a36Sopenharmony_ci 124762306a36Sopenharmony_ci ret = g2d_check_reg_offset(g2d, node, req->cmd_nr, false); 124862306a36Sopenharmony_ci if (ret < 0) 124962306a36Sopenharmony_ci goto err_free_event; 125062306a36Sopenharmony_ci 125162306a36Sopenharmony_ci node->buf_info.map_nr = req->cmd_buf_nr; 125262306a36Sopenharmony_ci if (req->cmd_buf_nr) { 125362306a36Sopenharmony_ci struct drm_exynos_g2d_cmd *cmd_buf; 125462306a36Sopenharmony_ci 125562306a36Sopenharmony_ci cmd_buf = (struct drm_exynos_g2d_cmd *) 125662306a36Sopenharmony_ci (unsigned long)req->cmd_buf; 125762306a36Sopenharmony_ci 125862306a36Sopenharmony_ci if (copy_from_user(cmdlist->data + cmdlist->last, 125962306a36Sopenharmony_ci (void __user *)cmd_buf, 126062306a36Sopenharmony_ci sizeof(*cmd_buf) * req->cmd_buf_nr)) { 126162306a36Sopenharmony_ci ret = -EFAULT; 126262306a36Sopenharmony_ci goto err_free_event; 126362306a36Sopenharmony_ci } 126462306a36Sopenharmony_ci cmdlist->last += req->cmd_buf_nr * 2; 126562306a36Sopenharmony_ci 126662306a36Sopenharmony_ci ret = g2d_check_reg_offset(g2d, node, req->cmd_buf_nr, true); 126762306a36Sopenharmony_ci if (ret < 0) 126862306a36Sopenharmony_ci goto err_free_event; 126962306a36Sopenharmony_ci 127062306a36Sopenharmony_ci ret = g2d_map_cmdlist_gem(g2d, node, drm_dev, file); 127162306a36Sopenharmony_ci if (ret < 0) 127262306a36Sopenharmony_ci goto err_unmap; 127362306a36Sopenharmony_ci } 127462306a36Sopenharmony_ci 127562306a36Sopenharmony_ci cmdlist->data[cmdlist->last++] = G2D_BITBLT_START; 127662306a36Sopenharmony_ci cmdlist->data[cmdlist->last++] = G2D_START_BITBLT; 127762306a36Sopenharmony_ci 127862306a36Sopenharmony_ci /* head */ 127962306a36Sopenharmony_ci cmdlist->head = cmdlist->last / 2; 128062306a36Sopenharmony_ci 128162306a36Sopenharmony_ci /* tail */ 128262306a36Sopenharmony_ci cmdlist->data[cmdlist->last] = 0; 128362306a36Sopenharmony_ci 128462306a36Sopenharmony_ci g2d_add_cmdlist_to_inuse(file_priv, node); 128562306a36Sopenharmony_ci 128662306a36Sopenharmony_ci return 0; 128762306a36Sopenharmony_ci 128862306a36Sopenharmony_cierr_unmap: 128962306a36Sopenharmony_ci g2d_unmap_cmdlist_gem(g2d, node, file); 129062306a36Sopenharmony_cierr_free_event: 129162306a36Sopenharmony_ci if (node->event) 129262306a36Sopenharmony_ci drm_event_cancel_free(drm_dev, &node->event->base); 129362306a36Sopenharmony_cierr: 129462306a36Sopenharmony_ci g2d_put_cmdlist(g2d, node); 129562306a36Sopenharmony_ci return ret; 129662306a36Sopenharmony_ci} 129762306a36Sopenharmony_ci 129862306a36Sopenharmony_ciint exynos_g2d_exec_ioctl(struct drm_device *drm_dev, void *data, 129962306a36Sopenharmony_ci struct drm_file *file) 130062306a36Sopenharmony_ci{ 130162306a36Sopenharmony_ci struct drm_exynos_file_private *file_priv = file->driver_priv; 130262306a36Sopenharmony_ci struct exynos_drm_private *priv = drm_dev->dev_private; 130362306a36Sopenharmony_ci struct g2d_data *g2d = dev_get_drvdata(priv->g2d_dev); 130462306a36Sopenharmony_ci struct drm_exynos_g2d_exec *req = data; 130562306a36Sopenharmony_ci struct g2d_runqueue_node *runqueue_node; 130662306a36Sopenharmony_ci struct list_head *run_cmdlist; 130762306a36Sopenharmony_ci struct list_head *event_list; 130862306a36Sopenharmony_ci 130962306a36Sopenharmony_ci runqueue_node = kmem_cache_alloc(g2d->runqueue_slab, GFP_KERNEL); 131062306a36Sopenharmony_ci if (!runqueue_node) 131162306a36Sopenharmony_ci return -ENOMEM; 131262306a36Sopenharmony_ci 131362306a36Sopenharmony_ci run_cmdlist = &runqueue_node->run_cmdlist; 131462306a36Sopenharmony_ci event_list = &runqueue_node->event_list; 131562306a36Sopenharmony_ci INIT_LIST_HEAD(run_cmdlist); 131662306a36Sopenharmony_ci INIT_LIST_HEAD(event_list); 131762306a36Sopenharmony_ci init_completion(&runqueue_node->complete); 131862306a36Sopenharmony_ci runqueue_node->async = req->async; 131962306a36Sopenharmony_ci 132062306a36Sopenharmony_ci list_splice_init(&file_priv->inuse_cmdlist, run_cmdlist); 132162306a36Sopenharmony_ci list_splice_init(&file_priv->event_list, event_list); 132262306a36Sopenharmony_ci 132362306a36Sopenharmony_ci if (list_empty(run_cmdlist)) { 132462306a36Sopenharmony_ci dev_err(g2d->dev, "there is no inuse cmdlist\n"); 132562306a36Sopenharmony_ci kmem_cache_free(g2d->runqueue_slab, runqueue_node); 132662306a36Sopenharmony_ci return -EPERM; 132762306a36Sopenharmony_ci } 132862306a36Sopenharmony_ci 132962306a36Sopenharmony_ci mutex_lock(&g2d->runqueue_mutex); 133062306a36Sopenharmony_ci runqueue_node->pid = current->pid; 133162306a36Sopenharmony_ci runqueue_node->filp = file; 133262306a36Sopenharmony_ci list_add_tail(&runqueue_node->list, &g2d->runqueue); 133362306a36Sopenharmony_ci mutex_unlock(&g2d->runqueue_mutex); 133462306a36Sopenharmony_ci 133562306a36Sopenharmony_ci /* Let the runqueue know that there is work to do. */ 133662306a36Sopenharmony_ci queue_work(g2d->g2d_workq, &g2d->runqueue_work); 133762306a36Sopenharmony_ci 133862306a36Sopenharmony_ci if (req->async) 133962306a36Sopenharmony_ci goto out; 134062306a36Sopenharmony_ci 134162306a36Sopenharmony_ci wait_for_completion(&runqueue_node->complete); 134262306a36Sopenharmony_ci g2d_free_runqueue_node(g2d, runqueue_node); 134362306a36Sopenharmony_ci 134462306a36Sopenharmony_ciout: 134562306a36Sopenharmony_ci return 0; 134662306a36Sopenharmony_ci} 134762306a36Sopenharmony_ci 134862306a36Sopenharmony_ciint g2d_open(struct drm_device *drm_dev, struct drm_file *file) 134962306a36Sopenharmony_ci{ 135062306a36Sopenharmony_ci struct drm_exynos_file_private *file_priv = file->driver_priv; 135162306a36Sopenharmony_ci 135262306a36Sopenharmony_ci INIT_LIST_HEAD(&file_priv->inuse_cmdlist); 135362306a36Sopenharmony_ci INIT_LIST_HEAD(&file_priv->event_list); 135462306a36Sopenharmony_ci INIT_LIST_HEAD(&file_priv->userptr_list); 135562306a36Sopenharmony_ci 135662306a36Sopenharmony_ci return 0; 135762306a36Sopenharmony_ci} 135862306a36Sopenharmony_ci 135962306a36Sopenharmony_civoid g2d_close(struct drm_device *drm_dev, struct drm_file *file) 136062306a36Sopenharmony_ci{ 136162306a36Sopenharmony_ci struct drm_exynos_file_private *file_priv = file->driver_priv; 136262306a36Sopenharmony_ci struct exynos_drm_private *priv = drm_dev->dev_private; 136362306a36Sopenharmony_ci struct g2d_data *g2d; 136462306a36Sopenharmony_ci struct g2d_cmdlist_node *node, *n; 136562306a36Sopenharmony_ci 136662306a36Sopenharmony_ci if (!priv->g2d_dev) 136762306a36Sopenharmony_ci return; 136862306a36Sopenharmony_ci 136962306a36Sopenharmony_ci g2d = dev_get_drvdata(priv->g2d_dev); 137062306a36Sopenharmony_ci 137162306a36Sopenharmony_ci /* Remove the runqueue nodes that belong to us. */ 137262306a36Sopenharmony_ci mutex_lock(&g2d->runqueue_mutex); 137362306a36Sopenharmony_ci g2d_remove_runqueue_nodes(g2d, file); 137462306a36Sopenharmony_ci mutex_unlock(&g2d->runqueue_mutex); 137562306a36Sopenharmony_ci 137662306a36Sopenharmony_ci /* 137762306a36Sopenharmony_ci * Wait for the runqueue worker to finish its current node. 137862306a36Sopenharmony_ci * After this the engine should no longer be accessing any 137962306a36Sopenharmony_ci * memory belonging to us. 138062306a36Sopenharmony_ci */ 138162306a36Sopenharmony_ci g2d_wait_finish(g2d, file); 138262306a36Sopenharmony_ci 138362306a36Sopenharmony_ci /* 138462306a36Sopenharmony_ci * Even after the engine is idle, there might still be stale cmdlists 138562306a36Sopenharmony_ci * (i.e. cmdlisst which we submitted but never executed) around, with 138662306a36Sopenharmony_ci * their corresponding GEM/userptr buffers. 138762306a36Sopenharmony_ci * Properly unmap these buffers here. 138862306a36Sopenharmony_ci */ 138962306a36Sopenharmony_ci mutex_lock(&g2d->cmdlist_mutex); 139062306a36Sopenharmony_ci list_for_each_entry_safe(node, n, &file_priv->inuse_cmdlist, list) { 139162306a36Sopenharmony_ci g2d_unmap_cmdlist_gem(g2d, node, file); 139262306a36Sopenharmony_ci list_move_tail(&node->list, &g2d->free_cmdlist); 139362306a36Sopenharmony_ci } 139462306a36Sopenharmony_ci mutex_unlock(&g2d->cmdlist_mutex); 139562306a36Sopenharmony_ci 139662306a36Sopenharmony_ci /* release all g2d_userptr in pool. */ 139762306a36Sopenharmony_ci g2d_userptr_free_all(g2d, file); 139862306a36Sopenharmony_ci} 139962306a36Sopenharmony_ci 140062306a36Sopenharmony_cistatic int g2d_bind(struct device *dev, struct device *master, void *data) 140162306a36Sopenharmony_ci{ 140262306a36Sopenharmony_ci struct g2d_data *g2d = dev_get_drvdata(dev); 140362306a36Sopenharmony_ci struct drm_device *drm_dev = data; 140462306a36Sopenharmony_ci struct exynos_drm_private *priv = drm_dev->dev_private; 140562306a36Sopenharmony_ci int ret; 140662306a36Sopenharmony_ci 140762306a36Sopenharmony_ci g2d->drm_dev = drm_dev; 140862306a36Sopenharmony_ci 140962306a36Sopenharmony_ci /* allocate dma-aware cmdlist buffer. */ 141062306a36Sopenharmony_ci ret = g2d_init_cmdlist(g2d); 141162306a36Sopenharmony_ci if (ret < 0) { 141262306a36Sopenharmony_ci dev_err(dev, "cmdlist init failed\n"); 141362306a36Sopenharmony_ci return ret; 141462306a36Sopenharmony_ci } 141562306a36Sopenharmony_ci 141662306a36Sopenharmony_ci ret = exynos_drm_register_dma(drm_dev, dev, &g2d->dma_priv); 141762306a36Sopenharmony_ci if (ret < 0) { 141862306a36Sopenharmony_ci dev_err(dev, "failed to enable iommu.\n"); 141962306a36Sopenharmony_ci g2d_fini_cmdlist(g2d); 142062306a36Sopenharmony_ci return ret; 142162306a36Sopenharmony_ci } 142262306a36Sopenharmony_ci priv->g2d_dev = dev; 142362306a36Sopenharmony_ci 142462306a36Sopenharmony_ci dev_info(dev, "The Exynos G2D (ver %d.%d) successfully registered.\n", 142562306a36Sopenharmony_ci G2D_HW_MAJOR_VER, G2D_HW_MINOR_VER); 142662306a36Sopenharmony_ci return 0; 142762306a36Sopenharmony_ci} 142862306a36Sopenharmony_ci 142962306a36Sopenharmony_cistatic void g2d_unbind(struct device *dev, struct device *master, void *data) 143062306a36Sopenharmony_ci{ 143162306a36Sopenharmony_ci struct g2d_data *g2d = dev_get_drvdata(dev); 143262306a36Sopenharmony_ci struct drm_device *drm_dev = data; 143362306a36Sopenharmony_ci struct exynos_drm_private *priv = drm_dev->dev_private; 143462306a36Sopenharmony_ci 143562306a36Sopenharmony_ci /* Suspend operation and wait for engine idle. */ 143662306a36Sopenharmony_ci set_bit(G2D_BIT_SUSPEND_RUNQUEUE, &g2d->flags); 143762306a36Sopenharmony_ci g2d_wait_finish(g2d, NULL); 143862306a36Sopenharmony_ci priv->g2d_dev = NULL; 143962306a36Sopenharmony_ci 144062306a36Sopenharmony_ci cancel_work_sync(&g2d->runqueue_work); 144162306a36Sopenharmony_ci exynos_drm_unregister_dma(g2d->drm_dev, dev, &g2d->dma_priv); 144262306a36Sopenharmony_ci} 144362306a36Sopenharmony_ci 144462306a36Sopenharmony_cistatic const struct component_ops g2d_component_ops = { 144562306a36Sopenharmony_ci .bind = g2d_bind, 144662306a36Sopenharmony_ci .unbind = g2d_unbind, 144762306a36Sopenharmony_ci}; 144862306a36Sopenharmony_ci 144962306a36Sopenharmony_cistatic int g2d_probe(struct platform_device *pdev) 145062306a36Sopenharmony_ci{ 145162306a36Sopenharmony_ci struct device *dev = &pdev->dev; 145262306a36Sopenharmony_ci struct g2d_data *g2d; 145362306a36Sopenharmony_ci int ret; 145462306a36Sopenharmony_ci 145562306a36Sopenharmony_ci g2d = devm_kzalloc(dev, sizeof(*g2d), GFP_KERNEL); 145662306a36Sopenharmony_ci if (!g2d) 145762306a36Sopenharmony_ci return -ENOMEM; 145862306a36Sopenharmony_ci 145962306a36Sopenharmony_ci g2d->runqueue_slab = kmem_cache_create("g2d_runqueue_slab", 146062306a36Sopenharmony_ci sizeof(struct g2d_runqueue_node), 0, 0, NULL); 146162306a36Sopenharmony_ci if (!g2d->runqueue_slab) 146262306a36Sopenharmony_ci return -ENOMEM; 146362306a36Sopenharmony_ci 146462306a36Sopenharmony_ci g2d->dev = dev; 146562306a36Sopenharmony_ci 146662306a36Sopenharmony_ci g2d->g2d_workq = create_singlethread_workqueue("g2d"); 146762306a36Sopenharmony_ci if (!g2d->g2d_workq) { 146862306a36Sopenharmony_ci dev_err(dev, "failed to create workqueue\n"); 146962306a36Sopenharmony_ci ret = -EINVAL; 147062306a36Sopenharmony_ci goto err_destroy_slab; 147162306a36Sopenharmony_ci } 147262306a36Sopenharmony_ci 147362306a36Sopenharmony_ci INIT_WORK(&g2d->runqueue_work, g2d_runqueue_worker); 147462306a36Sopenharmony_ci INIT_LIST_HEAD(&g2d->free_cmdlist); 147562306a36Sopenharmony_ci INIT_LIST_HEAD(&g2d->runqueue); 147662306a36Sopenharmony_ci 147762306a36Sopenharmony_ci mutex_init(&g2d->cmdlist_mutex); 147862306a36Sopenharmony_ci mutex_init(&g2d->runqueue_mutex); 147962306a36Sopenharmony_ci 148062306a36Sopenharmony_ci g2d->gate_clk = devm_clk_get(dev, "fimg2d"); 148162306a36Sopenharmony_ci if (IS_ERR(g2d->gate_clk)) { 148262306a36Sopenharmony_ci dev_err(dev, "failed to get gate clock\n"); 148362306a36Sopenharmony_ci ret = PTR_ERR(g2d->gate_clk); 148462306a36Sopenharmony_ci goto err_destroy_workqueue; 148562306a36Sopenharmony_ci } 148662306a36Sopenharmony_ci 148762306a36Sopenharmony_ci pm_runtime_use_autosuspend(dev); 148862306a36Sopenharmony_ci pm_runtime_set_autosuspend_delay(dev, 2000); 148962306a36Sopenharmony_ci pm_runtime_enable(dev); 149062306a36Sopenharmony_ci clear_bit(G2D_BIT_SUSPEND_RUNQUEUE, &g2d->flags); 149162306a36Sopenharmony_ci clear_bit(G2D_BIT_ENGINE_BUSY, &g2d->flags); 149262306a36Sopenharmony_ci 149362306a36Sopenharmony_ci g2d->regs = devm_platform_ioremap_resource(pdev, 0); 149462306a36Sopenharmony_ci if (IS_ERR(g2d->regs)) { 149562306a36Sopenharmony_ci ret = PTR_ERR(g2d->regs); 149662306a36Sopenharmony_ci goto err_put_clk; 149762306a36Sopenharmony_ci } 149862306a36Sopenharmony_ci 149962306a36Sopenharmony_ci g2d->irq = platform_get_irq(pdev, 0); 150062306a36Sopenharmony_ci if (g2d->irq < 0) { 150162306a36Sopenharmony_ci ret = g2d->irq; 150262306a36Sopenharmony_ci goto err_put_clk; 150362306a36Sopenharmony_ci } 150462306a36Sopenharmony_ci 150562306a36Sopenharmony_ci ret = devm_request_irq(dev, g2d->irq, g2d_irq_handler, 0, 150662306a36Sopenharmony_ci "drm_g2d", g2d); 150762306a36Sopenharmony_ci if (ret < 0) { 150862306a36Sopenharmony_ci dev_err(dev, "irq request failed\n"); 150962306a36Sopenharmony_ci goto err_put_clk; 151062306a36Sopenharmony_ci } 151162306a36Sopenharmony_ci 151262306a36Sopenharmony_ci g2d->max_pool = MAX_POOL; 151362306a36Sopenharmony_ci 151462306a36Sopenharmony_ci platform_set_drvdata(pdev, g2d); 151562306a36Sopenharmony_ci 151662306a36Sopenharmony_ci ret = component_add(dev, &g2d_component_ops); 151762306a36Sopenharmony_ci if (ret < 0) { 151862306a36Sopenharmony_ci dev_err(dev, "failed to register drm g2d device\n"); 151962306a36Sopenharmony_ci goto err_put_clk; 152062306a36Sopenharmony_ci } 152162306a36Sopenharmony_ci 152262306a36Sopenharmony_ci return 0; 152362306a36Sopenharmony_ci 152462306a36Sopenharmony_cierr_put_clk: 152562306a36Sopenharmony_ci pm_runtime_disable(dev); 152662306a36Sopenharmony_cierr_destroy_workqueue: 152762306a36Sopenharmony_ci destroy_workqueue(g2d->g2d_workq); 152862306a36Sopenharmony_cierr_destroy_slab: 152962306a36Sopenharmony_ci kmem_cache_destroy(g2d->runqueue_slab); 153062306a36Sopenharmony_ci return ret; 153162306a36Sopenharmony_ci} 153262306a36Sopenharmony_ci 153362306a36Sopenharmony_cistatic int g2d_remove(struct platform_device *pdev) 153462306a36Sopenharmony_ci{ 153562306a36Sopenharmony_ci struct g2d_data *g2d = platform_get_drvdata(pdev); 153662306a36Sopenharmony_ci 153762306a36Sopenharmony_ci component_del(&pdev->dev, &g2d_component_ops); 153862306a36Sopenharmony_ci 153962306a36Sopenharmony_ci /* There should be no locking needed here. */ 154062306a36Sopenharmony_ci g2d_remove_runqueue_nodes(g2d, NULL); 154162306a36Sopenharmony_ci 154262306a36Sopenharmony_ci pm_runtime_dont_use_autosuspend(&pdev->dev); 154362306a36Sopenharmony_ci pm_runtime_disable(&pdev->dev); 154462306a36Sopenharmony_ci 154562306a36Sopenharmony_ci g2d_fini_cmdlist(g2d); 154662306a36Sopenharmony_ci destroy_workqueue(g2d->g2d_workq); 154762306a36Sopenharmony_ci kmem_cache_destroy(g2d->runqueue_slab); 154862306a36Sopenharmony_ci 154962306a36Sopenharmony_ci return 0; 155062306a36Sopenharmony_ci} 155162306a36Sopenharmony_ci 155262306a36Sopenharmony_cistatic int g2d_suspend(struct device *dev) 155362306a36Sopenharmony_ci{ 155462306a36Sopenharmony_ci struct g2d_data *g2d = dev_get_drvdata(dev); 155562306a36Sopenharmony_ci 155662306a36Sopenharmony_ci /* 155762306a36Sopenharmony_ci * Suspend the runqueue worker operation and wait until the G2D 155862306a36Sopenharmony_ci * engine is idle. 155962306a36Sopenharmony_ci */ 156062306a36Sopenharmony_ci set_bit(G2D_BIT_SUSPEND_RUNQUEUE, &g2d->flags); 156162306a36Sopenharmony_ci g2d_wait_finish(g2d, NULL); 156262306a36Sopenharmony_ci flush_work(&g2d->runqueue_work); 156362306a36Sopenharmony_ci 156462306a36Sopenharmony_ci return 0; 156562306a36Sopenharmony_ci} 156662306a36Sopenharmony_ci 156762306a36Sopenharmony_cistatic int g2d_resume(struct device *dev) 156862306a36Sopenharmony_ci{ 156962306a36Sopenharmony_ci struct g2d_data *g2d = dev_get_drvdata(dev); 157062306a36Sopenharmony_ci 157162306a36Sopenharmony_ci clear_bit(G2D_BIT_SUSPEND_RUNQUEUE, &g2d->flags); 157262306a36Sopenharmony_ci queue_work(g2d->g2d_workq, &g2d->runqueue_work); 157362306a36Sopenharmony_ci 157462306a36Sopenharmony_ci return 0; 157562306a36Sopenharmony_ci} 157662306a36Sopenharmony_ci 157762306a36Sopenharmony_cistatic int g2d_runtime_suspend(struct device *dev) 157862306a36Sopenharmony_ci{ 157962306a36Sopenharmony_ci struct g2d_data *g2d = dev_get_drvdata(dev); 158062306a36Sopenharmony_ci 158162306a36Sopenharmony_ci clk_disable_unprepare(g2d->gate_clk); 158262306a36Sopenharmony_ci 158362306a36Sopenharmony_ci return 0; 158462306a36Sopenharmony_ci} 158562306a36Sopenharmony_ci 158662306a36Sopenharmony_cistatic int g2d_runtime_resume(struct device *dev) 158762306a36Sopenharmony_ci{ 158862306a36Sopenharmony_ci struct g2d_data *g2d = dev_get_drvdata(dev); 158962306a36Sopenharmony_ci int ret; 159062306a36Sopenharmony_ci 159162306a36Sopenharmony_ci ret = clk_prepare_enable(g2d->gate_clk); 159262306a36Sopenharmony_ci if (ret < 0) 159362306a36Sopenharmony_ci dev_warn(dev, "failed to enable clock.\n"); 159462306a36Sopenharmony_ci 159562306a36Sopenharmony_ci return ret; 159662306a36Sopenharmony_ci} 159762306a36Sopenharmony_ci 159862306a36Sopenharmony_cistatic const struct dev_pm_ops g2d_pm_ops = { 159962306a36Sopenharmony_ci SYSTEM_SLEEP_PM_OPS(g2d_suspend, g2d_resume) 160062306a36Sopenharmony_ci RUNTIME_PM_OPS(g2d_runtime_suspend, g2d_runtime_resume, NULL) 160162306a36Sopenharmony_ci}; 160262306a36Sopenharmony_ci 160362306a36Sopenharmony_cistatic const struct of_device_id exynos_g2d_match[] = { 160462306a36Sopenharmony_ci { .compatible = "samsung,exynos5250-g2d" }, 160562306a36Sopenharmony_ci { .compatible = "samsung,exynos4212-g2d" }, 160662306a36Sopenharmony_ci {}, 160762306a36Sopenharmony_ci}; 160862306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, exynos_g2d_match); 160962306a36Sopenharmony_ci 161062306a36Sopenharmony_cistruct platform_driver g2d_driver = { 161162306a36Sopenharmony_ci .probe = g2d_probe, 161262306a36Sopenharmony_ci .remove = g2d_remove, 161362306a36Sopenharmony_ci .driver = { 161462306a36Sopenharmony_ci .name = "exynos-drm-g2d", 161562306a36Sopenharmony_ci .owner = THIS_MODULE, 161662306a36Sopenharmony_ci .pm = pm_ptr(&g2d_pm_ops), 161762306a36Sopenharmony_ci .of_match_table = exynos_g2d_match, 161862306a36Sopenharmony_ci }, 161962306a36Sopenharmony_ci}; 1620