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