
This change brings msm display driver including sde, dp, dsi, rotator, dsi pll and dp pll from base 4.19 kernel project. It is first source code snapshot from base kernel project. Change-Id: Iec864c064ce5ea04e170f24414c728684002f284 Signed-off-by: Narendra Muppalla <NarendraM@codeaurora.org>
3739 baris
102 KiB
C
3739 baris
102 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2015-2019, The Linux Foundation. All rights reserved.
|
|
*/
|
|
#define pr_fmt(fmt) "%s:%d: " fmt, __func__, __LINE__
|
|
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/wait.h>
|
|
#include <linux/of.h>
|
|
#include <media/v4l2-ioctl.h>
|
|
#include <media/v4l2-event.h>
|
|
#include <media/videobuf2-v4l2.h>
|
|
#include <media/v4l2-mem2mem.h>
|
|
|
|
#include "sde_rotator_inline.h"
|
|
#include "sde_rotator_base.h"
|
|
#include "sde_rotator_core.h"
|
|
#include "sde_rotator_dev.h"
|
|
#include "sde_rotator_debug.h"
|
|
#include "sde_rotator_trace.h"
|
|
|
|
/* Start v4l2 device number (default allocation) */
|
|
#define SDE_ROTATOR_BASE_DEVICE_NUMBER -1
|
|
|
|
/* Default value for early_submit flag */
|
|
#define SDE_ROTATOR_EARLY_SUBMIT 1
|
|
|
|
/* Timeout (msec) waiting for stream to turn off. */
|
|
#define SDE_ROTATOR_STREAM_OFF_TIMEOUT 500
|
|
|
|
/* acquire fence time out, following other driver fence time out practice */
|
|
#define SDE_ROTATOR_FENCE_TIMEOUT MSEC_PER_SEC
|
|
|
|
/* Timeout (msec) waiting for ctx open */
|
|
#define SDE_ROTATOR_CTX_OPEN_TIMEOUT 500
|
|
|
|
/* Rotator default fps */
|
|
#define SDE_ROTATOR_DEFAULT_FPS 60
|
|
|
|
/* Rotator rotation angles */
|
|
#define SDE_ROTATOR_DEGREE_270 270
|
|
#define SDE_ROTATOR_DEGREE_180 180
|
|
#define SDE_ROTATOR_DEGREE_90 90
|
|
|
|
/* Inline rotator qos request */
|
|
#define SDE_ROTATOR_ADD_REQUEST 1
|
|
#define SDE_ROTATOR_REMOVE_REQUEST 0
|
|
|
|
|
|
static void sde_rotator_submit_handler(struct kthread_work *work);
|
|
static void sde_rotator_retire_handler(struct kthread_work *work);
|
|
static void sde_rotator_pm_qos_request(struct sde_rotator_device *rot_dev,
|
|
bool add_request);
|
|
#ifdef CONFIG_COMPAT
|
|
static long sde_rotator_compat_ioctl32(struct file *file,
|
|
unsigned int cmd, unsigned long arg);
|
|
#endif
|
|
|
|
/*
|
|
* sde_rotator_ctx_from_fh - Get rotator context from v4l2 fh.
|
|
* @fh: Pointer to v4l2 fh.
|
|
*/
|
|
static inline struct sde_rotator_ctx *sde_rotator_ctx_from_fh(
|
|
struct v4l2_fh *fh)
|
|
{
|
|
return container_of(fh, struct sde_rotator_ctx, fh);
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_get_flags_from_ctx - Get low-level command flag
|
|
* @ctx: Pointer to rotator context.
|
|
*/
|
|
static uint32_t sde_rotator_get_flags_from_ctx(struct sde_rotator_ctx *ctx)
|
|
{
|
|
uint32_t ret_flags = 0;
|
|
|
|
if (ctx->rotate == SDE_ROTATOR_DEGREE_270)
|
|
ret_flags |= SDE_ROTATION_270;
|
|
else if (ctx->rotate == SDE_ROTATOR_DEGREE_180)
|
|
ret_flags |= SDE_ROTATION_180;
|
|
else if (ctx->rotate == SDE_ROTATOR_DEGREE_90)
|
|
ret_flags |= SDE_ROTATION_90;
|
|
if (ctx->hflip)
|
|
ret_flags ^= SDE_ROTATION_FLIP_LR;
|
|
if (ctx->vflip)
|
|
ret_flags ^= SDE_ROTATION_FLIP_UD;
|
|
if (ctx->secure)
|
|
ret_flags |= SDE_ROTATION_SECURE;
|
|
if (ctx->secure_camera)
|
|
ret_flags |= SDE_ROTATION_SECURE_CAMERA;
|
|
if (ctx->format_out.fmt.pix.field == V4L2_FIELD_INTERLACED &&
|
|
ctx->format_cap.fmt.pix.field == V4L2_FIELD_NONE)
|
|
ret_flags |= SDE_ROTATION_DEINTERLACE;
|
|
|
|
return ret_flags;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_get_config_from_ctx - Fill rotator configure structure.
|
|
* @ctx: Pointer to rotator ctx.
|
|
* @config: Pointer to config structure.
|
|
*/
|
|
static void sde_rotator_get_config_from_ctx(struct sde_rotator_ctx *ctx,
|
|
struct sde_rotation_config *config)
|
|
{
|
|
memset(config, 0, sizeof(struct sde_rotation_config));
|
|
config->flags = sde_rotator_get_flags_from_ctx(ctx);
|
|
config->frame_rate = (ctx->timeperframe.numerator) ?
|
|
ctx->timeperframe.denominator
|
|
/ ctx->timeperframe.numerator : 0;
|
|
config->session_id = ctx->session_id;
|
|
config->input.width = ctx->crop_out.width;
|
|
config->input.height = ctx->crop_out.height;
|
|
config->input.format = ctx->format_out.fmt.pix.pixelformat;
|
|
config->input.comp_ratio.numer = 1;
|
|
config->input.comp_ratio.denom = 1;
|
|
config->output.width = ctx->crop_cap.width;
|
|
config->output.height = ctx->crop_cap.height;
|
|
config->output.format = ctx->format_cap.fmt.pix.pixelformat;
|
|
config->output.comp_ratio.numer = 1;
|
|
config->output.comp_ratio.denom = 1;
|
|
|
|
/*
|
|
* Use compression ratio of the first buffer to estimate
|
|
* performance requirement of the session. If core layer does
|
|
* not support dynamic per buffer compression ratio recalculation,
|
|
* this configuration will determine the overall static compression
|
|
* ratio of the session.
|
|
*/
|
|
if (ctx->vbinfo_out)
|
|
config->input.comp_ratio = ctx->vbinfo_out[0].comp_ratio;
|
|
if (ctx->vbinfo_cap)
|
|
config->output.comp_ratio = ctx->vbinfo_cap[0].comp_ratio;
|
|
|
|
SDEDEV_DBG(ctx->rot_dev->dev, "config s:%d out_cr:%u/%u cap_cr:%u/%u\n",
|
|
ctx->session_id,
|
|
config->input.comp_ratio.numer,
|
|
config->input.comp_ratio.denom,
|
|
config->output.comp_ratio.numer,
|
|
config->output.comp_ratio.denom);
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_get_item_from_ctx - Fill rotator item structure.
|
|
* @ctx: Pointer to rotator ctx.
|
|
* @item: Pointer to item structure.
|
|
*/
|
|
static void sde_rotator_get_item_from_ctx(struct sde_rotator_ctx *ctx,
|
|
struct sde_rotation_item *item)
|
|
{
|
|
memset(item, 0, sizeof(struct sde_rotation_item));
|
|
item->flags = sde_rotator_get_flags_from_ctx(ctx);
|
|
item->session_id = ctx->session_id;
|
|
item->sequence_id = 0;
|
|
/* assign high/low priority */
|
|
item->wb_idx = (ctx->fh.prio >= V4L2_PRIORITY_DEFAULT) ? 0 : 1;
|
|
item->src_rect.x = ctx->crop_out.left;
|
|
item->src_rect.y = ctx->crop_out.top;
|
|
item->src_rect.w = ctx->crop_out.width;
|
|
item->src_rect.h = ctx->crop_out.height;
|
|
item->input.width = ctx->format_out.fmt.pix.width;
|
|
item->input.height = ctx->format_out.fmt.pix.height;
|
|
item->input.format = ctx->format_out.fmt.pix.pixelformat;
|
|
item->input.planes[0].fd = -1;
|
|
item->input.planes[0].offset = 0;
|
|
item->input.planes[0].stride = ctx->format_out.fmt.pix.bytesperline;
|
|
item->input.plane_count = 1;
|
|
item->input.fence = NULL;
|
|
item->input.comp_ratio.numer = 1;
|
|
item->input.comp_ratio.denom = 1;
|
|
|
|
item->dst_rect.x = ctx->crop_cap.left;
|
|
item->dst_rect.y = ctx->crop_cap.top;
|
|
item->dst_rect.w = ctx->crop_cap.width;
|
|
item->dst_rect.h = ctx->crop_cap.height;
|
|
item->output.width = ctx->format_cap.fmt.pix.width;
|
|
item->output.height = ctx->format_cap.fmt.pix.height;
|
|
item->output.format = ctx->format_cap.fmt.pix.pixelformat;
|
|
item->output.planes[0].fd = -1;
|
|
item->output.planes[0].offset = 0;
|
|
item->output.planes[0].stride = ctx->format_cap.fmt.pix.bytesperline;
|
|
item->output.plane_count = 1;
|
|
item->output.fence = NULL;
|
|
item->output.comp_ratio.numer = 1;
|
|
item->output.comp_ratio.denom = 1;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_format_recalc - Recalculate format parameters.
|
|
* @f: v4l2 format.
|
|
*/
|
|
static void sde_rotator_format_recalc(struct v4l2_format *f)
|
|
{
|
|
int ret;
|
|
struct sde_mdp_format_params *fmt;
|
|
struct sde_mdp_plane_sizes ps;
|
|
|
|
fmt = sde_get_format_params(f->fmt.pix.pixelformat);
|
|
if (!fmt) {
|
|
SDEROT_ERR("invalid format\n");
|
|
goto error_fmt;
|
|
}
|
|
|
|
ret = sde_mdp_get_plane_sizes(fmt,
|
|
f->fmt.pix.width, f->fmt.pix.height, &ps, 0, 0);
|
|
if (ret) {
|
|
SDEROT_ERR("invalid plane size\n");
|
|
goto error_fmt;
|
|
}
|
|
|
|
f->fmt.pix.bytesperline = ps.ystride[0];
|
|
f->fmt.pix.sizeimage = ps.total_size;
|
|
|
|
return;
|
|
error_fmt:
|
|
f->fmt.pix.bytesperline = 0;
|
|
f->fmt.pix.sizeimage = 0;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_validate_item - Check if rotator item is valid for processing.
|
|
* @ctx: Pointer to rotator ctx.
|
|
* @item: Pointer to item structure
|
|
*/
|
|
static int sde_rotator_validate_item(struct sde_rotator_ctx *ctx,
|
|
struct sde_rotation_item *item)
|
|
{
|
|
int ret;
|
|
struct sde_rot_entry_container *req;
|
|
struct sde_rotator_device *rot_dev = ctx->rot_dev;
|
|
|
|
sde_rot_mgr_lock(rot_dev->mgr);
|
|
req = sde_rotator_req_init(rot_dev->mgr, ctx->private, item, 1, 0);
|
|
if (IS_ERR_OR_NULL(req)) {
|
|
SDEDEV_ERR(rot_dev->dev, "fail allocate item\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ret = sde_rotator_validate_request(rot_dev->mgr, ctx->private, req);
|
|
sde_rot_mgr_unlock(rot_dev->mgr);
|
|
devm_kfree(rot_dev->dev, req);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_queue_setup - vb2_ops queue_setup callback.
|
|
* @q: Pointer to vb2 queue struct.
|
|
* @num_buffers: Pointer of number of buffers requested.
|
|
* @num_planes: Pointer to number of planes requested.
|
|
* @sizes: Array containing sizes of planes.
|
|
* @alloc_ctxs: Array of allocated contexts for each plane.
|
|
*/
|
|
static int sde_rotator_queue_setup(struct vb2_queue *q,
|
|
unsigned int *num_buffers, unsigned int *num_planes,
|
|
unsigned int sizes[], struct device *alloc_devs[])
|
|
{
|
|
struct sde_rotator_ctx *ctx = vb2_get_drv_priv(q);
|
|
int i;
|
|
|
|
if (!num_buffers)
|
|
return -EINVAL;
|
|
|
|
switch (q->type) {
|
|
case V4L2_BUF_TYPE_VIDEO_OUTPUT:
|
|
sizes[0] = ctx->format_out.fmt.pix.sizeimage;
|
|
break;
|
|
case V4L2_BUF_TYPE_VIDEO_CAPTURE:
|
|
sizes[0] = ctx->format_cap.fmt.pix.sizeimage;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
*num_planes = 1;
|
|
alloc_devs[0] = (struct device *)ctx;
|
|
|
|
switch (q->type) {
|
|
case V4L2_BUF_TYPE_VIDEO_OUTPUT:
|
|
ctx->nbuf_out = *num_buffers;
|
|
kfree(ctx->vbinfo_out);
|
|
ctx->vbinfo_out = kcalloc(ctx->nbuf_out,
|
|
sizeof(struct sde_rotator_vbinfo), GFP_KERNEL);
|
|
if (!ctx->vbinfo_out)
|
|
return -ENOMEM;
|
|
for (i = 0; i < ctx->nbuf_out; i++) {
|
|
ctx->vbinfo_out[i].fd = -1;
|
|
ctx->vbinfo_out[i].comp_ratio.numer = 1;
|
|
ctx->vbinfo_out[i].comp_ratio.denom = 1;
|
|
}
|
|
break;
|
|
case V4L2_BUF_TYPE_VIDEO_CAPTURE:
|
|
ctx->nbuf_cap = *num_buffers;
|
|
kfree(ctx->vbinfo_cap);
|
|
ctx->vbinfo_cap = kcalloc(ctx->nbuf_cap,
|
|
sizeof(struct sde_rotator_vbinfo), GFP_KERNEL);
|
|
if (!ctx->vbinfo_cap)
|
|
return -ENOMEM;
|
|
for (i = 0; i < ctx->nbuf_cap; i++) {
|
|
ctx->vbinfo_cap[i].fd = -1;
|
|
ctx->vbinfo_cap[i].comp_ratio.numer = 1;
|
|
ctx->vbinfo_cap[i].comp_ratio.denom = 1;
|
|
}
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_buf_queue - vb2_ops buf_queue callback.
|
|
* @vb: Pointer to vb2 buffer struct.
|
|
*/
|
|
static void sde_rotator_buf_queue(struct vb2_buffer *vb)
|
|
{
|
|
struct sde_rotator_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
|
|
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
|
|
|
|
v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf);
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_buf_finish - vb2_ops buf_finish to finalize buffer before going
|
|
* back to user space
|
|
* @vb: Pointer to vb2 buffer struct.
|
|
*/
|
|
static void sde_rotator_buf_finish(struct vb2_buffer *vb)
|
|
{
|
|
struct sde_rotator_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
|
|
int i;
|
|
|
|
SDEDEV_DBG(ctx->rot_dev->dev,
|
|
"buf_finish t:%d i:%d s:%d m:%u np:%d up:%lu\n",
|
|
vb->type, vb->index, vb->state,
|
|
vb->vb2_queue->memory,
|
|
vb->num_planes,
|
|
vb->planes[0].m.userptr);
|
|
|
|
if (vb->vb2_queue->memory != VB2_MEMORY_USERPTR)
|
|
return;
|
|
|
|
/*
|
|
* We use userptr to tunnel fd, and fd can be the same across qbuf
|
|
* even though the underlying buffer is different. Since vb2 layer
|
|
* optimizes memory mapping for userptr by first checking if userptr
|
|
* has changed, it will not trigger put_userptr if fd value does
|
|
* not change. In order to force buffer release, we need to clear
|
|
* userptr when the current buffer is done and ready to go back to
|
|
* user mode. Since 0 is a valid fd, reset userptr to -1 instead.
|
|
*/
|
|
for (i = 0; i < vb->num_planes; i++)
|
|
vb->planes[i].m.userptr = ~0;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_return_all_buffers - Return all buffers with the given status.
|
|
* @q: Pointer to vb2 buffer queue struct.
|
|
* @state: State of the buffer
|
|
*/
|
|
static void sde_rotator_return_all_buffers(struct vb2_queue *q,
|
|
enum vb2_buffer_state state)
|
|
{
|
|
struct sde_rotator_ctx *ctx = vb2_get_drv_priv(q);
|
|
struct sde_rotator_device *rot_dev = ctx->rot_dev;
|
|
|
|
SDEDEV_DBG(rot_dev->dev,
|
|
"return q t:%d c:%d dc:%d s:%d\n",
|
|
q->type, q->queued_count,
|
|
atomic_read(&q->owned_by_drv_count),
|
|
state);
|
|
|
|
/* return buffers according videobuffer2-core.h */
|
|
if (q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
|
|
struct vb2_v4l2_buffer *buf;
|
|
|
|
while ((buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx))) {
|
|
SDEDEV_DBG(rot_dev->dev,
|
|
"return vb t:%d i:%d\n",
|
|
buf->vb2_buf.type,
|
|
buf->vb2_buf.index);
|
|
v4l2_m2m_buf_done(buf, state);
|
|
}
|
|
} else if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) {
|
|
struct vb2_v4l2_buffer *buf;
|
|
|
|
while ((buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx))) {
|
|
SDEDEV_DBG(rot_dev->dev,
|
|
"return vb t:%d i:%d\n",
|
|
buf->vb2_buf.type,
|
|
buf->vb2_buf.index);
|
|
v4l2_m2m_buf_done(buf, state);
|
|
}
|
|
} else {
|
|
SDEDEV_ERR(rot_dev->dev, "unsupported vb t:%d\n", q->type);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_start_streaming - vb2_ops start_streaming callback.
|
|
* @q: Pointer to vb2 queue struct.
|
|
* @count: Number of buffer queued before stream on call.
|
|
*/
|
|
static int sde_rotator_start_streaming(struct vb2_queue *q, unsigned int count)
|
|
{
|
|
struct sde_rotator_ctx *ctx = vb2_get_drv_priv(q);
|
|
struct sde_rotator_device *rot_dev = ctx->rot_dev;
|
|
|
|
SDEDEV_DBG(rot_dev->dev, "start streaming s:%d t:%d\n",
|
|
ctx->session_id, q->type);
|
|
|
|
if (!list_empty(&ctx->pending_list)) {
|
|
SDEDEV_ERR(rot_dev->dev,
|
|
"command pending error s:%d t:%d p:%d\n",
|
|
ctx->session_id, q->type,
|
|
!list_empty(&ctx->pending_list));
|
|
return -EINVAL;
|
|
}
|
|
|
|
ctx->abort_pending = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_stop_streaming - vb2_ops stop_streaming callback.
|
|
* @q: Pointer to vb2 queue struct.
|
|
*
|
|
* This function will block waiting for stream to stop. Unlock queue
|
|
* lock to avoid deadlock.
|
|
*/
|
|
static void sde_rotator_stop_streaming(struct vb2_queue *q)
|
|
{
|
|
struct sde_rotator_ctx *ctx = vb2_get_drv_priv(q);
|
|
struct sde_rotator_device *rot_dev = ctx->rot_dev;
|
|
struct sde_rotator_request *request;
|
|
struct list_head *curr, *next;
|
|
int i;
|
|
int ret;
|
|
|
|
SDEDEV_DBG(rot_dev->dev, "stop streaming s:%d t:%d p:%d\n",
|
|
ctx->session_id, q->type,
|
|
!list_empty(&ctx->pending_list));
|
|
ctx->abort_pending = 1;
|
|
mutex_unlock(q->lock);
|
|
ret = wait_event_timeout(ctx->wait_queue,
|
|
list_empty(&ctx->pending_list),
|
|
msecs_to_jiffies(rot_dev->streamoff_timeout));
|
|
mutex_lock(q->lock);
|
|
if (!ret) {
|
|
SDEDEV_ERR(rot_dev->dev,
|
|
"timeout to stream off s:%d t:%d p:%d\n",
|
|
ctx->session_id, q->type,
|
|
!list_empty(&ctx->pending_list));
|
|
SDEROT_EVTLOG(ctx->session_id, q->type,
|
|
!list_empty(&ctx->pending_list),
|
|
SDE_ROT_EVTLOG_ERROR);
|
|
sde_rot_mgr_lock(rot_dev->mgr);
|
|
sde_rotator_cancel_all_requests(rot_dev->mgr, ctx->private);
|
|
sde_rot_mgr_unlock(rot_dev->mgr);
|
|
list_for_each_safe(curr, next, &ctx->pending_list) {
|
|
request = container_of(curr, struct sde_rotator_request,
|
|
list);
|
|
|
|
SDEDEV_DBG(rot_dev->dev, "cancel request s:%d\n",
|
|
ctx->session_id);
|
|
mutex_unlock(q->lock);
|
|
kthread_cancel_work_sync(&request->submit_work);
|
|
kthread_cancel_work_sync(&request->retire_work);
|
|
mutex_lock(q->lock);
|
|
spin_lock(&ctx->list_lock);
|
|
list_del_init(&request->list);
|
|
list_add_tail(&request->list, &ctx->retired_list);
|
|
spin_unlock(&ctx->list_lock);
|
|
}
|
|
}
|
|
|
|
sde_rotator_return_all_buffers(q, VB2_BUF_STATE_ERROR);
|
|
|
|
/* clear fence for buffer */
|
|
sde_rotator_resync_timeline(ctx->work_queue.timeline);
|
|
if (q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
|
|
for (i = 0; i < ctx->nbuf_cap; i++) {
|
|
struct sde_rotator_vbinfo *vbinfo =
|
|
&ctx->vbinfo_cap[i];
|
|
|
|
if (vbinfo->fence) {
|
|
/* fence is not used */
|
|
SDEDEV_DBG(rot_dev->dev,
|
|
"put fence s:%d t:%d i:%d\n",
|
|
ctx->session_id, q->type, i);
|
|
sde_rotator_put_sync_fence(vbinfo->fence);
|
|
}
|
|
vbinfo->fence = NULL;
|
|
vbinfo->fd = -1;
|
|
}
|
|
} else if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) {
|
|
for (i = 0; i < ctx->nbuf_out; i++) {
|
|
struct sde_rotator_vbinfo *vbinfo =
|
|
&ctx->vbinfo_out[i];
|
|
|
|
if (vbinfo->fence) {
|
|
SDEDEV_DBG(rot_dev->dev,
|
|
"put fence s:%d t:%d i:%d\n",
|
|
ctx->session_id, q->type, i);
|
|
sde_rotator_put_sync_fence(vbinfo->fence);
|
|
}
|
|
vbinfo->fence = NULL;
|
|
vbinfo->fd = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Videobuf2 queue callbacks. */
|
|
static const struct vb2_ops sde_rotator_vb2_q_ops = {
|
|
.queue_setup = sde_rotator_queue_setup,
|
|
.buf_queue = sde_rotator_buf_queue,
|
|
.start_streaming = sde_rotator_start_streaming,
|
|
.stop_streaming = sde_rotator_stop_streaming,
|
|
.wait_prepare = vb2_ops_wait_prepare,
|
|
.wait_finish = vb2_ops_wait_finish,
|
|
.buf_finish = sde_rotator_buf_finish,
|
|
};
|
|
|
|
/*
|
|
* sde_rotator_get_userptr - Map and get buffer handler for user pointer buffer.
|
|
* @dev: device allocated in buf_setup.
|
|
* @vaddr: Virtual addr passed from userpsace (in our case ion fd)
|
|
* @size: Size of the buffer
|
|
* @dma_dir: DMA data direction of the given buffer.
|
|
*/
|
|
static void *sde_rotator_get_userptr(struct device *dev,
|
|
unsigned long vaddr, unsigned long size,
|
|
enum dma_data_direction dma_dir)
|
|
{
|
|
struct sde_rotator_ctx *ctx = (struct sde_rotator_ctx *)dev;
|
|
struct sde_rotator_device *rot_dev = ctx->rot_dev;
|
|
struct sde_rotator_buf_handle *buf;
|
|
|
|
buf = kzalloc(sizeof(*buf), GFP_KERNEL);
|
|
if (!buf)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
buf->fd = vaddr;
|
|
buf->secure = ctx->secure || ctx->secure_camera;
|
|
buf->ctx = ctx;
|
|
buf->rot_dev = rot_dev;
|
|
buf->size = size;
|
|
buf->buffer = dma_buf_get(buf->fd);
|
|
if (IS_ERR_OR_NULL(buf->buffer)) {
|
|
SDEDEV_ERR(rot_dev->dev,
|
|
"fail get dmabuf fd:%d r:%ld\n",
|
|
buf->fd, PTR_ERR(buf->buffer));
|
|
goto error_buf_get;
|
|
}
|
|
SDEDEV_DBG(rot_dev->dev,
|
|
"get dmabuf s:%d fd:%d buf:%pad\n",
|
|
buf->ctx->session_id,
|
|
buf->fd, &buf->buffer);
|
|
|
|
return buf;
|
|
error_buf_get:
|
|
kfree(buf);
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_put_userptr - Unmap and free buffer handler.
|
|
* @buf_priv: Buffer handler allocated get_userptr callback.
|
|
*/
|
|
static void sde_rotator_put_userptr(void *buf_priv)
|
|
{
|
|
struct sde_rotator_buf_handle *buf = buf_priv;
|
|
|
|
if (IS_ERR_OR_NULL(buf))
|
|
return;
|
|
|
|
if (!buf->rot_dev || !buf->ctx) {
|
|
WARN_ON(!buf->rot_dev || !buf->ctx);
|
|
SDEROT_ERR("null rotator device/context\n");
|
|
return;
|
|
}
|
|
|
|
SDEDEV_DBG(buf->rot_dev->dev, "put dmabuf s:%d fd:%d buf:%pad\n",
|
|
buf->ctx->session_id,
|
|
buf->fd, &buf->buffer);
|
|
|
|
if (buf->buffer) {
|
|
dma_buf_put(buf->buffer);
|
|
buf->buffer = NULL;
|
|
}
|
|
|
|
kfree(buf_priv);
|
|
}
|
|
|
|
/* Videobuf2 memory callbacks. */
|
|
static struct vb2_mem_ops sde_rotator_vb2_mem_ops = {
|
|
.get_userptr = sde_rotator_get_userptr,
|
|
.put_userptr = sde_rotator_put_userptr,
|
|
};
|
|
|
|
/*
|
|
* sde_rotator_s_ctx_ctrl - set context control variable to v4l2 control
|
|
* @ctx: Pointer to rotator context.
|
|
* @ctx_ctrl: Pointer to context control variable
|
|
* @ctrl: Pointer to v4l2 control variable
|
|
*/
|
|
static int sde_rotator_s_ctx_ctrl(struct sde_rotator_ctx *ctx,
|
|
s32 *ctx_ctrl, struct v4l2_ctrl *ctrl)
|
|
{
|
|
*ctx_ctrl = ctrl->val;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_s_ctrl - Set control.
|
|
* @ctrl: Pointer to v4l2 control structure.
|
|
*/
|
|
static int sde_rotator_s_ctrl(struct v4l2_ctrl *ctrl)
|
|
{
|
|
struct sde_rotator_ctx *ctx =
|
|
container_of(ctrl->handler,
|
|
struct sde_rotator_ctx, ctrl_handler);
|
|
struct sde_rotator_device *rot_dev = ctx->rot_dev;
|
|
int ret;
|
|
|
|
SDEDEV_DBG(rot_dev->dev, "set %s:%d s:%d\n", ctrl->name, ctrl->val,
|
|
ctx->session_id);
|
|
|
|
sde_rot_mgr_lock(rot_dev->mgr);
|
|
|
|
switch (ctrl->id) {
|
|
case V4L2_CID_HFLIP:
|
|
ret = sde_rotator_s_ctx_ctrl(ctx, &ctx->hflip, ctrl);
|
|
break;
|
|
|
|
case V4L2_CID_VFLIP:
|
|
ret = sde_rotator_s_ctx_ctrl(ctx, &ctx->vflip, ctrl);
|
|
break;
|
|
|
|
case V4L2_CID_ROTATE:
|
|
ret = sde_rotator_s_ctx_ctrl(ctx, &ctx->rotate, ctrl);
|
|
break;
|
|
|
|
case V4L2_CID_SDE_ROTATOR_SECURE:
|
|
ret = sde_rotator_s_ctx_ctrl(ctx, &ctx->secure, ctrl);
|
|
break;
|
|
|
|
case V4L2_CID_SDE_ROTATOR_SECURE_CAMERA:
|
|
ret = sde_rotator_s_ctx_ctrl(ctx, &ctx->secure_camera, ctrl);
|
|
break;
|
|
default:
|
|
v4l2_warn(&rot_dev->v4l2_dev, "invalid control %d\n", ctrl->id);
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
sde_rot_mgr_unlock(rot_dev->mgr);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_ctrl_ops - Control operations.
|
|
*/
|
|
static const struct v4l2_ctrl_ops sde_rotator_ctrl_ops = {
|
|
.s_ctrl = sde_rotator_s_ctrl,
|
|
};
|
|
|
|
/*
|
|
* sde_rotator_ctrl_secure - Non-secure/Secure.
|
|
*/
|
|
static const struct v4l2_ctrl_config sde_rotator_ctrl_secure = {
|
|
.ops = &sde_rotator_ctrl_ops,
|
|
.id = V4L2_CID_SDE_ROTATOR_SECURE,
|
|
.name = "Non-secure/Secure Domain",
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
.def = 0,
|
|
.min = 0,
|
|
.max = 1,
|
|
.step = 1,
|
|
};
|
|
|
|
static const struct v4l2_ctrl_config sde_rotator_ctrl_secure_camera = {
|
|
.ops = &sde_rotator_ctrl_ops,
|
|
.id = V4L2_CID_SDE_ROTATOR_SECURE_CAMERA,
|
|
.name = "Secure Camera content",
|
|
.type = V4L2_CTRL_TYPE_INTEGER,
|
|
.def = 0,
|
|
.min = 0,
|
|
.max = 1,
|
|
.step = 1,
|
|
};
|
|
|
|
/*
|
|
* sde_rotator_ctx_show - show context state.
|
|
*/
|
|
static ssize_t sde_rotator_ctx_show(struct kobject *kobj,
|
|
struct kobj_attribute *attr, char *buf)
|
|
{
|
|
size_t len = PAGE_SIZE;
|
|
int cnt = 0;
|
|
struct sde_rotator_ctx *ctx =
|
|
container_of(kobj, struct sde_rotator_ctx, kobj);
|
|
|
|
if (!ctx)
|
|
return cnt;
|
|
|
|
#define SPRINT(fmt, ...) \
|
|
(cnt += scnprintf(buf + cnt, len - cnt, fmt, ##__VA_ARGS__))
|
|
|
|
SPRINT("rotate=%d\n", ctx->rotate);
|
|
SPRINT("hflip=%d\n", ctx->hflip);
|
|
SPRINT("vflip=%d\n", ctx->vflip);
|
|
SPRINT("priority=%d\n", ctx->fh.prio);
|
|
SPRINT("secure=%d\n", ctx->secure);
|
|
SPRINT("timeperframe=%u %u\n", ctx->timeperframe.numerator,
|
|
ctx->timeperframe.denominator);
|
|
SPRINT("nbuf_out=%d\n", ctx->nbuf_out);
|
|
SPRINT("nbuf_cap=%d\n", ctx->nbuf_cap);
|
|
SPRINT("crop_out=%u %u %u %u\n",
|
|
ctx->crop_out.left, ctx->crop_out.top,
|
|
ctx->crop_out.width, ctx->crop_out.height);
|
|
SPRINT("crop_cap=%u %u %u %u\n",
|
|
ctx->crop_cap.left, ctx->crop_cap.top,
|
|
ctx->crop_cap.width, ctx->crop_cap.height);
|
|
SPRINT("fmt_out=%c%c%c%c %u %u %u %u\n",
|
|
(ctx->format_out.fmt.pix.pixelformat>>0)&0xff,
|
|
(ctx->format_out.fmt.pix.pixelformat>>8)&0xff,
|
|
(ctx->format_out.fmt.pix.pixelformat>>16)&0xff,
|
|
(ctx->format_out.fmt.pix.pixelformat>>24)&0xff,
|
|
ctx->format_out.fmt.pix.width,
|
|
ctx->format_out.fmt.pix.height,
|
|
ctx->format_out.fmt.pix.bytesperline,
|
|
ctx->format_out.fmt.pix.sizeimage);
|
|
SPRINT("fmt_cap=%c%c%c%c %u %u %u %u\n",
|
|
(ctx->format_cap.fmt.pix.pixelformat>>0)&0xff,
|
|
(ctx->format_cap.fmt.pix.pixelformat>>8)&0xff,
|
|
(ctx->format_cap.fmt.pix.pixelformat>>16)&0xff,
|
|
(ctx->format_cap.fmt.pix.pixelformat>>24)&0xff,
|
|
ctx->format_cap.fmt.pix.width,
|
|
ctx->format_cap.fmt.pix.height,
|
|
ctx->format_cap.fmt.pix.bytesperline,
|
|
ctx->format_cap.fmt.pix.sizeimage);
|
|
SPRINT("abort_pending=%d\n", ctx->abort_pending);
|
|
SPRINT("command_pending=%d\n", !list_empty(&ctx->pending_list));
|
|
SPRINT("sequence=%u\n",
|
|
sde_rotator_get_timeline_commit_ts(ctx->work_queue.timeline));
|
|
SPRINT("timestamp=%u\n",
|
|
sde_rotator_get_timeline_retire_ts(ctx->work_queue.timeline));
|
|
return cnt;
|
|
}
|
|
|
|
static struct kobj_attribute sde_rotator_ctx_attr =
|
|
__ATTR(state, 0664, sde_rotator_ctx_show, NULL);
|
|
|
|
static struct attribute *sde_rotator_fs_attrs[] = {
|
|
&sde_rotator_ctx_attr.attr,
|
|
NULL
|
|
};
|
|
|
|
static struct attribute_group sde_rotator_fs_attr_group = {
|
|
.attrs = sde_rotator_fs_attrs
|
|
};
|
|
|
|
/*
|
|
* sde_rotator_ctx_show - sysfs show callback.
|
|
*/
|
|
static ssize_t sde_rotator_fs_show(struct kobject *kobj,
|
|
struct attribute *attr, char *buf)
|
|
{
|
|
ssize_t ret = -EIO;
|
|
struct kobj_attribute *kattr =
|
|
container_of(attr, struct kobj_attribute, attr);
|
|
if (kattr->show)
|
|
ret = kattr->show(kobj, kattr, buf);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_fs_store - sysfs store callback.
|
|
*/
|
|
static ssize_t sde_rotator_fs_store(struct kobject *kobj,
|
|
struct attribute *attr, const char *buf, size_t count)
|
|
{
|
|
ssize_t ret = -EIO;
|
|
struct kobj_attribute *kattr =
|
|
container_of(attr, struct kobj_attribute, attr);
|
|
if (kattr->store)
|
|
ret = kattr->store(kobj, kattr, buf, count);
|
|
return ret;
|
|
}
|
|
|
|
static const struct sysfs_ops sde_rotator_fs_ops = {
|
|
.show = sde_rotator_fs_show,
|
|
.store = sde_rotator_fs_store,
|
|
};
|
|
|
|
static struct kobj_type sde_rotator_fs_ktype = {
|
|
.sysfs_ops = &sde_rotator_fs_ops,
|
|
};
|
|
|
|
/*
|
|
* sde_rotator_queue_init - m2m_ops queue_setup callback.
|
|
* @priv: Pointer to rotator ctx.
|
|
* @src_vq: vb2 source queue.
|
|
* @dst_vq: vb2 destination queue.
|
|
*/
|
|
static int sde_rotator_queue_init(void *priv, struct vb2_queue *src_vq,
|
|
struct vb2_queue *dst_vq)
|
|
{
|
|
struct sde_rotator_ctx *ctx = priv;
|
|
int ret;
|
|
|
|
src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
|
|
src_vq->io_modes = VB2_USERPTR;
|
|
src_vq->drv_priv = ctx;
|
|
src_vq->mem_ops = &sde_rotator_vb2_mem_ops;
|
|
src_vq->ops = &sde_rotator_vb2_q_ops;
|
|
src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
|
|
src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
|
|
src_vq->lock = &ctx->rot_dev->lock;
|
|
src_vq->min_buffers_needed = 1;
|
|
src_vq->dev = ctx->rot_dev->dev;
|
|
|
|
ret = vb2_queue_init(src_vq);
|
|
if (ret) {
|
|
SDEDEV_ERR(ctx->rot_dev->dev,
|
|
"fail init src queue r:%d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
dst_vq->io_modes = VB2_USERPTR;
|
|
dst_vq->drv_priv = ctx;
|
|
dst_vq->mem_ops = &sde_rotator_vb2_mem_ops;
|
|
dst_vq->ops = &sde_rotator_vb2_q_ops;
|
|
dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
|
|
dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
|
|
dst_vq->lock = &ctx->rot_dev->lock;
|
|
dst_vq->min_buffers_needed = 1;
|
|
src_vq->dev = ctx->rot_dev->dev;
|
|
|
|
ret = vb2_queue_init(dst_vq);
|
|
if (ret) {
|
|
SDEDEV_ERR(ctx->rot_dev->dev,
|
|
"fail init dst queue r:%d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_ctx_open - Rotator device open method.
|
|
* @rot_dev: Pointer to rotator device structure
|
|
* @file: Pointer to file struct (optional)
|
|
* return: Pointer rotator context if success; ptr error code, otherwise.
|
|
*/
|
|
struct sde_rotator_ctx *sde_rotator_ctx_open(
|
|
struct sde_rotator_device *rot_dev, struct file *file)
|
|
{
|
|
struct video_device *video = file ? video_devdata(file) : NULL;
|
|
struct sde_rotator_ctx *ctx;
|
|
struct v4l2_ctrl_handler *ctrl_handler;
|
|
char name[32];
|
|
int i, ret;
|
|
|
|
if (atomic_read(&rot_dev->mgr->device_suspended))
|
|
return ERR_PTR(-EPERM);
|
|
|
|
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
|
|
if (!ctx)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
if (mutex_lock_interruptible(&rot_dev->lock)) {
|
|
ret = -ERESTARTSYS;
|
|
goto error_lock;
|
|
}
|
|
|
|
/* wait until exclusive ctx, if exists, finishes or timeout */
|
|
while (rot_dev->excl_ctx) {
|
|
SDEROT_DBG("waiting to open %s session %d ...\n",
|
|
file ? "v4l2" : "excl", rot_dev->session_id);
|
|
mutex_unlock(&rot_dev->lock);
|
|
ret = wait_event_interruptible_timeout(rot_dev->open_wq,
|
|
!rot_dev->excl_ctx,
|
|
msecs_to_jiffies(rot_dev->open_timeout));
|
|
if (ret < 0) {
|
|
goto error_lock;
|
|
} else if (!ret) {
|
|
SDEROT_WARN("timeout to open session %d\n",
|
|
rot_dev->session_id);
|
|
SDEROT_EVTLOG(rot_dev->session_id,
|
|
SDE_ROT_EVTLOG_ERROR);
|
|
ret = -EBUSY;
|
|
goto error_lock;
|
|
} else if (mutex_lock_interruptible(&rot_dev->lock)) {
|
|
ret = -ERESTARTSYS;
|
|
goto error_lock;
|
|
}
|
|
}
|
|
|
|
ctx->rot_dev = rot_dev;
|
|
ctx->file = file;
|
|
|
|
/* Set context defaults */
|
|
ctx->session_id = rot_dev->session_id++;
|
|
SDEDEV_DBG(ctx->rot_dev->dev, "open %d\n", ctx->session_id);
|
|
ctx->timeperframe.numerator = 1;
|
|
ctx->timeperframe.denominator = SDE_ROTATOR_DEFAULT_FPS;
|
|
ctx->hflip = 0;
|
|
ctx->vflip = 0;
|
|
ctx->rotate = 0;
|
|
ctx->secure = 0;
|
|
ctx->abort_pending = 0;
|
|
ctx->kthread_id = -1;
|
|
ctx->format_cap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
ctx->format_cap.fmt.pix.pixelformat = SDE_PIX_FMT_Y_CBCR_H2V2;
|
|
ctx->format_cap.fmt.pix.width = 640;
|
|
ctx->format_cap.fmt.pix.height = 480;
|
|
ctx->crop_cap.width = 640;
|
|
ctx->crop_cap.height = 480;
|
|
ctx->format_out.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
|
|
ctx->format_out.fmt.pix.pixelformat = SDE_PIX_FMT_Y_CBCR_H2V2;
|
|
ctx->format_out.fmt.pix.width = 640;
|
|
ctx->format_out.fmt.pix.height = 480;
|
|
ctx->crop_out.width = 640;
|
|
ctx->crop_out.height = 480;
|
|
init_waitqueue_head(&ctx->wait_queue);
|
|
spin_lock_init(&ctx->list_lock);
|
|
INIT_LIST_HEAD(&ctx->pending_list);
|
|
INIT_LIST_HEAD(&ctx->retired_list);
|
|
|
|
for (i = 0 ; i < ARRAY_SIZE(ctx->requests); i++) {
|
|
struct sde_rotator_request *request = &ctx->requests[i];
|
|
|
|
kthread_init_work(&request->submit_work,
|
|
sde_rotator_submit_handler);
|
|
kthread_init_work(&request->retire_work,
|
|
sde_rotator_retire_handler);
|
|
request->ctx = ctx;
|
|
INIT_LIST_HEAD(&request->list);
|
|
list_add_tail(&request->list, &ctx->retired_list);
|
|
}
|
|
|
|
if (ctx->file) {
|
|
v4l2_fh_init(&ctx->fh, video);
|
|
file->private_data = &ctx->fh;
|
|
v4l2_fh_add(&ctx->fh);
|
|
|
|
ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(rot_dev->m2m_dev,
|
|
ctx, sde_rotator_queue_init);
|
|
if (IS_ERR_OR_NULL(ctx->fh.m2m_ctx)) {
|
|
ret = PTR_ERR(ctx->fh.m2m_ctx);
|
|
ctx->fh.m2m_ctx = NULL;
|
|
goto error_m2m_init;
|
|
}
|
|
}
|
|
|
|
ret = kobject_init_and_add(&ctx->kobj, &sde_rotator_fs_ktype,
|
|
&rot_dev->dev->kobj, "session_%d", ctx->session_id);
|
|
if (ret) {
|
|
SDEDEV_ERR(ctx->rot_dev->dev,
|
|
"fail initialize context kobject\n");
|
|
goto error_kobj_init;
|
|
}
|
|
|
|
ret = sysfs_create_group(&ctx->kobj, &sde_rotator_fs_attr_group);
|
|
if (ret) {
|
|
SDEDEV_ERR(ctx->rot_dev->dev,
|
|
"fail register rotator sysfs nodes\n");
|
|
goto error_create_sysfs;
|
|
}
|
|
|
|
for (i = 0; i < MAX_ROT_OPEN_SESSION; i++) {
|
|
if (rot_dev->kthread_free[i]) {
|
|
rot_dev->kthread_free[i] = false;
|
|
ctx->kthread_id = i;
|
|
ctx->work_queue.rot_kw = &rot_dev->rot_kw[i];
|
|
ctx->work_queue.rot_thread = rot_dev->rot_thread[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ctx->kthread_id < 0) {
|
|
SDEDEV_ERR(ctx->rot_dev->dev,
|
|
"fail to acquire the kthread\n");
|
|
ret = -EINVAL;
|
|
goto error_alloc_kthread;
|
|
}
|
|
|
|
snprintf(name, sizeof(name), "%d_%d", rot_dev->dev->id,
|
|
ctx->session_id);
|
|
ctx->work_queue.timeline = sde_rotator_create_timeline(name);
|
|
if (!ctx->work_queue.timeline)
|
|
SDEDEV_DBG(ctx->rot_dev->dev, "timeline is not available\n");
|
|
|
|
sde_rot_mgr_lock(rot_dev->mgr);
|
|
sde_rotator_pm_qos_request(rot_dev,
|
|
SDE_ROTATOR_ADD_REQUEST);
|
|
ret = sde_rotator_session_open(rot_dev->mgr, &ctx->private,
|
|
ctx->session_id, &ctx->work_queue);
|
|
if (ret < 0) {
|
|
SDEDEV_ERR(ctx->rot_dev->dev, "fail open session\n");
|
|
goto error_open_session;
|
|
}
|
|
sde_rot_mgr_unlock(rot_dev->mgr);
|
|
|
|
if (ctx->file) {
|
|
/* Create control */
|
|
ctrl_handler = &ctx->ctrl_handler;
|
|
v4l2_ctrl_handler_init(ctrl_handler, 4);
|
|
v4l2_ctrl_new_std(ctrl_handler,
|
|
&sde_rotator_ctrl_ops, V4L2_CID_HFLIP, 0, 1, 1, 0);
|
|
v4l2_ctrl_new_std(ctrl_handler,
|
|
&sde_rotator_ctrl_ops, V4L2_CID_VFLIP, 0, 1, 1, 0);
|
|
v4l2_ctrl_new_std(ctrl_handler,
|
|
&sde_rotator_ctrl_ops, V4L2_CID_ROTATE, 0, 270, 90, 0);
|
|
v4l2_ctrl_new_custom(ctrl_handler,
|
|
&sde_rotator_ctrl_secure, NULL);
|
|
v4l2_ctrl_new_custom(ctrl_handler,
|
|
&sde_rotator_ctrl_secure_camera, NULL);
|
|
if (ctrl_handler->error) {
|
|
ret = ctrl_handler->error;
|
|
v4l2_ctrl_handler_free(ctrl_handler);
|
|
goto error_ctrl_handler;
|
|
}
|
|
ctx->fh.ctrl_handler = ctrl_handler;
|
|
v4l2_ctrl_handler_setup(ctrl_handler);
|
|
} else {
|
|
/* acquire exclusive context */
|
|
SDEDEV_DBG(rot_dev->dev, "acquire exclusive session id:%u\n",
|
|
ctx->session_id);
|
|
SDEROT_EVTLOG(ctx->session_id);
|
|
rot_dev->excl_ctx = ctx;
|
|
}
|
|
|
|
mutex_unlock(&rot_dev->lock);
|
|
|
|
SDEDEV_DBG(ctx->rot_dev->dev, "SDE v4l2 rotator open success\n");
|
|
|
|
ATRACE_BEGIN(ctx->kobj.name);
|
|
|
|
return ctx;
|
|
error_ctrl_handler:
|
|
error_open_session:
|
|
sde_rot_mgr_unlock(rot_dev->mgr);
|
|
sde_rotator_destroy_timeline(ctx->work_queue.timeline);
|
|
rot_dev->kthread_free[ctx->kthread_id] = true;
|
|
ctx->kthread_id = -1;
|
|
error_alloc_kthread:
|
|
sysfs_remove_group(&ctx->kobj, &sde_rotator_fs_attr_group);
|
|
error_create_sysfs:
|
|
kobject_put(&ctx->kobj);
|
|
error_kobj_init:
|
|
if (ctx->file) {
|
|
v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
|
|
ctx->fh.m2m_ctx = NULL;
|
|
}
|
|
error_m2m_init:
|
|
if (ctx->file) {
|
|
v4l2_fh_del(&ctx->fh);
|
|
v4l2_fh_exit(&ctx->fh);
|
|
}
|
|
mutex_unlock(&rot_dev->lock);
|
|
error_lock:
|
|
kfree(ctx);
|
|
ctx = NULL;
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_ctx_release - Rotator device release method.
|
|
* @ctx: Pointer rotator context.
|
|
* @file: Pointer to file struct (optional)
|
|
* return: 0 if success; error code, otherwise
|
|
*/
|
|
static int sde_rotator_ctx_release(struct sde_rotator_ctx *ctx,
|
|
struct file *file)
|
|
{
|
|
struct sde_rotator_device *rot_dev;
|
|
u32 session_id;
|
|
struct list_head *curr, *next;
|
|
|
|
if (!ctx) {
|
|
SDEROT_DBG("ctx is NULL\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
rot_dev = ctx->rot_dev;
|
|
session_id = ctx->session_id;
|
|
|
|
ATRACE_END(ctx->kobj.name);
|
|
|
|
SDEDEV_DBG(rot_dev->dev, "release s:%d\n", session_id);
|
|
mutex_lock(&rot_dev->lock);
|
|
if (rot_dev->excl_ctx == ctx) {
|
|
SDEDEV_DBG(rot_dev->dev, "release exclusive session id:%u\n",
|
|
session_id);
|
|
SDEROT_EVTLOG(session_id);
|
|
rot_dev->excl_ctx = NULL;
|
|
}
|
|
if (ctx->file) {
|
|
v4l2_ctrl_handler_free(&ctx->ctrl_handler);
|
|
SDEDEV_DBG(rot_dev->dev, "release streams s:%d\n", session_id);
|
|
if (ctx->fh.m2m_ctx) {
|
|
v4l2_m2m_streamoff(file, ctx->fh.m2m_ctx,
|
|
V4L2_BUF_TYPE_VIDEO_OUTPUT);
|
|
v4l2_m2m_streamoff(file, ctx->fh.m2m_ctx,
|
|
V4L2_BUF_TYPE_VIDEO_CAPTURE);
|
|
}
|
|
}
|
|
mutex_unlock(&rot_dev->lock);
|
|
SDEDEV_DBG(rot_dev->dev, "release submit work s:%d\n", session_id);
|
|
list_for_each_safe(curr, next, &ctx->pending_list) {
|
|
struct sde_rotator_request *request =
|
|
container_of(curr, struct sde_rotator_request, list);
|
|
|
|
SDEDEV_DBG(rot_dev->dev, "release submit work s:%d\n",
|
|
session_id);
|
|
kthread_cancel_work_sync(&request->submit_work);
|
|
}
|
|
SDEDEV_DBG(rot_dev->dev, "release session s:%d\n", session_id);
|
|
sde_rot_mgr_lock(rot_dev->mgr);
|
|
sde_rotator_pm_qos_request(rot_dev,
|
|
SDE_ROTATOR_REMOVE_REQUEST);
|
|
sde_rotator_session_close(rot_dev->mgr, ctx->private, session_id);
|
|
sde_rot_mgr_unlock(rot_dev->mgr);
|
|
SDEDEV_DBG(rot_dev->dev, "release retire work s:%d\n", session_id);
|
|
list_for_each_safe(curr, next, &ctx->pending_list) {
|
|
struct sde_rotator_request *request =
|
|
container_of(curr, struct sde_rotator_request, list);
|
|
|
|
SDEDEV_DBG(rot_dev->dev, "release retire work s:%d\n",
|
|
session_id);
|
|
kthread_cancel_work_sync(&request->retire_work);
|
|
}
|
|
mutex_lock(&rot_dev->lock);
|
|
SDEDEV_DBG(rot_dev->dev, "release context s:%d\n", session_id);
|
|
sde_rotator_destroy_timeline(ctx->work_queue.timeline);
|
|
if (ctx->kthread_id >= 0 && ctx->work_queue.rot_kw) {
|
|
kthread_flush_worker(ctx->work_queue.rot_kw);
|
|
rot_dev->kthread_free[ctx->kthread_id] = true;
|
|
}
|
|
sysfs_remove_group(&ctx->kobj, &sde_rotator_fs_attr_group);
|
|
kobject_put(&ctx->kobj);
|
|
if (ctx->file) {
|
|
if (ctx->fh.m2m_ctx)
|
|
v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
|
|
if (ctx->fh.vdev) {
|
|
v4l2_fh_del(&ctx->fh);
|
|
v4l2_fh_exit(&ctx->fh);
|
|
}
|
|
}
|
|
kfree(ctx->vbinfo_out);
|
|
kfree(ctx->vbinfo_cap);
|
|
kfree(ctx);
|
|
wake_up_interruptible(&rot_dev->open_wq);
|
|
mutex_unlock(&rot_dev->lock);
|
|
SDEDEV_DBG(rot_dev->dev, "release complete s:%d\n", session_id);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_update_retire_sequence - update retired sequence of the context
|
|
* referenced in the request, and wake up any waiting for update event
|
|
* @request: Pointer to rotator request
|
|
*/
|
|
static void sde_rotator_update_retire_sequence(
|
|
struct sde_rotator_request *request)
|
|
{
|
|
struct sde_rotator_ctx *ctx;
|
|
|
|
if (!request || !request->ctx) {
|
|
SDEROT_ERR("invalid parameters\n");
|
|
return;
|
|
}
|
|
|
|
ctx = request->ctx;
|
|
ctx->retired_sequence_id = request->sequence_id;
|
|
|
|
wake_up(&ctx->wait_queue);
|
|
|
|
SDEROT_DBG("update sequence s:%d.%d\n",
|
|
ctx->session_id, ctx->retired_sequence_id);
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_retire_request - retire the given rotator request with
|
|
* device mutex locked
|
|
* @request: Pointer to rotator request
|
|
*/
|
|
static void sde_rotator_retire_request(struct sde_rotator_request *request)
|
|
{
|
|
struct sde_rotator_ctx *ctx;
|
|
|
|
if (!request || !request->ctx) {
|
|
SDEROT_ERR("invalid parameters\n");
|
|
return;
|
|
}
|
|
|
|
ctx = request->ctx;
|
|
|
|
request->req = NULL;
|
|
request->sequence_id = 0;
|
|
request->committed = false;
|
|
spin_lock(&ctx->list_lock);
|
|
list_del_init(&request->list);
|
|
list_add_tail(&request->list, &ctx->retired_list);
|
|
spin_unlock(&ctx->list_lock);
|
|
|
|
wake_up(&ctx->wait_queue);
|
|
|
|
SDEROT_DBG("retire request s:%d.%d\n",
|
|
ctx->session_id, ctx->retired_sequence_id);
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_is_request_retired - Return true if given request already expired
|
|
* @request: Pointer to rotator request
|
|
*/
|
|
static bool sde_rotator_is_request_retired(struct sde_rotator_request *request)
|
|
{
|
|
struct sde_rotator_ctx *ctx;
|
|
u32 sequence_id;
|
|
s32 retire_delta;
|
|
|
|
if (!request || !request->ctx)
|
|
return true;
|
|
|
|
ctx = request->ctx;
|
|
sequence_id = request->sequence_id;
|
|
|
|
retire_delta = (s32) (ctx->retired_sequence_id - sequence_id);
|
|
|
|
SDEROT_DBG("sequence:%u/%u\n", sequence_id, ctx->retired_sequence_id);
|
|
|
|
return retire_delta >= 0;
|
|
}
|
|
|
|
static void sde_rotator_pm_qos_remove(struct sde_rot_data_type *rot_mdata)
|
|
{
|
|
struct pm_qos_request *req;
|
|
u32 cpu_mask;
|
|
|
|
if (!rot_mdata) {
|
|
SDEROT_DBG("invalid rot device or context\n");
|
|
return;
|
|
}
|
|
|
|
cpu_mask = rot_mdata->rot_pm_qos_cpu_mask;
|
|
|
|
if (!cpu_mask)
|
|
return;
|
|
|
|
req = &rot_mdata->pm_qos_rot_cpu_req;
|
|
pm_qos_remove_request(req);
|
|
}
|
|
|
|
void sde_rotator_pm_qos_add(struct sde_rot_data_type *rot_mdata)
|
|
{
|
|
struct pm_qos_request *req;
|
|
u32 cpu_mask;
|
|
int cpu;
|
|
|
|
if (!rot_mdata) {
|
|
SDEROT_DBG("invalid rot device or context\n");
|
|
return;
|
|
}
|
|
|
|
cpu_mask = rot_mdata->rot_pm_qos_cpu_mask;
|
|
|
|
if (!cpu_mask)
|
|
return;
|
|
|
|
req = &rot_mdata->pm_qos_rot_cpu_req;
|
|
req->type = PM_QOS_REQ_AFFINE_CORES;
|
|
cpumask_empty(&req->cpus_affine);
|
|
for_each_possible_cpu(cpu) {
|
|
if ((1 << cpu) & cpu_mask)
|
|
cpumask_set_cpu(cpu, &req->cpus_affine);
|
|
}
|
|
pm_qos_add_request(req, PM_QOS_CPU_DMA_LATENCY,
|
|
PM_QOS_DEFAULT_VALUE);
|
|
|
|
SDEROT_DBG("rotator pmqos add mask %x latency %x\n",
|
|
rot_mdata->rot_pm_qos_cpu_mask,
|
|
rot_mdata->rot_pm_qos_cpu_dma_latency);
|
|
}
|
|
|
|
static void sde_rotator_pm_qos_request(struct sde_rotator_device *rot_dev,
|
|
bool add_request)
|
|
{
|
|
u32 cpu_mask;
|
|
u32 cpu_dma_latency;
|
|
bool changed = false;
|
|
|
|
if (!rot_dev) {
|
|
SDEROT_DBG("invalid rot device or context\n");
|
|
return;
|
|
}
|
|
|
|
cpu_mask = rot_dev->mdata->rot_pm_qos_cpu_mask;
|
|
cpu_dma_latency = rot_dev->mdata->rot_pm_qos_cpu_dma_latency;
|
|
|
|
if (!cpu_mask)
|
|
return;
|
|
|
|
if (add_request) {
|
|
if (rot_dev->mdata->rot_pm_qos_cpu_count == 0)
|
|
changed = true;
|
|
rot_dev->mdata->rot_pm_qos_cpu_count++;
|
|
} else {
|
|
if (rot_dev->mdata->rot_pm_qos_cpu_count != 0) {
|
|
rot_dev->mdata->rot_pm_qos_cpu_count--;
|
|
if (rot_dev->mdata->rot_pm_qos_cpu_count == 0)
|
|
changed = true;
|
|
} else {
|
|
SDEROT_DBG("%s: ref_count is not balanced\n",
|
|
__func__);
|
|
}
|
|
}
|
|
|
|
if (!changed)
|
|
return;
|
|
|
|
SDEROT_EVTLOG(add_request, cpu_mask, cpu_dma_latency);
|
|
|
|
if (!add_request) {
|
|
pm_qos_update_request(&rot_dev->mdata->pm_qos_rot_cpu_req,
|
|
PM_QOS_DEFAULT_VALUE);
|
|
return;
|
|
}
|
|
|
|
pm_qos_update_request(&rot_dev->mdata->pm_qos_rot_cpu_req,
|
|
cpu_dma_latency);
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_inline_open - open inline rotator session
|
|
* @pdev: Pointer to rotator platform device
|
|
* @video_mode: true if video mode is requested
|
|
* return: Pointer to new rotator session context
|
|
*/
|
|
void *sde_rotator_inline_open(struct platform_device *pdev)
|
|
{
|
|
struct sde_rotator_device *rot_dev;
|
|
struct sde_rotator_ctx *ctx;
|
|
int rc;
|
|
|
|
if (!pdev) {
|
|
SDEROT_ERR("invalid platform device\n");
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
rot_dev = (struct sde_rotator_device *) platform_get_drvdata(pdev);
|
|
if (!rot_dev) {
|
|
SDEROT_ERR("invalid rotator device\n");
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
ctx = sde_rotator_ctx_open(rot_dev, NULL);
|
|
if (IS_ERR_OR_NULL(ctx)) {
|
|
rc = PTR_ERR(ctx);
|
|
SDEROT_ERR("failed to open rotator context %d\n", rc);
|
|
goto rotator_open_error;
|
|
}
|
|
|
|
ctx->slice = llcc_slice_getd(LLCC_ROTATOR);
|
|
if (IS_ERR(ctx->slice)) {
|
|
rc = PTR_ERR(ctx->slice);
|
|
SDEROT_ERR("failed to get system cache %d\n", rc);
|
|
goto slice_getd_error;
|
|
}
|
|
|
|
if (!rot_dev->disable_syscache) {
|
|
rc = llcc_slice_activate(ctx->slice);
|
|
if (rc) {
|
|
SDEROT_ERR("failed to activate slice %d\n", rc);
|
|
goto activate_error;
|
|
}
|
|
SDEROT_DBG("scid %d size %zukb\n",
|
|
llcc_get_slice_id(ctx->slice),
|
|
llcc_get_slice_size(ctx->slice));
|
|
} else {
|
|
SDEROT_DBG("syscache bypassed\n");
|
|
}
|
|
|
|
SDEROT_EVTLOG(ctx->session_id, llcc_get_slice_id(ctx->slice),
|
|
llcc_get_slice_size(ctx->slice),
|
|
rot_dev->disable_syscache);
|
|
|
|
return ctx;
|
|
|
|
activate_error:
|
|
llcc_slice_putd(ctx->slice);
|
|
ctx->slice = NULL;
|
|
slice_getd_error:
|
|
sde_rotator_ctx_release(ctx, NULL);
|
|
rotator_open_error:
|
|
return ERR_PTR(rc);
|
|
}
|
|
EXPORT_SYMBOL(sde_rotator_inline_open);
|
|
|
|
int sde_rotator_inline_release(void *handle)
|
|
{
|
|
struct sde_rotator_device *rot_dev;
|
|
struct sde_rotator_ctx *ctx;
|
|
|
|
if (!handle) {
|
|
SDEROT_ERR("invalid rotator ctx\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ctx = handle;
|
|
rot_dev = ctx->rot_dev;
|
|
|
|
if (!rot_dev) {
|
|
SDEROT_ERR("invalid rotator device\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ctx->slice) {
|
|
if (!rot_dev->disable_syscache)
|
|
llcc_slice_deactivate(ctx->slice);
|
|
llcc_slice_putd(ctx->slice);
|
|
ctx->slice = NULL;
|
|
}
|
|
|
|
SDEROT_EVTLOG(ctx->session_id);
|
|
|
|
return sde_rotator_ctx_release(ctx, NULL);
|
|
}
|
|
EXPORT_SYMBOL(sde_rotator_inline_release);
|
|
|
|
/*
|
|
* sde_rotator_inline_get_dst_pixfmt - determine output pixel format
|
|
* @pdev: Pointer to platform device
|
|
* @src_pixfmt: input pixel format
|
|
* @dst_pixfmt: Pointer to output pixel format (output)
|
|
* return: 0 if success; error code otherwise
|
|
*/
|
|
int sde_rotator_inline_get_dst_pixfmt(struct platform_device *pdev,
|
|
u32 src_pixfmt, u32 *dst_pixfmt)
|
|
{
|
|
int rc;
|
|
|
|
if (!src_pixfmt || !dst_pixfmt)
|
|
return -EINVAL;
|
|
|
|
rc = sde_rot_get_base_tilea5x_pixfmt(src_pixfmt, dst_pixfmt);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/*
|
|
* Currently, NV21 tile is not supported as output; hence,
|
|
* override with NV12 tile.
|
|
*/
|
|
if (*dst_pixfmt == SDE_PIX_FMT_Y_CRCB_H2V2_TILE)
|
|
*dst_pixfmt = SDE_PIX_FMT_Y_CBCR_H2V2_TILE;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(sde_rotator_inline_get_dst_pixfmt);
|
|
|
|
/*
|
|
* sde_rotator_inline_get_downscale_caps - get scaling capability
|
|
* @pdev: Pointer to platform device
|
|
* @caps: string buffer for capability
|
|
* @len: length of string buffer
|
|
* return: length of capability string
|
|
*/
|
|
int sde_rotator_inline_get_downscale_caps(struct platform_device *pdev,
|
|
char *caps, int len)
|
|
{
|
|
struct sde_rotator_device *rot_dev;
|
|
int rc;
|
|
|
|
if (!pdev) {
|
|
SDEROT_ERR("invalid platform device\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
rot_dev = (struct sde_rotator_device *) platform_get_drvdata(pdev);
|
|
if (!rot_dev || !rot_dev->mgr) {
|
|
SDEROT_ERR("invalid rotator device\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
sde_rot_mgr_lock(rot_dev->mgr);
|
|
rc = sde_rotator_get_downscale_caps(rot_dev->mgr, caps, len);
|
|
sde_rot_mgr_unlock(rot_dev->mgr);
|
|
|
|
return rc;
|
|
}
|
|
EXPORT_SYMBOL(sde_rotator_inline_get_downscale_caps);
|
|
|
|
/*
|
|
* sde_rotator_inline_get_maxlinewidth - get maximum line width of rotator
|
|
* @pdev: Pointer to platform device
|
|
* return: maximum line width
|
|
*/
|
|
int sde_rotator_inline_get_maxlinewidth(struct platform_device *pdev)
|
|
{
|
|
struct sde_rotator_device *rot_dev;
|
|
int maxlinewidth;
|
|
|
|
if (!pdev) {
|
|
SDEROT_ERR("invalid platform device\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
rot_dev = (struct sde_rotator_device *)platform_get_drvdata(pdev);
|
|
if (!rot_dev || !rot_dev->mgr) {
|
|
SDEROT_ERR("invalid rotator device\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
sde_rot_mgr_lock(rot_dev->mgr);
|
|
maxlinewidth = sde_rotator_get_maxlinewidth(rot_dev->mgr);
|
|
sde_rot_mgr_unlock(rot_dev->mgr);
|
|
|
|
return maxlinewidth;
|
|
}
|
|
EXPORT_SYMBOL(sde_rotator_inline_get_maxlinewidth);
|
|
|
|
/*
|
|
* sde_rotator_inline_get_pixfmt_caps - get pixel format capability
|
|
* @pdev: Pointer to platform device
|
|
* @pixfmt: array of pixel format buffer
|
|
* @len: length of pixel format buffer
|
|
* return: length of pixel format capability if success; error code otherwise
|
|
*/
|
|
int sde_rotator_inline_get_pixfmt_caps(struct platform_device *pdev,
|
|
bool input, u32 *pixfmts, int len)
|
|
{
|
|
struct sde_rotator_device *rot_dev;
|
|
u32 i, pixfmt;
|
|
|
|
if (!pdev) {
|
|
SDEROT_ERR("invalid platform device\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
rot_dev = (struct sde_rotator_device *) platform_get_drvdata(pdev);
|
|
if (!rot_dev || !rot_dev->mgr) {
|
|
SDEROT_ERR("invalid rotator device\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
sde_rot_mgr_lock(rot_dev->mgr);
|
|
for (i = 0;; i++) {
|
|
pixfmt = sde_rotator_get_pixfmt(rot_dev->mgr, i, input,
|
|
SDE_ROTATOR_MODE_SBUF);
|
|
if (!pixfmt)
|
|
break;
|
|
if (pixfmts && i < len)
|
|
pixfmts[i] = pixfmt;
|
|
}
|
|
sde_rot_mgr_unlock(rot_dev->mgr);
|
|
|
|
return i;
|
|
}
|
|
EXPORT_SYMBOL(sde_rotator_inline_get_pixfmt_caps);
|
|
|
|
/*
|
|
* _sde_rotator_inline_cleanup - perform inline related request cleanup
|
|
* This function assumes rot_dev->mgr lock has been taken when called.
|
|
* @handle: Pointer to rotator context
|
|
* @request: Pointer to rotation request
|
|
* return: 0 if success; -EAGAIN if cleanup should be retried
|
|
*/
|
|
static int _sde_rotator_inline_cleanup(void *handle,
|
|
struct sde_rotator_request *request)
|
|
{
|
|
struct sde_rotator_ctx *ctx;
|
|
struct sde_rotator_device *rot_dev;
|
|
int ret;
|
|
|
|
if (!handle || !request) {
|
|
SDEROT_ERR("invalid rotator handle/request\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ctx = handle;
|
|
rot_dev = ctx->rot_dev;
|
|
|
|
if (!rot_dev || !rot_dev->mgr) {
|
|
SDEROT_ERR("invalid rotator device\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (request->committed) {
|
|
/* wait until request is finished */
|
|
sde_rot_mgr_unlock(rot_dev->mgr);
|
|
mutex_unlock(&rot_dev->lock);
|
|
ret = wait_event_timeout(ctx->wait_queue,
|
|
sde_rotator_is_request_retired(request),
|
|
msecs_to_jiffies(rot_dev->streamoff_timeout));
|
|
mutex_lock(&rot_dev->lock);
|
|
sde_rot_mgr_lock(rot_dev->mgr);
|
|
|
|
if (!ret) {
|
|
SDEROT_ERR("timeout w/o retire s:%d\n",
|
|
ctx->session_id);
|
|
SDEROT_EVTLOG(ctx->session_id, SDE_ROT_EVTLOG_ERROR);
|
|
sde_rotator_abort_inline_request(rot_dev->mgr,
|
|
ctx->private, request->req);
|
|
return -EAGAIN;
|
|
} else if (ret == 1) {
|
|
SDEROT_ERR("timeout w/ retire s:%d\n", ctx->session_id);
|
|
SDEROT_EVTLOG(ctx->session_id, SDE_ROT_EVTLOG_ERROR);
|
|
}
|
|
}
|
|
|
|
sde_rotator_req_finish(rot_dev->mgr, ctx->private, request->req);
|
|
sde_rotator_retire_request(request);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_inline_commit - commit given rotator command
|
|
* @handle: Pointer to rotator context
|
|
* @cmd: Pointer to rotator command
|
|
* @cmd_type: command type - validate/prepare/commit/cleanup
|
|
* return: 0 if success; error code otherwise
|
|
*/
|
|
int sde_rotator_inline_commit(void *handle, struct sde_rotator_inline_cmd *cmd,
|
|
enum sde_rotator_inline_cmd_type cmd_type)
|
|
{
|
|
struct sde_rotator_ctx *ctx;
|
|
struct sde_rotator_device *rot_dev;
|
|
struct sde_rotator_request *request = NULL;
|
|
struct sde_rot_entry_container *req = NULL;
|
|
struct sde_rotation_config rotcfg;
|
|
struct sde_rot_trace_entry rot_trace;
|
|
ktime_t *ts;
|
|
u32 flags = 0;
|
|
int i, ret = 0;
|
|
|
|
if (!handle || !cmd) {
|
|
SDEROT_ERR("invalid rotator handle/cmd\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ctx = handle;
|
|
rot_dev = ctx->rot_dev;
|
|
|
|
if (!rot_dev || !rot_dev->mgr) {
|
|
SDEROT_ERR("invalid rotator device\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
SDEROT_DBG(
|
|
"s:%d.%u src:(%u,%u,%u,%u)/%ux%u/%c%c%c%c dst:(%u,%u,%u,%u)/%c%c%c%c r:%d f:%d/%d s:%d fps:%u clk:%llu bw:%llu prefill:%llu wb:%d vid:%d cmd:%d\n",
|
|
ctx->session_id, cmd->sequence_id,
|
|
cmd->src_rect_x, cmd->src_rect_y,
|
|
cmd->src_rect_w, cmd->src_rect_h,
|
|
cmd->src_width, cmd->src_height,
|
|
cmd->src_pixfmt >> 0, cmd->src_pixfmt >> 8,
|
|
cmd->src_pixfmt >> 16, cmd->src_pixfmt >> 24,
|
|
cmd->dst_rect_x, cmd->dst_rect_y,
|
|
cmd->dst_rect_w, cmd->dst_rect_h,
|
|
cmd->dst_pixfmt >> 0, cmd->dst_pixfmt >> 8,
|
|
cmd->dst_pixfmt >> 16, cmd->dst_pixfmt >> 24,
|
|
cmd->rot90, cmd->hflip, cmd->vflip, cmd->secure, cmd->fps,
|
|
cmd->clkrate, cmd->data_bw, cmd->prefill_bw,
|
|
cmd->dst_writeback, cmd->video_mode, cmd_type);
|
|
SDEROT_EVTLOG(ctx->session_id, cmd->sequence_id,
|
|
cmd->src_rect_x, cmd->src_rect_y,
|
|
cmd->src_rect_w, cmd->src_rect_h,
|
|
cmd->src_pixfmt,
|
|
cmd->dst_rect_w, cmd->dst_rect_h,
|
|
cmd->dst_pixfmt,
|
|
cmd->fps, cmd->clkrate, cmd->data_bw, cmd->prefill_bw,
|
|
(cmd->rot90 << 0) | (cmd->hflip << 1) | (cmd->vflip << 2) |
|
|
(cmd->secure << 3) | (cmd->dst_writeback << 4) |
|
|
(cmd->video_mode << 5) |
|
|
(cmd_type << 24));
|
|
|
|
mutex_lock(&rot_dev->lock);
|
|
sde_rot_mgr_lock(rot_dev->mgr);
|
|
|
|
if (cmd_type == SDE_ROTATOR_INLINE_CMD_VALIDATE ||
|
|
cmd_type == SDE_ROTATOR_INLINE_CMD_COMMIT) {
|
|
|
|
struct sde_rotation_item item;
|
|
struct sde_rotator_statistics *stats = &rot_dev->stats;
|
|
int scid = llcc_get_slice_id(ctx->slice);
|
|
|
|
/* allocate slot for timestamp */
|
|
ts = stats->ts[stats->count % SDE_ROTATOR_NUM_EVENTS];
|
|
if (cmd_type == SDE_ROTATOR_INLINE_CMD_COMMIT)
|
|
stats->count++;
|
|
|
|
if (cmd->rot90)
|
|
flags |= SDE_ROTATION_90;
|
|
if (cmd->hflip)
|
|
flags |= SDE_ROTATION_FLIP_LR;
|
|
if (cmd->vflip)
|
|
flags |= SDE_ROTATION_FLIP_UD;
|
|
if (cmd->secure)
|
|
flags |= SDE_ROTATION_SECURE;
|
|
|
|
flags |= SDE_ROTATION_EXT_PERF;
|
|
|
|
/* fill in item work structure */
|
|
memset(&item, 0, sizeof(struct sde_rotation_item));
|
|
item.flags = flags | SDE_ROTATION_EXT_IOVA;
|
|
item.trigger = cmd->video_mode ? SDE_ROTATOR_TRIGGER_VIDEO :
|
|
SDE_ROTATOR_TRIGGER_COMMAND;
|
|
item.prefill_bw = cmd->prefill_bw;
|
|
item.session_id = ctx->session_id;
|
|
item.sequence_id = cmd->sequence_id;
|
|
item.src_rect.x = cmd->src_rect_x;
|
|
item.src_rect.y = cmd->src_rect_y;
|
|
item.src_rect.w = cmd->src_rect_w;
|
|
item.src_rect.h = cmd->src_rect_h;
|
|
item.input.width = cmd->src_width;
|
|
item.input.height = cmd->src_height;
|
|
item.input.format = cmd->src_pixfmt;
|
|
|
|
for (i = 0; i < SDE_ROTATOR_INLINE_PLANE_MAX; i++) {
|
|
item.input.planes[i].addr = cmd->src_addr[i];
|
|
item.input.planes[i].len = cmd->src_len[i];
|
|
item.input.planes[i].fd = -1;
|
|
}
|
|
item.input.plane_count = cmd->src_planes;
|
|
item.input.comp_ratio.numer = 1;
|
|
item.input.comp_ratio.denom = 1;
|
|
|
|
item.output.width = cmd->dst_rect_x + cmd->dst_rect_w;
|
|
item.output.height = cmd->dst_rect_y + cmd->dst_rect_h;
|
|
item.dst_rect.x = cmd->dst_rect_x;
|
|
item.dst_rect.y = cmd->dst_rect_y;
|
|
item.dst_rect.w = cmd->dst_rect_w;
|
|
item.dst_rect.h = cmd->dst_rect_h;
|
|
item.output.sbuf = true;
|
|
item.output.scid = scid;
|
|
item.output.writeback = cmd->dst_writeback;
|
|
item.output.format = cmd->dst_pixfmt;
|
|
|
|
for (i = 0; i < SDE_ROTATOR_INLINE_PLANE_MAX; i++) {
|
|
item.output.planes[i].addr = cmd->dst_addr[i];
|
|
item.output.planes[i].len = cmd->dst_len[i];
|
|
item.output.planes[i].fd = -1;
|
|
}
|
|
item.output.plane_count = cmd->dst_planes;
|
|
item.output.comp_ratio.numer = 1;
|
|
item.output.comp_ratio.denom = 1;
|
|
item.sequence_id = ++(ctx->commit_sequence_id);
|
|
item.ts = ts;
|
|
|
|
req = sde_rotator_req_init(rot_dev->mgr, ctx->private,
|
|
&item, 1, 0);
|
|
if (IS_ERR_OR_NULL(req)) {
|
|
SDEROT_ERR("fail allocate request s:%d\n",
|
|
ctx->session_id);
|
|
ret = -ENOMEM;
|
|
goto error_init_request;
|
|
}
|
|
|
|
/* initialize session configuration */
|
|
memset(&rotcfg, 0, sizeof(struct sde_rotation_config));
|
|
rotcfg.flags = flags;
|
|
rotcfg.frame_rate = cmd->fps;
|
|
rotcfg.clk_rate = cmd->clkrate;
|
|
rotcfg.data_bw = cmd->data_bw;
|
|
rotcfg.session_id = ctx->session_id;
|
|
rotcfg.input.width = cmd->src_rect_w;
|
|
rotcfg.input.height = cmd->src_rect_h;
|
|
rotcfg.input.format = cmd->src_pixfmt;
|
|
rotcfg.input.comp_ratio.numer = 1;
|
|
rotcfg.input.comp_ratio.denom = 1;
|
|
rotcfg.output.width = cmd->dst_rect_w;
|
|
rotcfg.output.height = cmd->dst_rect_h;
|
|
rotcfg.output.format = cmd->dst_pixfmt;
|
|
rotcfg.output.comp_ratio.numer = 1;
|
|
rotcfg.output.comp_ratio.denom = 1;
|
|
rotcfg.output.sbuf = true;
|
|
}
|
|
|
|
if (cmd_type == SDE_ROTATOR_INLINE_CMD_VALIDATE) {
|
|
|
|
ret = sde_rotator_session_validate(rot_dev->mgr,
|
|
ctx->private, &rotcfg);
|
|
if (ret) {
|
|
SDEROT_WARN("fail session validation s:%d\n",
|
|
ctx->session_id);
|
|
goto error_session_validate;
|
|
}
|
|
|
|
devm_kfree(rot_dev->dev, req);
|
|
req = NULL;
|
|
|
|
} else if (cmd_type == SDE_ROTATOR_INLINE_CMD_COMMIT) {
|
|
|
|
if (memcmp(&rotcfg, &ctx->rotcfg, sizeof(rotcfg))) {
|
|
ret = sde_rotator_session_config(rot_dev->mgr,
|
|
ctx->private, &rotcfg);
|
|
if (ret) {
|
|
SDEROT_ERR("fail session config s:%d\n",
|
|
ctx->session_id);
|
|
goto error_session_config;
|
|
}
|
|
|
|
ctx->rotcfg = rotcfg;
|
|
}
|
|
|
|
request = list_first_entry_or_null(&ctx->retired_list,
|
|
struct sde_rotator_request, list);
|
|
if (!request) {
|
|
/* should not happen */
|
|
ret = -ENOMEM;
|
|
SDEROT_ERR("no free request s:%d\n", ctx->session_id);
|
|
goto error_retired_list;
|
|
}
|
|
|
|
request->req = req;
|
|
request->sequence_id = req->entries[0].item.sequence_id;
|
|
|
|
spin_lock(&ctx->list_lock);
|
|
list_del_init(&request->list);
|
|
list_add_tail(&request->list, &ctx->pending_list);
|
|
spin_unlock(&ctx->list_lock);
|
|
|
|
ts = req->entries[0].item.ts;
|
|
if (ts) {
|
|
ts[SDE_ROTATOR_TS_SRCQB] = ktime_get();
|
|
ts[SDE_ROTATOR_TS_DSTQB] = ktime_get();
|
|
ts[SDE_ROTATOR_TS_FENCE] = ktime_get();
|
|
} else {
|
|
SDEROT_ERR("invalid stats timestamp\n");
|
|
}
|
|
req->retire_kw = ctx->work_queue.rot_kw;
|
|
req->retire_work = &request->retire_work;
|
|
|
|
/* Set values to pass to trace */
|
|
rot_trace.wb_idx = req->entries[0].item.wb_idx;
|
|
rot_trace.flags = req->entries[0].item.flags;
|
|
rot_trace.input_format = req->entries[0].item.input.format;
|
|
rot_trace.input_width = req->entries[0].item.input.width;
|
|
rot_trace.input_height = req->entries[0].item.input.height;
|
|
rot_trace.src_x = req->entries[0].item.src_rect.x;
|
|
rot_trace.src_y = req->entries[0].item.src_rect.y;
|
|
rot_trace.src_w = req->entries[0].item.src_rect.w;
|
|
rot_trace.src_h = req->entries[0].item.src_rect.h;
|
|
rot_trace.output_format = req->entries[0].item.output.format;
|
|
rot_trace.output_width = req->entries[0].item.output.width;
|
|
rot_trace.output_height = req->entries[0].item.output.height;
|
|
rot_trace.dst_x = req->entries[0].item.dst_rect.x;
|
|
rot_trace.dst_y = req->entries[0].item.dst_rect.y;
|
|
rot_trace.dst_w = req->entries[0].item.dst_rect.w;
|
|
rot_trace.dst_h = req->entries[0].item.dst_rect.h;
|
|
|
|
|
|
trace_rot_entry_fence(
|
|
ctx->session_id, cmd->sequence_id, &rot_trace);
|
|
|
|
ret = sde_rotator_handle_request_common(
|
|
rot_dev->mgr, ctx->private, req);
|
|
if (ret) {
|
|
SDEROT_ERR("fail handle request s:%d\n",
|
|
ctx->session_id);
|
|
goto error_handle_request;
|
|
}
|
|
|
|
sde_rotator_req_reset_start(rot_dev->mgr, req);
|
|
|
|
sde_rotator_queue_request(rot_dev->mgr, ctx->private, req);
|
|
|
|
request->committed = true;
|
|
|
|
/* save request in private handle */
|
|
cmd->priv_handle = request;
|
|
|
|
} else if (cmd_type == SDE_ROTATOR_INLINE_CMD_START) {
|
|
if (!cmd->priv_handle) {
|
|
ret = -EINVAL;
|
|
SDEROT_ERR("invalid private handle\n");
|
|
goto error_invalid_handle;
|
|
}
|
|
|
|
request = cmd->priv_handle;
|
|
sde_rotator_req_set_start(rot_dev->mgr, request->req);
|
|
} else if (cmd_type == SDE_ROTATOR_INLINE_CMD_CLEANUP) {
|
|
if (!cmd->priv_handle) {
|
|
ret = -EINVAL;
|
|
SDEROT_ERR("invalid private handle\n");
|
|
goto error_invalid_handle;
|
|
}
|
|
|
|
request = cmd->priv_handle;
|
|
|
|
/* attempt single retry if first cleanup attempt failed */
|
|
if (_sde_rotator_inline_cleanup(handle, request) == -EAGAIN)
|
|
_sde_rotator_inline_cleanup(handle, request);
|
|
|
|
cmd->priv_handle = NULL;
|
|
} else if (cmd_type == SDE_ROTATOR_INLINE_CMD_ABORT) {
|
|
if (!cmd->priv_handle) {
|
|
ret = -EINVAL;
|
|
SDEROT_ERR("invalid private handle\n");
|
|
goto error_invalid_handle;
|
|
}
|
|
|
|
request = cmd->priv_handle;
|
|
if (!sde_rotator_is_request_retired(request))
|
|
sde_rotator_abort_inline_request(rot_dev->mgr,
|
|
ctx->private, request->req);
|
|
}
|
|
|
|
sde_rot_mgr_unlock(rot_dev->mgr);
|
|
mutex_unlock(&rot_dev->lock);
|
|
return 0;
|
|
|
|
error_handle_request:
|
|
sde_rotator_update_retire_sequence(request);
|
|
sde_rotator_retire_request(request);
|
|
error_retired_list:
|
|
error_session_validate:
|
|
error_session_config:
|
|
devm_kfree(rot_dev->dev, req);
|
|
error_invalid_handle:
|
|
error_init_request:
|
|
sde_rot_mgr_unlock(rot_dev->mgr);
|
|
mutex_unlock(&rot_dev->lock);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(sde_rotator_inline_commit);
|
|
|
|
void sde_rotator_inline_reg_dump(struct platform_device *pdev)
|
|
{
|
|
struct sde_rotator_device *rot_dev;
|
|
|
|
if (!pdev) {
|
|
SDEROT_ERR("invalid platform device\n");
|
|
return;
|
|
}
|
|
|
|
rot_dev = (struct sde_rotator_device *) platform_get_drvdata(pdev);
|
|
if (!rot_dev || !rot_dev->mgr) {
|
|
SDEROT_ERR("invalid rotator device\n");
|
|
return;
|
|
}
|
|
|
|
sde_rot_mgr_lock(rot_dev->mgr);
|
|
sde_rotator_core_dump(rot_dev->mgr);
|
|
sde_rot_mgr_unlock(rot_dev->mgr);
|
|
}
|
|
EXPORT_SYMBOL(sde_rotator_inline_reg_dump);
|
|
|
|
/*
|
|
* sde_rotator_open - Rotator device open method.
|
|
* @file: Pointer to file struct.
|
|
*/
|
|
static int sde_rotator_open(struct file *file)
|
|
{
|
|
struct sde_rotator_device *rot_dev = video_drvdata(file);
|
|
struct sde_rotator_ctx *ctx;
|
|
int ret = 0;
|
|
|
|
ctx = sde_rotator_ctx_open(rot_dev, file);
|
|
if (IS_ERR_OR_NULL(ctx)) {
|
|
SDEDEV_DBG(rot_dev->dev, "failed to open %d\n", ret);
|
|
ret = PTR_ERR(ctx);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_release - Rotator device release method.
|
|
* @file: Pointer to file struct.
|
|
*/
|
|
static int sde_rotator_release(struct file *file)
|
|
{
|
|
struct sde_rotator_ctx *ctx =
|
|
sde_rotator_ctx_from_fh(file->private_data);
|
|
|
|
return sde_rotator_ctx_release(ctx, file);
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_poll - rotator device pool method.
|
|
* @file: Pointer to file struct.
|
|
* @wait: Pointer to poll table struct.
|
|
*/
|
|
static unsigned int sde_rotator_poll(struct file *file,
|
|
struct poll_table_struct *wait)
|
|
{
|
|
struct sde_rotator_device *rot_dev = video_drvdata(file);
|
|
struct sde_rotator_ctx *ctx =
|
|
sde_rotator_ctx_from_fh(file->private_data);
|
|
int ret;
|
|
|
|
mutex_lock(&rot_dev->lock);
|
|
ret = v4l2_m2m_poll(file, ctx->fh.m2m_ctx, wait);
|
|
mutex_unlock(&rot_dev->lock);
|
|
return ret;
|
|
}
|
|
|
|
/* rotator device file operations callbacks */
|
|
static const struct v4l2_file_operations sde_rotator_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = sde_rotator_open,
|
|
.release = sde_rotator_release,
|
|
.poll = sde_rotator_poll,
|
|
.unlocked_ioctl = video_ioctl2,
|
|
#ifdef CONFIG_COMPAT
|
|
.compat_ioctl32 = sde_rotator_compat_ioctl32,
|
|
#endif
|
|
};
|
|
|
|
/*
|
|
* sde_rotator_querycap - V4l2 ioctl query capability handler.
|
|
* @file: Pointer to file struct.
|
|
* @fh: V4l2 File handle.
|
|
* @cap: Pointer to v4l2_capability struct need to be filled.
|
|
*/
|
|
static int sde_rotator_querycap(struct file *file,
|
|
void *fh, struct v4l2_capability *cap)
|
|
{
|
|
cap->bus_info[0] = 0;
|
|
strlcpy(cap->driver, SDE_ROTATOR_DRV_NAME, sizeof(cap->driver));
|
|
strlcpy(cap->card, SDE_ROTATOR_DRV_NAME, sizeof(cap->card));
|
|
cap->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M |
|
|
V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_VIDEO_CAPTURE;
|
|
cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_enum_fmt_vid_cap - V4l2 ioctl enumerate output format handler.
|
|
* @file: Pointer to file struct.
|
|
* @fh: V4l2 File handle.
|
|
* @f: Pointer to v4l2_fmtdesc struct need to be filled.
|
|
*/
|
|
static int sde_rotator_enum_fmt_vid_cap(struct file *file,
|
|
void *fh, struct v4l2_fmtdesc *f)
|
|
{
|
|
struct sde_rotator_ctx *ctx = sde_rotator_ctx_from_fh(fh);
|
|
struct sde_rotator_device *rot_dev = ctx->rot_dev;
|
|
struct sde_mdp_format_params *fmt;
|
|
u32 i, index, pixfmt;
|
|
bool found = false;
|
|
|
|
for (i = 0, index = 0; index <= f->index; i++) {
|
|
pixfmt = sde_rotator_get_pixfmt(rot_dev->mgr, i, false,
|
|
SDE_ROTATOR_MODE_OFFLINE);
|
|
if (!pixfmt)
|
|
return -EINVAL;
|
|
|
|
fmt = sde_get_format_params(pixfmt);
|
|
if (!fmt)
|
|
return -EINVAL;
|
|
|
|
if (sde_mdp_is_private_format(fmt))
|
|
continue;
|
|
|
|
if (index == f->index) {
|
|
found = true;
|
|
break;
|
|
}
|
|
|
|
index++;
|
|
}
|
|
|
|
if (!found)
|
|
return -EINVAL;
|
|
|
|
f->pixelformat = pixfmt;
|
|
strlcpy(f->description, fmt->description, sizeof(f->description));
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_enum_fmt_vid_out - V4l2 ioctl enumerate capture format handler.
|
|
* @file: Pointer to file struct.
|
|
* @fh: V4l2 File handle.
|
|
* @f: Pointer to v4l2_fmtdesc struct need to be filled.
|
|
*/
|
|
static int sde_rotator_enum_fmt_vid_out(struct file *file,
|
|
void *fh, struct v4l2_fmtdesc *f)
|
|
{
|
|
struct sde_rotator_ctx *ctx = sde_rotator_ctx_from_fh(fh);
|
|
struct sde_rotator_device *rot_dev = ctx->rot_dev;
|
|
struct sde_mdp_format_params *fmt;
|
|
u32 i, index, pixfmt;
|
|
bool found = false;
|
|
|
|
for (i = 0, index = 0; index <= f->index; i++) {
|
|
pixfmt = sde_rotator_get_pixfmt(rot_dev->mgr, i, true,
|
|
SDE_ROTATOR_MODE_OFFLINE);
|
|
if (!pixfmt)
|
|
return -EINVAL;
|
|
|
|
fmt = sde_get_format_params(pixfmt);
|
|
if (!fmt)
|
|
return -EINVAL;
|
|
|
|
if (sde_mdp_is_private_format(fmt))
|
|
continue;
|
|
|
|
if (index == f->index) {
|
|
found = true;
|
|
break;
|
|
}
|
|
|
|
index++;
|
|
}
|
|
|
|
if (!found)
|
|
return -EINVAL;
|
|
|
|
f->pixelformat = pixfmt;
|
|
strlcpy(f->description, fmt->description, sizeof(f->description));
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_g_fmt_cap - V4l2 ioctl get capture format handler.
|
|
* @file: Pointer to file struct.
|
|
* @fh: V4l2 File handle.
|
|
* @f: Pointer to v4l2_format struct need to be filled.
|
|
*/
|
|
static int sde_rotator_g_fmt_cap(struct file *file, void *fh,
|
|
struct v4l2_format *f)
|
|
{
|
|
struct sde_rotator_ctx *ctx = sde_rotator_ctx_from_fh(fh);
|
|
|
|
*f = ctx->format_cap;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_g_fmt_out - V4l2 ioctl get output format handler.
|
|
* @file: Pointer to file struct.
|
|
* @fh: V4l2 File handle.
|
|
* @f: Pointer to v4l2_format struct need to be filled.
|
|
*/
|
|
static int sde_rotator_g_fmt_out(struct file *file, void *fh,
|
|
struct v4l2_format *f)
|
|
{
|
|
struct sde_rotator_ctx *ctx = sde_rotator_ctx_from_fh(fh);
|
|
|
|
*f = ctx->format_out;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_try_fmt_vid_cap - V4l2 ioctl try capture format handler.
|
|
* @file: Pointer to file struct.
|
|
* @fh: V4l2 File handle.
|
|
* @f: Pointer to v4l2_format struct.
|
|
*/
|
|
static int sde_rotator_try_fmt_vid_cap(struct file *file,
|
|
void *fh, struct v4l2_format *f)
|
|
{
|
|
struct sde_rotator_ctx *ctx = sde_rotator_ctx_from_fh(fh);
|
|
struct sde_rotator_device *rot_dev = ctx->rot_dev;
|
|
struct sde_rotation_config config;
|
|
int ret;
|
|
|
|
if ((f->fmt.pix.width == 0) || (f->fmt.pix.height == 0)) {
|
|
SDEDEV_WARN(ctx->rot_dev->dev,
|
|
"Not supporting 0 width/height: %dx%d\n",
|
|
f->fmt.pix.width, f->fmt.pix.height);
|
|
return -EINVAL;
|
|
}
|
|
|
|
sde_rot_mgr_lock(rot_dev->mgr);
|
|
sde_rotator_get_config_from_ctx(ctx, &config);
|
|
config.output.format = f->fmt.pix.pixelformat;
|
|
config.output.width = f->fmt.pix.width;
|
|
config.output.height = f->fmt.pix.height;
|
|
config.flags |= SDE_ROTATION_VERIFY_INPUT_ONLY;
|
|
ret = sde_rotator_verify_config_output(rot_dev->mgr, &config);
|
|
sde_rot_mgr_unlock(rot_dev->mgr);
|
|
if (ret) {
|
|
if ((config.output.width == f->fmt.pix.width) &&
|
|
(config.output.height == f->fmt.pix.height)) {
|
|
SDEDEV_WARN(ctx->rot_dev->dev,
|
|
"invalid capture format 0x%8.8x %dx%d\n",
|
|
f->fmt.pix.pixelformat,
|
|
f->fmt.pix.width,
|
|
f->fmt.pix.height);
|
|
return -EINVAL;
|
|
}
|
|
f->fmt.pix.width = config.output.width;
|
|
f->fmt.pix.height = config.output.height;
|
|
}
|
|
|
|
sde_rotator_format_recalc(f);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_try_fmt_vid_out - V4l2 ioctl try output format handler.
|
|
* @file: Pointer to file struct.
|
|
* @fh: V4l2 File handle.
|
|
* @f: Pointer to v4l2_format struct.
|
|
*/
|
|
static int sde_rotator_try_fmt_vid_out(struct file *file,
|
|
void *fh, struct v4l2_format *f)
|
|
{
|
|
struct sde_rotator_ctx *ctx = sde_rotator_ctx_from_fh(fh);
|
|
struct sde_rotator_device *rot_dev = ctx->rot_dev;
|
|
struct sde_rotation_config config;
|
|
int ret;
|
|
|
|
if ((f->fmt.pix.width == 0) || (f->fmt.pix.height == 0)) {
|
|
SDEDEV_WARN(ctx->rot_dev->dev,
|
|
"Not supporting 0 width/height: %dx%d\n",
|
|
f->fmt.pix.width, f->fmt.pix.height);
|
|
return -EINVAL;
|
|
}
|
|
|
|
sde_rot_mgr_lock(rot_dev->mgr);
|
|
sde_rotator_get_config_from_ctx(ctx, &config);
|
|
config.input.format = f->fmt.pix.pixelformat;
|
|
config.input.width = f->fmt.pix.width;
|
|
config.input.height = f->fmt.pix.height;
|
|
config.flags |= SDE_ROTATION_VERIFY_INPUT_ONLY;
|
|
ret = sde_rotator_verify_config_input(rot_dev->mgr, &config);
|
|
sde_rot_mgr_unlock(rot_dev->mgr);
|
|
if (ret) {
|
|
if ((config.input.width == f->fmt.pix.width) &&
|
|
(config.input.height == f->fmt.pix.height)) {
|
|
SDEDEV_WARN(ctx->rot_dev->dev,
|
|
"invalid output format 0x%8.8x %dx%d\n",
|
|
f->fmt.pix.pixelformat,
|
|
f->fmt.pix.width,
|
|
f->fmt.pix.height);
|
|
return -EINVAL;
|
|
}
|
|
f->fmt.pix.width = config.input.width;
|
|
f->fmt.pix.height = config.input.height;
|
|
}
|
|
|
|
sde_rotator_format_recalc(f);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_s_fmt_vid_cap - V4l2 ioctl set capture format handler.
|
|
* @file: Pointer to file struct.
|
|
* @fh: V4l2 File handle.
|
|
* @f: Pointer to v4l2_format struct.
|
|
*/
|
|
static int sde_rotator_s_fmt_vid_cap(struct file *file,
|
|
void *fh, struct v4l2_format *f)
|
|
{
|
|
struct sde_rotator_ctx *ctx = sde_rotator_ctx_from_fh(fh);
|
|
struct sde_rotator_device *rot_dev = ctx->rot_dev;
|
|
int ret;
|
|
|
|
ret = sde_rotator_try_fmt_vid_cap(file, fh, f);
|
|
if (ret)
|
|
return -EINVAL;
|
|
|
|
/* Initialize crop */
|
|
ctx->crop_cap.top = 0;
|
|
ctx->crop_cap.left = 0;
|
|
ctx->crop_cap.width = f->fmt.pix.width;
|
|
ctx->crop_cap.height = f->fmt.pix.height;
|
|
|
|
ctx->format_cap = *f;
|
|
|
|
SDEDEV_DBG(rot_dev->dev,
|
|
"s_fmt s:%d t:%d fmt:0x%8.8x field:%u (%u,%u)\n",
|
|
ctx->session_id, f->type,
|
|
f->fmt.pix.pixelformat,
|
|
f->fmt.pix.field,
|
|
f->fmt.pix.width, f->fmt.pix.height);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_s_fmt_vid_out - V4l2 ioctl set output format handler.
|
|
* @file: Pointer to file struct.
|
|
* @fh: V4l2 File handle.
|
|
* @f: Pointer to v4l2_format struct.
|
|
*/
|
|
static int sde_rotator_s_fmt_vid_out(struct file *file,
|
|
void *fh, struct v4l2_format *f)
|
|
{
|
|
struct sde_rotator_ctx *ctx = sde_rotator_ctx_from_fh(fh);
|
|
struct sde_rotator_device *rot_dev = ctx->rot_dev;
|
|
int ret;
|
|
|
|
ret = sde_rotator_try_fmt_vid_out(file, fh, f);
|
|
if (ret)
|
|
return -EINVAL;
|
|
|
|
/* Initialize crop */
|
|
ctx->crop_out.top = 0;
|
|
ctx->crop_out.left = 0;
|
|
ctx->crop_out.width = f->fmt.pix.width;
|
|
ctx->crop_out.height = f->fmt.pix.height;
|
|
|
|
ctx->format_out = *f;
|
|
|
|
SDEDEV_DBG(rot_dev->dev,
|
|
"s_fmt s:%d t:%d fmt:0x%8.8x field:%u (%u,%u)\n",
|
|
ctx->session_id, f->type,
|
|
f->fmt.pix.pixelformat,
|
|
f->fmt.pix.field,
|
|
f->fmt.pix.width, f->fmt.pix.height);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_reqbufs - V4l2 ioctl request buffers handler.
|
|
* @file: Pointer to file struct.
|
|
* @fh: V4l2 File handle.
|
|
* @req: Pointer to v4l2_requestbuffer struct.
|
|
*/
|
|
static int sde_rotator_reqbufs(struct file *file,
|
|
void *fh, struct v4l2_requestbuffers *req)
|
|
{
|
|
struct sde_rotator_ctx *ctx = sde_rotator_ctx_from_fh(fh);
|
|
|
|
return v4l2_m2m_reqbufs(file, ctx->fh.m2m_ctx, req);
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_qbuf - V4l2 ioctl queue buffer handler.
|
|
* @file: Pointer to file struct.
|
|
* @fh: V4l2 File handle.
|
|
* @buf: Pointer to v4l2_buffer struct.
|
|
*/
|
|
static int sde_rotator_qbuf(struct file *file, void *fh,
|
|
struct v4l2_buffer *buf)
|
|
{
|
|
struct sde_rotator_ctx *ctx = sde_rotator_ctx_from_fh(fh);
|
|
int ret;
|
|
|
|
/* create fence for capture buffer */
|
|
if ((buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
|
|
&& (buf->index < ctx->nbuf_cap)) {
|
|
int idx = buf->index;
|
|
|
|
ctx->vbinfo_cap[idx].fd = -1;
|
|
ctx->vbinfo_cap[idx].fence = sde_rotator_get_sync_fence(
|
|
ctx->work_queue.timeline, NULL,
|
|
&ctx->vbinfo_cap[idx].fence_ts);
|
|
ctx->vbinfo_cap[idx].qbuf_ts = ktime_get();
|
|
ctx->vbinfo_cap[idx].dqbuf_ts = NULL;
|
|
SDEDEV_DBG(ctx->rot_dev->dev,
|
|
"create buffer fence s:%d.%u i:%d f:%pK\n",
|
|
ctx->session_id,
|
|
ctx->vbinfo_cap[idx].fence_ts,
|
|
idx,
|
|
ctx->vbinfo_cap[idx].fence);
|
|
} else if ((buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT)
|
|
&& (buf->index < ctx->nbuf_out)) {
|
|
int idx = buf->index;
|
|
|
|
ctx->vbinfo_out[idx].qbuf_ts = ktime_get();
|
|
ctx->vbinfo_out[idx].dqbuf_ts = NULL;
|
|
}
|
|
|
|
ret = v4l2_m2m_qbuf(file, ctx->fh.m2m_ctx, buf);
|
|
if (ret < 0)
|
|
SDEDEV_ERR(ctx->rot_dev->dev, "fail qbuf s:%d t:%d r:%d\n",
|
|
ctx->session_id, buf->type, ret);
|
|
SDEROT_EVTLOG(buf->type, buf->bytesused, buf->length, buf->m.fd, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_dqbuf - V4l2 ioctl dequeue buffer handler.
|
|
* @file: Pointer to file struct.
|
|
* @fh: V4l2 File handle.
|
|
* @buf: Pointer to v4l2_buffer struct.
|
|
*/
|
|
static int sde_rotator_dqbuf(struct file *file,
|
|
void *fh, struct v4l2_buffer *buf)
|
|
{
|
|
struct sde_rotator_ctx *ctx = sde_rotator_ctx_from_fh(fh);
|
|
int ret;
|
|
|
|
ret = v4l2_m2m_dqbuf(file, ctx->fh.m2m_ctx, buf);
|
|
|
|
if (ret) {
|
|
SDEDEV_ERR(ctx->rot_dev->dev,
|
|
"fail dqbuf s:%d t:%d i:%d r:%d\n",
|
|
ctx->session_id, buf->type, buf->index, ret);
|
|
return ret;
|
|
}
|
|
|
|
/* clear fence for buffer */
|
|
if ((buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
|
|
&& (buf->index < ctx->nbuf_cap)) {
|
|
int idx = buf->index;
|
|
|
|
if (ctx->vbinfo_cap[idx].fence) {
|
|
/* fence is not used */
|
|
SDEDEV_DBG(ctx->rot_dev->dev, "put fence s:%d i:%d\n",
|
|
ctx->session_id, idx);
|
|
sde_rotator_put_sync_fence(ctx->vbinfo_cap[idx].fence);
|
|
}
|
|
ctx->vbinfo_cap[idx].fence = NULL;
|
|
ctx->vbinfo_cap[idx].fd = -1;
|
|
if (ctx->vbinfo_cap[idx].dqbuf_ts)
|
|
*(ctx->vbinfo_cap[idx].dqbuf_ts) = ktime_get();
|
|
} else if ((buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT)
|
|
&& (buf->index < ctx->nbuf_out)) {
|
|
int idx = buf->index;
|
|
|
|
ctx->vbinfo_out[idx].fence = NULL;
|
|
ctx->vbinfo_out[idx].fd = -1;
|
|
if (ctx->vbinfo_out[idx].dqbuf_ts)
|
|
*(ctx->vbinfo_out[idx].dqbuf_ts) = ktime_get();
|
|
} else {
|
|
SDEDEV_WARN(ctx->rot_dev->dev, "invalid dq s:%d t:%d i:%d\n",
|
|
ctx->session_id, buf->type, buf->index);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_querybuf - V4l2 ioctl query buffer handler.
|
|
* @file: Pointer to file struct.
|
|
* @fh: V4l2 File handle.
|
|
* @buf: Pointer to v4l2_buffer struct.
|
|
*/
|
|
static int sde_rotator_querybuf(struct file *file,
|
|
void *fh, struct v4l2_buffer *buf)
|
|
{
|
|
struct sde_rotator_ctx *ctx = sde_rotator_ctx_from_fh(fh);
|
|
|
|
return v4l2_m2m_querybuf(file, ctx->fh.m2m_ctx, buf);
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_streamon - V4l2 ioctl stream on handler.
|
|
* @file: Pointer to file struct.
|
|
* @fh: V4l2 File handle.
|
|
* @buf_type: V4l2 buffer type.
|
|
*/
|
|
static int sde_rotator_streamon(struct file *file,
|
|
void *fh, enum v4l2_buf_type buf_type)
|
|
{
|
|
struct sde_rotator_ctx *ctx = sde_rotator_ctx_from_fh(fh);
|
|
struct sde_rotator_device *rot_dev = ctx->rot_dev;
|
|
struct sde_rotation_config config;
|
|
struct vb2_queue *vq;
|
|
int ret;
|
|
|
|
SDEDEV_DBG(ctx->rot_dev->dev, "stream on s:%d t:%d\n",
|
|
ctx->session_id, buf_type);
|
|
|
|
vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx,
|
|
buf_type == V4L2_BUF_TYPE_VIDEO_OUTPUT ?
|
|
V4L2_BUF_TYPE_VIDEO_CAPTURE :
|
|
V4L2_BUF_TYPE_VIDEO_OUTPUT);
|
|
|
|
if (!vq) {
|
|
SDEDEV_ERR(ctx->rot_dev->dev, "fail to get vq on s:%d t:%d\n",
|
|
ctx->session_id, buf_type);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (vb2_is_streaming(vq)) {
|
|
sde_rot_mgr_lock(rot_dev->mgr);
|
|
sde_rotator_get_config_from_ctx(ctx, &config);
|
|
config.flags &= ~SDE_ROTATION_VERIFY_INPUT_ONLY;
|
|
ret = sde_rotator_session_config(rot_dev->mgr, ctx->private,
|
|
&config);
|
|
sde_rot_mgr_unlock(rot_dev->mgr);
|
|
if (ret < 0) {
|
|
SDEDEV_ERR(rot_dev->dev,
|
|
"fail config in stream on s:%d t:%d r:%d\n",
|
|
ctx->session_id, buf_type, ret);
|
|
return ret;
|
|
}
|
|
ctx->rotcfg = config;
|
|
}
|
|
|
|
ret = v4l2_m2m_streamon(file, ctx->fh.m2m_ctx, buf_type);
|
|
if (ret < 0)
|
|
SDEDEV_ERR(ctx->rot_dev->dev, "fail stream on s:%d t:%d\n",
|
|
ctx->session_id, buf_type);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_streamoff - V4l2 ioctl stream off handler.
|
|
* @file: Pointer to file struct.
|
|
* @fh: V4l2 File handle.
|
|
* @buf_type: V4l2 buffer type.
|
|
*/
|
|
static int sde_rotator_streamoff(struct file *file,
|
|
void *fh, enum v4l2_buf_type buf_type)
|
|
{
|
|
struct sde_rotator_ctx *ctx = sde_rotator_ctx_from_fh(fh);
|
|
int ret;
|
|
|
|
SDEDEV_DBG(ctx->rot_dev->dev, "stream off s:%d t:%d\n",
|
|
ctx->session_id, buf_type);
|
|
|
|
ret = v4l2_m2m_streamoff(file, ctx->fh.m2m_ctx, buf_type);
|
|
if (ret < 0)
|
|
SDEDEV_ERR(ctx->rot_dev->dev, "fail stream off s:%d t:%d\n",
|
|
ctx->session_id, buf_type);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_cropcap - V4l2 ioctl crop capabilities.
|
|
* @file: Pointer to file struct.
|
|
* @fh: V4l2 File handle.
|
|
* @a: Pointer to v4l2_cropcap struct need to be set.
|
|
*/
|
|
static int sde_rotator_cropcap(struct file *file, void *fh,
|
|
struct v4l2_cropcap *a)
|
|
{
|
|
struct sde_rotator_ctx *ctx = sde_rotator_ctx_from_fh(fh);
|
|
struct v4l2_format *format;
|
|
struct v4l2_rect *crop;
|
|
|
|
switch (a->type) {
|
|
case V4L2_BUF_TYPE_VIDEO_OUTPUT:
|
|
format = &ctx->format_out;
|
|
crop = &ctx->crop_out;
|
|
break;
|
|
case V4L2_BUF_TYPE_VIDEO_CAPTURE:
|
|
format = &ctx->format_cap;
|
|
crop = &ctx->crop_cap;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
a->bounds.top = 0;
|
|
a->bounds.left = 0;
|
|
a->bounds.width = format->fmt.pix.width;
|
|
a->bounds.height = format->fmt.pix.height;
|
|
|
|
a->defrect = *crop;
|
|
|
|
a->pixelaspect.numerator = 1;
|
|
a->pixelaspect.denominator = 1;
|
|
|
|
SDEROT_EVTLOG(format->fmt.pix.width, format->fmt.pix.height, a->type);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_g_crop - V4l2 ioctl get crop.
|
|
* @file: Pointer to file struct.
|
|
* @fh: V4l2 File handle.
|
|
* @crop: Pointer to v4l2_crop struct need to be set.
|
|
*/
|
|
static int sde_rotator_g_crop(struct file *file, void *fh,
|
|
struct v4l2_crop *crop)
|
|
{
|
|
struct sde_rotator_ctx *ctx = sde_rotator_ctx_from_fh(fh);
|
|
|
|
switch (crop->type) {
|
|
case V4L2_BUF_TYPE_VIDEO_OUTPUT:
|
|
crop->c = ctx->crop_out;
|
|
break;
|
|
case V4L2_BUF_TYPE_VIDEO_CAPTURE:
|
|
crop->c = ctx->crop_cap;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_s_crop - V4l2 ioctl set crop.
|
|
* @file: Pointer to file struct.
|
|
* @fh: V4l2 File handle.
|
|
* @crop: Pointer to v4l2_crop struct need to be set.
|
|
*/
|
|
static int sde_rotator_s_crop(struct file *file, void *fh,
|
|
const struct v4l2_crop *crop)
|
|
{
|
|
struct sde_rotator_ctx *ctx = sde_rotator_ctx_from_fh(fh);
|
|
struct sde_rotator_device *rot_dev = ctx->rot_dev;
|
|
struct sde_rotation_item item;
|
|
struct v4l2_rect rect;
|
|
|
|
sde_rotator_get_item_from_ctx(ctx, &item);
|
|
|
|
rect.left = max_t(__u32, crop->c.left, 0);
|
|
rect.top = max_t(__u32, crop->c.top, 0);
|
|
rect.height = max_t(__u32, crop->c.height, 0);
|
|
rect.width = max_t(__u32, crop->c.width, 0);
|
|
|
|
if (crop->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) {
|
|
rect.left = min_t(__u32, rect.left,
|
|
ctx->format_out.fmt.pix.width - 1);
|
|
rect.top = min_t(__u32, rect.top,
|
|
ctx->format_out.fmt.pix.height - 1);
|
|
rect.width = min_t(__u32, rect.width,
|
|
(ctx->format_out.fmt.pix.width - rect.left));
|
|
rect.height = min_t(__u32, rect.height,
|
|
(ctx->format_out.fmt.pix.height - rect.top));
|
|
|
|
item.src_rect.x = rect.left;
|
|
item.src_rect.y = rect.top;
|
|
item.src_rect.w = rect.width;
|
|
item.src_rect.h = rect.height;
|
|
|
|
sde_rotator_validate_item(ctx, &item);
|
|
|
|
SDEDEV_DBG(rot_dev->dev,
|
|
"s_crop s:%d t:%d (%u,%u,%u,%u)->(%u,%u,%u,%u)\n",
|
|
ctx->session_id, crop->type,
|
|
crop->c.left, crop->c.top,
|
|
crop->c.width, crop->c.height,
|
|
item.src_rect.x, item.src_rect.y,
|
|
item.src_rect.w, item.src_rect.h);
|
|
|
|
ctx->crop_out.left = item.src_rect.x;
|
|
ctx->crop_out.top = item.src_rect.y;
|
|
ctx->crop_out.width = item.src_rect.w;
|
|
ctx->crop_out.height = item.src_rect.h;
|
|
} else if (crop->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
|
|
rect.left = min_t(__u32, rect.left,
|
|
ctx->format_cap.fmt.pix.width - 1);
|
|
rect.top = min_t(__u32, rect.top,
|
|
ctx->format_cap.fmt.pix.height - 1);
|
|
rect.width = min_t(__u32, rect.width,
|
|
(ctx->format_cap.fmt.pix.width - rect.left));
|
|
rect.height = min_t(__u32, rect.height,
|
|
(ctx->format_cap.fmt.pix.height - rect.top));
|
|
|
|
item.dst_rect.x = rect.left;
|
|
item.dst_rect.y = rect.top;
|
|
item.dst_rect.w = rect.width;
|
|
item.dst_rect.h = rect.height;
|
|
|
|
sde_rotator_validate_item(ctx, &item);
|
|
|
|
SDEDEV_DBG(rot_dev->dev,
|
|
"s_crop s:%d t:%d (%u,%u,%u,%u)->(%u,%u,%u,%u)\n",
|
|
ctx->session_id, crop->type,
|
|
crop->c.left, crop->c.top,
|
|
crop->c.width, crop->c.height,
|
|
item.dst_rect.x, item.dst_rect.y,
|
|
item.dst_rect.w, item.dst_rect.h);
|
|
|
|
ctx->crop_cap.left = item.dst_rect.x;
|
|
ctx->crop_cap.top = item.dst_rect.y;
|
|
ctx->crop_cap.width = item.dst_rect.w;
|
|
ctx->crop_cap.height = item.dst_rect.h;
|
|
} else {
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_g_parm - V4l2 ioctl get parm.
|
|
* @file: Pointer to file struct.
|
|
* @fh: V4l2 File handle.
|
|
* @a: Pointer to v4l2_streamparm struct need to be filled.
|
|
*/
|
|
static int sde_rotator_g_parm(struct file *file, void *fh,
|
|
struct v4l2_streamparm *a)
|
|
{
|
|
struct sde_rotator_ctx *ctx = sde_rotator_ctx_from_fh(fh);
|
|
|
|
/* Get param is supported only for input buffers */
|
|
if (a->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
|
|
return -EINVAL;
|
|
|
|
a->parm.output.capability = 0;
|
|
a->parm.output.extendedmode = 0;
|
|
a->parm.output.outputmode = 0;
|
|
a->parm.output.writebuffers = 0;
|
|
a->parm.output.timeperframe = ctx->timeperframe;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_s_parm - V4l2 ioctl set parm.
|
|
* @file: Pointer to file struct.
|
|
* @fh: V4l2 File handle.
|
|
* @a: Pointer to v4l2_streamparm struct need to be set.
|
|
*/
|
|
static int sde_rotator_s_parm(struct file *file, void *fh,
|
|
struct v4l2_streamparm *a)
|
|
{
|
|
struct sde_rotator_ctx *ctx = sde_rotator_ctx_from_fh(fh);
|
|
|
|
/* Set param is supported only for input buffers */
|
|
if (a->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
|
|
return -EINVAL;
|
|
|
|
if (!a->parm.output.timeperframe.numerator ||
|
|
!a->parm.output.timeperframe.denominator)
|
|
return -EINVAL;
|
|
|
|
ctx->timeperframe = a->parm.output.timeperframe;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_private_ioctl - V4l2 private ioctl handler.
|
|
* @file: Pointer to file struct.
|
|
* @fd: V4l2 device file handle.
|
|
* @valid_prio: Priority ioctl valid flag.
|
|
* @cmd: Ioctl command.
|
|
* @arg: Ioctl argument.
|
|
*/
|
|
static long sde_rotator_private_ioctl(struct file *file, void *fh,
|
|
bool valid_prio, unsigned int cmd, void *arg)
|
|
{
|
|
struct sde_rotator_ctx *ctx =
|
|
sde_rotator_ctx_from_fh(file->private_data);
|
|
struct sde_rotator_device *rot_dev = ctx->rot_dev;
|
|
struct msm_sde_rotator_fence *fence = arg;
|
|
struct msm_sde_rotator_comp_ratio *comp_ratio = arg;
|
|
struct sde_rotator_vbinfo *vbinfo;
|
|
int ret;
|
|
|
|
switch (cmd) {
|
|
case VIDIOC_S_SDE_ROTATOR_FENCE:
|
|
if (!fence)
|
|
return -EINVAL;
|
|
|
|
if (fence->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
|
|
return -EINVAL;
|
|
|
|
if (fence->index >= ctx->nbuf_out)
|
|
return -EINVAL;
|
|
|
|
SDEDEV_DBG(rot_dev->dev,
|
|
"VIDIOC_S_SDE_ROTATOR_FENCE s:%d i:%d fd:%d\n",
|
|
ctx->session_id, fence->index,
|
|
fence->fd);
|
|
|
|
vbinfo = &ctx->vbinfo_out[fence->index];
|
|
|
|
if (vbinfo->fd >= 0) {
|
|
if (vbinfo->fence) {
|
|
SDEDEV_DBG(rot_dev->dev,
|
|
"put fence s:%d t:%d i:%d\n",
|
|
ctx->session_id,
|
|
fence->type, fence->index);
|
|
sde_rotator_put_sync_fence(vbinfo->fence);
|
|
}
|
|
vbinfo->fence = NULL;
|
|
vbinfo->fd = -1;
|
|
}
|
|
|
|
vbinfo->fd = fence->fd;
|
|
if (vbinfo->fd >= 0) {
|
|
vbinfo->fence =
|
|
sde_rotator_get_fd_sync_fence(vbinfo->fd);
|
|
if (!vbinfo->fence) {
|
|
SDEDEV_WARN(rot_dev->dev,
|
|
"invalid input fence fd s:%d fd:%d\n",
|
|
ctx->session_id, vbinfo->fd);
|
|
vbinfo->fd = -1;
|
|
return -EINVAL;
|
|
}
|
|
} else {
|
|
vbinfo->fence = NULL;
|
|
}
|
|
break;
|
|
case VIDIOC_G_SDE_ROTATOR_FENCE:
|
|
if (!fence)
|
|
return -EINVAL;
|
|
|
|
if (fence->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
|
|
return -EINVAL;
|
|
|
|
if (fence->index >= ctx->nbuf_cap)
|
|
return -EINVAL;
|
|
|
|
vbinfo = &ctx->vbinfo_cap[fence->index];
|
|
|
|
if (!vbinfo)
|
|
return -EINVAL;
|
|
|
|
if (vbinfo->fence) {
|
|
ret = sde_rotator_get_sync_fence_fd(vbinfo->fence);
|
|
if (ret < 0) {
|
|
SDEDEV_ERR(rot_dev->dev,
|
|
"fail get fence fd s:%d\n",
|
|
ctx->session_id);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Cache fence descriptor in case user calls this
|
|
* ioctl multiple times. Cached value would be stale
|
|
* if user duplicated and closed old descriptor.
|
|
*/
|
|
vbinfo->fd = ret;
|
|
} else if (!sde_rotator_get_fd_sync_fence(vbinfo->fd)) {
|
|
/**
|
|
* User has closed cached fence descriptor.
|
|
* Invalidate descriptor cache.
|
|
*/
|
|
vbinfo->fd = -1;
|
|
}
|
|
fence->fd = vbinfo->fd;
|
|
|
|
SDEDEV_DBG(rot_dev->dev,
|
|
"VIDIOC_G_SDE_ROTATOR_FENCE s:%d i:%d fd:%d\n",
|
|
ctx->session_id, fence->index,
|
|
fence->fd);
|
|
break;
|
|
case VIDIOC_S_SDE_ROTATOR_COMP_RATIO:
|
|
if (!comp_ratio)
|
|
return -EINVAL;
|
|
else if (!comp_ratio->numer || !comp_ratio->denom)
|
|
return -EINVAL;
|
|
else if (comp_ratio->type == V4L2_BUF_TYPE_VIDEO_OUTPUT &&
|
|
comp_ratio->index < ctx->nbuf_out)
|
|
vbinfo = &ctx->vbinfo_out[comp_ratio->index];
|
|
else if (comp_ratio->type == V4L2_BUF_TYPE_VIDEO_CAPTURE &&
|
|
comp_ratio->index < ctx->nbuf_cap)
|
|
vbinfo = &ctx->vbinfo_cap[comp_ratio->index];
|
|
else
|
|
return -EINVAL;
|
|
|
|
vbinfo->comp_ratio.numer = comp_ratio->numer;
|
|
vbinfo->comp_ratio.denom = comp_ratio->denom;
|
|
|
|
SDEDEV_DBG(rot_dev->dev,
|
|
"VIDIOC_S_SDE_ROTATOR_COMP_RATIO s:%d i:%d t:%d cr:%u/%u\n",
|
|
ctx->session_id, comp_ratio->index,
|
|
comp_ratio->type,
|
|
vbinfo->comp_ratio.numer,
|
|
vbinfo->comp_ratio.denom);
|
|
break;
|
|
case VIDIOC_G_SDE_ROTATOR_COMP_RATIO:
|
|
if (!comp_ratio)
|
|
return -EINVAL;
|
|
else if (comp_ratio->type == V4L2_BUF_TYPE_VIDEO_OUTPUT &&
|
|
comp_ratio->index < ctx->nbuf_out)
|
|
vbinfo = &ctx->vbinfo_out[comp_ratio->index];
|
|
else if (comp_ratio->type == V4L2_BUF_TYPE_VIDEO_CAPTURE &&
|
|
comp_ratio->index < ctx->nbuf_cap)
|
|
vbinfo = &ctx->vbinfo_cap[comp_ratio->index];
|
|
else
|
|
return -EINVAL;
|
|
|
|
comp_ratio->numer = vbinfo->comp_ratio.numer;
|
|
comp_ratio->denom = vbinfo->comp_ratio.denom;
|
|
|
|
SDEDEV_DBG(rot_dev->dev,
|
|
"VIDIOC_G_SDE_ROTATOR_COMP_RATIO s:%d i:%d t:%d cr:%u/%u\n",
|
|
ctx->session_id, comp_ratio->index,
|
|
comp_ratio->type,
|
|
comp_ratio->numer,
|
|
comp_ratio->denom);
|
|
break;
|
|
default:
|
|
SDEDEV_WARN(rot_dev->dev, "invalid ioctl type %x\n", cmd);
|
|
return -ENOTTY;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_COMPAT
|
|
/*
|
|
* sde_rotator_compat_ioctl32 - Compat ioctl handler function.
|
|
* @file: Pointer to file struct.
|
|
* @cmd: Ioctl command.
|
|
* @arg: Ioctl argument.
|
|
*/
|
|
static long sde_rotator_compat_ioctl32(struct file *file,
|
|
unsigned int cmd, unsigned long arg)
|
|
{
|
|
struct video_device *vdev = video_devdata(file);
|
|
struct sde_rotator_ctx *ctx =
|
|
sde_rotator_ctx_from_fh(file->private_data);
|
|
long ret;
|
|
|
|
mutex_lock(vdev->lock);
|
|
|
|
switch (cmd) {
|
|
case VIDIOC_S_SDE_ROTATOR_FENCE:
|
|
case VIDIOC_G_SDE_ROTATOR_FENCE:
|
|
{
|
|
struct msm_sde_rotator_fence fence;
|
|
|
|
if (copy_from_user(&fence, (void __user *)arg,
|
|
sizeof(struct msm_sde_rotator_fence)))
|
|
goto ioctl32_error;
|
|
|
|
ret = sde_rotator_private_ioctl(file, file->private_data,
|
|
0, cmd, (void *)&fence);
|
|
|
|
if (copy_to_user((void __user *)arg, &fence,
|
|
sizeof(struct msm_sde_rotator_fence)))
|
|
goto ioctl32_error;
|
|
|
|
break;
|
|
}
|
|
case VIDIOC_S_SDE_ROTATOR_COMP_RATIO:
|
|
case VIDIOC_G_SDE_ROTATOR_COMP_RATIO:
|
|
{
|
|
struct msm_sde_rotator_comp_ratio comp_ratio;
|
|
|
|
if (copy_from_user(&comp_ratio, (void __user *)arg,
|
|
sizeof(struct msm_sde_rotator_comp_ratio)))
|
|
goto ioctl32_error;
|
|
|
|
ret = sde_rotator_private_ioctl(file, file->private_data,
|
|
0, cmd, (void *)&comp_ratio);
|
|
|
|
if (copy_to_user((void __user *)arg, &comp_ratio,
|
|
sizeof(struct msm_sde_rotator_comp_ratio)))
|
|
goto ioctl32_error;
|
|
|
|
break;
|
|
}
|
|
default:
|
|
SDEDEV_ERR(ctx->rot_dev->dev, "invalid ioctl32 type:%x\n", cmd);
|
|
ret = -ENOIOCTLCMD;
|
|
break;
|
|
|
|
}
|
|
|
|
mutex_unlock(vdev->lock);
|
|
return ret;
|
|
|
|
ioctl32_error:
|
|
mutex_unlock(vdev->lock);
|
|
SDEDEV_ERR(ctx->rot_dev->dev, "error handling ioctl32 cmd:%x\n", cmd);
|
|
return -EFAULT;
|
|
}
|
|
#endif
|
|
|
|
static int sde_rotator_ctrl_subscribe_event(struct v4l2_fh *fh,
|
|
const struct v4l2_event_subscription *sub)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int sde_rotator_event_unsubscribe(struct v4l2_fh *fh,
|
|
const struct v4l2_event_subscription *sub)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* V4l2 ioctl handlers */
|
|
static const struct v4l2_ioctl_ops sde_rotator_ioctl_ops = {
|
|
.vidioc_querycap = sde_rotator_querycap,
|
|
.vidioc_enum_fmt_vid_out = sde_rotator_enum_fmt_vid_out,
|
|
.vidioc_enum_fmt_vid_cap = sde_rotator_enum_fmt_vid_cap,
|
|
.vidioc_g_fmt_vid_out = sde_rotator_g_fmt_out,
|
|
.vidioc_g_fmt_vid_cap = sde_rotator_g_fmt_cap,
|
|
.vidioc_try_fmt_vid_out = sde_rotator_try_fmt_vid_out,
|
|
.vidioc_try_fmt_vid_cap = sde_rotator_try_fmt_vid_cap,
|
|
.vidioc_s_fmt_vid_out = sde_rotator_s_fmt_vid_out,
|
|
.vidioc_s_fmt_vid_cap = sde_rotator_s_fmt_vid_cap,
|
|
.vidioc_reqbufs = sde_rotator_reqbufs,
|
|
.vidioc_qbuf = sde_rotator_qbuf,
|
|
.vidioc_dqbuf = sde_rotator_dqbuf,
|
|
.vidioc_querybuf = sde_rotator_querybuf,
|
|
.vidioc_streamon = sde_rotator_streamon,
|
|
.vidioc_streamoff = sde_rotator_streamoff,
|
|
.vidioc_cropcap = sde_rotator_cropcap,
|
|
.vidioc_g_crop = sde_rotator_g_crop,
|
|
.vidioc_s_crop = sde_rotator_s_crop,
|
|
.vidioc_g_parm = sde_rotator_g_parm,
|
|
.vidioc_s_parm = sde_rotator_s_parm,
|
|
.vidioc_default = sde_rotator_private_ioctl,
|
|
.vidioc_log_status = v4l2_ctrl_log_status,
|
|
.vidioc_subscribe_event = sde_rotator_ctrl_subscribe_event,
|
|
.vidioc_unsubscribe_event = sde_rotator_event_unsubscribe,
|
|
};
|
|
|
|
/*
|
|
* sde_rotator_retire_handler - Invoked by hal when processing is done.
|
|
* @work: Pointer to work structure.
|
|
*
|
|
* This function is scheduled in work queue context.
|
|
*/
|
|
static void sde_rotator_retire_handler(struct kthread_work *work)
|
|
{
|
|
struct vb2_v4l2_buffer *src_buf;
|
|
struct vb2_v4l2_buffer *dst_buf;
|
|
struct sde_rotator_ctx *ctx;
|
|
struct sde_rotator_device *rot_dev;
|
|
struct sde_rotator_request *request;
|
|
|
|
request = container_of(work, struct sde_rotator_request, retire_work);
|
|
ctx = request->ctx;
|
|
|
|
if (!ctx || !ctx->rot_dev) {
|
|
SDEROT_ERR("null context/device\n");
|
|
return;
|
|
}
|
|
|
|
rot_dev = ctx->rot_dev;
|
|
|
|
SDEDEV_DBG(rot_dev->dev, "retire handler s:%d\n", ctx->session_id);
|
|
|
|
mutex_lock(&rot_dev->lock);
|
|
if (ctx->abort_pending) {
|
|
SDEDEV_DBG(rot_dev->dev, "abort command in retire s:%d\n",
|
|
ctx->session_id);
|
|
sde_rotator_update_retire_sequence(request);
|
|
sde_rotator_retire_request(request);
|
|
mutex_unlock(&rot_dev->lock);
|
|
return;
|
|
}
|
|
|
|
if (!ctx->file) {
|
|
sde_rotator_update_retire_sequence(request);
|
|
} else if (rot_dev->early_submit) {
|
|
if (IS_ERR_OR_NULL(request->req)) {
|
|
/* fail pending request or something wrong */
|
|
SDEDEV_ERR(rot_dev->dev,
|
|
"pending request fail in retire s:%d\n",
|
|
ctx->session_id);
|
|
}
|
|
|
|
/* pending request. reschedule this context. */
|
|
v4l2_m2m_try_schedule(ctx->fh.m2m_ctx);
|
|
} else {
|
|
/* no pending request. acknowledge the usual way. */
|
|
src_buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
|
|
dst_buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
|
|
|
|
if (!src_buf || !dst_buf) {
|
|
SDEDEV_ERR(rot_dev->dev,
|
|
"null buffer in retire s:%d sb:%pK db:%pK\n",
|
|
ctx->session_id,
|
|
src_buf, dst_buf);
|
|
}
|
|
|
|
sde_rotator_update_retire_sequence(request);
|
|
sde_rotator_retire_request(request);
|
|
v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_DONE);
|
|
v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_DONE);
|
|
v4l2_m2m_job_finish(rot_dev->m2m_dev, ctx->fh.m2m_ctx);
|
|
}
|
|
mutex_unlock(&rot_dev->lock);
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_process_buffers - Start rotator processing.
|
|
* @ctx: Pointer rotator context.
|
|
* @src_buf: Pointer to Vb2 source buffer.
|
|
* @dst_buf: Pointer to Vb2 destination buffer.
|
|
* @request: Pointer to rotator request
|
|
*/
|
|
static int sde_rotator_process_buffers(struct sde_rotator_ctx *ctx,
|
|
struct vb2_buffer *src_buf, struct vb2_buffer *dst_buf,
|
|
struct sde_rotator_request *request)
|
|
{
|
|
struct sde_rotator_device *rot_dev = ctx->rot_dev;
|
|
struct sde_rotation_item item;
|
|
struct sde_rot_entry_container *req = NULL;
|
|
struct sde_rotator_buf_handle *src_handle;
|
|
struct sde_rotator_buf_handle *dst_handle;
|
|
struct sde_rotator_statistics *stats = &rot_dev->stats;
|
|
struct sde_rotator_vbinfo *vbinfo_out;
|
|
struct sde_rotator_vbinfo *vbinfo_cap;
|
|
struct sde_rot_trace_entry rot_trace;
|
|
ktime_t *ts;
|
|
int ret;
|
|
|
|
if (!src_buf || !dst_buf) {
|
|
SDEDEV_ERR(rot_dev->dev, "null vb2 buffers\n");
|
|
ret = -EINVAL;
|
|
goto error_null_buffer;
|
|
}
|
|
|
|
src_handle = src_buf->planes[0].mem_priv;
|
|
dst_handle = dst_buf->planes[0].mem_priv;
|
|
|
|
if (!src_handle || !dst_handle) {
|
|
SDEDEV_ERR(rot_dev->dev, "null buffer handle\n");
|
|
ret = -EINVAL;
|
|
goto error_null_buffer;
|
|
}
|
|
|
|
vbinfo_out = &ctx->vbinfo_out[src_buf->index];
|
|
vbinfo_cap = &ctx->vbinfo_cap[dst_buf->index];
|
|
|
|
SDEDEV_DBG(rot_dev->dev,
|
|
"process buffer s:%d.%u src:(%u,%u,%u,%u) dst:(%u,%u,%u,%u) rot:%d flip:%d/%d sec:%d src_cr:%u/%u dst_cr:%u/%u\n",
|
|
ctx->session_id, vbinfo_cap->fence_ts,
|
|
ctx->crop_out.left, ctx->crop_out.top,
|
|
ctx->crop_out.width, ctx->crop_out.height,
|
|
ctx->crop_cap.left, ctx->crop_cap.top,
|
|
ctx->crop_cap.width, ctx->crop_cap.height,
|
|
ctx->rotate, ctx->hflip, ctx->vflip, ctx->secure,
|
|
vbinfo_out->comp_ratio.numer, vbinfo_out->comp_ratio.denom,
|
|
vbinfo_cap->comp_ratio.numer, vbinfo_cap->comp_ratio.denom);
|
|
|
|
/* allocate slot for timestamp */
|
|
ts = stats->ts[stats->count++ % SDE_ROTATOR_NUM_EVENTS];
|
|
ts[SDE_ROTATOR_TS_SRCQB] = vbinfo_out->qbuf_ts;
|
|
ts[SDE_ROTATOR_TS_DSTQB] = vbinfo_cap->qbuf_ts;
|
|
vbinfo_out->dqbuf_ts = &ts[SDE_ROTATOR_TS_SRCDQB];
|
|
vbinfo_cap->dqbuf_ts = &ts[SDE_ROTATOR_TS_DSTDQB];
|
|
|
|
ts[SDE_ROTATOR_TS_FENCE] = ktime_get();
|
|
|
|
/* Set values to pass to trace */
|
|
rot_trace.wb_idx = ctx->fh.prio;
|
|
rot_trace.flags = (ctx->rotate << 0) | (ctx->hflip << 8) |
|
|
(ctx->hflip << 9) | (ctx->secure << 10);
|
|
rot_trace.input_format = ctx->format_out.fmt.pix.pixelformat;
|
|
rot_trace.input_width = ctx->format_out.fmt.pix.width;
|
|
rot_trace.input_height = ctx->format_out.fmt.pix.height;
|
|
rot_trace.src_x = ctx->crop_out.left;
|
|
rot_trace.src_y = ctx->crop_out.top;
|
|
rot_trace.src_w = ctx->crop_out.width;
|
|
rot_trace.src_h = ctx->crop_out.height;
|
|
rot_trace.output_format = ctx->format_cap.fmt.pix.pixelformat;
|
|
rot_trace.output_width = ctx->format_cap.fmt.pix.width;
|
|
rot_trace.output_height = ctx->format_cap.fmt.pix.height;
|
|
rot_trace.dst_x = ctx->crop_cap.left;
|
|
rot_trace.dst_y = ctx->crop_cap.top;
|
|
rot_trace.dst_w = ctx->crop_cap.width;
|
|
rot_trace.dst_h = ctx->crop_cap.height;
|
|
|
|
trace_rot_entry_fence(
|
|
ctx->session_id, vbinfo_cap->fence_ts, &rot_trace);
|
|
|
|
if (vbinfo_out->fence) {
|
|
sde_rot_mgr_unlock(rot_dev->mgr);
|
|
mutex_unlock(&rot_dev->lock);
|
|
SDEDEV_DBG(rot_dev->dev, "fence enter s:%d.%d fd:%d\n",
|
|
ctx->session_id, vbinfo_cap->fence_ts, vbinfo_out->fd);
|
|
ret = sde_rotator_wait_sync_fence(vbinfo_out->fence,
|
|
rot_dev->fence_timeout);
|
|
mutex_lock(&rot_dev->lock);
|
|
sde_rot_mgr_lock(rot_dev->mgr);
|
|
sde_rotator_put_sync_fence(vbinfo_out->fence);
|
|
vbinfo_out->fence = NULL;
|
|
if (ret) {
|
|
SDEDEV_ERR(rot_dev->dev,
|
|
"error waiting for fence s:%d.%d fd:%d r:%d\n",
|
|
ctx->session_id,
|
|
vbinfo_cap->fence_ts, vbinfo_out->fd, ret);
|
|
SDEROT_EVTLOG(ctx->session_id, vbinfo_cap->fence_ts,
|
|
vbinfo_out->fd, ret,
|
|
SDE_ROT_EVTLOG_ERROR);
|
|
goto error_fence_wait;
|
|
} else {
|
|
SDEDEV_DBG(rot_dev->dev, "fence exit s:%d.%d fd:%d\n",
|
|
ctx->session_id,
|
|
vbinfo_cap->fence_ts, vbinfo_out->fd);
|
|
}
|
|
}
|
|
|
|
/* fill in item work structure */
|
|
sde_rotator_get_item_from_ctx(ctx, &item);
|
|
item.flags |= SDE_ROTATION_EXT_DMA_BUF;
|
|
item.input.planes[0].fd = src_handle->fd;
|
|
item.input.planes[0].buffer = src_handle->buffer;
|
|
item.input.planes[0].offset = src_handle->addr;
|
|
item.input.planes[0].stride = ctx->format_out.fmt.pix.bytesperline;
|
|
item.input.plane_count = 1;
|
|
item.input.fence = NULL;
|
|
item.input.comp_ratio = vbinfo_out->comp_ratio;
|
|
item.output.planes[0].fd = dst_handle->fd;
|
|
item.output.planes[0].buffer = dst_handle->buffer;
|
|
item.output.planes[0].offset = dst_handle->addr;
|
|
item.output.planes[0].stride = ctx->format_cap.fmt.pix.bytesperline;
|
|
item.output.plane_count = 1;
|
|
item.output.fence = NULL;
|
|
item.output.comp_ratio = vbinfo_cap->comp_ratio;
|
|
item.sequence_id = vbinfo_cap->fence_ts;
|
|
item.ts = ts;
|
|
|
|
req = sde_rotator_req_init(rot_dev->mgr, ctx->private, &item, 1, 0);
|
|
if (IS_ERR_OR_NULL(req)) {
|
|
SDEDEV_ERR(rot_dev->dev, "fail allocate rotation request\n");
|
|
ret = -ENOMEM;
|
|
goto error_init_request;
|
|
}
|
|
|
|
req->retire_kw = ctx->work_queue.rot_kw;
|
|
req->retire_work = &request->retire_work;
|
|
|
|
ret = sde_rotator_handle_request_common(
|
|
rot_dev->mgr, ctx->private, req);
|
|
if (ret) {
|
|
SDEDEV_ERR(rot_dev->dev, "fail handle request\n");
|
|
goto error_handle_request;
|
|
}
|
|
|
|
sde_rotator_queue_request(rot_dev->mgr, ctx->private, req);
|
|
request->req = req;
|
|
request->sequence_id = item.sequence_id;
|
|
request->committed = true;
|
|
|
|
return 0;
|
|
error_handle_request:
|
|
devm_kfree(rot_dev->dev, req);
|
|
error_init_request:
|
|
error_fence_wait:
|
|
error_null_buffer:
|
|
request->req = NULL;
|
|
request->sequence_id = 0;
|
|
request->committed = false;
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_submit_handler - Invoked by m2m to submit job.
|
|
* @work: Pointer to work structure.
|
|
*
|
|
* This function is scheduled in work queue context.
|
|
*/
|
|
static void sde_rotator_submit_handler(struct kthread_work *work)
|
|
{
|
|
struct sde_rotator_ctx *ctx;
|
|
struct sde_rotator_device *rot_dev;
|
|
struct vb2_v4l2_buffer *src_buf;
|
|
struct vb2_v4l2_buffer *dst_buf;
|
|
struct sde_rotator_request *request;
|
|
int ret;
|
|
|
|
request = container_of(work, struct sde_rotator_request, submit_work);
|
|
ctx = request->ctx;
|
|
|
|
if (!ctx || !ctx->rot_dev) {
|
|
SDEROT_ERR("null device\n");
|
|
return;
|
|
}
|
|
|
|
rot_dev = ctx->rot_dev;
|
|
SDEDEV_DBG(rot_dev->dev, "submit handler s:%d\n", ctx->session_id);
|
|
|
|
mutex_lock(&rot_dev->lock);
|
|
if (ctx->abort_pending) {
|
|
SDEDEV_DBG(rot_dev->dev, "abort command in submit s:%d\n",
|
|
ctx->session_id);
|
|
sde_rotator_update_retire_sequence(request);
|
|
sde_rotator_retire_request(request);
|
|
mutex_unlock(&rot_dev->lock);
|
|
return;
|
|
}
|
|
|
|
/* submit new request */
|
|
dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx);
|
|
src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
|
|
sde_rot_mgr_lock(rot_dev->mgr);
|
|
ret = sde_rotator_process_buffers(ctx, &src_buf->vb2_buf,
|
|
&dst_buf->vb2_buf, request);
|
|
sde_rot_mgr_unlock(rot_dev->mgr);
|
|
if (ret) {
|
|
SDEDEV_ERR(rot_dev->dev,
|
|
"fail process buffer in submit s:%d\n",
|
|
ctx->session_id);
|
|
/* advance to device run to clean up buffers */
|
|
v4l2_m2m_try_schedule(ctx->fh.m2m_ctx);
|
|
}
|
|
|
|
mutex_unlock(&rot_dev->lock);
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_device_run - rotator m2m device run callback
|
|
* @priv: Pointer rotator context.
|
|
*/
|
|
static void sde_rotator_device_run(void *priv)
|
|
{
|
|
struct sde_rotator_ctx *ctx = priv;
|
|
struct sde_rotator_device *rot_dev;
|
|
struct vb2_v4l2_buffer *src_buf;
|
|
struct vb2_v4l2_buffer *dst_buf;
|
|
struct sde_rotator_request *request;
|
|
int ret;
|
|
|
|
if (!ctx || !ctx->rot_dev) {
|
|
SDEROT_ERR("null context/device\n");
|
|
return;
|
|
}
|
|
|
|
rot_dev = ctx->rot_dev;
|
|
SDEDEV_DBG(rot_dev->dev, "device run s:%d\n", ctx->session_id);
|
|
|
|
if (rot_dev->early_submit) {
|
|
request = list_first_entry_or_null(&ctx->pending_list,
|
|
struct sde_rotator_request, list);
|
|
|
|
/* pending request mode, check for completion */
|
|
if (!request || IS_ERR_OR_NULL(request->req)) {
|
|
/* pending request fails or something wrong. */
|
|
SDEDEV_ERR(rot_dev->dev,
|
|
"pending request fail in device run s:%d\n",
|
|
ctx->session_id);
|
|
rot_dev->stats.fail_count++;
|
|
ATRACE_INT("fail_count", rot_dev->stats.fail_count);
|
|
goto error_process_buffers;
|
|
|
|
} else if (!atomic_read(&request->req->pending_count)) {
|
|
/* pending request completed. signal done. */
|
|
int failed_count =
|
|
atomic_read(&request->req->failed_count);
|
|
SDEDEV_DBG(rot_dev->dev,
|
|
"pending request completed in device run s:%d\n",
|
|
ctx->session_id);
|
|
|
|
/* disconnect request (will be freed by core layer) */
|
|
sde_rot_mgr_lock(rot_dev->mgr);
|
|
sde_rotator_req_finish(rot_dev->mgr, ctx->private,
|
|
request->req);
|
|
sde_rot_mgr_unlock(rot_dev->mgr);
|
|
|
|
if (failed_count) {
|
|
SDEDEV_ERR(rot_dev->dev,
|
|
"pending request failed in device run s:%d f:%d\n",
|
|
ctx->session_id,
|
|
failed_count);
|
|
rot_dev->stats.fail_count++;
|
|
ATRACE_INT("fail_count",
|
|
rot_dev->stats.fail_count);
|
|
goto error_process_buffers;
|
|
}
|
|
|
|
src_buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
|
|
dst_buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
|
|
if (!src_buf || !dst_buf) {
|
|
SDEDEV_ERR(rot_dev->dev,
|
|
"null buffer in device run s:%d sb:%pK db:%pK\n",
|
|
ctx->session_id,
|
|
src_buf, dst_buf);
|
|
goto error_process_buffers;
|
|
}
|
|
|
|
sde_rotator_update_retire_sequence(request);
|
|
sde_rotator_retire_request(request);
|
|
v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_DONE);
|
|
v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_DONE);
|
|
v4l2_m2m_job_finish(rot_dev->m2m_dev, ctx->fh.m2m_ctx);
|
|
} else {
|
|
/* pending request not complete. something wrong. */
|
|
SDEDEV_ERR(rot_dev->dev,
|
|
"Incomplete pending request in device run s:%d\n",
|
|
ctx->session_id);
|
|
|
|
/* disconnect request (will be freed by core layer) */
|
|
sde_rot_mgr_lock(rot_dev->mgr);
|
|
sde_rotator_req_finish(rot_dev->mgr, ctx->private,
|
|
request->req);
|
|
sde_rot_mgr_unlock(rot_dev->mgr);
|
|
|
|
goto error_process_buffers;
|
|
}
|
|
} else {
|
|
request = list_first_entry_or_null(&ctx->retired_list,
|
|
struct sde_rotator_request, list);
|
|
if (!request) {
|
|
SDEDEV_ERR(rot_dev->dev,
|
|
"no free request in device run s:%d\n",
|
|
ctx->session_id);
|
|
goto error_retired_list;
|
|
}
|
|
|
|
spin_lock(&ctx->list_lock);
|
|
list_del_init(&request->list);
|
|
list_add_tail(&request->list, &ctx->pending_list);
|
|
spin_unlock(&ctx->list_lock);
|
|
|
|
/* no pending request. submit buffer the usual way. */
|
|
dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx);
|
|
src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
|
|
if (!src_buf || !dst_buf) {
|
|
SDEDEV_ERR(rot_dev->dev,
|
|
"null buffer in device run s:%d sb:%pK db:%pK\n",
|
|
ctx->session_id,
|
|
src_buf, dst_buf);
|
|
goto error_empty_buffer;
|
|
}
|
|
|
|
sde_rot_mgr_lock(rot_dev->mgr);
|
|
ret = sde_rotator_process_buffers(ctx, &src_buf->vb2_buf,
|
|
&dst_buf->vb2_buf, request);
|
|
sde_rot_mgr_unlock(rot_dev->mgr);
|
|
if (ret) {
|
|
SDEDEV_ERR(rot_dev->dev,
|
|
"fail process buffer in device run s:%d\n",
|
|
ctx->session_id);
|
|
rot_dev->stats.fail_count++;
|
|
ATRACE_INT("fail_count", rot_dev->stats.fail_count);
|
|
goto error_process_buffers;
|
|
}
|
|
}
|
|
|
|
return;
|
|
error_process_buffers:
|
|
error_empty_buffer:
|
|
error_retired_list:
|
|
sde_rotator_update_retire_sequence(request);
|
|
sde_rotator_retire_request(request);
|
|
src_buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
|
|
dst_buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
|
|
if (src_buf)
|
|
v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR);
|
|
if (dst_buf)
|
|
v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_ERROR);
|
|
sde_rotator_resync_timeline(ctx->work_queue.timeline);
|
|
v4l2_m2m_job_finish(rot_dev->m2m_dev, ctx->fh.m2m_ctx);
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_job_abort - rotator m2m job abort callback
|
|
* @priv: Pointer rotator context.
|
|
*/
|
|
static void sde_rotator_job_abort(void *priv)
|
|
{
|
|
struct sde_rotator_ctx *ctx = priv;
|
|
struct sde_rotator_device *rot_dev;
|
|
|
|
if (!ctx || !ctx->rot_dev) {
|
|
SDEROT_ERR("null context/device\n");
|
|
return;
|
|
}
|
|
|
|
rot_dev = ctx->rot_dev;
|
|
SDEDEV_DBG(rot_dev->dev, "job abort s:%d\n", ctx->session_id);
|
|
|
|
v4l2_m2m_job_finish(rot_dev->m2m_dev, ctx->fh.m2m_ctx);
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_job_ready - rotator m2m job ready callback
|
|
* @priv: Pointer rotator context.
|
|
*/
|
|
static int sde_rotator_job_ready(void *priv)
|
|
{
|
|
struct sde_rotator_ctx *ctx = priv;
|
|
struct sde_rotator_device *rot_dev;
|
|
struct sde_rotator_request *request;
|
|
int ret = 0;
|
|
|
|
if (!ctx || !ctx->rot_dev) {
|
|
SDEROT_ERR("null context/device\n");
|
|
return 0;
|
|
}
|
|
|
|
rot_dev = ctx->rot_dev;
|
|
SDEDEV_DBG(rot_dev->dev, "job ready s:%d\n", ctx->session_id);
|
|
|
|
request = list_first_entry_or_null(&ctx->pending_list,
|
|
struct sde_rotator_request, list);
|
|
|
|
if (!rot_dev->early_submit) {
|
|
/* always ready in normal mode. */
|
|
ret = 1;
|
|
} else if (request && IS_ERR_OR_NULL(request->req)) {
|
|
/* if pending request fails, forward to device run state. */
|
|
SDEDEV_DBG(rot_dev->dev,
|
|
"pending request fail in job ready s:%d\n",
|
|
ctx->session_id);
|
|
ret = 1;
|
|
} else if (list_empty(&ctx->pending_list)) {
|
|
/* if no pending request, submit a new request. */
|
|
SDEDEV_DBG(rot_dev->dev,
|
|
"submit job s:%d sc:%d dc:%d p:%d\n",
|
|
ctx->session_id,
|
|
v4l2_m2m_num_src_bufs_ready(ctx->fh.m2m_ctx),
|
|
v4l2_m2m_num_dst_bufs_ready(ctx->fh.m2m_ctx),
|
|
!list_empty(&ctx->pending_list));
|
|
|
|
request = list_first_entry_or_null(&ctx->retired_list,
|
|
struct sde_rotator_request, list);
|
|
if (!request) {
|
|
/* should not happen */
|
|
SDEDEV_ERR(rot_dev->dev,
|
|
"no free request in job ready s:%d\n",
|
|
ctx->session_id);
|
|
} else {
|
|
spin_lock(&ctx->list_lock);
|
|
list_del_init(&request->list);
|
|
list_add_tail(&request->list, &ctx->pending_list);
|
|
spin_unlock(&ctx->list_lock);
|
|
kthread_queue_work(ctx->work_queue.rot_kw,
|
|
&request->submit_work);
|
|
}
|
|
} else if (request && !atomic_read(&request->req->pending_count)) {
|
|
/* if pending request completed, forward to device run state */
|
|
SDEDEV_DBG(rot_dev->dev,
|
|
"pending request completed in job ready s:%d\n",
|
|
ctx->session_id);
|
|
ret = 1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* V4l2 mem2mem handlers */
|
|
static struct v4l2_m2m_ops sde_rotator_m2m_ops = {
|
|
.device_run = sde_rotator_device_run,
|
|
.job_abort = sde_rotator_job_abort,
|
|
.job_ready = sde_rotator_job_ready,
|
|
};
|
|
|
|
/* Device tree match struct */
|
|
static const struct of_device_id sde_rotator_dt_match[] = {
|
|
{
|
|
.compatible = "qcom,sde_rotator",
|
|
.data = NULL,
|
|
},
|
|
{}
|
|
};
|
|
|
|
/*
|
|
* sde_rotator_get_drv_data - rotator device driver data.
|
|
* @dev: Pointer to device.
|
|
*/
|
|
static const void *sde_rotator_get_drv_data(struct device *dev)
|
|
{
|
|
const struct of_device_id *match;
|
|
|
|
match = of_match_node(sde_rotator_dt_match, dev->of_node);
|
|
|
|
if (match)
|
|
return match->data;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_probe - rotator device probe method.
|
|
* @pdev: Pointer to rotator platform device.
|
|
*/
|
|
static int sde_rotator_probe(struct platform_device *pdev)
|
|
{
|
|
struct sde_rotator_device *rot_dev;
|
|
struct video_device *vdev;
|
|
int ret, i;
|
|
char name[32];
|
|
|
|
SDEDEV_DBG(&pdev->dev, "SDE v4l2 rotator probed\n");
|
|
|
|
/* sde rotator device struct */
|
|
rot_dev = kzalloc(sizeof(struct sde_rotator_device), GFP_KERNEL);
|
|
if (!rot_dev)
|
|
return -ENOMEM;
|
|
|
|
mutex_init(&rot_dev->lock);
|
|
rot_dev->early_submit = SDE_ROTATOR_EARLY_SUBMIT;
|
|
rot_dev->fence_timeout = SDE_ROTATOR_FENCE_TIMEOUT;
|
|
rot_dev->streamoff_timeout = SDE_ROTATOR_STREAM_OFF_TIMEOUT;
|
|
rot_dev->min_rot_clk = 0;
|
|
rot_dev->min_bw = 0;
|
|
rot_dev->min_overhead_us = 0;
|
|
rot_dev->drvdata = sde_rotator_get_drv_data(&pdev->dev);
|
|
rot_dev->open_timeout = SDE_ROTATOR_CTX_OPEN_TIMEOUT;
|
|
init_waitqueue_head(&rot_dev->open_wq);
|
|
|
|
rot_dev->pdev = pdev;
|
|
rot_dev->dev = &pdev->dev;
|
|
platform_set_drvdata(pdev, rot_dev);
|
|
|
|
ret = sde_rotator_base_init(&rot_dev->mdata, pdev, rot_dev->drvdata);
|
|
if (ret < 0) {
|
|
SDEDEV_ERR(&pdev->dev, "fail init base data %d\n", ret);
|
|
goto error_rotator_base_init;
|
|
}
|
|
|
|
ret = sde_rotator_core_init(&rot_dev->mgr, pdev);
|
|
if (ret < 0) {
|
|
if (ret == -EPROBE_DEFER)
|
|
SDEDEV_INFO(&pdev->dev, "probe defer for core init\n");
|
|
else
|
|
SDEDEV_ERR(&pdev->dev, "fail init core %d\n", ret);
|
|
goto error_rotator_core_init;
|
|
}
|
|
|
|
/* mem2mem device */
|
|
rot_dev->m2m_dev = v4l2_m2m_init(&sde_rotator_m2m_ops);
|
|
if (IS_ERR(rot_dev->m2m_dev)) {
|
|
ret = PTR_ERR(rot_dev->m2m_dev);
|
|
SDEDEV_ERR(&pdev->dev, "fail init mem2mem device %d\n", ret);
|
|
goto error_m2m_init;
|
|
}
|
|
|
|
/* v4l2 device */
|
|
ret = v4l2_device_register(&pdev->dev, &rot_dev->v4l2_dev);
|
|
if (ret < 0) {
|
|
SDEDEV_ERR(&pdev->dev, "fail register v4l2 device %d\n", ret);
|
|
goto error_v4l2_register;
|
|
}
|
|
|
|
vdev = video_device_alloc();
|
|
if (!vdev) {
|
|
SDEDEV_ERR(&pdev->dev, "fail allocate video device\n");
|
|
goto error_alloc_video_device;
|
|
}
|
|
|
|
vdev->fops = &sde_rotator_fops;
|
|
vdev->ioctl_ops = &sde_rotator_ioctl_ops;
|
|
vdev->lock = &rot_dev->lock;
|
|
vdev->minor = -1;
|
|
vdev->release = video_device_release;
|
|
vdev->v4l2_dev = &rot_dev->v4l2_dev;
|
|
vdev->vfl_dir = VFL_DIR_M2M;
|
|
vdev->vfl_type = VFL_TYPE_GRABBER;
|
|
strlcpy(vdev->name, SDE_ROTATOR_DRV_NAME, sizeof(vdev->name));
|
|
|
|
ret = video_register_device(vdev, VFL_TYPE_GRABBER,
|
|
SDE_ROTATOR_BASE_DEVICE_NUMBER);
|
|
if (ret < 0) {
|
|
SDEDEV_ERR(&pdev->dev, "fail register video device %d\n",
|
|
ret);
|
|
goto error_video_register;
|
|
}
|
|
|
|
rot_dev->vdev = vdev;
|
|
video_set_drvdata(rot_dev->vdev, rot_dev);
|
|
|
|
rot_dev->debugfs_root = sde_rotator_create_debugfs(rot_dev);
|
|
|
|
for (i = 0; i < MAX_ROT_OPEN_SESSION; i++) {
|
|
snprintf(name, sizeof(name), "rot_fenceq_%d_%d",
|
|
rot_dev->dev->id, i);
|
|
kthread_init_worker(&rot_dev->rot_kw[i]);
|
|
rot_dev->rot_thread[i] = kthread_run(kthread_worker_fn,
|
|
&rot_dev->rot_kw[i], name);
|
|
if (IS_ERR(rot_dev->rot_thread[i])) {
|
|
SDEDEV_ERR(rot_dev->dev,
|
|
"fail allocate kthread i:%d\n", i);
|
|
ret = -EPERM;
|
|
goto error_kthread_create;
|
|
}
|
|
rot_dev->kthread_free[i] = true;
|
|
}
|
|
|
|
SDEDEV_INFO(&pdev->dev, "SDE v4l2 rotator probe success\n");
|
|
|
|
return 0;
|
|
error_kthread_create:
|
|
for (i--; i >= 0; i--)
|
|
kthread_stop(rot_dev->rot_thread[i]);
|
|
sde_rotator_destroy_debugfs(rot_dev->debugfs_root);
|
|
video_unregister_device(rot_dev->vdev);
|
|
error_video_register:
|
|
video_device_release(vdev);
|
|
error_alloc_video_device:
|
|
v4l2_device_unregister(&rot_dev->v4l2_dev);
|
|
error_v4l2_register:
|
|
v4l2_m2m_release(rot_dev->m2m_dev);
|
|
error_m2m_init:
|
|
sde_rotator_core_destroy(rot_dev->mgr);
|
|
error_rotator_core_init:
|
|
sde_rotator_base_destroy(rot_dev->mdata);
|
|
error_rotator_base_init:
|
|
kfree(rot_dev);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* sde_rotator_remove - rotator device remove method.
|
|
* @pdev: Pointer rotator platform device.
|
|
*/
|
|
static int sde_rotator_remove(struct platform_device *pdev)
|
|
{
|
|
struct sde_rotator_device *rot_dev;
|
|
int i;
|
|
|
|
rot_dev = platform_get_drvdata(pdev);
|
|
if (rot_dev == NULL) {
|
|
SDEDEV_ERR(&pdev->dev, "fail get rotator drvdata\n");
|
|
return 0;
|
|
}
|
|
|
|
sde_rotator_pm_qos_remove(rot_dev->mdata);
|
|
for (i = MAX_ROT_OPEN_SESSION - 1; i >= 0; i--)
|
|
kthread_stop(rot_dev->rot_thread[i]);
|
|
sde_rotator_destroy_debugfs(rot_dev->debugfs_root);
|
|
video_unregister_device(rot_dev->vdev);
|
|
video_device_release(rot_dev->vdev);
|
|
v4l2_device_unregister(&rot_dev->v4l2_dev);
|
|
v4l2_m2m_release(rot_dev->m2m_dev);
|
|
sde_rotator_core_destroy(rot_dev->mgr);
|
|
sde_rotator_base_destroy(rot_dev->mdata);
|
|
kfree(rot_dev);
|
|
return 0;
|
|
}
|
|
|
|
static const struct dev_pm_ops sde_rotator_pm_ops = {
|
|
SET_SYSTEM_SLEEP_PM_OPS(sde_rotator_pm_suspend, sde_rotator_pm_resume)
|
|
SET_RUNTIME_PM_OPS(sde_rotator_runtime_suspend,
|
|
sde_rotator_runtime_resume,
|
|
sde_rotator_runtime_idle)
|
|
};
|
|
|
|
/* SDE Rotator platform driver definition */
|
|
static struct platform_driver rotator_driver = {
|
|
.probe = sde_rotator_probe,
|
|
.remove = sde_rotator_remove,
|
|
.suspend = sde_rotator_suspend,
|
|
.resume = sde_rotator_resume,
|
|
.driver = {
|
|
.name = SDE_ROTATOR_DRV_NAME,
|
|
.of_match_table = sde_rotator_dt_match,
|
|
.pm = &sde_rotator_pm_ops,
|
|
},
|
|
};
|
|
|
|
static int __init sde_rotator_init_module(void)
|
|
{
|
|
return platform_driver_register(&rotator_driver);
|
|
}
|
|
|
|
static void __exit sde_rotator_exit_module(void)
|
|
{
|
|
platform_driver_unregister(&rotator_driver);
|
|
}
|
|
|
|
module_init(sde_rotator_init_module);
|
|
module_exit(sde_rotator_exit_module);
|
|
MODULE_DESCRIPTION("MSM SDE ROTATOR driver");
|