blob: 7f6509cc112cd63d27d254abf19bd3b165f49951 [file] [log] [blame]
/*
* Samsung Exynos SoC series VIPx driver
*
* Copyright (c) 2018 Samsung Electronics Co., Ltd
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <asm/cacheflush.h>
#include <linux/slab.h>
#include <linux/ion_exynos.h>
#include "vipx-log.h"
#include "vipx-device.h"
#include "vipx-core.h"
#include "vipx-context.h"
#include "vipx-queue.h"
static struct vipx_format_type vipx_fmts[] = {
{
.name = "RGB",
.colorspace = VS4L_DF_IMAGE_RGB,
.planes = 1,
.bitsperpixel = { 24 }
}, {
.name = "ARGB",
.colorspace = VS4L_DF_IMAGE_RGBX,
.planes = 1,
.bitsperpixel = { 32 }
}, {
.name = "YUV 4:2:0 planar, Y/CbCr",
.colorspace = VS4L_DF_IMAGE_NV12,
.planes = 2,
.bitsperpixel = { 8, 8 }
}, {
.name = "YUV 4:2:0 planar, Y/CrCb",
.colorspace = VS4L_DF_IMAGE_NV21,
.planes = 2,
.bitsperpixel = { 8, 8 }
}, {
.name = "YUV 4:2:0 planar, Y/Cr/Cb",
.colorspace = VS4L_DF_IMAGE_YV12,
.planes = 3,
.bitsperpixel = { 8, 4, 4 }
}, {
.name = "YUV 4:2:0 planar, Y/Cb/Cr",
.colorspace = VS4L_DF_IMAGE_I420,
.planes = 3,
.bitsperpixel = { 8, 4, 4 }
}, {
.name = "YUV 4:2:2 planar, Y/Cb/Cr",
.colorspace = VS4L_DF_IMAGE_I422,
.planes = 3,
.bitsperpixel = { 8, 4, 4 }
}, {
.name = "YUV 4:2:2 packed, YCbYCr",
.colorspace = VS4L_DF_IMAGE_YUYV,
.planes = 1,
.bitsperpixel = { 16 }
}, {
.name = "YUV 4:4:4 packed, YCbCr",
.colorspace = VS4L_DF_IMAGE_YUV4,
.planes = 1,
.bitsperpixel = { 24 }
}, {
.name = "VX unsigned 8 bit",
.colorspace = VS4L_DF_IMAGE_U8,
.planes = 1,
.bitsperpixel = { 8 }
}, {
.name = "VX unsigned 16 bit",
.colorspace = VS4L_DF_IMAGE_U16,
.planes = 1,
.bitsperpixel = { 16 }
}, {
.name = "VX unsigned 32 bit",
.colorspace = VS4L_DF_IMAGE_U32,
.planes = 1,
.bitsperpixel = { 32 }
}, {
.name = "VX signed 16 bit",
.colorspace = VS4L_DF_IMAGE_S16,
.planes = 1,
.bitsperpixel = { 16 }
}, {
.name = "VX signed 32 bit",
.colorspace = VS4L_DF_IMAGE_S32,
.planes = 1,
.bitsperpixel = { 32 }
}
};
static struct vipx_format_type *__vipx_queue_find_format(unsigned int format)
{
int ret;
unsigned int idx;
struct vipx_format_type *fmt = NULL;
vipx_enter();
for (idx = 0; idx < ARRAY_SIZE(vipx_fmts); ++idx) {
if (vipx_fmts[idx].colorspace == format) {
fmt = &vipx_fmts[idx];
break;
}
}
if (!fmt) {
ret = -EINVAL;
vipx_err("Vision format is invalid (%u)\n", format);
goto p_err;
}
vipx_leave();
return fmt;
p_err:
return ERR_PTR(ret);
}
static int __vipx_queue_plane_size(struct vipx_buffer_format *format)
{
int ret;
unsigned int plane;
struct vipx_format_type *fmt;
unsigned int width, height;
vipx_enter();
fmt = format->fmt;
if (fmt->planes > VIPX_MAX_PLANES) {
ret = -EINVAL;
vipx_err("planes(%u) is too big (%u)\n",
fmt->planes, VIPX_MAX_PLANES);
goto p_err;
}
for (plane = 0; plane < fmt->planes; ++plane) {
width = format->width *
fmt->bitsperpixel[plane] / BITS_PER_BYTE;
height = format->height *
fmt->bitsperpixel[plane] / BITS_PER_BYTE;
format->size[plane] = width * height;
}
vipx_leave();
return 0;
p_err:
return ret;
}
static int __vipx_queue_wait_for_done(struct vipx_queue *queue)
{
int ret;
/* TODO: check wait time */
unsigned long wait_time = 10000;
vipx_enter();
if (!queue->streaming) {
ret = -EINVAL;
vipx_err("queue is not streaming status\n");
goto p_err;
}
if (!list_empty(&queue->done_list))
return 0;
mutex_unlock(queue->lock);
ret = wait_event_interruptible_timeout(queue->done_wq,
!list_empty(&queue->done_list) || !queue->streaming,
msecs_to_jiffies(wait_time));
if (!ret) {
ret = -ETIMEDOUT;
vipx_err("Wait time(%lu ms) is over\n", wait_time);
} else if (ret > 0) {
ret = 0;
} else {
vipx_err("Waiting has ended abnormaly (%d)\n", ret);
}
mutex_lock(queue->lock);
vipx_leave();
return 0;
p_err:
return ret;
}
static struct vipx_bundle *__vipx_queue_get_done_bundle(
struct vipx_queue *queue,
struct vs4l_container_list *clist)
{
int ret;
struct vipx_bundle *bundle;
unsigned long flags;
vipx_enter();
/* Wait for at least one buffer to become available on the done_list */
ret = __vipx_queue_wait_for_done(queue);
if (ret)
goto p_err;
/*
* Driver's lock has been held since we last verified that done_list
* is not empty, so no need for another list_empty(done_list) check.
*/
spin_lock_irqsave(&queue->done_lock, flags);
bundle = list_first_entry(&queue->done_list, struct vipx_bundle,
done_entry);
list_del(&bundle->done_entry);
atomic_dec(&queue->done_count);
spin_unlock_irqrestore(&queue->done_lock, flags);
vipx_leave();
return bundle;
p_err:
return ERR_PTR(ret);
}
static void __vipx_queue_fill_vs4l_buffer(struct vipx_bundle *bundle,
struct vs4l_container_list *kclist)
{
struct vipx_container_list *vclist;
vipx_enter();
vclist = &bundle->clist;
kclist->flags &= ~(1 << VS4L_CL_FLAG_TIMESTAMP);
kclist->flags &= ~(1 << VS4L_CL_FLAG_PREPARE);
kclist->flags &= ~(1 << VS4L_CL_FLAG_INVALID);
kclist->flags &= ~(1 << VS4L_CL_FLAG_DONE);
if (test_bit(VS4L_CL_FLAG_TIMESTAMP, &vclist->flags)) {
if (sizeof(kclist->timestamp) == sizeof(vclist->timestamp)) {
kclist->flags |= (1 << VS4L_CL_FLAG_TIMESTAMP);
memcpy(kclist->timestamp, vclist->timestamp,
sizeof(vclist->timestamp));
} else {
vipx_warn("timestamp of clist is different (%zu/%zu)\n",
sizeof(kclist->timestamp),
sizeof(vclist->timestamp));
}
}
if (test_bit(VS4L_CL_FLAG_PREPARE, &bundle->flags))
kclist->flags |= (1 << VS4L_CL_FLAG_PREPARE);
if (test_bit(VS4L_CL_FLAG_INVALID, &bundle->flags))
kclist->flags |= (1 << VS4L_CL_FLAG_INVALID);
if (test_bit(VS4L_CL_FLAG_DONE, &bundle->flags))
kclist->flags |= (1 << VS4L_CL_FLAG_DONE);
kclist->index = vclist->index;
kclist->id = vclist->id;
vipx_leave();
}
static int __vipx_queue_map_dmabuf(struct vipx_queue *queue,
struct vipx_buffer *buf, size_t size)
{
int ret;
struct vipx_core *core;
struct vipx_memory *mem;
vipx_enter();
core = queue->qlist->vctx->core;
mem = &core->system->memory;
buf->size = size;
ret = mem->mops->map_dmabuf(mem, buf);
if (ret)
goto p_err;
vipx_leave();
return 0;
p_err:
return ret;
}
static void __vipx_queue_unmap_dmabuf(struct vipx_queue *queue,
struct vipx_buffer *buf)
{
struct vipx_core *core;
struct vipx_memory *mem;
vipx_enter();
core = queue->qlist->vctx->core;
mem = &core->system->memory;
mem->mops->unmap_dmabuf(mem, buf);
vipx_leave();
}
static int __vipx_queue_bundle_prepare(struct vipx_queue *queue,
struct vipx_bundle *bundle)
{
int ret;
struct vipx_container *con;
struct vipx_buffer *buf;
struct vipx_buffer_format *fmt;
unsigned int c_cnt, count, b_cnt;
vipx_enter();
if (test_bit(VS4L_CL_FLAG_PREPARE, &bundle->flags))
return 0;
con = bundle->clist.containers;
for (c_cnt = 0; c_cnt < bundle->clist.count; ++c_cnt) {
switch (con[c_cnt].type) {
case VS4L_BUFFER_LIST:
case VS4L_BUFFER_ROI:
case VS4L_BUFFER_PYRAMID:
count = con[c_cnt].count;
break;
default:
ret = -EINVAL;
vipx_err("container type is invalid (%u)\n",
con[c_cnt].type);
goto p_err;
}
fmt = con[c_cnt].format;
switch (con[c_cnt].memory) {
case VS4L_MEMORY_DMABUF:
buf = con[c_cnt].buffers;
for (b_cnt = 0; b_cnt < count; ++b_cnt) {
ret = __vipx_queue_map_dmabuf(queue,
&buf[b_cnt],
fmt->size[b_cnt]);
if (ret)
goto p_err;
}
break;
default:
ret = -EINVAL;
vipx_err("container memory type is invalid (%u)\n",
con[c_cnt].memory);
goto p_err;
}
}
set_bit(VS4L_CL_FLAG_PREPARE, &bundle->flags);
vipx_leave();
return 0;
p_err:
return ret;
}
static int __vipx_queue_bundle_unprepare(struct vipx_queue *queue,
struct vipx_bundle *bundle)
{
int ret;
struct vipx_container *con;
struct vipx_buffer *buf;
unsigned int c_cnt, count, b_cnt;
vipx_enter();
if (!test_bit(VS4L_CL_FLAG_PREPARE, &bundle->flags))
goto p_err;
con = bundle->clist.containers;
for (c_cnt = 0; c_cnt < bundle->clist.count; ++c_cnt) {
switch (con[c_cnt].type) {
case VS4L_BUFFER_LIST:
case VS4L_BUFFER_ROI:
case VS4L_BUFFER_PYRAMID:
count = con[c_cnt].count;
break;
default:
ret = -EINVAL;
vipx_err("container type is invalid (%u)\n",
con[c_cnt].type);
goto p_err;
}
switch (con[c_cnt].memory) {
case VS4L_MEMORY_DMABUF:
buf = con[c_cnt].buffers;
for (b_cnt = 0; b_cnt < count; ++b_cnt)
__vipx_queue_unmap_dmabuf(queue, &buf[b_cnt]);
break;
default:
ret = -EINVAL;
vipx_err("container memory type is invalid (%u)\n",
con[c_cnt].memory);
goto p_err;
}
}
clear_bit(VS4L_CL_FLAG_PREPARE, &bundle->flags);
vipx_leave();
return 0;
p_err:
return ret;
}
static int __vipx_queue_bundle_check(struct vipx_bundle *bundle,
struct vs4l_container_list *kclist)
{
int ret;
struct vs4l_container *kcon;
struct vs4l_buffer *kbuf;
struct vipx_container_list *vclist;
struct vipx_container *vcon;
struct vipx_buffer *vbuf;
unsigned int c_cnt, b_cnt;
vipx_enter();
vclist = &bundle->clist;
if (vclist->direction != kclist->direction) {
ret = -EINVAL;
vipx_err("direction of clist is different (%u/%u)\n",
vclist->direction, kclist->direction);
goto p_err;
}
if (vclist->index != kclist->index) {
ret = -EINVAL;
vipx_err("index of clist is different (%u/%u)\n",
vclist->index, kclist->index);
goto p_err;
}
if (vclist->count != kclist->count) {
ret = -EINVAL;
vipx_err("container count of clist is differnet (%u/%u)\n",
vclist->count, kclist->count);
goto p_err;
}
vclist->id = kclist->id;
vclist->flags = kclist->flags;
vcon = vclist->containers;
kcon = kclist->containers;
for (c_cnt = 0; c_cnt < vclist->count; ++c_cnt) {
if (vcon[c_cnt].target != kcon[c_cnt].target) {
ret = -EINVAL;
vipx_err("target of container is different (%u/%u)\n",
vcon[c_cnt].target, kcon[c_cnt].target);
goto p_err;
}
if (vcon[c_cnt].count != kcon[c_cnt].count) {
ret = -EINVAL;
vipx_err("count of container is different (%u/%u)\n",
vcon[c_cnt].count, kcon[c_cnt].count);
goto p_err;
}
vbuf = vcon[c_cnt].buffers;
kbuf = kcon[c_cnt].buffers;
for (b_cnt = 0; b_cnt < vcon[c_cnt].count; ++b_cnt) {
vbuf[b_cnt].roi = kbuf[b_cnt].roi;
if (vbuf[b_cnt].m.fd != kbuf[b_cnt].m.fd) {
ret = -EINVAL;
vipx_err("fd of buffer is different (%d/%d)\n",
vbuf[b_cnt].m.fd,
kbuf[b_cnt].m.fd);
goto p_err;
}
}
}
vipx_leave();
return 0;
p_err:
return ret;
}
static int __vipx_queue_bundle_alloc(struct vipx_queue *queue,
struct vs4l_container_list *kclist)
{
int ret;
struct vs4l_container *kcon;
struct vs4l_buffer *kbuf;
struct vipx_bundle *bundle;
struct vipx_container_list *vclist;
struct vipx_container *vcon;
struct vipx_buffer *vbuf;
size_t alloc_size;
char *ptr;
unsigned int c_cnt, f_cnt, b_cnt;
vipx_enter();
/* All memory allocation and pointer value assignment */
kcon = kclist->containers;
alloc_size = sizeof(*bundle);
for (c_cnt = 0; c_cnt < kclist->count; ++c_cnt)
alloc_size += sizeof(*vcon) + sizeof(*vbuf) * kcon[c_cnt].count;
bundle = kzalloc(alloc_size, GFP_KERNEL);
if (!bundle) {
ret = -ENOMEM;
vipx_err("Failed to alloc bundle (%zu)\n", alloc_size);
goto p_err_alloc;
}
vclist = &bundle->clist;
ptr = (char *)bundle + sizeof(*bundle);
vclist->containers = (struct vipx_container *)ptr;
ptr += sizeof(*vcon) * kclist->count;
for (c_cnt = 0; c_cnt < kclist->count; ++c_cnt) {
vclist->containers[c_cnt].buffers = (struct vipx_buffer *)ptr;
ptr += sizeof(*vbuf) * kcon[c_cnt].count;
}
vclist->direction = kclist->direction;
vclist->id = kclist->id;
vclist->index = kclist->index;
vclist->flags = kclist->flags;
vclist->count = kclist->count;
if (sizeof(vclist->user_params) != sizeof(kclist->user_params)) {
ret = -EINVAL;
vipx_err("user params size is invalid (%zu/%zu)\n",
sizeof(vclist->user_params),
sizeof(kclist->user_params));
goto p_err_param;
}
memcpy(vclist->user_params, kclist->user_params,
sizeof(vclist->user_params));
vcon = vclist->containers;
for (c_cnt = 0; c_cnt < kclist->count; ++c_cnt) {
vcon[c_cnt].type = kcon[c_cnt].type;
vcon[c_cnt].target = kcon[c_cnt].target;
vcon[c_cnt].memory = kcon[c_cnt].memory;
if (sizeof(vcon[c_cnt].reserved) !=
sizeof(kcon[c_cnt].reserved)) {
ret = -EINVAL;
vipx_err("container reserve is invalid (%zu/%zu)\n",
sizeof(vcon[c_cnt].reserved),
sizeof(kcon[c_cnt].reserved));
goto p_err_param;
}
memcpy(vcon->reserved, kcon[c_cnt].reserved,
sizeof(vcon->reserved));
vcon[c_cnt].count = kcon[c_cnt].count;
for (f_cnt = 0; f_cnt < queue->flist.count; ++f_cnt) {
if (vcon[c_cnt].target ==
queue->flist.formats[f_cnt].target) {
vcon[c_cnt].format =
&queue->flist.formats[f_cnt];
break;
}
}
if (!vcon[c_cnt].format) {
ret = -EINVAL;
vipx_err("Format(%u) is not set\n", vcon[c_cnt].target);
goto p_err_param;
}
vbuf = vcon[c_cnt].buffers;
kbuf = kcon[c_cnt].buffers;
for (b_cnt = 0; b_cnt < kcon[c_cnt].count; ++b_cnt) {
vbuf[b_cnt].roi = kbuf[b_cnt].roi;
vbuf[b_cnt].m.fd = kbuf[b_cnt].m.fd;
}
}
queue->bufs[kclist->index] = bundle;
queue->num_buffers++;
vipx_leave();
return 0;
p_err_param:
kfree(bundle);
p_err_alloc:
return ret;
}
static void __vipx_queue_bundle_free(struct vipx_queue *queue,
struct vipx_bundle *bundle)
{
vipx_enter();
queue->num_buffers--;
queue->bufs[bundle->clist.index] = NULL;
kfree(bundle);
vipx_leave();
}
static int __vipx_queue_s_format(struct vipx_queue *queue,
struct vs4l_format_list *flist)
{
int ret;
unsigned int idx;
struct vipx_buffer_format *vformat;
struct vs4l_format *kformat;
struct vipx_format_type *fmt;
vipx_enter();
vformat = kcalloc(flist->count, sizeof(*vformat), GFP_KERNEL);
if (!vformat) {
ret = -ENOMEM;
vipx_err("Failed to allocate vformats\n");
goto p_err_alloc;
}
queue->flist.count = flist->count;
queue->flist.formats = vformat;
kformat = flist->formats;
for (idx = 0; idx < flist->count; ++idx) {
fmt = __vipx_queue_find_format(kformat[idx].format);
if (IS_ERR(fmt)) {
ret = PTR_ERR(fmt);
goto p_err_find;
}
vformat[idx].fmt = fmt;
vformat[idx].colorspace = kformat[idx].format;
vformat[idx].target = kformat[idx].target;
vformat[idx].plane = kformat[idx].plane;
vformat[idx].width = kformat[idx].width;
vformat[idx].height = kformat[idx].height;
ret = __vipx_queue_plane_size(&vformat[idx]);
if (ret)
goto p_err_plane;
}
vipx_leave();
return 0;
p_err_plane:
p_err_find:
kfree(vformat);
p_err_alloc:
return ret;
}
static int __vipx_queue_qbuf(struct vipx_queue *queue,
struct vs4l_container_list *clist)
{
int ret;
struct vipx_bundle *bundle;
struct vipx_container *con;
struct vipx_buffer *buf;
int dma_dir;
unsigned int c_cnt, b_cnt;
vipx_enter();
if (clist->index >= VIPX_MAX_BUFFER) {
ret = -EINVAL;
vipx_err("clist index is out of range (%u/%u)\n",
clist->index, VIPX_MAX_BUFFER);
goto p_err;
}
if (queue->bufs[clist->index]) {
bundle = queue->bufs[clist->index];
ret = __vipx_queue_bundle_check(bundle, clist);
if (ret)
goto p_err;
} else {
ret = __vipx_queue_bundle_alloc(queue, clist);
if (ret)
goto p_err;
bundle = queue->bufs[clist->index];
}
if (bundle->state != VIPX_BUNDLE_STATE_DEQUEUED) {
ret = -EINVAL;
vipx_err("bundle is already in use (%u)\n", bundle->state);
goto p_err;
}
ret = __vipx_queue_bundle_prepare(queue, bundle);
if (ret)
goto p_err;
if (queue->direction == VS4L_DIRECTION_OT)
dma_dir = DMA_FROM_DEVICE;
else
dma_dir = DMA_TO_DEVICE;
/* TODO: check sync */
con = bundle->clist.containers;
for (c_cnt = 0; c_cnt < bundle->clist.count; ++c_cnt) {
buf = con[c_cnt].buffers;
for (b_cnt = 0; b_cnt < con[c_cnt].count; ++b_cnt)
__dma_map_area(buf[b_cnt].kvaddr, buf[b_cnt].size,
dma_dir);
}
/*
* Add to the queued buffers list, a buffer will stay on it until
* dequeued in dqbuf.
*/
list_add_tail(&bundle->queued_entry, &queue->queued_list);
bundle->state = VIPX_BUNDLE_STATE_QUEUED;
atomic_inc(&queue->queued_count);
vipx_leave();
return 0;
p_err:
return ret;
}
static void __vipx_queue_process(struct vipx_queue *queue,
struct vipx_bundle *bundle)
{
vipx_enter();
bundle->state = VIPX_BUNDLE_STATE_PROCESS;
list_add_tail(&bundle->process_entry, &queue->process_list);
atomic_inc(&queue->process_count);
vipx_leave();
}
static int __vipx_queue_dqbuf(struct vipx_queue *queue,
struct vs4l_container_list *clist)
{
int ret;
struct vipx_bundle *bundle;
vipx_enter();
if (queue->direction != clist->direction) {
ret = -EINVAL;
vipx_err("direction of queue is different (%u/%u)\n",
queue->direction, clist->direction);
goto p_err;
}
bundle = __vipx_queue_get_done_bundle(queue, clist);
if (IS_ERR(bundle)) {
ret = PTR_ERR(bundle);
goto p_err;
}
if (bundle->state != VIPX_BUNDLE_STATE_DONE) {
ret = -EINVAL;
vipx_err("bundle state is not done (%u)\n", bundle->state);
goto p_err;
}
/* Fill buffer information for the userspace */
__vipx_queue_fill_vs4l_buffer(bundle, clist);
bundle->state = VIPX_BUNDLE_STATE_DEQUEUED;
list_del(&bundle->queued_entry);
atomic_dec(&queue->queued_count);
vipx_leave();
return 0;
p_err:
return ret;
}
static int __vipx_queue_start(struct vipx_queue *queue)
{
vipx_enter();
queue->streaming = 1;
vipx_leave();
return 0;
}
static int __vipx_queue_stop(struct vipx_queue *queue)
{
int ret;
struct vipx_bundle **bundle;
unsigned int idx;
vipx_enter();
queue->streaming = 0;
wake_up_all(&queue->done_wq);
if (atomic_read(&queue->queued_count)) {
ret = -EINVAL;
vipx_err("queued list is not empty (%d)\n",
atomic_read(&queue->queued_count));
goto p_err;
}
if (atomic_read(&queue->process_count)) {
ret = -EINVAL;
vipx_err("process list is not empty (%d)\n",
atomic_read(&queue->process_count));
goto p_err;
}
if (atomic_read(&queue->done_count)) {
ret = -EINVAL;
vipx_err("done list is not empty (%d)\n",
atomic_read(&queue->done_count));
goto p_err;
}
bundle = queue->bufs;
for (idx = 0; queue->num_buffers && idx < VIPX_MAX_BUFFER; ++idx) {
if (!bundle[idx])
continue;
ret = __vipx_queue_bundle_unprepare(queue, bundle[idx]);
if (ret)
goto p_err;
__vipx_queue_bundle_free(queue, bundle[idx]);
}
if (queue->num_buffers) {
ret = -EINVAL;
vipx_err("bundle leak issued (%u)\n", queue->num_buffers);
goto p_err;
}
vipx_leave();
return 0;
p_err:
return ret;
}
static int vipx_queue_poll(struct vipx_queue_list *qlist, struct file *file,
struct poll_table_struct *poll)
{
int ret = 0;
unsigned long events;
struct vipx_queue *inq, *outq;
vipx_enter();
events = poll_requested_events(poll);
inq = &qlist->in_queue;
outq = &qlist->out_queue;
if (events & POLLIN) {
if (list_empty(&inq->done_list))
poll_wait(file, &inq->done_wq, poll);
if (list_empty(&inq->done_list))
ret |= POLLIN | POLLWRNORM;
}
if (events & POLLOUT) {
if (list_empty(&outq->done_list))
poll_wait(file, &outq->done_wq, poll);
if (list_empty(&outq->done_list))
ret |= POLLOUT | POLLWRNORM;
}
vipx_leave();
return ret;
}
static int vipx_queue_set_graph(struct vipx_queue_list *qlist,
struct vs4l_graph *ginfo)
{
int ret;
vipx_enter();
ret = qlist->gops->set_graph(qlist->vctx->graph, ginfo);
if (ret)
goto p_err_gops;
vipx_leave();
return 0;
p_err_gops:
return ret;
}
static int vipx_queue_set_format(struct vipx_queue_list *qlist,
struct vs4l_format_list *flist)
{
int ret;
struct vipx_queue *queue;
vipx_enter();
if (flist->direction == VS4L_DIRECTION_IN)
queue = &qlist->in_queue;
else
queue = &qlist->out_queue;
ret = qlist->gops->set_format(qlist->vctx->graph, flist);
if (ret)
goto p_err_gops;
ret = __vipx_queue_s_format(queue, flist);
if (ret)
goto p_err_s_format;
vipx_leave();
return 0;
p_err_s_format:
p_err_gops:
return ret;
}
static int vipx_queue_set_param(struct vipx_queue_list *qlist,
struct vs4l_param_list *plist)
{
int ret;
vipx_enter();
ret = qlist->gops->set_param(qlist->vctx->graph, plist);
if (ret)
goto p_err_gops;
vipx_leave();
return 0;
p_err_gops:
return ret;
}
static int vipx_queue_set_ctrl(struct vipx_queue_list *qlist,
struct vs4l_ctrl *ctrl)
{
int ret;
vipx_enter();
switch (ctrl->ctrl) {
default:
ret = -EINVAL;
vipx_err("vs4l control is invalid(%d)\n", ctrl->ctrl);
goto p_err;
}
vipx_leave();
return 0;
p_err:
return ret;
}
static int vipx_queue_qbuf(struct vipx_queue_list *qlist,
struct vs4l_container_list *clist)
{
int ret;
struct vipx_queue *inq, *outq, *queue;
struct vipx_bundle *invb, *otvb;
vipx_enter();
inq = &qlist->in_queue;
outq = &qlist->out_queue;
if (clist->direction == VS4L_DIRECTION_IN)
queue = inq;
else
queue = outq;
ret = __vipx_queue_qbuf(queue, clist);
if (ret)
goto p_err_qbuf;
if (list_empty(&inq->queued_list) || list_empty(&outq->queued_list))
return 0;
invb = list_first_entry(&inq->queued_list,
struct vipx_bundle, queued_entry);
otvb = list_first_entry(&outq->queued_list,
struct vipx_bundle, queued_entry);
__vipx_queue_process(inq, invb);
__vipx_queue_process(outq, otvb);
ret = qlist->gops->queue(qlist->vctx->graph,
&invb->clist, &otvb->clist);
if (ret)
goto p_err_gops;
vipx_leave();
return 0;
p_err_gops:
p_err_qbuf:
return ret;
}
static int vipx_queue_dqbuf(struct vipx_queue_list *qlist,
struct vs4l_container_list *clist)
{
int ret;
struct vipx_queue *queue;
struct vipx_bundle *bundle;
vipx_enter();
if (clist->direction == VS4L_DIRECTION_IN)
queue = &qlist->in_queue;
else
queue = &qlist->out_queue;
ret = __vipx_queue_dqbuf(queue, clist);
if (ret)
goto p_err_dqbuf;
if (clist->index >= VIPX_MAX_BUFFER) {
ret = -EINVAL;
vipx_err("clist index is out of range (%u/%u)\n",
clist->index, VIPX_MAX_BUFFER);
goto p_err_index;
}
bundle = queue->bufs[clist->index];
if (!bundle) {
ret = -EINVAL;
vipx_err("bundle(%u) is NULL\n", clist->index);
goto p_err_bundle;
}
if (bundle->clist.index != clist->index) {
ret = -EINVAL;
vipx_err("index of clist is different (%u/%u)\n",
bundle->clist.index, clist->index);
goto p_err_bundle_index;
}
ret = qlist->gops->deque(qlist->vctx->graph, &bundle->clist);
if (ret)
goto p_err_gops;
vipx_leave();
return 0;
p_err_gops:
p_err_bundle_index:
p_err_bundle:
p_err_index:
p_err_dqbuf:
return ret;
}
static int vipx_queue_streamon(struct vipx_queue_list *qlist)
{
int ret;
struct vipx_queue *inq, *outq;
vipx_enter();
inq = &qlist->in_queue;
outq = &qlist->out_queue;
__vipx_queue_start(inq);
__vipx_queue_start(outq);
ret = qlist->gops->start(qlist->vctx->graph);
if (ret)
goto p_err_gops;
vipx_leave();
return 0;
p_err_gops:
return ret;
}
static int vipx_queue_streamoff(struct vipx_queue_list *qlist)
{
int ret;
struct vipx_queue *inq, *outq;
vipx_enter();
inq = &qlist->in_queue;
outq = &qlist->out_queue;
ret = qlist->gops->stop(qlist->vctx->graph);
if (ret)
goto p_err_gops;
__vipx_queue_stop(inq);
__vipx_queue_stop(outq);
vipx_leave();
return 0;
p_err_gops:
return ret;
}
const struct vipx_context_qops vipx_context_qops = {
.poll = vipx_queue_poll,
.set_graph = vipx_queue_set_graph,
.set_format = vipx_queue_set_format,
.set_param = vipx_queue_set_param,
.set_ctrl = vipx_queue_set_ctrl,
.qbuf = vipx_queue_qbuf,
.dqbuf = vipx_queue_dqbuf,
.streamon = vipx_queue_streamon,
.streamoff = vipx_queue_streamoff
};
static void __vipx_queue_done(struct vipx_queue *queue,
struct vipx_bundle *bundle)
{
int dma_dir;
struct vipx_container *con;
struct vipx_buffer *buf;
unsigned int c_cnt, b_cnt;
unsigned long flags;
vipx_enter();
if (queue->direction != bundle->clist.direction) {
vipx_err("direction of queue is different (%u/%u)\n",
queue->direction, bundle->clist.direction);
}
if (queue->direction == VS4L_DIRECTION_OT)
dma_dir = DMA_FROM_DEVICE;
else
dma_dir = DMA_TO_DEVICE;
/* TODO: check sync */
con = bundle->clist.containers;
for (c_cnt = 0; c_cnt < bundle->clist.count; ++c_cnt) {
buf = con[c_cnt].buffers;
for (b_cnt = 0; b_cnt < con[c_cnt].count; ++b_cnt)
__dma_map_area(buf[b_cnt].kvaddr, buf[b_cnt].size,
dma_dir);
}
spin_lock_irqsave(&queue->done_lock, flags);
list_del(&bundle->process_entry);
atomic_dec(&queue->process_count);
bundle->state = VIPX_BUNDLE_STATE_DONE;
list_add_tail(&bundle->done_entry, &queue->done_list);
atomic_inc(&queue->done_count);
spin_unlock_irqrestore(&queue->done_lock, flags);
wake_up(&queue->done_wq);
vipx_leave();
}
void vipx_queue_done(struct vipx_queue_list *qlist,
struct vipx_container_list *incl,
struct vipx_container_list *otcl,
unsigned long flags)
{
struct vipx_queue *inq, *outq;
struct vipx_bundle *invb, *otvb;
vipx_enter();
inq = &qlist->in_queue;
outq = &qlist->out_queue;
if (!list_empty(&outq->process_list)) {
otvb = container_of(otcl, struct vipx_bundle, clist);
if (otvb->state == VIPX_BUNDLE_STATE_PROCESS) {
otvb->flags |= flags;
__vipx_queue_done(outq, otvb);
} else {
vipx_err("out-bundle state(%d) is not process\n",
otvb->state);
}
} else {
vipx_err("out-queue is empty\n");
}
if (!list_empty(&inq->process_list)) {
invb = container_of(incl, struct vipx_bundle, clist);
if (invb->state == VIPX_BUNDLE_STATE_PROCESS) {
invb->flags |= flags;
__vipx_queue_done(inq, invb);
} else {
vipx_err("in-bundle state(%u) is not process\n",
invb->state);
}
} else {
vipx_err("in-queue is empty\n");
}
vipx_leave();
}
static void __vipx_queue_init(struct vipx_context *vctx,
struct vipx_queue *queue, unsigned int direction)
{
vipx_enter();
queue->direction = direction;
queue->streaming = 0;
queue->lock = &vctx->lock;
INIT_LIST_HEAD(&queue->queued_list);
atomic_set(&queue->queued_count, 0);
INIT_LIST_HEAD(&queue->process_list);
atomic_set(&queue->process_count, 0);
INIT_LIST_HEAD(&queue->done_list);
atomic_set(&queue->done_count, 0);
spin_lock_init(&queue->done_lock);
init_waitqueue_head(&queue->done_wq);
queue->flist.count = 0;
queue->flist.formats = NULL;
queue->num_buffers = 0;
queue->qlist = &vctx->queue_list;
vipx_leave();
}
int vipx_queue_init(struct vipx_context *vctx)
{
struct vipx_queue_list *qlist;
struct vipx_queue *inq, *outq;
vipx_enter();
vctx->queue_ops = &vipx_context_qops;
qlist = &vctx->queue_list;
qlist->vctx = vctx;
qlist->gops = &vipx_queue_gops;
inq = &qlist->in_queue;
outq = &qlist->out_queue;
__vipx_queue_init(vctx, inq, VS4L_DIRECTION_IN);
__vipx_queue_init(vctx, outq, VS4L_DIRECTION_OT);
vipx_leave();
return 0;
}