162306a36Sopenharmony_ci/*
262306a36Sopenharmony_ci * Copyright (C) 2017 Samsung Electronics Co.Ltd
362306a36Sopenharmony_ci * Authors:
462306a36Sopenharmony_ci *	Marek Szyprowski <m.szyprowski@samsung.com>
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * Exynos DRM Image Post Processing (IPP) related functions
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * Permission is hereby granted, free of charge, to any person obtaining a
962306a36Sopenharmony_ci * copy of this software and associated documentation files (the "Software"),
1062306a36Sopenharmony_ci * to deal in the Software without restriction, including without limitation
1162306a36Sopenharmony_ci * the rights to use, copy, modify, merge, publish, distribute, sublicense,
1262306a36Sopenharmony_ci * and/or sell copies of the Software, and to permit persons to whom the
1362306a36Sopenharmony_ci * Software is furnished to do so, subject to the following conditions:
1462306a36Sopenharmony_ci *
1562306a36Sopenharmony_ci * The above copyright notice and this permission notice shall be included in
1662306a36Sopenharmony_ci * all copies or substantial portions of the Software.
1762306a36Sopenharmony_ci */
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#include <linux/uaccess.h>
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#include <drm/drm_blend.h>
2262306a36Sopenharmony_ci#include <drm/drm_file.h>
2362306a36Sopenharmony_ci#include <drm/drm_fourcc.h>
2462306a36Sopenharmony_ci#include <drm/drm_mode.h>
2562306a36Sopenharmony_ci#include <drm/exynos_drm.h>
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci#include "exynos_drm_drv.h"
2862306a36Sopenharmony_ci#include "exynos_drm_gem.h"
2962306a36Sopenharmony_ci#include "exynos_drm_ipp.h"
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_cistatic int num_ipp;
3262306a36Sopenharmony_cistatic LIST_HEAD(ipp_list);
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci/**
3562306a36Sopenharmony_ci * exynos_drm_ipp_register - Register a new picture processor hardware module
3662306a36Sopenharmony_ci * @dev: DRM device
3762306a36Sopenharmony_ci * @ipp: ipp module to init
3862306a36Sopenharmony_ci * @funcs: callbacks for the new ipp object
3962306a36Sopenharmony_ci * @caps: bitmask of ipp capabilities (%DRM_EXYNOS_IPP_CAP_*)
4062306a36Sopenharmony_ci * @formats: array of supported formats
4162306a36Sopenharmony_ci * @num_formats: size of the supported formats array
4262306a36Sopenharmony_ci * @name: name (for debugging purposes)
4362306a36Sopenharmony_ci *
4462306a36Sopenharmony_ci * Initializes a ipp module.
4562306a36Sopenharmony_ci *
4662306a36Sopenharmony_ci * Returns:
4762306a36Sopenharmony_ci * Zero on success, error code on failure.
4862306a36Sopenharmony_ci */
4962306a36Sopenharmony_ciint exynos_drm_ipp_register(struct device *dev, struct exynos_drm_ipp *ipp,
5062306a36Sopenharmony_ci		const struct exynos_drm_ipp_funcs *funcs, unsigned int caps,
5162306a36Sopenharmony_ci		const struct exynos_drm_ipp_formats *formats,
5262306a36Sopenharmony_ci		unsigned int num_formats, const char *name)
5362306a36Sopenharmony_ci{
5462306a36Sopenharmony_ci	WARN_ON(!ipp);
5562306a36Sopenharmony_ci	WARN_ON(!funcs);
5662306a36Sopenharmony_ci	WARN_ON(!formats);
5762306a36Sopenharmony_ci	WARN_ON(!num_formats);
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	spin_lock_init(&ipp->lock);
6062306a36Sopenharmony_ci	INIT_LIST_HEAD(&ipp->todo_list);
6162306a36Sopenharmony_ci	init_waitqueue_head(&ipp->done_wq);
6262306a36Sopenharmony_ci	ipp->dev = dev;
6362306a36Sopenharmony_ci	ipp->funcs = funcs;
6462306a36Sopenharmony_ci	ipp->capabilities = caps;
6562306a36Sopenharmony_ci	ipp->name = name;
6662306a36Sopenharmony_ci	ipp->formats = formats;
6762306a36Sopenharmony_ci	ipp->num_formats = num_formats;
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	/* ipp_list modification is serialized by component framework */
7062306a36Sopenharmony_ci	list_add_tail(&ipp->head, &ipp_list);
7162306a36Sopenharmony_ci	ipp->id = num_ipp++;
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	DRM_DEV_DEBUG_DRIVER(dev, "Registered ipp %d\n", ipp->id);
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	return 0;
7662306a36Sopenharmony_ci}
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci/**
7962306a36Sopenharmony_ci * exynos_drm_ipp_unregister - Unregister the picture processor module
8062306a36Sopenharmony_ci * @dev: DRM device
8162306a36Sopenharmony_ci * @ipp: ipp module
8262306a36Sopenharmony_ci */
8362306a36Sopenharmony_civoid exynos_drm_ipp_unregister(struct device *dev,
8462306a36Sopenharmony_ci			       struct exynos_drm_ipp *ipp)
8562306a36Sopenharmony_ci{
8662306a36Sopenharmony_ci	WARN_ON(ipp->task);
8762306a36Sopenharmony_ci	WARN_ON(!list_empty(&ipp->todo_list));
8862306a36Sopenharmony_ci	list_del(&ipp->head);
8962306a36Sopenharmony_ci}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci/**
9262306a36Sopenharmony_ci * exynos_drm_ipp_get_res_ioctl - enumerate all ipp modules
9362306a36Sopenharmony_ci * @dev: DRM device
9462306a36Sopenharmony_ci * @data: ioctl data
9562306a36Sopenharmony_ci * @file_priv: DRM file info
9662306a36Sopenharmony_ci *
9762306a36Sopenharmony_ci * Construct a list of ipp ids.
9862306a36Sopenharmony_ci *
9962306a36Sopenharmony_ci * Called by the user via ioctl.
10062306a36Sopenharmony_ci *
10162306a36Sopenharmony_ci * Returns:
10262306a36Sopenharmony_ci * Zero on success, negative errno on failure.
10362306a36Sopenharmony_ci */
10462306a36Sopenharmony_ciint exynos_drm_ipp_get_res_ioctl(struct drm_device *dev, void *data,
10562306a36Sopenharmony_ci				 struct drm_file *file_priv)
10662306a36Sopenharmony_ci{
10762306a36Sopenharmony_ci	struct drm_exynos_ioctl_ipp_get_res *resp = data;
10862306a36Sopenharmony_ci	struct exynos_drm_ipp *ipp;
10962306a36Sopenharmony_ci	uint32_t __user *ipp_ptr = (uint32_t __user *)
11062306a36Sopenharmony_ci						(unsigned long)resp->ipp_id_ptr;
11162306a36Sopenharmony_ci	unsigned int count = num_ipp, copied = 0;
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	/*
11462306a36Sopenharmony_ci	 * This ioctl is called twice, once to determine how much space is
11562306a36Sopenharmony_ci	 * needed, and the 2nd time to fill it.
11662306a36Sopenharmony_ci	 */
11762306a36Sopenharmony_ci	if (count && resp->count_ipps >= count) {
11862306a36Sopenharmony_ci		list_for_each_entry(ipp, &ipp_list, head) {
11962306a36Sopenharmony_ci			if (put_user(ipp->id, ipp_ptr + copied))
12062306a36Sopenharmony_ci				return -EFAULT;
12162306a36Sopenharmony_ci			copied++;
12262306a36Sopenharmony_ci		}
12362306a36Sopenharmony_ci	}
12462306a36Sopenharmony_ci	resp->count_ipps = count;
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	return 0;
12762306a36Sopenharmony_ci}
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_cistatic inline struct exynos_drm_ipp *__ipp_get(uint32_t id)
13062306a36Sopenharmony_ci{
13162306a36Sopenharmony_ci	struct exynos_drm_ipp *ipp;
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	list_for_each_entry(ipp, &ipp_list, head)
13462306a36Sopenharmony_ci		if (ipp->id == id)
13562306a36Sopenharmony_ci			return ipp;
13662306a36Sopenharmony_ci	return NULL;
13762306a36Sopenharmony_ci}
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci/**
14062306a36Sopenharmony_ci * exynos_drm_ipp_get_caps_ioctl - get ipp module capabilities and formats
14162306a36Sopenharmony_ci * @dev: DRM device
14262306a36Sopenharmony_ci * @data: ioctl data
14362306a36Sopenharmony_ci * @file_priv: DRM file info
14462306a36Sopenharmony_ci *
14562306a36Sopenharmony_ci * Construct a structure describing ipp module capabilities.
14662306a36Sopenharmony_ci *
14762306a36Sopenharmony_ci * Called by the user via ioctl.
14862306a36Sopenharmony_ci *
14962306a36Sopenharmony_ci * Returns:
15062306a36Sopenharmony_ci * Zero on success, negative errno on failure.
15162306a36Sopenharmony_ci */
15262306a36Sopenharmony_ciint exynos_drm_ipp_get_caps_ioctl(struct drm_device *dev, void *data,
15362306a36Sopenharmony_ci				  struct drm_file *file_priv)
15462306a36Sopenharmony_ci{
15562306a36Sopenharmony_ci	struct drm_exynos_ioctl_ipp_get_caps *resp = data;
15662306a36Sopenharmony_ci	void __user *ptr = (void __user *)(unsigned long)resp->formats_ptr;
15762306a36Sopenharmony_ci	struct exynos_drm_ipp *ipp;
15862306a36Sopenharmony_ci	int i;
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	ipp = __ipp_get(resp->ipp_id);
16162306a36Sopenharmony_ci	if (!ipp)
16262306a36Sopenharmony_ci		return -ENOENT;
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	resp->ipp_id = ipp->id;
16562306a36Sopenharmony_ci	resp->capabilities = ipp->capabilities;
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	/*
16862306a36Sopenharmony_ci	 * This ioctl is called twice, once to determine how much space is
16962306a36Sopenharmony_ci	 * needed, and the 2nd time to fill it.
17062306a36Sopenharmony_ci	 */
17162306a36Sopenharmony_ci	if (resp->formats_count >= ipp->num_formats) {
17262306a36Sopenharmony_ci		for (i = 0; i < ipp->num_formats; i++) {
17362306a36Sopenharmony_ci			struct drm_exynos_ipp_format tmp = {
17462306a36Sopenharmony_ci				.fourcc = ipp->formats[i].fourcc,
17562306a36Sopenharmony_ci				.type = ipp->formats[i].type,
17662306a36Sopenharmony_ci				.modifier = ipp->formats[i].modifier,
17762306a36Sopenharmony_ci			};
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci			if (copy_to_user(ptr, &tmp, sizeof(tmp)))
18062306a36Sopenharmony_ci				return -EFAULT;
18162306a36Sopenharmony_ci			ptr += sizeof(tmp);
18262306a36Sopenharmony_ci		}
18362306a36Sopenharmony_ci	}
18462306a36Sopenharmony_ci	resp->formats_count = ipp->num_formats;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	return 0;
18762306a36Sopenharmony_ci}
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_cistatic inline const struct exynos_drm_ipp_formats *__ipp_format_get(
19062306a36Sopenharmony_ci				struct exynos_drm_ipp *ipp, uint32_t fourcc,
19162306a36Sopenharmony_ci				uint64_t mod, unsigned int type)
19262306a36Sopenharmony_ci{
19362306a36Sopenharmony_ci	int i;
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	for (i = 0; i < ipp->num_formats; i++) {
19662306a36Sopenharmony_ci		if ((ipp->formats[i].type & type) &&
19762306a36Sopenharmony_ci		    ipp->formats[i].fourcc == fourcc &&
19862306a36Sopenharmony_ci		    ipp->formats[i].modifier == mod)
19962306a36Sopenharmony_ci			return &ipp->formats[i];
20062306a36Sopenharmony_ci	}
20162306a36Sopenharmony_ci	return NULL;
20262306a36Sopenharmony_ci}
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci/**
20562306a36Sopenharmony_ci * exynos_drm_ipp_get_limits_ioctl - get ipp module limits
20662306a36Sopenharmony_ci * @dev: DRM device
20762306a36Sopenharmony_ci * @data: ioctl data
20862306a36Sopenharmony_ci * @file_priv: DRM file info
20962306a36Sopenharmony_ci *
21062306a36Sopenharmony_ci * Construct a structure describing ipp module limitations for provided
21162306a36Sopenharmony_ci * picture format.
21262306a36Sopenharmony_ci *
21362306a36Sopenharmony_ci * Called by the user via ioctl.
21462306a36Sopenharmony_ci *
21562306a36Sopenharmony_ci * Returns:
21662306a36Sopenharmony_ci * Zero on success, negative errno on failure.
21762306a36Sopenharmony_ci */
21862306a36Sopenharmony_ciint exynos_drm_ipp_get_limits_ioctl(struct drm_device *dev, void *data,
21962306a36Sopenharmony_ci				    struct drm_file *file_priv)
22062306a36Sopenharmony_ci{
22162306a36Sopenharmony_ci	struct drm_exynos_ioctl_ipp_get_limits *resp = data;
22262306a36Sopenharmony_ci	void __user *ptr = (void __user *)(unsigned long)resp->limits_ptr;
22362306a36Sopenharmony_ci	const struct exynos_drm_ipp_formats *format;
22462306a36Sopenharmony_ci	struct exynos_drm_ipp *ipp;
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci	if (resp->type != DRM_EXYNOS_IPP_FORMAT_SOURCE &&
22762306a36Sopenharmony_ci	    resp->type != DRM_EXYNOS_IPP_FORMAT_DESTINATION)
22862306a36Sopenharmony_ci		return -EINVAL;
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	ipp = __ipp_get(resp->ipp_id);
23162306a36Sopenharmony_ci	if (!ipp)
23262306a36Sopenharmony_ci		return -ENOENT;
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci	format = __ipp_format_get(ipp, resp->fourcc, resp->modifier,
23562306a36Sopenharmony_ci				  resp->type);
23662306a36Sopenharmony_ci	if (!format)
23762306a36Sopenharmony_ci		return -EINVAL;
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci	/*
24062306a36Sopenharmony_ci	 * This ioctl is called twice, once to determine how much space is
24162306a36Sopenharmony_ci	 * needed, and the 2nd time to fill it.
24262306a36Sopenharmony_ci	 */
24362306a36Sopenharmony_ci	if (format->num_limits && resp->limits_count >= format->num_limits)
24462306a36Sopenharmony_ci		if (copy_to_user((void __user *)ptr, format->limits,
24562306a36Sopenharmony_ci				 sizeof(*format->limits) * format->num_limits))
24662306a36Sopenharmony_ci			return -EFAULT;
24762306a36Sopenharmony_ci	resp->limits_count = format->num_limits;
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci	return 0;
25062306a36Sopenharmony_ci}
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_cistruct drm_pending_exynos_ipp_event {
25362306a36Sopenharmony_ci	struct drm_pending_event base;
25462306a36Sopenharmony_ci	struct drm_exynos_ipp_event event;
25562306a36Sopenharmony_ci};
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_cistatic inline struct exynos_drm_ipp_task *
25862306a36Sopenharmony_ci			exynos_drm_ipp_task_alloc(struct exynos_drm_ipp *ipp)
25962306a36Sopenharmony_ci{
26062306a36Sopenharmony_ci	struct exynos_drm_ipp_task *task;
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ci	task = kzalloc(sizeof(*task), GFP_KERNEL);
26362306a36Sopenharmony_ci	if (!task)
26462306a36Sopenharmony_ci		return NULL;
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	task->dev = ipp->dev;
26762306a36Sopenharmony_ci	task->ipp = ipp;
26862306a36Sopenharmony_ci
26962306a36Sopenharmony_ci	/* some defaults */
27062306a36Sopenharmony_ci	task->src.rect.w = task->dst.rect.w = UINT_MAX;
27162306a36Sopenharmony_ci	task->src.rect.h = task->dst.rect.h = UINT_MAX;
27262306a36Sopenharmony_ci	task->transform.rotation = DRM_MODE_ROTATE_0;
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ci	DRM_DEV_DEBUG_DRIVER(task->dev, "Allocated task %pK\n", task);
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_ci	return task;
27762306a36Sopenharmony_ci}
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_cistatic const struct exynos_drm_param_map {
28062306a36Sopenharmony_ci	unsigned int id;
28162306a36Sopenharmony_ci	unsigned int size;
28262306a36Sopenharmony_ci	unsigned int offset;
28362306a36Sopenharmony_ci} exynos_drm_ipp_params_maps[] = {
28462306a36Sopenharmony_ci	{
28562306a36Sopenharmony_ci		DRM_EXYNOS_IPP_TASK_BUFFER | DRM_EXYNOS_IPP_TASK_TYPE_SOURCE,
28662306a36Sopenharmony_ci		sizeof(struct drm_exynos_ipp_task_buffer),
28762306a36Sopenharmony_ci		offsetof(struct exynos_drm_ipp_task, src.buf),
28862306a36Sopenharmony_ci	}, {
28962306a36Sopenharmony_ci		DRM_EXYNOS_IPP_TASK_BUFFER |
29062306a36Sopenharmony_ci			DRM_EXYNOS_IPP_TASK_TYPE_DESTINATION,
29162306a36Sopenharmony_ci		sizeof(struct drm_exynos_ipp_task_buffer),
29262306a36Sopenharmony_ci		offsetof(struct exynos_drm_ipp_task, dst.buf),
29362306a36Sopenharmony_ci	}, {
29462306a36Sopenharmony_ci		DRM_EXYNOS_IPP_TASK_RECTANGLE | DRM_EXYNOS_IPP_TASK_TYPE_SOURCE,
29562306a36Sopenharmony_ci		sizeof(struct drm_exynos_ipp_task_rect),
29662306a36Sopenharmony_ci		offsetof(struct exynos_drm_ipp_task, src.rect),
29762306a36Sopenharmony_ci	}, {
29862306a36Sopenharmony_ci		DRM_EXYNOS_IPP_TASK_RECTANGLE |
29962306a36Sopenharmony_ci			DRM_EXYNOS_IPP_TASK_TYPE_DESTINATION,
30062306a36Sopenharmony_ci		sizeof(struct drm_exynos_ipp_task_rect),
30162306a36Sopenharmony_ci		offsetof(struct exynos_drm_ipp_task, dst.rect),
30262306a36Sopenharmony_ci	}, {
30362306a36Sopenharmony_ci		DRM_EXYNOS_IPP_TASK_TRANSFORM,
30462306a36Sopenharmony_ci		sizeof(struct drm_exynos_ipp_task_transform),
30562306a36Sopenharmony_ci		offsetof(struct exynos_drm_ipp_task, transform),
30662306a36Sopenharmony_ci	}, {
30762306a36Sopenharmony_ci		DRM_EXYNOS_IPP_TASK_ALPHA,
30862306a36Sopenharmony_ci		sizeof(struct drm_exynos_ipp_task_alpha),
30962306a36Sopenharmony_ci		offsetof(struct exynos_drm_ipp_task, alpha),
31062306a36Sopenharmony_ci	},
31162306a36Sopenharmony_ci};
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_cistatic int exynos_drm_ipp_task_set(struct exynos_drm_ipp_task *task,
31462306a36Sopenharmony_ci				   struct drm_exynos_ioctl_ipp_commit *arg)
31562306a36Sopenharmony_ci{
31662306a36Sopenharmony_ci	const struct exynos_drm_param_map *map = exynos_drm_ipp_params_maps;
31762306a36Sopenharmony_ci	void __user *params = (void __user *)(unsigned long)arg->params_ptr;
31862306a36Sopenharmony_ci	unsigned int size = arg->params_size;
31962306a36Sopenharmony_ci	uint32_t id;
32062306a36Sopenharmony_ci	int i;
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_ci	while (size) {
32362306a36Sopenharmony_ci		if (get_user(id, (uint32_t __user *)params))
32462306a36Sopenharmony_ci			return -EFAULT;
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_ci		for (i = 0; i < ARRAY_SIZE(exynos_drm_ipp_params_maps); i++)
32762306a36Sopenharmony_ci			if (map[i].id == id)
32862306a36Sopenharmony_ci				break;
32962306a36Sopenharmony_ci		if (i == ARRAY_SIZE(exynos_drm_ipp_params_maps) ||
33062306a36Sopenharmony_ci		    map[i].size > size)
33162306a36Sopenharmony_ci			return -EINVAL;
33262306a36Sopenharmony_ci
33362306a36Sopenharmony_ci		if (copy_from_user((void *)task + map[i].offset, params,
33462306a36Sopenharmony_ci				   map[i].size))
33562306a36Sopenharmony_ci			return -EFAULT;
33662306a36Sopenharmony_ci
33762306a36Sopenharmony_ci		params += map[i].size;
33862306a36Sopenharmony_ci		size -= map[i].size;
33962306a36Sopenharmony_ci	}
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_ci	DRM_DEV_DEBUG_DRIVER(task->dev,
34262306a36Sopenharmony_ci			     "Got task %pK configuration from userspace\n",
34362306a36Sopenharmony_ci			     task);
34462306a36Sopenharmony_ci	return 0;
34562306a36Sopenharmony_ci}
34662306a36Sopenharmony_ci
34762306a36Sopenharmony_cistatic int exynos_drm_ipp_task_setup_buffer(struct exynos_drm_ipp_buffer *buf,
34862306a36Sopenharmony_ci					    struct drm_file *filp)
34962306a36Sopenharmony_ci{
35062306a36Sopenharmony_ci	int ret = 0;
35162306a36Sopenharmony_ci	int i;
35262306a36Sopenharmony_ci
35362306a36Sopenharmony_ci	/* get GEM buffers and check their size */
35462306a36Sopenharmony_ci	for (i = 0; i < buf->format->num_planes; i++) {
35562306a36Sopenharmony_ci		unsigned int height = (i == 0) ? buf->buf.height :
35662306a36Sopenharmony_ci			     DIV_ROUND_UP(buf->buf.height, buf->format->vsub);
35762306a36Sopenharmony_ci		unsigned long size = height * buf->buf.pitch[i];
35862306a36Sopenharmony_ci		struct exynos_drm_gem *gem = exynos_drm_gem_get(filp,
35962306a36Sopenharmony_ci							    buf->buf.gem_id[i]);
36062306a36Sopenharmony_ci		if (!gem) {
36162306a36Sopenharmony_ci			ret = -ENOENT;
36262306a36Sopenharmony_ci			goto gem_free;
36362306a36Sopenharmony_ci		}
36462306a36Sopenharmony_ci		buf->exynos_gem[i] = gem;
36562306a36Sopenharmony_ci
36662306a36Sopenharmony_ci		if (size + buf->buf.offset[i] > buf->exynos_gem[i]->size) {
36762306a36Sopenharmony_ci			i++;
36862306a36Sopenharmony_ci			ret = -EINVAL;
36962306a36Sopenharmony_ci			goto gem_free;
37062306a36Sopenharmony_ci		}
37162306a36Sopenharmony_ci		buf->dma_addr[i] = buf->exynos_gem[i]->dma_addr +
37262306a36Sopenharmony_ci				   buf->buf.offset[i];
37362306a36Sopenharmony_ci	}
37462306a36Sopenharmony_ci
37562306a36Sopenharmony_ci	return 0;
37662306a36Sopenharmony_cigem_free:
37762306a36Sopenharmony_ci	while (i--) {
37862306a36Sopenharmony_ci		exynos_drm_gem_put(buf->exynos_gem[i]);
37962306a36Sopenharmony_ci		buf->exynos_gem[i] = NULL;
38062306a36Sopenharmony_ci	}
38162306a36Sopenharmony_ci	return ret;
38262306a36Sopenharmony_ci}
38362306a36Sopenharmony_ci
38462306a36Sopenharmony_cistatic void exynos_drm_ipp_task_release_buf(struct exynos_drm_ipp_buffer *buf)
38562306a36Sopenharmony_ci{
38662306a36Sopenharmony_ci	int i;
38762306a36Sopenharmony_ci
38862306a36Sopenharmony_ci	if (!buf->exynos_gem[0])
38962306a36Sopenharmony_ci		return;
39062306a36Sopenharmony_ci	for (i = 0; i < buf->format->num_planes; i++)
39162306a36Sopenharmony_ci		exynos_drm_gem_put(buf->exynos_gem[i]);
39262306a36Sopenharmony_ci}
39362306a36Sopenharmony_ci
39462306a36Sopenharmony_cistatic void exynos_drm_ipp_task_free(struct exynos_drm_ipp *ipp,
39562306a36Sopenharmony_ci				 struct exynos_drm_ipp_task *task)
39662306a36Sopenharmony_ci{
39762306a36Sopenharmony_ci	DRM_DEV_DEBUG_DRIVER(task->dev, "Freeing task %pK\n", task);
39862306a36Sopenharmony_ci
39962306a36Sopenharmony_ci	exynos_drm_ipp_task_release_buf(&task->src);
40062306a36Sopenharmony_ci	exynos_drm_ipp_task_release_buf(&task->dst);
40162306a36Sopenharmony_ci	if (task->event)
40262306a36Sopenharmony_ci		drm_event_cancel_free(ipp->drm_dev, &task->event->base);
40362306a36Sopenharmony_ci	kfree(task);
40462306a36Sopenharmony_ci}
40562306a36Sopenharmony_ci
40662306a36Sopenharmony_cistruct drm_ipp_limit {
40762306a36Sopenharmony_ci	struct drm_exynos_ipp_limit_val h;
40862306a36Sopenharmony_ci	struct drm_exynos_ipp_limit_val v;
40962306a36Sopenharmony_ci};
41062306a36Sopenharmony_ci
41162306a36Sopenharmony_cienum drm_ipp_size_id {
41262306a36Sopenharmony_ci	IPP_LIMIT_BUFFER, IPP_LIMIT_AREA, IPP_LIMIT_ROTATED, IPP_LIMIT_MAX
41362306a36Sopenharmony_ci};
41462306a36Sopenharmony_ci
41562306a36Sopenharmony_cistatic const enum drm_exynos_ipp_limit_type limit_id_fallback[IPP_LIMIT_MAX][4] = {
41662306a36Sopenharmony_ci	[IPP_LIMIT_BUFFER]  = { DRM_EXYNOS_IPP_LIMIT_SIZE_BUFFER },
41762306a36Sopenharmony_ci	[IPP_LIMIT_AREA]    = { DRM_EXYNOS_IPP_LIMIT_SIZE_AREA,
41862306a36Sopenharmony_ci				DRM_EXYNOS_IPP_LIMIT_SIZE_BUFFER },
41962306a36Sopenharmony_ci	[IPP_LIMIT_ROTATED] = { DRM_EXYNOS_IPP_LIMIT_SIZE_ROTATED,
42062306a36Sopenharmony_ci				DRM_EXYNOS_IPP_LIMIT_SIZE_AREA,
42162306a36Sopenharmony_ci				DRM_EXYNOS_IPP_LIMIT_SIZE_BUFFER },
42262306a36Sopenharmony_ci};
42362306a36Sopenharmony_ci
42462306a36Sopenharmony_cistatic inline void __limit_set_val(unsigned int *ptr, unsigned int val)
42562306a36Sopenharmony_ci{
42662306a36Sopenharmony_ci	if (!*ptr)
42762306a36Sopenharmony_ci		*ptr = val;
42862306a36Sopenharmony_ci}
42962306a36Sopenharmony_ci
43062306a36Sopenharmony_cistatic void __get_size_limit(const struct drm_exynos_ipp_limit *limits,
43162306a36Sopenharmony_ci			     unsigned int num_limits, enum drm_ipp_size_id id,
43262306a36Sopenharmony_ci			     struct drm_ipp_limit *res)
43362306a36Sopenharmony_ci{
43462306a36Sopenharmony_ci	const struct drm_exynos_ipp_limit *l = limits;
43562306a36Sopenharmony_ci	int i = 0;
43662306a36Sopenharmony_ci
43762306a36Sopenharmony_ci	memset(res, 0, sizeof(*res));
43862306a36Sopenharmony_ci	for (i = 0; limit_id_fallback[id][i]; i++)
43962306a36Sopenharmony_ci		for (l = limits; l - limits < num_limits; l++) {
44062306a36Sopenharmony_ci			if (((l->type & DRM_EXYNOS_IPP_LIMIT_TYPE_MASK) !=
44162306a36Sopenharmony_ci			      DRM_EXYNOS_IPP_LIMIT_TYPE_SIZE) ||
44262306a36Sopenharmony_ci			    ((l->type & DRM_EXYNOS_IPP_LIMIT_SIZE_MASK) !=
44362306a36Sopenharmony_ci						     limit_id_fallback[id][i]))
44462306a36Sopenharmony_ci				continue;
44562306a36Sopenharmony_ci			__limit_set_val(&res->h.min, l->h.min);
44662306a36Sopenharmony_ci			__limit_set_val(&res->h.max, l->h.max);
44762306a36Sopenharmony_ci			__limit_set_val(&res->h.align, l->h.align);
44862306a36Sopenharmony_ci			__limit_set_val(&res->v.min, l->v.min);
44962306a36Sopenharmony_ci			__limit_set_val(&res->v.max, l->v.max);
45062306a36Sopenharmony_ci			__limit_set_val(&res->v.align, l->v.align);
45162306a36Sopenharmony_ci		}
45262306a36Sopenharmony_ci}
45362306a36Sopenharmony_ci
45462306a36Sopenharmony_cistatic inline bool __align_check(unsigned int val, unsigned int align)
45562306a36Sopenharmony_ci{
45662306a36Sopenharmony_ci	if (align && (val & (align - 1))) {
45762306a36Sopenharmony_ci		DRM_DEBUG_DRIVER("Value %d exceeds HW limits (align %d)\n",
45862306a36Sopenharmony_ci				 val, align);
45962306a36Sopenharmony_ci		return false;
46062306a36Sopenharmony_ci	}
46162306a36Sopenharmony_ci	return true;
46262306a36Sopenharmony_ci}
46362306a36Sopenharmony_ci
46462306a36Sopenharmony_cistatic inline bool __size_limit_check(unsigned int val,
46562306a36Sopenharmony_ci				 struct drm_exynos_ipp_limit_val *l)
46662306a36Sopenharmony_ci{
46762306a36Sopenharmony_ci	if ((l->min && val < l->min) || (l->max && val > l->max)) {
46862306a36Sopenharmony_ci		DRM_DEBUG_DRIVER("Value %d exceeds HW limits (min %d, max %d)\n",
46962306a36Sopenharmony_ci				 val, l->min, l->max);
47062306a36Sopenharmony_ci		return false;
47162306a36Sopenharmony_ci	}
47262306a36Sopenharmony_ci	return __align_check(val, l->align);
47362306a36Sopenharmony_ci}
47462306a36Sopenharmony_ci
47562306a36Sopenharmony_cistatic int exynos_drm_ipp_check_size_limits(struct exynos_drm_ipp_buffer *buf,
47662306a36Sopenharmony_ci	const struct drm_exynos_ipp_limit *limits, unsigned int num_limits,
47762306a36Sopenharmony_ci	bool rotate, bool swap)
47862306a36Sopenharmony_ci{
47962306a36Sopenharmony_ci	enum drm_ipp_size_id id = rotate ? IPP_LIMIT_ROTATED : IPP_LIMIT_AREA;
48062306a36Sopenharmony_ci	struct drm_ipp_limit l;
48162306a36Sopenharmony_ci	struct drm_exynos_ipp_limit_val *lh = &l.h, *lv = &l.v;
48262306a36Sopenharmony_ci	int real_width = buf->buf.pitch[0] / buf->format->cpp[0];
48362306a36Sopenharmony_ci
48462306a36Sopenharmony_ci	if (!limits)
48562306a36Sopenharmony_ci		return 0;
48662306a36Sopenharmony_ci
48762306a36Sopenharmony_ci	__get_size_limit(limits, num_limits, IPP_LIMIT_BUFFER, &l);
48862306a36Sopenharmony_ci	if (!__size_limit_check(real_width, &l.h) ||
48962306a36Sopenharmony_ci	    !__size_limit_check(buf->buf.height, &l.v))
49062306a36Sopenharmony_ci		return -EINVAL;
49162306a36Sopenharmony_ci
49262306a36Sopenharmony_ci	if (swap) {
49362306a36Sopenharmony_ci		lv = &l.h;
49462306a36Sopenharmony_ci		lh = &l.v;
49562306a36Sopenharmony_ci	}
49662306a36Sopenharmony_ci	__get_size_limit(limits, num_limits, id, &l);
49762306a36Sopenharmony_ci	if (!__size_limit_check(buf->rect.w, lh) ||
49862306a36Sopenharmony_ci	    !__align_check(buf->rect.x, lh->align) ||
49962306a36Sopenharmony_ci	    !__size_limit_check(buf->rect.h, lv) ||
50062306a36Sopenharmony_ci	    !__align_check(buf->rect.y, lv->align))
50162306a36Sopenharmony_ci		return -EINVAL;
50262306a36Sopenharmony_ci
50362306a36Sopenharmony_ci	return 0;
50462306a36Sopenharmony_ci}
50562306a36Sopenharmony_ci
50662306a36Sopenharmony_cistatic inline bool __scale_limit_check(unsigned int src, unsigned int dst,
50762306a36Sopenharmony_ci				       unsigned int min, unsigned int max)
50862306a36Sopenharmony_ci{
50962306a36Sopenharmony_ci	if ((max && (dst << 16) > src * max) ||
51062306a36Sopenharmony_ci	    (min && (dst << 16) < src * min)) {
51162306a36Sopenharmony_ci		DRM_DEBUG_DRIVER("Scale from %d to %d exceeds HW limits (ratio min %d.%05d, max %d.%05d)\n",
51262306a36Sopenharmony_ci			 src, dst,
51362306a36Sopenharmony_ci			 min >> 16, 100000 * (min & 0xffff) / (1 << 16),
51462306a36Sopenharmony_ci			 max >> 16, 100000 * (max & 0xffff) / (1 << 16));
51562306a36Sopenharmony_ci		return false;
51662306a36Sopenharmony_ci	}
51762306a36Sopenharmony_ci	return true;
51862306a36Sopenharmony_ci}
51962306a36Sopenharmony_ci
52062306a36Sopenharmony_cistatic int exynos_drm_ipp_check_scale_limits(
52162306a36Sopenharmony_ci				struct drm_exynos_ipp_task_rect *src,
52262306a36Sopenharmony_ci				struct drm_exynos_ipp_task_rect *dst,
52362306a36Sopenharmony_ci				const struct drm_exynos_ipp_limit *limits,
52462306a36Sopenharmony_ci				unsigned int num_limits, bool swap)
52562306a36Sopenharmony_ci{
52662306a36Sopenharmony_ci	const struct drm_exynos_ipp_limit_val *lh, *lv;
52762306a36Sopenharmony_ci	int dw, dh;
52862306a36Sopenharmony_ci
52962306a36Sopenharmony_ci	for (; num_limits; limits++, num_limits--)
53062306a36Sopenharmony_ci		if ((limits->type & DRM_EXYNOS_IPP_LIMIT_TYPE_MASK) ==
53162306a36Sopenharmony_ci		    DRM_EXYNOS_IPP_LIMIT_TYPE_SCALE)
53262306a36Sopenharmony_ci			break;
53362306a36Sopenharmony_ci	if (!num_limits)
53462306a36Sopenharmony_ci		return 0;
53562306a36Sopenharmony_ci
53662306a36Sopenharmony_ci	lh = (!swap) ? &limits->h : &limits->v;
53762306a36Sopenharmony_ci	lv = (!swap) ? &limits->v : &limits->h;
53862306a36Sopenharmony_ci	dw = (!swap) ? dst->w : dst->h;
53962306a36Sopenharmony_ci	dh = (!swap) ? dst->h : dst->w;
54062306a36Sopenharmony_ci
54162306a36Sopenharmony_ci	if (!__scale_limit_check(src->w, dw, lh->min, lh->max) ||
54262306a36Sopenharmony_ci	    !__scale_limit_check(src->h, dh, lv->min, lv->max))
54362306a36Sopenharmony_ci		return -EINVAL;
54462306a36Sopenharmony_ci
54562306a36Sopenharmony_ci	return 0;
54662306a36Sopenharmony_ci}
54762306a36Sopenharmony_ci
54862306a36Sopenharmony_cistatic int exynos_drm_ipp_check_format(struct exynos_drm_ipp_task *task,
54962306a36Sopenharmony_ci				       struct exynos_drm_ipp_buffer *buf,
55062306a36Sopenharmony_ci				       struct exynos_drm_ipp_buffer *src,
55162306a36Sopenharmony_ci				       struct exynos_drm_ipp_buffer *dst,
55262306a36Sopenharmony_ci				       bool rotate, bool swap)
55362306a36Sopenharmony_ci{
55462306a36Sopenharmony_ci	const struct exynos_drm_ipp_formats *fmt;
55562306a36Sopenharmony_ci	int ret, i;
55662306a36Sopenharmony_ci
55762306a36Sopenharmony_ci	fmt = __ipp_format_get(task->ipp, buf->buf.fourcc, buf->buf.modifier,
55862306a36Sopenharmony_ci			       buf == src ? DRM_EXYNOS_IPP_FORMAT_SOURCE :
55962306a36Sopenharmony_ci					    DRM_EXYNOS_IPP_FORMAT_DESTINATION);
56062306a36Sopenharmony_ci	if (!fmt) {
56162306a36Sopenharmony_ci		DRM_DEV_DEBUG_DRIVER(task->dev,
56262306a36Sopenharmony_ci				     "Task %pK: %s format not supported\n",
56362306a36Sopenharmony_ci				     task, buf == src ? "src" : "dst");
56462306a36Sopenharmony_ci		return -EINVAL;
56562306a36Sopenharmony_ci	}
56662306a36Sopenharmony_ci
56762306a36Sopenharmony_ci	/* basic checks */
56862306a36Sopenharmony_ci	if (buf->buf.width == 0 || buf->buf.height == 0)
56962306a36Sopenharmony_ci		return -EINVAL;
57062306a36Sopenharmony_ci
57162306a36Sopenharmony_ci	buf->format = drm_format_info(buf->buf.fourcc);
57262306a36Sopenharmony_ci	for (i = 0; i < buf->format->num_planes; i++) {
57362306a36Sopenharmony_ci		unsigned int width = (i == 0) ? buf->buf.width :
57462306a36Sopenharmony_ci			     DIV_ROUND_UP(buf->buf.width, buf->format->hsub);
57562306a36Sopenharmony_ci
57662306a36Sopenharmony_ci		if (buf->buf.pitch[i] == 0)
57762306a36Sopenharmony_ci			buf->buf.pitch[i] = width * buf->format->cpp[i];
57862306a36Sopenharmony_ci		if (buf->buf.pitch[i] < width * buf->format->cpp[i])
57962306a36Sopenharmony_ci			return -EINVAL;
58062306a36Sopenharmony_ci		if (!buf->buf.gem_id[i])
58162306a36Sopenharmony_ci			return -ENOENT;
58262306a36Sopenharmony_ci	}
58362306a36Sopenharmony_ci
58462306a36Sopenharmony_ci	/* pitch for additional planes must match */
58562306a36Sopenharmony_ci	if (buf->format->num_planes > 2 &&
58662306a36Sopenharmony_ci	    buf->buf.pitch[1] != buf->buf.pitch[2])
58762306a36Sopenharmony_ci		return -EINVAL;
58862306a36Sopenharmony_ci
58962306a36Sopenharmony_ci	/* check driver limits */
59062306a36Sopenharmony_ci	ret = exynos_drm_ipp_check_size_limits(buf, fmt->limits,
59162306a36Sopenharmony_ci					       fmt->num_limits,
59262306a36Sopenharmony_ci					       rotate,
59362306a36Sopenharmony_ci					       buf == dst ? swap : false);
59462306a36Sopenharmony_ci	if (ret)
59562306a36Sopenharmony_ci		return ret;
59662306a36Sopenharmony_ci	ret = exynos_drm_ipp_check_scale_limits(&src->rect, &dst->rect,
59762306a36Sopenharmony_ci						fmt->limits,
59862306a36Sopenharmony_ci						fmt->num_limits, swap);
59962306a36Sopenharmony_ci	return ret;
60062306a36Sopenharmony_ci}
60162306a36Sopenharmony_ci
60262306a36Sopenharmony_cistatic int exynos_drm_ipp_task_check(struct exynos_drm_ipp_task *task)
60362306a36Sopenharmony_ci{
60462306a36Sopenharmony_ci	struct exynos_drm_ipp *ipp = task->ipp;
60562306a36Sopenharmony_ci	struct exynos_drm_ipp_buffer *src = &task->src, *dst = &task->dst;
60662306a36Sopenharmony_ci	unsigned int rotation = task->transform.rotation;
60762306a36Sopenharmony_ci	int ret = 0;
60862306a36Sopenharmony_ci	bool swap = drm_rotation_90_or_270(rotation);
60962306a36Sopenharmony_ci	bool rotate = (rotation != DRM_MODE_ROTATE_0);
61062306a36Sopenharmony_ci	bool scale = false;
61162306a36Sopenharmony_ci
61262306a36Sopenharmony_ci	DRM_DEV_DEBUG_DRIVER(task->dev, "Checking task %pK\n", task);
61362306a36Sopenharmony_ci
61462306a36Sopenharmony_ci	if (src->rect.w == UINT_MAX)
61562306a36Sopenharmony_ci		src->rect.w = src->buf.width;
61662306a36Sopenharmony_ci	if (src->rect.h == UINT_MAX)
61762306a36Sopenharmony_ci		src->rect.h = src->buf.height;
61862306a36Sopenharmony_ci	if (dst->rect.w == UINT_MAX)
61962306a36Sopenharmony_ci		dst->rect.w = dst->buf.width;
62062306a36Sopenharmony_ci	if (dst->rect.h == UINT_MAX)
62162306a36Sopenharmony_ci		dst->rect.h = dst->buf.height;
62262306a36Sopenharmony_ci
62362306a36Sopenharmony_ci	if (src->rect.x + src->rect.w > (src->buf.width) ||
62462306a36Sopenharmony_ci	    src->rect.y + src->rect.h > (src->buf.height) ||
62562306a36Sopenharmony_ci	    dst->rect.x + dst->rect.w > (dst->buf.width) ||
62662306a36Sopenharmony_ci	    dst->rect.y + dst->rect.h > (dst->buf.height)) {
62762306a36Sopenharmony_ci		DRM_DEV_DEBUG_DRIVER(task->dev,
62862306a36Sopenharmony_ci				     "Task %pK: defined area is outside provided buffers\n",
62962306a36Sopenharmony_ci				     task);
63062306a36Sopenharmony_ci		return -EINVAL;
63162306a36Sopenharmony_ci	}
63262306a36Sopenharmony_ci
63362306a36Sopenharmony_ci	if ((!swap && (src->rect.w != dst->rect.w ||
63462306a36Sopenharmony_ci		       src->rect.h != dst->rect.h)) ||
63562306a36Sopenharmony_ci	    (swap && (src->rect.w != dst->rect.h ||
63662306a36Sopenharmony_ci		      src->rect.h != dst->rect.w)))
63762306a36Sopenharmony_ci		scale = true;
63862306a36Sopenharmony_ci
63962306a36Sopenharmony_ci	if ((!(ipp->capabilities & DRM_EXYNOS_IPP_CAP_CROP) &&
64062306a36Sopenharmony_ci	     (src->rect.x || src->rect.y || dst->rect.x || dst->rect.y)) ||
64162306a36Sopenharmony_ci	    (!(ipp->capabilities & DRM_EXYNOS_IPP_CAP_ROTATE) && rotate) ||
64262306a36Sopenharmony_ci	    (!(ipp->capabilities & DRM_EXYNOS_IPP_CAP_SCALE) && scale) ||
64362306a36Sopenharmony_ci	    (!(ipp->capabilities & DRM_EXYNOS_IPP_CAP_CONVERT) &&
64462306a36Sopenharmony_ci	     src->buf.fourcc != dst->buf.fourcc)) {
64562306a36Sopenharmony_ci		DRM_DEV_DEBUG_DRIVER(task->dev, "Task %pK: hw capabilities exceeded\n",
64662306a36Sopenharmony_ci				     task);
64762306a36Sopenharmony_ci		return -EINVAL;
64862306a36Sopenharmony_ci	}
64962306a36Sopenharmony_ci
65062306a36Sopenharmony_ci	ret = exynos_drm_ipp_check_format(task, src, src, dst, rotate, swap);
65162306a36Sopenharmony_ci	if (ret)
65262306a36Sopenharmony_ci		return ret;
65362306a36Sopenharmony_ci
65462306a36Sopenharmony_ci	ret = exynos_drm_ipp_check_format(task, dst, src, dst, false, swap);
65562306a36Sopenharmony_ci	if (ret)
65662306a36Sopenharmony_ci		return ret;
65762306a36Sopenharmony_ci
65862306a36Sopenharmony_ci	DRM_DEV_DEBUG_DRIVER(ipp->dev, "Task %pK: all checks done.\n",
65962306a36Sopenharmony_ci			     task);
66062306a36Sopenharmony_ci
66162306a36Sopenharmony_ci	return ret;
66262306a36Sopenharmony_ci}
66362306a36Sopenharmony_ci
66462306a36Sopenharmony_cistatic int exynos_drm_ipp_task_setup_buffers(struct exynos_drm_ipp_task *task,
66562306a36Sopenharmony_ci				     struct drm_file *filp)
66662306a36Sopenharmony_ci{
66762306a36Sopenharmony_ci	struct exynos_drm_ipp_buffer *src = &task->src, *dst = &task->dst;
66862306a36Sopenharmony_ci	int ret = 0;
66962306a36Sopenharmony_ci
67062306a36Sopenharmony_ci	DRM_DEV_DEBUG_DRIVER(task->dev, "Setting buffer for task %pK\n",
67162306a36Sopenharmony_ci			     task);
67262306a36Sopenharmony_ci
67362306a36Sopenharmony_ci	ret = exynos_drm_ipp_task_setup_buffer(src, filp);
67462306a36Sopenharmony_ci	if (ret) {
67562306a36Sopenharmony_ci		DRM_DEV_DEBUG_DRIVER(task->dev,
67662306a36Sopenharmony_ci				     "Task %pK: src buffer setup failed\n",
67762306a36Sopenharmony_ci				     task);
67862306a36Sopenharmony_ci		return ret;
67962306a36Sopenharmony_ci	}
68062306a36Sopenharmony_ci	ret = exynos_drm_ipp_task_setup_buffer(dst, filp);
68162306a36Sopenharmony_ci	if (ret) {
68262306a36Sopenharmony_ci		DRM_DEV_DEBUG_DRIVER(task->dev,
68362306a36Sopenharmony_ci				     "Task %pK: dst buffer setup failed\n",
68462306a36Sopenharmony_ci				     task);
68562306a36Sopenharmony_ci		return ret;
68662306a36Sopenharmony_ci	}
68762306a36Sopenharmony_ci
68862306a36Sopenharmony_ci	DRM_DEV_DEBUG_DRIVER(task->dev, "Task %pK: buffers prepared.\n",
68962306a36Sopenharmony_ci			     task);
69062306a36Sopenharmony_ci
69162306a36Sopenharmony_ci	return ret;
69262306a36Sopenharmony_ci}
69362306a36Sopenharmony_ci
69462306a36Sopenharmony_ci
69562306a36Sopenharmony_cistatic int exynos_drm_ipp_event_create(struct exynos_drm_ipp_task *task,
69662306a36Sopenharmony_ci				 struct drm_file *file_priv, uint64_t user_data)
69762306a36Sopenharmony_ci{
69862306a36Sopenharmony_ci	struct drm_pending_exynos_ipp_event *e = NULL;
69962306a36Sopenharmony_ci	int ret;
70062306a36Sopenharmony_ci
70162306a36Sopenharmony_ci	e = kzalloc(sizeof(*e), GFP_KERNEL);
70262306a36Sopenharmony_ci	if (!e)
70362306a36Sopenharmony_ci		return -ENOMEM;
70462306a36Sopenharmony_ci
70562306a36Sopenharmony_ci	e->event.base.type = DRM_EXYNOS_IPP_EVENT;
70662306a36Sopenharmony_ci	e->event.base.length = sizeof(e->event);
70762306a36Sopenharmony_ci	e->event.user_data = user_data;
70862306a36Sopenharmony_ci
70962306a36Sopenharmony_ci	ret = drm_event_reserve_init(task->ipp->drm_dev, file_priv, &e->base,
71062306a36Sopenharmony_ci				     &e->event.base);
71162306a36Sopenharmony_ci	if (ret)
71262306a36Sopenharmony_ci		goto free;
71362306a36Sopenharmony_ci
71462306a36Sopenharmony_ci	task->event = e;
71562306a36Sopenharmony_ci	return 0;
71662306a36Sopenharmony_cifree:
71762306a36Sopenharmony_ci	kfree(e);
71862306a36Sopenharmony_ci	return ret;
71962306a36Sopenharmony_ci}
72062306a36Sopenharmony_ci
72162306a36Sopenharmony_cistatic void exynos_drm_ipp_event_send(struct exynos_drm_ipp_task *task)
72262306a36Sopenharmony_ci{
72362306a36Sopenharmony_ci	struct timespec64 now;
72462306a36Sopenharmony_ci
72562306a36Sopenharmony_ci	ktime_get_ts64(&now);
72662306a36Sopenharmony_ci	task->event->event.tv_sec = now.tv_sec;
72762306a36Sopenharmony_ci	task->event->event.tv_usec = now.tv_nsec / NSEC_PER_USEC;
72862306a36Sopenharmony_ci	task->event->event.sequence = atomic_inc_return(&task->ipp->sequence);
72962306a36Sopenharmony_ci
73062306a36Sopenharmony_ci	drm_send_event(task->ipp->drm_dev, &task->event->base);
73162306a36Sopenharmony_ci}
73262306a36Sopenharmony_ci
73362306a36Sopenharmony_cistatic int exynos_drm_ipp_task_cleanup(struct exynos_drm_ipp_task *task)
73462306a36Sopenharmony_ci{
73562306a36Sopenharmony_ci	int ret = task->ret;
73662306a36Sopenharmony_ci
73762306a36Sopenharmony_ci	if (ret == 0 && task->event) {
73862306a36Sopenharmony_ci		exynos_drm_ipp_event_send(task);
73962306a36Sopenharmony_ci		/* ensure event won't be canceled on task free */
74062306a36Sopenharmony_ci		task->event = NULL;
74162306a36Sopenharmony_ci	}
74262306a36Sopenharmony_ci
74362306a36Sopenharmony_ci	exynos_drm_ipp_task_free(task->ipp, task);
74462306a36Sopenharmony_ci	return ret;
74562306a36Sopenharmony_ci}
74662306a36Sopenharmony_ci
74762306a36Sopenharmony_cistatic void exynos_drm_ipp_cleanup_work(struct work_struct *work)
74862306a36Sopenharmony_ci{
74962306a36Sopenharmony_ci	struct exynos_drm_ipp_task *task = container_of(work,
75062306a36Sopenharmony_ci				      struct exynos_drm_ipp_task, cleanup_work);
75162306a36Sopenharmony_ci
75262306a36Sopenharmony_ci	exynos_drm_ipp_task_cleanup(task);
75362306a36Sopenharmony_ci}
75462306a36Sopenharmony_ci
75562306a36Sopenharmony_cistatic void exynos_drm_ipp_next_task(struct exynos_drm_ipp *ipp);
75662306a36Sopenharmony_ci
75762306a36Sopenharmony_ci/**
75862306a36Sopenharmony_ci * exynos_drm_ipp_task_done - finish given task and set return code
75962306a36Sopenharmony_ci * @task: ipp task to finish
76062306a36Sopenharmony_ci * @ret: error code or 0 if operation has been performed successfully
76162306a36Sopenharmony_ci */
76262306a36Sopenharmony_civoid exynos_drm_ipp_task_done(struct exynos_drm_ipp_task *task, int ret)
76362306a36Sopenharmony_ci{
76462306a36Sopenharmony_ci	struct exynos_drm_ipp *ipp = task->ipp;
76562306a36Sopenharmony_ci	unsigned long flags;
76662306a36Sopenharmony_ci
76762306a36Sopenharmony_ci	DRM_DEV_DEBUG_DRIVER(task->dev, "ipp: %d, task %pK done: %d\n",
76862306a36Sopenharmony_ci			     ipp->id, task, ret);
76962306a36Sopenharmony_ci
77062306a36Sopenharmony_ci	spin_lock_irqsave(&ipp->lock, flags);
77162306a36Sopenharmony_ci	if (ipp->task == task)
77262306a36Sopenharmony_ci		ipp->task = NULL;
77362306a36Sopenharmony_ci	task->flags |= DRM_EXYNOS_IPP_TASK_DONE;
77462306a36Sopenharmony_ci	task->ret = ret;
77562306a36Sopenharmony_ci	spin_unlock_irqrestore(&ipp->lock, flags);
77662306a36Sopenharmony_ci
77762306a36Sopenharmony_ci	exynos_drm_ipp_next_task(ipp);
77862306a36Sopenharmony_ci	wake_up(&ipp->done_wq);
77962306a36Sopenharmony_ci
78062306a36Sopenharmony_ci	if (task->flags & DRM_EXYNOS_IPP_TASK_ASYNC) {
78162306a36Sopenharmony_ci		INIT_WORK(&task->cleanup_work, exynos_drm_ipp_cleanup_work);
78262306a36Sopenharmony_ci		schedule_work(&task->cleanup_work);
78362306a36Sopenharmony_ci	}
78462306a36Sopenharmony_ci}
78562306a36Sopenharmony_ci
78662306a36Sopenharmony_cistatic void exynos_drm_ipp_next_task(struct exynos_drm_ipp *ipp)
78762306a36Sopenharmony_ci{
78862306a36Sopenharmony_ci	struct exynos_drm_ipp_task *task;
78962306a36Sopenharmony_ci	unsigned long flags;
79062306a36Sopenharmony_ci	int ret;
79162306a36Sopenharmony_ci
79262306a36Sopenharmony_ci	DRM_DEV_DEBUG_DRIVER(ipp->dev, "ipp: %d, try to run new task\n",
79362306a36Sopenharmony_ci			     ipp->id);
79462306a36Sopenharmony_ci
79562306a36Sopenharmony_ci	spin_lock_irqsave(&ipp->lock, flags);
79662306a36Sopenharmony_ci
79762306a36Sopenharmony_ci	if (ipp->task || list_empty(&ipp->todo_list)) {
79862306a36Sopenharmony_ci		spin_unlock_irqrestore(&ipp->lock, flags);
79962306a36Sopenharmony_ci		return;
80062306a36Sopenharmony_ci	}
80162306a36Sopenharmony_ci
80262306a36Sopenharmony_ci	task = list_first_entry(&ipp->todo_list, struct exynos_drm_ipp_task,
80362306a36Sopenharmony_ci				head);
80462306a36Sopenharmony_ci	list_del_init(&task->head);
80562306a36Sopenharmony_ci	ipp->task = task;
80662306a36Sopenharmony_ci
80762306a36Sopenharmony_ci	spin_unlock_irqrestore(&ipp->lock, flags);
80862306a36Sopenharmony_ci
80962306a36Sopenharmony_ci	DRM_DEV_DEBUG_DRIVER(ipp->dev,
81062306a36Sopenharmony_ci			     "ipp: %d, selected task %pK to run\n", ipp->id,
81162306a36Sopenharmony_ci			     task);
81262306a36Sopenharmony_ci
81362306a36Sopenharmony_ci	ret = ipp->funcs->commit(ipp, task);
81462306a36Sopenharmony_ci	if (ret)
81562306a36Sopenharmony_ci		exynos_drm_ipp_task_done(task, ret);
81662306a36Sopenharmony_ci}
81762306a36Sopenharmony_ci
81862306a36Sopenharmony_cistatic void exynos_drm_ipp_schedule_task(struct exynos_drm_ipp *ipp,
81962306a36Sopenharmony_ci					 struct exynos_drm_ipp_task *task)
82062306a36Sopenharmony_ci{
82162306a36Sopenharmony_ci	unsigned long flags;
82262306a36Sopenharmony_ci
82362306a36Sopenharmony_ci	spin_lock_irqsave(&ipp->lock, flags);
82462306a36Sopenharmony_ci	list_add(&task->head, &ipp->todo_list);
82562306a36Sopenharmony_ci	spin_unlock_irqrestore(&ipp->lock, flags);
82662306a36Sopenharmony_ci
82762306a36Sopenharmony_ci	exynos_drm_ipp_next_task(ipp);
82862306a36Sopenharmony_ci}
82962306a36Sopenharmony_ci
83062306a36Sopenharmony_cistatic void exynos_drm_ipp_task_abort(struct exynos_drm_ipp *ipp,
83162306a36Sopenharmony_ci				      struct exynos_drm_ipp_task *task)
83262306a36Sopenharmony_ci{
83362306a36Sopenharmony_ci	unsigned long flags;
83462306a36Sopenharmony_ci
83562306a36Sopenharmony_ci	spin_lock_irqsave(&ipp->lock, flags);
83662306a36Sopenharmony_ci	if (task->flags & DRM_EXYNOS_IPP_TASK_DONE) {
83762306a36Sopenharmony_ci		/* already completed task */
83862306a36Sopenharmony_ci		exynos_drm_ipp_task_cleanup(task);
83962306a36Sopenharmony_ci	} else if (ipp->task != task) {
84062306a36Sopenharmony_ci		/* task has not been scheduled for execution yet */
84162306a36Sopenharmony_ci		list_del_init(&task->head);
84262306a36Sopenharmony_ci		exynos_drm_ipp_task_cleanup(task);
84362306a36Sopenharmony_ci	} else {
84462306a36Sopenharmony_ci		/*
84562306a36Sopenharmony_ci		 * currently processed task, call abort() and perform
84662306a36Sopenharmony_ci		 * cleanup with async worker
84762306a36Sopenharmony_ci		 */
84862306a36Sopenharmony_ci		task->flags |= DRM_EXYNOS_IPP_TASK_ASYNC;
84962306a36Sopenharmony_ci		spin_unlock_irqrestore(&ipp->lock, flags);
85062306a36Sopenharmony_ci		if (ipp->funcs->abort)
85162306a36Sopenharmony_ci			ipp->funcs->abort(ipp, task);
85262306a36Sopenharmony_ci		return;
85362306a36Sopenharmony_ci	}
85462306a36Sopenharmony_ci	spin_unlock_irqrestore(&ipp->lock, flags);
85562306a36Sopenharmony_ci}
85662306a36Sopenharmony_ci
85762306a36Sopenharmony_ci/**
85862306a36Sopenharmony_ci * exynos_drm_ipp_commit_ioctl - perform image processing operation
85962306a36Sopenharmony_ci * @dev: DRM device
86062306a36Sopenharmony_ci * @data: ioctl data
86162306a36Sopenharmony_ci * @file_priv: DRM file info
86262306a36Sopenharmony_ci *
86362306a36Sopenharmony_ci * Construct a ipp task from the set of properties provided from the user
86462306a36Sopenharmony_ci * and try to schedule it to framebuffer processor hardware.
86562306a36Sopenharmony_ci *
86662306a36Sopenharmony_ci * Called by the user via ioctl.
86762306a36Sopenharmony_ci *
86862306a36Sopenharmony_ci * Returns:
86962306a36Sopenharmony_ci * Zero on success, negative errno on failure.
87062306a36Sopenharmony_ci */
87162306a36Sopenharmony_ciint exynos_drm_ipp_commit_ioctl(struct drm_device *dev, void *data,
87262306a36Sopenharmony_ci				struct drm_file *file_priv)
87362306a36Sopenharmony_ci{
87462306a36Sopenharmony_ci	struct drm_exynos_ioctl_ipp_commit *arg = data;
87562306a36Sopenharmony_ci	struct exynos_drm_ipp *ipp;
87662306a36Sopenharmony_ci	struct exynos_drm_ipp_task *task;
87762306a36Sopenharmony_ci	int ret = 0;
87862306a36Sopenharmony_ci
87962306a36Sopenharmony_ci	if ((arg->flags & ~DRM_EXYNOS_IPP_FLAGS) || arg->reserved)
88062306a36Sopenharmony_ci		return -EINVAL;
88162306a36Sopenharmony_ci
88262306a36Sopenharmony_ci	/* can't test and expect an event at the same time */
88362306a36Sopenharmony_ci	if ((arg->flags & DRM_EXYNOS_IPP_FLAG_TEST_ONLY) &&
88462306a36Sopenharmony_ci			(arg->flags & DRM_EXYNOS_IPP_FLAG_EVENT))
88562306a36Sopenharmony_ci		return -EINVAL;
88662306a36Sopenharmony_ci
88762306a36Sopenharmony_ci	ipp = __ipp_get(arg->ipp_id);
88862306a36Sopenharmony_ci	if (!ipp)
88962306a36Sopenharmony_ci		return -ENOENT;
89062306a36Sopenharmony_ci
89162306a36Sopenharmony_ci	task = exynos_drm_ipp_task_alloc(ipp);
89262306a36Sopenharmony_ci	if (!task)
89362306a36Sopenharmony_ci		return -ENOMEM;
89462306a36Sopenharmony_ci
89562306a36Sopenharmony_ci	ret = exynos_drm_ipp_task_set(task, arg);
89662306a36Sopenharmony_ci	if (ret)
89762306a36Sopenharmony_ci		goto free;
89862306a36Sopenharmony_ci
89962306a36Sopenharmony_ci	ret = exynos_drm_ipp_task_check(task);
90062306a36Sopenharmony_ci	if (ret)
90162306a36Sopenharmony_ci		goto free;
90262306a36Sopenharmony_ci
90362306a36Sopenharmony_ci	ret = exynos_drm_ipp_task_setup_buffers(task, file_priv);
90462306a36Sopenharmony_ci	if (ret || arg->flags & DRM_EXYNOS_IPP_FLAG_TEST_ONLY)
90562306a36Sopenharmony_ci		goto free;
90662306a36Sopenharmony_ci
90762306a36Sopenharmony_ci	if (arg->flags & DRM_EXYNOS_IPP_FLAG_EVENT) {
90862306a36Sopenharmony_ci		ret = exynos_drm_ipp_event_create(task, file_priv,
90962306a36Sopenharmony_ci						 arg->user_data);
91062306a36Sopenharmony_ci		if (ret)
91162306a36Sopenharmony_ci			goto free;
91262306a36Sopenharmony_ci	}
91362306a36Sopenharmony_ci
91462306a36Sopenharmony_ci	/*
91562306a36Sopenharmony_ci	 * Queue task for processing on the hardware. task object will be
91662306a36Sopenharmony_ci	 * then freed after exynos_drm_ipp_task_done()
91762306a36Sopenharmony_ci	 */
91862306a36Sopenharmony_ci	if (arg->flags & DRM_EXYNOS_IPP_FLAG_NONBLOCK) {
91962306a36Sopenharmony_ci		DRM_DEV_DEBUG_DRIVER(ipp->dev,
92062306a36Sopenharmony_ci				     "ipp: %d, nonblocking processing task %pK\n",
92162306a36Sopenharmony_ci				     ipp->id, task);
92262306a36Sopenharmony_ci
92362306a36Sopenharmony_ci		task->flags |= DRM_EXYNOS_IPP_TASK_ASYNC;
92462306a36Sopenharmony_ci		exynos_drm_ipp_schedule_task(task->ipp, task);
92562306a36Sopenharmony_ci		ret = 0;
92662306a36Sopenharmony_ci	} else {
92762306a36Sopenharmony_ci		DRM_DEV_DEBUG_DRIVER(ipp->dev, "ipp: %d, processing task %pK\n",
92862306a36Sopenharmony_ci				     ipp->id, task);
92962306a36Sopenharmony_ci		exynos_drm_ipp_schedule_task(ipp, task);
93062306a36Sopenharmony_ci		ret = wait_event_interruptible(ipp->done_wq,
93162306a36Sopenharmony_ci					task->flags & DRM_EXYNOS_IPP_TASK_DONE);
93262306a36Sopenharmony_ci		if (ret)
93362306a36Sopenharmony_ci			exynos_drm_ipp_task_abort(ipp, task);
93462306a36Sopenharmony_ci		else
93562306a36Sopenharmony_ci			ret = exynos_drm_ipp_task_cleanup(task);
93662306a36Sopenharmony_ci	}
93762306a36Sopenharmony_ci	return ret;
93862306a36Sopenharmony_cifree:
93962306a36Sopenharmony_ci	exynos_drm_ipp_task_free(ipp, task);
94062306a36Sopenharmony_ci
94162306a36Sopenharmony_ci	return ret;
94262306a36Sopenharmony_ci}
943