diff options
author | Eunchul Kim <chulspro.kim@samsung.com> | 2012-12-14 18:10:31 +0900 |
---|---|---|
committer | Inki Dae <daeinki@gmail.com> | 2012-12-15 02:29:08 +0900 |
commit | cb471f14b5eebfed22bb9f2d0f06601f171c574a (patch) | |
tree | 955334335ef7d041553dc6d6b1351b9a8f3fa593 /drivers/gpu/drm/exynos | |
parent | d636ead86fb806085de4ce98693e8d91c419d8f3 (diff) |
drm/exynos: add ipp subsystem
This patch adds Image Post Processing(IPP) support for exynos drm driver.
IPP supports image scaler/rotator and input/output DMA operations
using IPP subsystem framework to control FIMC, Rotator and GSC hardware
and supports some user interfaces for user side.
And each IPP-based drivers support Memory to Memory operations
with various converting. And in case of FIMC hardware, it also supports
Writeback and Display output operations through local path.
Features:
- Memory to Memory operation support.
- Various pixel formats support.
- Image scaling support.
- Color Space Conversion support.
- Image crop operation support.
- Rotate operation support to 90, 180 or 270 degree.
- Flip operation support to vertical, horizontal or both.
- Writeback operation support to display blended image of FIMD fifo on screen
A summary to IPP Subsystem operations:
First of all, user should get property capabilities from IPP subsystem
and set these properties to hardware registers for desired operations.
The properties could be pixel format, position, rotation degree and
flip operation.
And next, user should set source and destination buffer data using
DRM_EXYNOS_IPP_QUEUE_BUF ioctl command with gem handles to source and
destinition buffers.
And next, user can control user-desired hardware with desired operations
such as play, stop, pause and resume controls.
And finally, user can aware of dma operation completion and also get
destination buffer that it contains user-desried result through dequeue
command.
IOCTL commands:
- DRM_EXYNOS_IPP_GET_PROPERTY
. get ipp driver capabilitis and id.
- DRM_EXYNOS_IPP_SET_PROPERTY
. set format, position, rotation, flip to source and destination buffers
- DRM_EXYNOS_IPP_QUEUE_BUF
. enqueue/dequeue buffer and make event list.
- DRM_EXYNOS_IPP_CMD_CTRL
. play/stop/pause/resume control.
Event:
- DRM_EXYNOS_IPP_EVENT
. a event to notify dma operation completion to user side.
Basic control flow:
Open -> Get properties -> User choose desired IPP sub driver(FIMC, Rotator
or GSCALER) -> Set Property -> Create gem handle -> Enqueue to source and
destination buffers -> Command control(Play) -> Event is notified to User
-> User gets destinition buffer complated -> (Enqueue to source and
destination buffers -> Event is notified to User) * N -> Queue/Dequeue to
source and destination buffers -> Command control(Stop) -> Free gem handle
-> Close
Changelog v1 ~ v5:
- added comments, code fixups and cleanups.
Signed-off-by: Eunchul Kim <chulspro.kim@samsung.com>
Signed-off-by: Jinyoung Jeon <jy0.jeon@samsung.com>
Signed-off-by: Inki Dae <inki.dae@samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
Diffstat (limited to 'drivers/gpu/drm/exynos')
-rw-r--r-- | drivers/gpu/drm/exynos/Kconfig | 6 | ||||
-rw-r--r-- | drivers/gpu/drm/exynos/Makefile | 1 | ||||
-rw-r--r-- | drivers/gpu/drm/exynos/exynos_drm_drv.c | 24 | ||||
-rw-r--r-- | drivers/gpu/drm/exynos/exynos_drm_drv.h | 7 | ||||
-rw-r--r-- | drivers/gpu/drm/exynos/exynos_drm_ipp.c | 2042 | ||||
-rw-r--r-- | drivers/gpu/drm/exynos/exynos_drm_ipp.h | 266 |
6 files changed, 2346 insertions, 0 deletions
diff --git a/drivers/gpu/drm/exynos/Kconfig b/drivers/gpu/drm/exynos/Kconfig index 86fb75d3fca..80ab242e273 100644 --- a/drivers/gpu/drm/exynos/Kconfig +++ b/drivers/gpu/drm/exynos/Kconfig @@ -45,3 +45,9 @@ config DRM_EXYNOS_G2D depends on DRM_EXYNOS && !VIDEO_SAMSUNG_S5P_G2D help Choose this option if you want to use Exynos G2D for DRM. + +config DRM_EXYNOS_IPP + bool "Exynos DRM IPP" + depends on DRM_EXYNOS + help + Choose this option if you want to use IPP feature for DRM. diff --git a/drivers/gpu/drm/exynos/Makefile b/drivers/gpu/drm/exynos/Makefile index 26813b8a505..6c536ce4d95 100644 --- a/drivers/gpu/drm/exynos/Makefile +++ b/drivers/gpu/drm/exynos/Makefile @@ -16,5 +16,6 @@ exynosdrm-$(CONFIG_DRM_EXYNOS_HDMI) += exynos_hdmi.o exynos_mixer.o \ exynos_drm_hdmi.o exynosdrm-$(CONFIG_DRM_EXYNOS_VIDI) += exynos_drm_vidi.o exynosdrm-$(CONFIG_DRM_EXYNOS_G2D) += exynos_drm_g2d.o +exynosdrm-$(CONFIG_DRM_EXYNOS_IPP) += exynos_drm_ipp.o obj-$(CONFIG_DRM_EXYNOS) += exynosdrm.o diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.c b/drivers/gpu/drm/exynos/exynos_drm_drv.c index 4a1168d3e90..0eb8a972e21 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_drv.c +++ b/drivers/gpu/drm/exynos/exynos_drm_drv.c @@ -40,6 +40,7 @@ #include "exynos_drm_vidi.h" #include "exynos_drm_dmabuf.h" #include "exynos_drm_g2d.h" +#include "exynos_drm_ipp.h" #include "exynos_drm_iommu.h" #define DRIVER_NAME "exynos" @@ -249,6 +250,14 @@ static struct drm_ioctl_desc exynos_ioctls[] = { exynos_g2d_set_cmdlist_ioctl, DRM_UNLOCKED | DRM_AUTH), DRM_IOCTL_DEF_DRV(EXYNOS_G2D_EXEC, exynos_g2d_exec_ioctl, DRM_UNLOCKED | DRM_AUTH), + DRM_IOCTL_DEF_DRV(EXYNOS_IPP_GET_PROPERTY, + exynos_drm_ipp_get_property, DRM_UNLOCKED | DRM_AUTH), + DRM_IOCTL_DEF_DRV(EXYNOS_IPP_SET_PROPERTY, + exynos_drm_ipp_set_property, DRM_UNLOCKED | DRM_AUTH), + DRM_IOCTL_DEF_DRV(EXYNOS_IPP_QUEUE_BUF, + exynos_drm_ipp_queue_buf, DRM_UNLOCKED | DRM_AUTH), + DRM_IOCTL_DEF_DRV(EXYNOS_IPP_CMD_CTRL, + exynos_drm_ipp_cmd_ctrl, DRM_UNLOCKED | DRM_AUTH), }; static const struct file_operations exynos_drm_driver_fops = { @@ -363,6 +372,12 @@ static int __init exynos_drm_init(void) goto out_g2d; #endif +#ifdef CONFIG_DRM_EXYNOS_IPP + ret = platform_driver_register(&ipp_driver); + if (ret < 0) + goto out_ipp; +#endif + ret = platform_driver_register(&exynos_drm_platform_driver); if (ret < 0) goto out_drm; @@ -380,6 +395,11 @@ out: platform_driver_unregister(&exynos_drm_platform_driver); out_drm: +#ifdef CONFIG_DRM_EXYNOS_IPP + platform_driver_unregister(&ipp_driver); +out_ipp: +#endif + #ifdef CONFIG_DRM_EXYNOS_G2D platform_driver_unregister(&g2d_driver); out_g2d: @@ -416,6 +436,10 @@ static void __exit exynos_drm_exit(void) platform_driver_unregister(&exynos_drm_platform_driver); +#ifdef CONFIG_DRM_EXYNOS_IPP + platform_driver_unregister(&ipp_driver); +#endif + #ifdef CONFIG_DRM_EXYNOS_G2D platform_driver_unregister(&g2d_driver); #endif diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.h b/drivers/gpu/drm/exynos/exynos_drm_drv.h index e4ea74df4fc..44ab3c7b6a9 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_drv.h +++ b/drivers/gpu/drm/exynos/exynos_drm_drv.h @@ -232,8 +232,14 @@ struct exynos_drm_g2d_private { struct list_head userptr_list; }; +struct exynos_drm_ipp_private { + struct device *dev; + struct list_head event_list; +}; + struct drm_exynos_file_private { struct exynos_drm_g2d_private *g2d_priv; + struct exynos_drm_ipp_private *ipp_priv; }; /* @@ -343,4 +349,5 @@ extern struct platform_driver mixer_driver; extern struct platform_driver exynos_drm_common_hdmi_driver; extern struct platform_driver vidi_driver; extern struct platform_driver g2d_driver; +extern struct platform_driver ipp_driver; #endif diff --git a/drivers/gpu/drm/exynos/exynos_drm_ipp.c b/drivers/gpu/drm/exynos/exynos_drm_ipp.c new file mode 100644 index 00000000000..c640935ab7d --- /dev/null +++ b/drivers/gpu/drm/exynos/exynos_drm_ipp.c @@ -0,0 +1,2042 @@ +/* + * Copyright (C) 2012 Samsung Electronics Co.Ltd + * Authors: + * Eunchul Kim <chulspro.kim@samsung.com> + * Jinyoung Jeon <jy0.jeon@samsung.com> + * Sangmin Lee <lsmin.lee@samsung.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/types.h> +#include <linux/clk.h> +#include <linux/pm_runtime.h> +#include <plat/map-base.h> + +#include <drm/drmP.h> +#include <drm/exynos_drm.h> +#include "exynos_drm_drv.h" +#include "exynos_drm_gem.h" +#include "exynos_drm_ipp.h" + +/* + * IPP is stand for Image Post Processing and + * supports image scaler/rotator and input/output DMA operations. + * using FIMC, GSC, Rotator, so on. + * IPP is integration device driver of same attribute h/w + */ + +/* + * TODO + * 1. expand command control id. + * 2. integrate property and config. + * 3. removed send_event id check routine. + * 4. compare send_event id if needed. + * 5. free subdrv_remove notifier callback list if needed. + * 6. need to check subdrv_open about multi-open. + * 7. need to power_on implement power and sysmmu ctrl. + */ + +#define get_ipp_context(dev) platform_get_drvdata(to_platform_device(dev)) +#define ipp_is_m2m_cmd(c) (c == IPP_CMD_M2M) + +/* + * A structure of event. + * + * @base: base of event. + * @event: ipp event. + */ +struct drm_exynos_ipp_send_event { + struct drm_pending_event base; + struct drm_exynos_ipp_event event; +}; + +/* + * A structure of memory node. + * + * @list: list head to memory queue information. + * @ops_id: id of operations. + * @prop_id: id of property. + * @buf_id: id of buffer. + * @buf_info: gem objects and dma address, size. + * @filp: a pointer to drm_file. + */ +struct drm_exynos_ipp_mem_node { + struct list_head list; + enum drm_exynos_ops_id ops_id; + u32 prop_id; + u32 buf_id; + struct drm_exynos_ipp_buf_info buf_info; + struct drm_file *filp; +}; + +/* + * A structure of ipp context. + * + * @subdrv: prepare initialization using subdrv. + * @ipp_lock: lock for synchronization of access to ipp_idr. + * @prop_lock: lock for synchronization of access to prop_idr. + * @ipp_idr: ipp driver idr. + * @prop_idr: property idr. + * @event_workq: event work queue. + * @cmd_workq: command work queue. + */ +struct ipp_context { + struct exynos_drm_subdrv subdrv; + struct mutex ipp_lock; + struct mutex prop_lock; + struct idr ipp_idr; + struct idr prop_idr; + struct workqueue_struct *event_workq; + struct workqueue_struct *cmd_workq; +}; + +static LIST_HEAD(exynos_drm_ippdrv_list); +static DEFINE_MUTEX(exynos_drm_ippdrv_lock); +static BLOCKING_NOTIFIER_HEAD(exynos_drm_ippnb_list); + +int exynos_drm_ippdrv_register(struct exynos_drm_ippdrv *ippdrv) +{ + DRM_DEBUG_KMS("%s\n", __func__); + + if (!ippdrv) + return -EINVAL; + + mutex_lock(&exynos_drm_ippdrv_lock); + list_add_tail(&ippdrv->drv_list, &exynos_drm_ippdrv_list); + mutex_unlock(&exynos_drm_ippdrv_lock); + + return 0; +} + +int exynos_drm_ippdrv_unregister(struct exynos_drm_ippdrv *ippdrv) +{ + DRM_DEBUG_KMS("%s\n", __func__); + + if (!ippdrv) + return -EINVAL; + + mutex_lock(&exynos_drm_ippdrv_lock); + list_del(&ippdrv->drv_list); + mutex_unlock(&exynos_drm_ippdrv_lock); + + return 0; +} + +static int ipp_create_id(struct idr *id_idr, struct mutex *lock, void *obj, + u32 *idp) +{ + int ret; + + DRM_DEBUG_KMS("%s\n", __func__); + +again: + /* ensure there is space available to allocate a handle */ + if (idr_pre_get(id_idr, GFP_KERNEL) == 0) { + DRM_ERROR("failed to get idr.\n"); + return -ENOMEM; + } + + /* do the allocation under our mutexlock */ + mutex_lock(lock); + ret = idr_get_new_above(id_idr, obj, 1, (int *)idp); + mutex_unlock(lock); + if (ret == -EAGAIN) + goto again; + + return ret; +} + +static void *ipp_find_obj(struct idr *id_idr, struct mutex *lock, u32 id) +{ + void *obj; + + DRM_DEBUG_KMS("%s:id[%d]\n", __func__, id); + + mutex_lock(lock); + + /* find object using handle */ + obj = idr_find(id_idr, id); + if (!obj) { + DRM_ERROR("failed to find object.\n"); + mutex_unlock(lock); + return ERR_PTR(-ENODEV); + } + + mutex_unlock(lock); + + return obj; +} + +static inline bool ipp_check_dedicated(struct exynos_drm_ippdrv *ippdrv, + enum drm_exynos_ipp_cmd cmd) +{ + /* + * check dedicated flag and WB, OUTPUT operation with + * power on state. + */ + if (ippdrv->dedicated || (!ipp_is_m2m_cmd(cmd) && + !pm_runtime_suspended(ippdrv->dev))) + return true; + + return false; +} + +static struct exynos_drm_ippdrv *ipp_find_driver(struct ipp_context *ctx, + struct drm_exynos_ipp_property *property) +{ + struct exynos_drm_ippdrv *ippdrv; + u32 ipp_id = property->ipp_id; + + DRM_DEBUG_KMS("%s:ipp_id[%d]\n", __func__, ipp_id); + + if (ipp_id) { + /* find ipp driver using idr */ + ippdrv = ipp_find_obj(&ctx->ipp_idr, &ctx->ipp_lock, + ipp_id); + if (IS_ERR_OR_NULL(ippdrv)) { + DRM_ERROR("not found ipp%d driver.\n", ipp_id); + return ippdrv; + } + + /* + * WB, OUTPUT opertion not supported multi-operation. + * so, make dedicated state at set property ioctl. + * when ipp driver finished operations, clear dedicated flags. + */ + if (ipp_check_dedicated(ippdrv, property->cmd)) { + DRM_ERROR("already used choose device.\n"); + return ERR_PTR(-EBUSY); + } + + /* + * This is necessary to find correct device in ipp drivers. + * ipp drivers have different abilities, + * so need to check property. + */ + if (ippdrv->check_property && + ippdrv->check_property(ippdrv->dev, property)) { + DRM_ERROR("not support property.\n"); + return ERR_PTR(-EINVAL); + } + + return ippdrv; + } else { + /* + * This case is search all ipp driver for finding. + * user application don't set ipp_id in this case, + * so ipp subsystem search correct driver in driver list. + */ + list_for_each_entry(ippdrv, &exynos_drm_ippdrv_list, drv_list) { + if (ipp_check_dedicated(ippdrv, property->cmd)) { + DRM_DEBUG_KMS("%s:used device.\n", __func__); + continue; + } + + if (ippdrv->check_property && + ippdrv->check_property(ippdrv->dev, property)) { + DRM_DEBUG_KMS("%s:not support property.\n", + __func__); + continue; + } + + return ippdrv; + } + + DRM_ERROR("not support ipp driver operations.\n"); + } + + return ERR_PTR(-ENODEV); +} + +static struct exynos_drm_ippdrv *ipp_find_drv_by_handle(u32 prop_id) +{ + struct exynos_drm_ippdrv *ippdrv; + struct drm_exynos_ipp_cmd_node *c_node; + int count = 0; + + DRM_DEBUG_KMS("%s:prop_id[%d]\n", __func__, prop_id); + + if (list_empty(&exynos_drm_ippdrv_list)) { + DRM_DEBUG_KMS("%s:ippdrv_list is empty.\n", __func__); + return ERR_PTR(-ENODEV); + } + + /* + * This case is search ipp driver by prop_id handle. + * sometimes, ipp subsystem find driver by prop_id. + * e.g PAUSE state, queue buf, command contro. + */ + list_for_each_entry(ippdrv, &exynos_drm_ippdrv_list, drv_list) { + DRM_DEBUG_KMS("%s:count[%d]ippdrv[0x%x]\n", __func__, + count++, (int)ippdrv); + + if (!list_empty(&ippdrv->cmd_list)) { + list_for_each_entry(c_node, &ippdrv->cmd_list, list) + if (c_node->property.prop_id == prop_id) + return ippdrv; + } + } + + return ERR_PTR(-ENODEV); +} + +int exynos_drm_ipp_get_property(struct drm_device *drm_dev, void *data, + struct drm_file *file) +{ + struct drm_exynos_file_private *file_priv = file->driver_priv; + struct exynos_drm_ipp_private *priv = file_priv->ipp_priv; + struct device *dev = priv->dev; + struct ipp_context *ctx = get_ipp_context(dev); + struct drm_exynos_ipp_prop_list *prop_list = data; + struct exynos_drm_ippdrv *ippdrv; + int count = 0; + + DRM_DEBUG_KMS("%s\n", __func__); + + if (!ctx) { + DRM_ERROR("invalid context.\n"); + return -EINVAL; + } + + if (!prop_list) { + DRM_ERROR("invalid property parameter.\n"); + return -EINVAL; + } + + DRM_DEBUG_KMS("%s:ipp_id[%d]\n", __func__, prop_list->ipp_id); + + if (!prop_list->ipp_id) { + list_for_each_entry(ippdrv, &exynos_drm_ippdrv_list, drv_list) + count++; + /* + * Supports ippdrv list count for user application. + * First step user application getting ippdrv count. + * and second step getting ippdrv capability using ipp_id. + */ + prop_list->count = count; + } else { + /* + * Getting ippdrv capability by ipp_id. + * some deivce not supported wb, output interface. + * so, user application detect correct ipp driver + * using this ioctl. + */ + ippdrv = ipp_find_obj(&ctx->ipp_idr, &ctx->ipp_lock, + prop_list->ipp_id); + if (!ippdrv) { + DRM_ERROR("not found ipp%d driver.\n", + prop_list->ipp_id); + return -EINVAL; + } + + prop_list = ippdrv->prop_list; + } + + return 0; +} + +static void ipp_print_property(struct drm_exynos_ipp_property *property, + int idx) +{ + struct drm_exynos_ipp_config *config = &property->config[idx]; + struct drm_exynos_pos *pos = &config->pos; + struct drm_exynos_sz *sz = &config->sz; + + DRM_DEBUG_KMS("%s:prop_id[%d]ops[%s]fmt[0x%x]\n", + __func__, property->prop_id, idx ? "dst" : "src", config->fmt); + + DRM_DEBUG_KMS("%s:pos[%d %d %d %d]sz[%d %d]f[%d]r[%d]\n", + __func__, pos->x, pos->y, pos->w, pos->h, + sz->hsize, sz->vsize, config->flip, config->degree); +} + +static int ipp_find_and_set_property(struct drm_exynos_ipp_property *property) +{ + struct exynos_drm_ippdrv *ippdrv; + struct drm_exynos_ipp_cmd_node *c_node; + u32 prop_id = property->prop_id; + + DRM_DEBUG_KMS("%s:prop_id[%d]\n", __func__, prop_id); + + ippdrv = ipp_find_drv_by_handle(prop_id); + if (IS_ERR_OR_NULL(ippdrv)) { + DRM_ERROR("failed to get ipp driver.\n"); + return -EINVAL; + } + + /* + * Find command node using command list in ippdrv. + * when we find this command no using prop_id. + * return property information set in this command node. + */ + list_for_each_entry(c_node, &ippdrv->cmd_list, list) { + if ((c_node->property.prop_id == prop_id) && + (c_node->state == IPP_STATE_STOP)) { + DRM_DEBUG_KMS("%s:found cmd[%d]ippdrv[0x%x]\n", + __func__, property->cmd, (int)ippdrv); + + c_node->property = *property; + return 0; + } + } + + DRM_ERROR("failed to search property.\n"); + + return -EINVAL; +} + +static struct drm_exynos_ipp_cmd_work *ipp_create_cmd_work(void) +{ + struct drm_exynos_ipp_cmd_work *cmd_work; + + DRM_DEBUG_KMS("%s\n", __func__); + + cmd_work = kzalloc(sizeof(*cmd_work), GFP_KERNEL); + if (!cmd_work) { + DRM_ERROR("failed to alloc cmd_work.\n"); + return ERR_PTR(-ENOMEM); + } + + INIT_WORK((struct work_struct *)cmd_work, ipp_sched_cmd); + + return cmd_work; +} + +static struct drm_exynos_ipp_event_work *ipp_create_event_work(void) +{ + struct drm_exynos_ipp_event_work *event_work; + + DRM_DEBUG_KMS("%s\n", __func__); + + event_work = kzalloc(sizeof(*event_work), GFP_KERNEL); + if (!event_work) { + DRM_ERROR("failed to alloc event_work.\n"); + return ERR_PTR(-ENOMEM); + } + + INIT_WORK((struct work_struct *)event_work, ipp_sched_event); + + return event_work; +} + +int exynos_drm_ipp_set_property(struct drm_device *drm_dev, void *data, + struct drm_file *file) +{ + struct drm_exynos_file_private *file_priv = file->driver_priv; + struct exynos_drm_ipp_private *priv = file_priv->ipp_priv; + struct device *dev = priv->dev; + struct ipp_context *ctx = get_ipp_context(dev); + struct drm_exynos_ipp_property *property = data; + struct exynos_drm_ippdrv *ippdrv; + struct drm_exynos_ipp_cmd_node *c_node; + int ret, i; + + DRM_DEBUG_KMS("%s\n", __func__); + + if (!ctx) { + DRM_ERROR("invalid context.\n"); + return -EINVAL; + } + + if (!property) { + DRM_ERROR("invalid property parameter.\n"); + return -EINVAL; + } + + /* + * This is log print for user application property. + * user application set various property. + */ + for_each_ipp_ops(i) + ipp_print_property(property, i); + + /* + * set property ioctl generated new prop_id. + * but in this case already asigned prop_id using old set property. + * e.g PAUSE state. this case supports find current prop_id and use it + * instead of allocation. + */ + if (property->prop_id) { + DRM_DEBUG_KMS("%s:prop_id[%d]\n", __func__, property->prop_id); + return ipp_find_and_set_property(property); + } + + /* find ipp driver using ipp id */ + ippdrv = ipp_find_driver(ctx, property); + if (IS_ERR_OR_NULL(ippdrv)) { + DRM_ERROR("failed to get ipp driver.\n"); + return -EINVAL; + } + + /* allocate command node */ + c_node = kzalloc(sizeof(*c_node), GFP_KERNEL); + if (!c_node) { + DRM_ERROR("failed to allocate map node.\n"); + return -ENOMEM; + } + + /* create property id */ + ret = ipp_create_id(&ctx->prop_idr, &ctx->prop_lock, c_node, + &property->prop_id); + if (ret) { + DRM_ERROR("failed to create id.\n"); + goto err_clear; + } + + DRM_DEBUG_KMS("%s:created prop_id[%d]cmd[%d]ippdrv[0x%x]\n", + __func__, property->prop_id, property->cmd, (int)ippdrv); + + /* stored property information and ippdrv in private data */ + c_node->priv = priv; + c_node->property = *property; + c_node->state = IPP_STATE_IDLE; + + c_node->start_work = ipp_create_cmd_work(); + if (IS_ERR_OR_NULL(c_node->start_work)) { + DRM_ERROR("failed to create start work.\n"); + goto err_clear; + } + + c_node->stop_work = ipp_create_cmd_work(); + if (IS_ERR_OR_NULL(c_node->stop_work)) { + DRM_ERROR("failed to create stop work.\n"); + goto err_free_start; + } + + c_node->event_work = ipp_create_event_work(); + if (IS_ERR_OR_NULL(c_node->event_work)) { + DRM_ERROR("failed to create event work.\n"); + goto err_free_stop; + } + + mutex_init(&c_node->cmd_lock); + mutex_init(&c_node->mem_lock); + mutex_init(&c_node->event_lock); + + init_completion(&c_node->start_complete); + init_completion(&c_node->stop_complete); + + for_each_ipp_ops(i) + INIT_LIST_HEAD(&c_node->mem_list[i]); + + INIT_LIST_HEAD(&c_node->event_list); + list_splice_init(&priv->event_list, &c_node->event_list); + list_add_tail(&c_node->list, &ippdrv->cmd_list); + + /* make dedicated state without m2m */ + if (!ipp_is_m2m_cmd(property->cmd)) + ippdrv->dedicated = true; + + return 0; + +err_free_stop: + kfree(c_node->stop_work); +err_free_start: + kfree(c_node->start_work); +err_clear: + kfree(c_node); + return ret; +} + +static void ipp_clean_cmd_node(struct drm_exynos_ipp_cmd_node *c_node) +{ + DRM_DEBUG_KMS("%s\n", __func__); + + /* delete list */ + list_del(&c_node->list); + + /* destroy mutex */ + mutex_destroy(&c_node->cmd_lock); + mutex_destroy(&c_node->mem_lock); + mutex_destroy(&c_node->event_lock); + + /* free command node */ + kfree(c_node->start_work); + kfree(c_node->stop_work); + kfree(c_node->event_work); + kfree(c_node); +} + +static int ipp_check_mem_list(struct drm_exynos_ipp_cmd_node *c_node) +{ + struct drm_exynos_ipp_property *property = &c_node->property; + struct drm_exynos_ipp_mem_node *m_node; + struct list_head *head; + int ret, i, count[EXYNOS_DRM_OPS_MAX] = { 0, }; + + DRM_DEBUG_KMS("%s\n", __func__); + + mutex_lock(&c_node->mem_lock); + + for_each_ipp_ops(i) { + /* source/destination memory list */ + head = &c_node->mem_list[i]; + + if (list_empty(head)) { + DRM_DEBUG_KMS("%s:%s memory empty.\n", __func__, + i ? "dst" : "src"); + continue; + } + + /* find memory node entry */ + list_for_each_entry(m_node, head, list) { + DRM_DEBUG_KMS("%s:%s,count[%d]m_node[0x%x]\n", __func__, + i ? "dst" : "src", count[i], (int)m_node); + count[i]++; + } + } + + DRM_DEBUG_KMS("%s:min[%d]max[%d]\n", __func__, + min(count[EXYNOS_DRM_OPS_SRC], count[EXYNOS_DRM_OPS_DST]), + max(count[EXYNOS_DRM_OPS_SRC], count[EXYNOS_DRM_OPS_DST])); + + /* + * M2M operations should be need paired memory address. + * so, need to check minimum count about src, dst. + * other case not use paired memory, so use maximum count + */ + if (ipp_is_m2m_cmd(property->cmd)) + ret = min(count[EXYNOS_DRM_OPS_SRC], + count[EXYNOS_DRM_OPS_DST]); + else + ret = max(count[EXYNOS_DRM_OPS_SRC], + count[EXYNOS_DRM_OPS_DST]); + + mutex_unlock(&c_node->mem_lock); + + return ret; +} + +static struct drm_exynos_ipp_mem_node + *ipp_find_mem_node(struct drm_exynos_ipp_cmd_node *c_node, + struct drm_exynos_ipp_queue_buf *qbuf) +{ + struct drm_exynos_ipp_mem_node *m_node; + struct list_head *head; + int count = 0; + + DRM_DEBUG_KMS("%s:buf_id[%d]\n", __func__, qbuf->buf_id); + + /* source/destination memory list */ + head = &c_node->mem_list[qbuf->ops_id]; + + /* find memory node from memory list */ + list_for_each_entry(m_node, head, list) { + DRM_DEBUG_KMS("%s:count[%d]m_node[0x%x]\n", + __func__, count++, (int)m_node); + + /* compare buffer id */ + if (m_node->buf_id == qbuf->buf_id) + return m_node; + } + + return NULL; +} + +static int ipp_set_mem_node(struct exynos_drm_ippdrv *ippdrv, + struct drm_exynos_ipp_cmd_node *c_node, + struct drm_exynos_ipp_mem_node *m_node) +{ + struct exynos_drm_ipp_ops *ops = NULL; + int ret = 0; + + DRM_DEBUG_KMS("%s:node[0x%x]\n", __func__, (int)m_node); + + if (!m_node) { + DRM_ERROR("invalid queue node.\n"); + return -EFAULT; + } + + mutex_lock(&c_node->mem_lock); + + DRM_DEBUG_KMS("%s:ops_id[%d]\n", __func__, m_node->ops_id); + + /* get operations callback */ + ops = ippdrv->ops[m_node->ops_id]; + if (!ops) { + DRM_ERROR("not support ops.\n"); + ret = -EFAULT; + goto err_unlock; + } + + /* set address and enable irq */ + if (ops->set_addr) { + ret = ops->set_addr(ippdrv->dev, &m_node->buf_info, + m_node->buf_id, IPP_BUF_ENQUEUE); + if (ret) { + DRM_ERROR("failed to set addr.\n"); + goto err_unlock; + } + } + +err_unlock: + mutex_unlock(&c_node->mem_lock); + return ret; +} + +static struct drm_exynos_ipp_mem_node + *ipp_get_mem_node(struct drm_device *drm_dev, + struct drm_file *file, + struct drm_exynos_ipp_cmd_node *c_node, + struct drm_exynos_ipp_queue_buf *qbuf) +{ + struct drm_exynos_ipp_mem_node *m_node; + struct drm_exynos_ipp_buf_info buf_info; + void *addr; + int i; + + DRM_DEBUG_KMS("%s\n", __func__); + + mutex_lock(&c_node->mem_lock); + + m_node = kzalloc(sizeof(*m_node), GFP_KERNEL); + if (!m_node) { + DRM_ERROR("failed to allocate queue node.\n"); + goto err_unlock; + } + + /* clear base address for error handling */ + memset(&buf_info, 0x0, sizeof(buf_info)); + + /* operations, buffer id */ + m_node->ops_id = qbuf->ops_id; + m_node->prop_id = qbuf->prop_id; + m_node->buf_id = qbuf->buf_id; + + DRM_DEBUG_KMS("%s:m_node[0x%x]ops_id[%d]\n", __func__, + (int)m_node, qbuf->ops_id); + DRM_DEBUG_KMS("%s:prop_id[%d]buf_id[%d]\n", __func__, + qbuf->prop_id, m_node->buf_id); + + for_each_ipp_planar(i) { + DRM_DEBUG_KMS("%s:i[%d]handle[0x%x]\n", __func__, + i, qbuf->handle[i]); + + /* get dma address by handle */ + if (qbuf->handle[i]) { + addr = exynos_drm_gem_get_dma_addr(drm_dev, + qbuf->handle[i], file); + if (IS_ERR(addr)) { + DRM_ERROR("failed to get addr.\n"); + goto err_clear; + } + + buf_info.handles[i] = qbuf->handle[i]; + buf_info.base[i] = *(dma_addr_t *) addr; + DRM_DEBUG_KMS("%s:i[%d]base[0x%x]hd[0x%x]\n", + __func__, i, buf_info.base[i], + (int)buf_info.handles[i]); + } + } + + m_node->filp = file; + m_node->buf_info = buf_info; + list_add_tail(&m_node->list, &c_node->mem_list[qbuf->ops_id]); + + mutex_unlock(&c_node->mem_lock); + return m_node; + +err_clear: + kfree(m_node); +err_unlock: + mutex_unlock(&c_node->mem_lock); + return ERR_PTR(-EFAULT); +} + +static int ipp_put_mem_node(struct drm_device *drm_dev, + struct drm_exynos_ipp_cmd_node *c_node, + struct drm_exynos_ipp_mem_node *m_node) +{ + int i; + + DRM_DEBUG_KMS("%s:node[0x%x]\n", __func__, (int)m_node); + + if (!m_node) { + DRM_ERROR("invalid dequeue node.\n"); + return -EFAULT; + } + + if (list_empty(&m_node->list)) { + DRM_ERROR("empty memory node.\n"); + return -ENOMEM; + } + + mutex_lock(&c_node->mem_lock); + + DRM_DEBUG_KMS("%s:ops_id[%d]\n", __func__, m_node->ops_id); + + /* put gem buffer */ + for_each_ipp_planar(i) { + unsigned long handle = m_node->buf_info.handles[i]; + if (handle) + exynos_drm_gem_put_dma_addr(drm_dev, handle, + m_node->filp); + } + + /* delete list in queue */ + list_del(&m_node->list); + kfree(m_node); + + mutex_unlock(&c_node->mem_lock); + + return 0; +} + +static void ipp_free_event(struct drm_pending_event *event) +{ + kfree(event); +} + +static int ipp_get_event(struct drm_device *drm_dev, + struct drm_file *file, + struct drm_exynos_ipp_cmd_node *c_node, + struct drm_exynos_ipp_queue_buf *qbuf) +{ + struct drm_exynos_ipp_send_event *e; + unsigned long flags; + + DRM_DEBUG_KMS("%s:ops_id[%d]buf_id[%d]\n", __func__, + qbuf->ops_id, qbuf->buf_id); + + e = kzalloc(sizeof(*e), GFP_KERNEL); + + if (!e) { + DRM_ERROR("failed to allocate event.\n"); + spin_lock_irqsave(&drm_dev->event_lock, flags); + file->event_space += sizeof(e->event); + spin_unlock_irqrestore(&drm_dev->event_lock, flags); + return -ENOMEM; + } + + /* make event */ + e->event.base.type = DRM_EXYNOS_IPP_EVENT; + e->event.base.length = sizeof(e->event); + e->event.user_data = qbuf->user_data; + e->event.prop_id = qbuf->prop_id; + e->event.buf_id[EXYNOS_DRM_OPS_DST] = qbuf->buf_id; + e->base.event = &e->event.base; + e->base.file_priv = file; + e->base.destroy = ipp_free_event; + list_add_tail(&e->base.link, &c_node->event_list); + + return 0; +} + +static void ipp_put_event(struct drm_exynos_ipp_cmd_node *c_node, + struct drm_exynos_ipp_queue_buf *qbuf) +{ + struct drm_exynos_ipp_send_event *e, *te; + int count = 0; + + DRM_DEBUG_KMS("%s\n", __func__); + + if (list_empty(&c_node->event_list)) { + DRM_DEBUG_KMS("%s:event_list is empty.\n", __func__); + return; + } + + list_for_each_entry_safe(e, te, &c_node->event_list, base.link) { + DRM_DEBUG_KMS("%s:count[%d]e[0x%x]\n", + __func__, count++, (int)e); + + /* + * quf == NULL condition means all event deletion. + * stop operations want to delete all event list. + * another case delete only same buf id. + */ + if (!qbuf) { + /* delete list */ + list_del(&e->base.link); + kfree(e); + } + + /* compare buffer id */ + if (qbuf && (qbuf->buf_id == + e->event.buf_id[EXYNOS_DRM_OPS_DST])) { + /* delete list */ + list_del(&e->base.link); + kfree(e); + return; + } + } +} + +void ipp_handle_cmd_work(struct device *dev, + struct exynos_drm_ippdrv *ippdrv, + struct drm_exynos_ipp_cmd_work *cmd_work, + struct drm_exynos_ipp_cmd_node *c_node) +{ + struct ipp_context *ctx = get_ipp_context(dev); + + cmd_work->ippdrv = ippdrv; + cmd_work->c_node = c_node; + queue_work(ctx->cmd_workq, (struct work_struct *)cmd_work); +} + +static int ipp_queue_buf_with_run(struct device *dev, + struct drm_exynos_ipp_cmd_node *c_node, + struct drm_exynos_ipp_mem_node *m_node, + struct drm_exynos_ipp_queue_buf *qbuf) +{ + struct exynos_drm_ippdrv *ippdrv; + struct drm_exynos_ipp_property *property; + struct exynos_drm_ipp_ops *ops; + int ret; + + DRM_DEBUG_KMS("%s\n", __func__); + + ippdrv = ipp_find_drv_by_handle(qbuf->prop_id); + if (IS_ERR_OR_NULL(ippdrv)) { + DRM_ERROR("failed to get ipp driver.\n"); + return -EFAULT; + } + + ops = ippdrv->ops[qbuf->ops_id]; + if (!ops) { + DRM_ERROR("failed to get ops.\n"); + return -EFAULT; + } + + property = &c_node->property; + + if (c_node->state != IPP_STATE_START) { + DRM_DEBUG_KMS("%s:bypass for invalid state.\n" , __func__); + return 0; + } + + if (!ipp_check_mem_list(c_node)) { + DRM_DEBUG_KMS("%s:empty memory.\n", __func__); + return 0; + } + + /* + * If set destination buffer and enabled clock, + * then m2m operations need start operations at queue_buf + */ + if (ipp_is_m2m_cmd(property->cmd)) { + struct drm_exynos_ipp_cmd_work *cmd_work = c_node->start_work; + + cmd_work->ctrl = IPP_CTRL_PLAY; + ipp_handle_cmd_work(dev, ippdrv, cmd_work, c_node); + } else { + ret = ipp_set_mem_node(ippdrv, c_node, m_node); + if (ret) { + DRM_ERROR("failed to set m node.\n"); + return ret; + } + } + + return 0; +} + +static void ipp_clean_queue_buf(struct drm_device *drm_dev, + struct drm_exynos_ipp_cmd_node *c_node, + struct drm_exynos_ipp_queue_buf *qbuf) +{ + struct drm_exynos_ipp_mem_node *m_node, *tm_node; + + DRM_DEBUG_KMS("%s\n", __func__); + + if (!list_empty(&c_node->mem_list[qbuf->ops_id])) { + /* delete list */ + list_for_each_entry_safe(m_node, tm_node, + &c_node->mem_list[qbuf->ops_id], list) { + if (m_node->buf_id == qbuf->buf_id && + m_node->ops_id == qbuf->ops_id) + ipp_put_mem_node(drm_dev, c_node, m_node); + } + } +} + +int exynos_drm_ipp_queue_buf(struct drm_device *drm_dev, void *data, + struct drm_file *file) +{ + struct drm_exynos_file_private *file_priv = file->driver_priv; + stru |