![Marek Szyprowski](/assets/img/avatar_default.png)
MFC v6 and v7 has no register to read min scratch buffer size, so it has to be read conditionally only if hardware supports it. This fixes following NULL pointer exception on SoCs with MFC v6/v7: 8<--- cut here --- Unable to handle kernel NULL pointer dereference at virtual address 00000000 pgd = f25837f9 [00000000] *pgd=bd93d835 Internal error: Oops: 17 [#1] PREEMPT SMP ARM Modules linked in: btmrvl_sdio btmrvl bluetooth mwifiex_sdio mwifiex ecdh_generic ecc Hardware name: SAMSUNG EXYNOS (Flattened Device Tree) PC is at s5p_mfc_get_min_scratch_buf_size+0x30/0x3c LR is at s5p_mfc_get_min_scratch_buf_size+0x28/0x3c ... [<c074f998>] (s5p_mfc_get_min_scratch_buf_size) from [<c0745bc0>] (s5p_mfc_irq+0x814/0xa5c) [<c0745bc0>] (s5p_mfc_irq) from [<c019a218>] (__handle_irq_event_percpu+0x64/0x3f8) [<c019a218>] (__handle_irq_event_percpu) from [<c019a5d8>] (handle_irq_event_percpu+0x2c/0x7c) [<c019a5d8>] (handle_irq_event_percpu) from [<c019a660>] (handle_irq_event+0x38/0x5c) [<c019a660>] (handle_irq_event) from [<c019ebc4>] (handle_fasteoi_irq+0xc4/0x180) [<c019ebc4>] (handle_fasteoi_irq) from [<c0199270>] (generic_handle_irq+0x24/0x34) [<c0199270>] (generic_handle_irq) from [<c0199888>] (__handle_domain_irq+0x7c/0xec) [<c0199888>] (__handle_domain_irq) from [<c04ac298>] (gic_handle_irq+0x58/0x9c) [<c04ac298>] (gic_handle_irq) from [<c0101ab0>] (__irq_svc+0x70/0xb0) Exception stack(0xe73ddc60 to 0xe73ddca8) ... [<c0101ab0>] (__irq_svc) from [<c01967d8>] (console_unlock+0x5a8/0x6a8) [<c01967d8>] (console_unlock) from [<c01981d0>] (vprintk_emit+0x118/0x2d8) [<c01981d0>] (vprintk_emit) from [<c01983b0>] (vprintk_default+0x20/0x28) [<c01983b0>] (vprintk_default) from [<c01989b4>] (printk+0x30/0x54) [<c01989b4>] (printk) from [<c07500b8>] (s5p_mfc_init_decode_v6+0x1d4/0x284) [<c07500b8>] (s5p_mfc_init_decode_v6) from [<c07230d0>] (vb2_start_streaming+0x24/0x150) [<c07230d0>] (vb2_start_streaming) from [<c0724e4c>] (vb2_core_streamon+0x11c/0x15c) [<c0724e4c>] (vb2_core_streamon) from [<c07478b8>] (vidioc_streamon+0x64/0xa0) [<c07478b8>] (vidioc_streamon) from [<c0709640>] (__video_do_ioctl+0x28c/0x45c) [<c0709640>] (__video_do_ioctl) from [<c0709bc8>] (video_usercopy+0x260/0x8a4) [<c0709bc8>] (video_usercopy) from [<c02b3820>] (do_vfs_ioctl+0xb0/0x9fc) [<c02b3820>] (do_vfs_ioctl) from [<c02b41a0>] (ksys_ioctl+0x34/0x58) [<c02b41a0>] (ksys_ioctl) from [<c0101000>] (ret_fast_syscall+0x0/0x28) Exception stack(0xe73ddfa8 to 0xe73ddff0) ... ---[ end trace 376cf5ba6e0bee93 ]--- Signed-off-by: Marek Szyprowski <m.szyprowski@samsung.com> Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl> Signed-off-by: Mauro Carvalho Chehab <mchehab+samsung@kernel.org>
1680 lines
46 KiB
C
1680 lines
46 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Samsung S5P Multi Format Codec v 5.1
|
|
*
|
|
* Copyright (c) 2011 Samsung Electronics Co., Ltd.
|
|
* Kamil Debski, <k.debski@samsung.com>
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/io.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/videodev2.h>
|
|
#include <media/v4l2-event.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/of_reserved_mem.h>
|
|
#include <media/videobuf2-v4l2.h>
|
|
#include "s5p_mfc_common.h"
|
|
#include "s5p_mfc_ctrl.h"
|
|
#include "s5p_mfc_debug.h"
|
|
#include "s5p_mfc_dec.h"
|
|
#include "s5p_mfc_enc.h"
|
|
#include "s5p_mfc_intr.h"
|
|
#include "s5p_mfc_iommu.h"
|
|
#include "s5p_mfc_opr.h"
|
|
#include "s5p_mfc_cmd.h"
|
|
#include "s5p_mfc_pm.h"
|
|
|
|
#define S5P_MFC_DEC_NAME "s5p-mfc-dec"
|
|
#define S5P_MFC_ENC_NAME "s5p-mfc-enc"
|
|
|
|
int mfc_debug_level;
|
|
module_param_named(debug, mfc_debug_level, int, S_IRUGO | S_IWUSR);
|
|
MODULE_PARM_DESC(debug, "Debug level - higher value produces more verbose messages");
|
|
|
|
static char *mfc_mem_size;
|
|
module_param_named(mem, mfc_mem_size, charp, 0644);
|
|
MODULE_PARM_DESC(mem, "Preallocated memory size for the firmware and context buffers");
|
|
|
|
/* Helper functions for interrupt processing */
|
|
|
|
/* Remove from hw execution round robin */
|
|
void clear_work_bit(struct s5p_mfc_ctx *ctx)
|
|
{
|
|
struct s5p_mfc_dev *dev = ctx->dev;
|
|
|
|
spin_lock(&dev->condlock);
|
|
__clear_bit(ctx->num, &dev->ctx_work_bits);
|
|
spin_unlock(&dev->condlock);
|
|
}
|
|
|
|
/* Add to hw execution round robin */
|
|
void set_work_bit(struct s5p_mfc_ctx *ctx)
|
|
{
|
|
struct s5p_mfc_dev *dev = ctx->dev;
|
|
|
|
spin_lock(&dev->condlock);
|
|
__set_bit(ctx->num, &dev->ctx_work_bits);
|
|
spin_unlock(&dev->condlock);
|
|
}
|
|
|
|
/* Remove from hw execution round robin */
|
|
void clear_work_bit_irqsave(struct s5p_mfc_ctx *ctx)
|
|
{
|
|
struct s5p_mfc_dev *dev = ctx->dev;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&dev->condlock, flags);
|
|
__clear_bit(ctx->num, &dev->ctx_work_bits);
|
|
spin_unlock_irqrestore(&dev->condlock, flags);
|
|
}
|
|
|
|
/* Add to hw execution round robin */
|
|
void set_work_bit_irqsave(struct s5p_mfc_ctx *ctx)
|
|
{
|
|
struct s5p_mfc_dev *dev = ctx->dev;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&dev->condlock, flags);
|
|
__set_bit(ctx->num, &dev->ctx_work_bits);
|
|
spin_unlock_irqrestore(&dev->condlock, flags);
|
|
}
|
|
|
|
int s5p_mfc_get_new_ctx(struct s5p_mfc_dev *dev)
|
|
{
|
|
unsigned long flags;
|
|
int ctx;
|
|
|
|
spin_lock_irqsave(&dev->condlock, flags);
|
|
ctx = dev->curr_ctx;
|
|
do {
|
|
ctx = (ctx + 1) % MFC_NUM_CONTEXTS;
|
|
if (ctx == dev->curr_ctx) {
|
|
if (!test_bit(ctx, &dev->ctx_work_bits))
|
|
ctx = -EAGAIN;
|
|
break;
|
|
}
|
|
} while (!test_bit(ctx, &dev->ctx_work_bits));
|
|
spin_unlock_irqrestore(&dev->condlock, flags);
|
|
|
|
return ctx;
|
|
}
|
|
|
|
/* Wake up context wait_queue */
|
|
static void wake_up_ctx(struct s5p_mfc_ctx *ctx, unsigned int reason,
|
|
unsigned int err)
|
|
{
|
|
ctx->int_cond = 1;
|
|
ctx->int_type = reason;
|
|
ctx->int_err = err;
|
|
wake_up(&ctx->queue);
|
|
}
|
|
|
|
/* Wake up device wait_queue */
|
|
static void wake_up_dev(struct s5p_mfc_dev *dev, unsigned int reason,
|
|
unsigned int err)
|
|
{
|
|
dev->int_cond = 1;
|
|
dev->int_type = reason;
|
|
dev->int_err = err;
|
|
wake_up(&dev->queue);
|
|
}
|
|
|
|
void s5p_mfc_cleanup_queue(struct list_head *lh, struct vb2_queue *vq)
|
|
{
|
|
struct s5p_mfc_buf *b;
|
|
int i;
|
|
|
|
while (!list_empty(lh)) {
|
|
b = list_entry(lh->next, struct s5p_mfc_buf, list);
|
|
for (i = 0; i < b->b->vb2_buf.num_planes; i++)
|
|
vb2_set_plane_payload(&b->b->vb2_buf, i, 0);
|
|
vb2_buffer_done(&b->b->vb2_buf, VB2_BUF_STATE_ERROR);
|
|
list_del(&b->list);
|
|
}
|
|
}
|
|
|
|
static void s5p_mfc_watchdog(struct timer_list *t)
|
|
{
|
|
struct s5p_mfc_dev *dev = from_timer(dev, t, watchdog_timer);
|
|
|
|
if (test_bit(0, &dev->hw_lock))
|
|
atomic_inc(&dev->watchdog_cnt);
|
|
if (atomic_read(&dev->watchdog_cnt) >= MFC_WATCHDOG_CNT) {
|
|
/* This means that hw is busy and no interrupts were
|
|
* generated by hw for the Nth time of running this
|
|
* watchdog timer. This usually means a serious hw
|
|
* error. Now it is time to kill all instances and
|
|
* reset the MFC. */
|
|
mfc_err("Time out during waiting for HW\n");
|
|
schedule_work(&dev->watchdog_work);
|
|
}
|
|
dev->watchdog_timer.expires = jiffies +
|
|
msecs_to_jiffies(MFC_WATCHDOG_INTERVAL);
|
|
add_timer(&dev->watchdog_timer);
|
|
}
|
|
|
|
static void s5p_mfc_watchdog_worker(struct work_struct *work)
|
|
{
|
|
struct s5p_mfc_dev *dev;
|
|
struct s5p_mfc_ctx *ctx;
|
|
unsigned long flags;
|
|
int mutex_locked;
|
|
int i, ret;
|
|
|
|
dev = container_of(work, struct s5p_mfc_dev, watchdog_work);
|
|
|
|
mfc_err("Driver timeout error handling\n");
|
|
/* Lock the mutex that protects open and release.
|
|
* This is necessary as they may load and unload firmware. */
|
|
mutex_locked = mutex_trylock(&dev->mfc_mutex);
|
|
if (!mutex_locked)
|
|
mfc_err("Error: some instance may be closing/opening\n");
|
|
spin_lock_irqsave(&dev->irqlock, flags);
|
|
|
|
s5p_mfc_clock_off();
|
|
|
|
for (i = 0; i < MFC_NUM_CONTEXTS; i++) {
|
|
ctx = dev->ctx[i];
|
|
if (!ctx)
|
|
continue;
|
|
ctx->state = MFCINST_ERROR;
|
|
s5p_mfc_cleanup_queue(&ctx->dst_queue, &ctx->vq_dst);
|
|
s5p_mfc_cleanup_queue(&ctx->src_queue, &ctx->vq_src);
|
|
clear_work_bit(ctx);
|
|
wake_up_ctx(ctx, S5P_MFC_R2H_CMD_ERR_RET, 0);
|
|
}
|
|
clear_bit(0, &dev->hw_lock);
|
|
spin_unlock_irqrestore(&dev->irqlock, flags);
|
|
|
|
/* De-init MFC */
|
|
s5p_mfc_deinit_hw(dev);
|
|
|
|
/* Double check if there is at least one instance running.
|
|
* If no instance is in memory than no firmware should be present */
|
|
if (dev->num_inst > 0) {
|
|
ret = s5p_mfc_load_firmware(dev);
|
|
if (ret) {
|
|
mfc_err("Failed to reload FW\n");
|
|
goto unlock;
|
|
}
|
|
s5p_mfc_clock_on();
|
|
ret = s5p_mfc_init_hw(dev);
|
|
s5p_mfc_clock_off();
|
|
if (ret)
|
|
mfc_err("Failed to reinit FW\n");
|
|
}
|
|
unlock:
|
|
if (mutex_locked)
|
|
mutex_unlock(&dev->mfc_mutex);
|
|
}
|
|
|
|
static void s5p_mfc_handle_frame_all_extracted(struct s5p_mfc_ctx *ctx)
|
|
{
|
|
struct s5p_mfc_buf *dst_buf;
|
|
struct s5p_mfc_dev *dev = ctx->dev;
|
|
|
|
ctx->state = MFCINST_FINISHED;
|
|
ctx->sequence++;
|
|
while (!list_empty(&ctx->dst_queue)) {
|
|
dst_buf = list_entry(ctx->dst_queue.next,
|
|
struct s5p_mfc_buf, list);
|
|
mfc_debug(2, "Cleaning up buffer: %d\n",
|
|
dst_buf->b->vb2_buf.index);
|
|
vb2_set_plane_payload(&dst_buf->b->vb2_buf, 0, 0);
|
|
vb2_set_plane_payload(&dst_buf->b->vb2_buf, 1, 0);
|
|
list_del(&dst_buf->list);
|
|
dst_buf->flags |= MFC_BUF_FLAG_EOS;
|
|
ctx->dst_queue_cnt--;
|
|
dst_buf->b->sequence = (ctx->sequence++);
|
|
|
|
if (s5p_mfc_hw_call(dev->mfc_ops, get_pic_type_top, ctx) ==
|
|
s5p_mfc_hw_call(dev->mfc_ops, get_pic_type_bot, ctx))
|
|
dst_buf->b->field = V4L2_FIELD_NONE;
|
|
else
|
|
dst_buf->b->field = V4L2_FIELD_INTERLACED;
|
|
dst_buf->b->flags |= V4L2_BUF_FLAG_LAST;
|
|
|
|
ctx->dec_dst_flag &= ~(1 << dst_buf->b->vb2_buf.index);
|
|
vb2_buffer_done(&dst_buf->b->vb2_buf, VB2_BUF_STATE_DONE);
|
|
}
|
|
}
|
|
|
|
static void s5p_mfc_handle_frame_copy_time(struct s5p_mfc_ctx *ctx)
|
|
{
|
|
struct s5p_mfc_dev *dev = ctx->dev;
|
|
struct s5p_mfc_buf *dst_buf, *src_buf;
|
|
u32 dec_y_addr;
|
|
unsigned int frame_type;
|
|
|
|
/* Make sure we actually have a new frame before continuing. */
|
|
frame_type = s5p_mfc_hw_call(dev->mfc_ops, get_dec_frame_type, dev);
|
|
if (frame_type == S5P_FIMV_DECODE_FRAME_SKIPPED)
|
|
return;
|
|
dec_y_addr = (u32)s5p_mfc_hw_call(dev->mfc_ops, get_dec_y_adr, dev);
|
|
|
|
/* Copy timestamp / timecode from decoded src to dst and set
|
|
appropriate flags. */
|
|
src_buf = list_entry(ctx->src_queue.next, struct s5p_mfc_buf, list);
|
|
list_for_each_entry(dst_buf, &ctx->dst_queue, list) {
|
|
u32 addr = (u32)vb2_dma_contig_plane_dma_addr(&dst_buf->b->vb2_buf, 0);
|
|
|
|
if (addr == dec_y_addr) {
|
|
dst_buf->b->timecode = src_buf->b->timecode;
|
|
dst_buf->b->vb2_buf.timestamp =
|
|
src_buf->b->vb2_buf.timestamp;
|
|
dst_buf->b->flags &=
|
|
~V4L2_BUF_FLAG_TSTAMP_SRC_MASK;
|
|
dst_buf->b->flags |=
|
|
src_buf->b->flags
|
|
& V4L2_BUF_FLAG_TSTAMP_SRC_MASK;
|
|
switch (frame_type) {
|
|
case S5P_FIMV_DECODE_FRAME_I_FRAME:
|
|
dst_buf->b->flags |=
|
|
V4L2_BUF_FLAG_KEYFRAME;
|
|
break;
|
|
case S5P_FIMV_DECODE_FRAME_P_FRAME:
|
|
dst_buf->b->flags |=
|
|
V4L2_BUF_FLAG_PFRAME;
|
|
break;
|
|
case S5P_FIMV_DECODE_FRAME_B_FRAME:
|
|
dst_buf->b->flags |=
|
|
V4L2_BUF_FLAG_BFRAME;
|
|
break;
|
|
default:
|
|
/* Don't know how to handle
|
|
S5P_FIMV_DECODE_FRAME_OTHER_FRAME. */
|
|
mfc_debug(2, "Unexpected frame type: %d\n",
|
|
frame_type);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void s5p_mfc_handle_frame_new(struct s5p_mfc_ctx *ctx, unsigned int err)
|
|
{
|
|
struct s5p_mfc_dev *dev = ctx->dev;
|
|
struct s5p_mfc_buf *dst_buf;
|
|
u32 dspl_y_addr;
|
|
unsigned int frame_type;
|
|
|
|
dspl_y_addr = (u32)s5p_mfc_hw_call(dev->mfc_ops, get_dspl_y_adr, dev);
|
|
if (IS_MFCV6_PLUS(dev))
|
|
frame_type = s5p_mfc_hw_call(dev->mfc_ops,
|
|
get_disp_frame_type, ctx);
|
|
else
|
|
frame_type = s5p_mfc_hw_call(dev->mfc_ops,
|
|
get_dec_frame_type, dev);
|
|
|
|
/* If frame is same as previous then skip and do not dequeue */
|
|
if (frame_type == S5P_FIMV_DECODE_FRAME_SKIPPED) {
|
|
if (!ctx->after_packed_pb)
|
|
ctx->sequence++;
|
|
ctx->after_packed_pb = 0;
|
|
return;
|
|
}
|
|
ctx->sequence++;
|
|
/* The MFC returns address of the buffer, now we have to
|
|
* check which videobuf does it correspond to */
|
|
list_for_each_entry(dst_buf, &ctx->dst_queue, list) {
|
|
u32 addr = (u32)vb2_dma_contig_plane_dma_addr(&dst_buf->b->vb2_buf, 0);
|
|
|
|
/* Check if this is the buffer we're looking for */
|
|
if (addr == dspl_y_addr) {
|
|
list_del(&dst_buf->list);
|
|
ctx->dst_queue_cnt--;
|
|
dst_buf->b->sequence = ctx->sequence;
|
|
if (s5p_mfc_hw_call(dev->mfc_ops,
|
|
get_pic_type_top, ctx) ==
|
|
s5p_mfc_hw_call(dev->mfc_ops,
|
|
get_pic_type_bot, ctx))
|
|
dst_buf->b->field = V4L2_FIELD_NONE;
|
|
else
|
|
dst_buf->b->field =
|
|
V4L2_FIELD_INTERLACED;
|
|
vb2_set_plane_payload(&dst_buf->b->vb2_buf, 0,
|
|
ctx->luma_size);
|
|
vb2_set_plane_payload(&dst_buf->b->vb2_buf, 1,
|
|
ctx->chroma_size);
|
|
clear_bit(dst_buf->b->vb2_buf.index,
|
|
&ctx->dec_dst_flag);
|
|
|
|
vb2_buffer_done(&dst_buf->b->vb2_buf, err ?
|
|
VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Handle frame decoding interrupt */
|
|
static void s5p_mfc_handle_frame(struct s5p_mfc_ctx *ctx,
|
|
unsigned int reason, unsigned int err)
|
|
{
|
|
struct s5p_mfc_dev *dev = ctx->dev;
|
|
unsigned int dst_frame_status;
|
|
unsigned int dec_frame_status;
|
|
struct s5p_mfc_buf *src_buf;
|
|
unsigned int res_change;
|
|
|
|
dst_frame_status = s5p_mfc_hw_call(dev->mfc_ops, get_dspl_status, dev)
|
|
& S5P_FIMV_DEC_STATUS_DECODING_STATUS_MASK;
|
|
dec_frame_status = s5p_mfc_hw_call(dev->mfc_ops, get_dec_status, dev)
|
|
& S5P_FIMV_DEC_STATUS_DECODING_STATUS_MASK;
|
|
res_change = (s5p_mfc_hw_call(dev->mfc_ops, get_dspl_status, dev)
|
|
& S5P_FIMV_DEC_STATUS_RESOLUTION_MASK)
|
|
>> S5P_FIMV_DEC_STATUS_RESOLUTION_SHIFT;
|
|
mfc_debug(2, "Frame Status: %x\n", dst_frame_status);
|
|
if (ctx->state == MFCINST_RES_CHANGE_INIT)
|
|
ctx->state = MFCINST_RES_CHANGE_FLUSH;
|
|
if (res_change == S5P_FIMV_RES_INCREASE ||
|
|
res_change == S5P_FIMV_RES_DECREASE) {
|
|
ctx->state = MFCINST_RES_CHANGE_INIT;
|
|
s5p_mfc_hw_call(dev->mfc_ops, clear_int_flags, dev);
|
|
wake_up_ctx(ctx, reason, err);
|
|
WARN_ON(test_and_clear_bit(0, &dev->hw_lock) == 0);
|
|
s5p_mfc_clock_off();
|
|
s5p_mfc_hw_call(dev->mfc_ops, try_run, dev);
|
|
return;
|
|
}
|
|
if (ctx->dpb_flush_flag)
|
|
ctx->dpb_flush_flag = 0;
|
|
|
|
/* All frames remaining in the buffer have been extracted */
|
|
if (dst_frame_status == S5P_FIMV_DEC_STATUS_DECODING_EMPTY) {
|
|
if (ctx->state == MFCINST_RES_CHANGE_FLUSH) {
|
|
static const struct v4l2_event ev_src_ch = {
|
|
.type = V4L2_EVENT_SOURCE_CHANGE,
|
|
.u.src_change.changes =
|
|
V4L2_EVENT_SRC_CH_RESOLUTION,
|
|
};
|
|
|
|
s5p_mfc_handle_frame_all_extracted(ctx);
|
|
ctx->state = MFCINST_RES_CHANGE_END;
|
|
v4l2_event_queue_fh(&ctx->fh, &ev_src_ch);
|
|
|
|
goto leave_handle_frame;
|
|
} else {
|
|
s5p_mfc_handle_frame_all_extracted(ctx);
|
|
}
|
|
}
|
|
|
|
if (dec_frame_status == S5P_FIMV_DEC_STATUS_DECODING_DISPLAY)
|
|
s5p_mfc_handle_frame_copy_time(ctx);
|
|
|
|
/* A frame has been decoded and is in the buffer */
|
|
if (dst_frame_status == S5P_FIMV_DEC_STATUS_DISPLAY_ONLY ||
|
|
dst_frame_status == S5P_FIMV_DEC_STATUS_DECODING_DISPLAY) {
|
|
s5p_mfc_handle_frame_new(ctx, err);
|
|
} else {
|
|
mfc_debug(2, "No frame decode\n");
|
|
}
|
|
/* Mark source buffer as complete */
|
|
if (dst_frame_status != S5P_FIMV_DEC_STATUS_DISPLAY_ONLY
|
|
&& !list_empty(&ctx->src_queue)) {
|
|
src_buf = list_entry(ctx->src_queue.next, struct s5p_mfc_buf,
|
|
list);
|
|
ctx->consumed_stream += s5p_mfc_hw_call(dev->mfc_ops,
|
|
get_consumed_stream, dev);
|
|
if (ctx->codec_mode != S5P_MFC_CODEC_H264_DEC &&
|
|
ctx->codec_mode != S5P_MFC_CODEC_VP8_DEC &&
|
|
ctx->consumed_stream + STUFF_BYTE <
|
|
src_buf->b->vb2_buf.planes[0].bytesused) {
|
|
/* Run MFC again on the same buffer */
|
|
mfc_debug(2, "Running again the same buffer\n");
|
|
ctx->after_packed_pb = 1;
|
|
} else {
|
|
mfc_debug(2, "MFC needs next buffer\n");
|
|
ctx->consumed_stream = 0;
|
|
if (src_buf->flags & MFC_BUF_FLAG_EOS)
|
|
ctx->state = MFCINST_FINISHING;
|
|
list_del(&src_buf->list);
|
|
ctx->src_queue_cnt--;
|
|
if (s5p_mfc_hw_call(dev->mfc_ops, err_dec, err) > 0)
|
|
vb2_buffer_done(&src_buf->b->vb2_buf,
|
|
VB2_BUF_STATE_ERROR);
|
|
else
|
|
vb2_buffer_done(&src_buf->b->vb2_buf,
|
|
VB2_BUF_STATE_DONE);
|
|
}
|
|
}
|
|
leave_handle_frame:
|
|
if ((ctx->src_queue_cnt == 0 && ctx->state != MFCINST_FINISHING)
|
|
|| ctx->dst_queue_cnt < ctx->pb_count)
|
|
clear_work_bit(ctx);
|
|
s5p_mfc_hw_call(dev->mfc_ops, clear_int_flags, dev);
|
|
wake_up_ctx(ctx, reason, err);
|
|
WARN_ON(test_and_clear_bit(0, &dev->hw_lock) == 0);
|
|
s5p_mfc_clock_off();
|
|
/* if suspending, wake up device and do not try_run again*/
|
|
if (test_bit(0, &dev->enter_suspend))
|
|
wake_up_dev(dev, reason, err);
|
|
else
|
|
s5p_mfc_hw_call(dev->mfc_ops, try_run, dev);
|
|
}
|
|
|
|
/* Error handling for interrupt */
|
|
static void s5p_mfc_handle_error(struct s5p_mfc_dev *dev,
|
|
struct s5p_mfc_ctx *ctx, unsigned int reason, unsigned int err)
|
|
{
|
|
mfc_err("Interrupt Error: %08x\n", err);
|
|
|
|
if (ctx) {
|
|
/* Error recovery is dependent on the state of context */
|
|
switch (ctx->state) {
|
|
case MFCINST_RES_CHANGE_INIT:
|
|
case MFCINST_RES_CHANGE_FLUSH:
|
|
case MFCINST_RES_CHANGE_END:
|
|
case MFCINST_FINISHING:
|
|
case MFCINST_FINISHED:
|
|
case MFCINST_RUNNING:
|
|
/* It is highly probable that an error occurred
|
|
* while decoding a frame */
|
|
clear_work_bit(ctx);
|
|
ctx->state = MFCINST_ERROR;
|
|
/* Mark all dst buffers as having an error */
|
|
s5p_mfc_cleanup_queue(&ctx->dst_queue, &ctx->vq_dst);
|
|
/* Mark all src buffers as having an error */
|
|
s5p_mfc_cleanup_queue(&ctx->src_queue, &ctx->vq_src);
|
|
wake_up_ctx(ctx, reason, err);
|
|
break;
|
|
default:
|
|
clear_work_bit(ctx);
|
|
ctx->state = MFCINST_ERROR;
|
|
wake_up_ctx(ctx, reason, err);
|
|
break;
|
|
}
|
|
}
|
|
WARN_ON(test_and_clear_bit(0, &dev->hw_lock) == 0);
|
|
s5p_mfc_hw_call(dev->mfc_ops, clear_int_flags, dev);
|
|
s5p_mfc_clock_off();
|
|
wake_up_dev(dev, reason, err);
|
|
}
|
|
|
|
/* Header parsing interrupt handling */
|
|
static void s5p_mfc_handle_seq_done(struct s5p_mfc_ctx *ctx,
|
|
unsigned int reason, unsigned int err)
|
|
{
|
|
struct s5p_mfc_dev *dev;
|
|
|
|
if (!ctx)
|
|
return;
|
|
dev = ctx->dev;
|
|
if (ctx->c_ops->post_seq_start) {
|
|
if (ctx->c_ops->post_seq_start(ctx))
|
|
mfc_err("post_seq_start() failed\n");
|
|
} else {
|
|
ctx->img_width = s5p_mfc_hw_call(dev->mfc_ops, get_img_width,
|
|
dev);
|
|
ctx->img_height = s5p_mfc_hw_call(dev->mfc_ops, get_img_height,
|
|
dev);
|
|
|
|
s5p_mfc_hw_call(dev->mfc_ops, dec_calc_dpb_size, ctx);
|
|
|
|
ctx->pb_count = s5p_mfc_hw_call(dev->mfc_ops, get_dpb_count,
|
|
dev);
|
|
ctx->mv_count = s5p_mfc_hw_call(dev->mfc_ops, get_mv_count,
|
|
dev);
|
|
if (FW_HAS_E_MIN_SCRATCH_BUF(dev))
|
|
ctx->scratch_buf_size = s5p_mfc_hw_call(dev->mfc_ops,
|
|
get_min_scratch_buf_size, dev);
|
|
if (ctx->img_width == 0 || ctx->img_height == 0)
|
|
ctx->state = MFCINST_ERROR;
|
|
else
|
|
ctx->state = MFCINST_HEAD_PARSED;
|
|
|
|
if ((ctx->codec_mode == S5P_MFC_CODEC_H264_DEC ||
|
|
ctx->codec_mode == S5P_MFC_CODEC_H264_MVC_DEC) &&
|
|
!list_empty(&ctx->src_queue)) {
|
|
struct s5p_mfc_buf *src_buf;
|
|
src_buf = list_entry(ctx->src_queue.next,
|
|
struct s5p_mfc_buf, list);
|
|
if (s5p_mfc_hw_call(dev->mfc_ops, get_consumed_stream,
|
|
dev) <
|
|
src_buf->b->vb2_buf.planes[0].bytesused)
|
|
ctx->head_processed = 0;
|
|
else
|
|
ctx->head_processed = 1;
|
|
} else {
|
|
ctx->head_processed = 1;
|
|
}
|
|
}
|
|
s5p_mfc_hw_call(dev->mfc_ops, clear_int_flags, dev);
|
|
clear_work_bit(ctx);
|
|
WARN_ON(test_and_clear_bit(0, &dev->hw_lock) == 0);
|
|
s5p_mfc_clock_off();
|
|
s5p_mfc_hw_call(dev->mfc_ops, try_run, dev);
|
|
wake_up_ctx(ctx, reason, err);
|
|
}
|
|
|
|
/* Header parsing interrupt handling */
|
|
static void s5p_mfc_handle_init_buffers(struct s5p_mfc_ctx *ctx,
|
|
unsigned int reason, unsigned int err)
|
|
{
|
|
struct s5p_mfc_buf *src_buf;
|
|
struct s5p_mfc_dev *dev;
|
|
|
|
if (!ctx)
|
|
return;
|
|
dev = ctx->dev;
|
|
s5p_mfc_hw_call(dev->mfc_ops, clear_int_flags, dev);
|
|
ctx->int_type = reason;
|
|
ctx->int_err = err;
|
|
ctx->int_cond = 1;
|
|
clear_work_bit(ctx);
|
|
if (err == 0) {
|
|
ctx->state = MFCINST_RUNNING;
|
|
if (!ctx->dpb_flush_flag && ctx->head_processed) {
|
|
if (!list_empty(&ctx->src_queue)) {
|
|
src_buf = list_entry(ctx->src_queue.next,
|
|
struct s5p_mfc_buf, list);
|
|
list_del(&src_buf->list);
|
|
ctx->src_queue_cnt--;
|
|
vb2_buffer_done(&src_buf->b->vb2_buf,
|
|
VB2_BUF_STATE_DONE);
|
|
}
|
|
} else {
|
|
ctx->dpb_flush_flag = 0;
|
|
}
|
|
WARN_ON(test_and_clear_bit(0, &dev->hw_lock) == 0);
|
|
|
|
s5p_mfc_clock_off();
|
|
|
|
wake_up(&ctx->queue);
|
|
s5p_mfc_hw_call(dev->mfc_ops, try_run, dev);
|
|
} else {
|
|
WARN_ON(test_and_clear_bit(0, &dev->hw_lock) == 0);
|
|
|
|
s5p_mfc_clock_off();
|
|
|
|
wake_up(&ctx->queue);
|
|
}
|
|
}
|
|
|
|
static void s5p_mfc_handle_stream_complete(struct s5p_mfc_ctx *ctx)
|
|
{
|
|
struct s5p_mfc_dev *dev = ctx->dev;
|
|
struct s5p_mfc_buf *mb_entry;
|
|
|
|
mfc_debug(2, "Stream completed\n");
|
|
|
|
ctx->state = MFCINST_FINISHED;
|
|
|
|
if (!list_empty(&ctx->dst_queue)) {
|
|
mb_entry = list_entry(ctx->dst_queue.next, struct s5p_mfc_buf,
|
|
list);
|
|
list_del(&mb_entry->list);
|
|
ctx->dst_queue_cnt--;
|
|
vb2_set_plane_payload(&mb_entry->b->vb2_buf, 0, 0);
|
|
vb2_buffer_done(&mb_entry->b->vb2_buf, VB2_BUF_STATE_DONE);
|
|
}
|
|
|
|
clear_work_bit(ctx);
|
|
|
|
WARN_ON(test_and_clear_bit(0, &dev->hw_lock) == 0);
|
|
|
|
s5p_mfc_clock_off();
|
|
wake_up(&ctx->queue);
|
|
s5p_mfc_hw_call(dev->mfc_ops, try_run, dev);
|
|
}
|
|
|
|
/* Interrupt processing */
|
|
static irqreturn_t s5p_mfc_irq(int irq, void *priv)
|
|
{
|
|
struct s5p_mfc_dev *dev = priv;
|
|
struct s5p_mfc_ctx *ctx;
|
|
unsigned int reason;
|
|
unsigned int err;
|
|
|
|
mfc_debug_enter();
|
|
/* Reset the timeout watchdog */
|
|
atomic_set(&dev->watchdog_cnt, 0);
|
|
spin_lock(&dev->irqlock);
|
|
ctx = dev->ctx[dev->curr_ctx];
|
|
/* Get the reason of interrupt and the error code */
|
|
reason = s5p_mfc_hw_call(dev->mfc_ops, get_int_reason, dev);
|
|
err = s5p_mfc_hw_call(dev->mfc_ops, get_int_err, dev);
|
|
mfc_debug(1, "Int reason: %d (err: %08x)\n", reason, err);
|
|
switch (reason) {
|
|
case S5P_MFC_R2H_CMD_ERR_RET:
|
|
/* An error has occurred */
|
|
if (ctx->state == MFCINST_RUNNING &&
|
|
(s5p_mfc_hw_call(dev->mfc_ops, err_dec, err) >=
|
|
dev->warn_start ||
|
|
err == S5P_FIMV_ERR_NO_VALID_SEQ_HDR ||
|
|
err == S5P_FIMV_ERR_INCOMPLETE_FRAME ||
|
|
err == S5P_FIMV_ERR_TIMEOUT))
|
|
s5p_mfc_handle_frame(ctx, reason, err);
|
|
else
|
|
s5p_mfc_handle_error(dev, ctx, reason, err);
|
|
clear_bit(0, &dev->enter_suspend);
|
|
break;
|
|
|
|
case S5P_MFC_R2H_CMD_SLICE_DONE_RET:
|
|
case S5P_MFC_R2H_CMD_FIELD_DONE_RET:
|
|
case S5P_MFC_R2H_CMD_FRAME_DONE_RET:
|
|
if (ctx->c_ops->post_frame_start) {
|
|
if (ctx->c_ops->post_frame_start(ctx))
|
|
mfc_err("post_frame_start() failed\n");
|
|
|
|
if (ctx->state == MFCINST_FINISHING &&
|
|
list_empty(&ctx->ref_queue)) {
|
|
s5p_mfc_hw_call(dev->mfc_ops, clear_int_flags, dev);
|
|
s5p_mfc_handle_stream_complete(ctx);
|
|
break;
|
|
}
|
|
s5p_mfc_hw_call(dev->mfc_ops, clear_int_flags, dev);
|
|
WARN_ON(test_and_clear_bit(0, &dev->hw_lock) == 0);
|
|
s5p_mfc_clock_off();
|
|
wake_up_ctx(ctx, reason, err);
|
|
s5p_mfc_hw_call(dev->mfc_ops, try_run, dev);
|
|
} else {
|
|
s5p_mfc_handle_frame(ctx, reason, err);
|
|
}
|
|
break;
|
|
|
|
case S5P_MFC_R2H_CMD_SEQ_DONE_RET:
|
|
s5p_mfc_handle_seq_done(ctx, reason, err);
|
|
break;
|
|
|
|
case S5P_MFC_R2H_CMD_OPEN_INSTANCE_RET:
|
|
ctx->inst_no = s5p_mfc_hw_call(dev->mfc_ops, get_inst_no, dev);
|
|
ctx->state = MFCINST_GOT_INST;
|
|
goto irq_cleanup_hw;
|
|
|
|
case S5P_MFC_R2H_CMD_CLOSE_INSTANCE_RET:
|
|
ctx->inst_no = MFC_NO_INSTANCE_SET;
|
|
ctx->state = MFCINST_FREE;
|
|
goto irq_cleanup_hw;
|
|
|
|
case S5P_MFC_R2H_CMD_SYS_INIT_RET:
|
|
case S5P_MFC_R2H_CMD_FW_STATUS_RET:
|
|
case S5P_MFC_R2H_CMD_SLEEP_RET:
|
|
case S5P_MFC_R2H_CMD_WAKEUP_RET:
|
|
if (ctx)
|
|
clear_work_bit(ctx);
|
|
s5p_mfc_hw_call(dev->mfc_ops, clear_int_flags, dev);
|
|
clear_bit(0, &dev->hw_lock);
|
|
clear_bit(0, &dev->enter_suspend);
|
|
wake_up_dev(dev, reason, err);
|
|
break;
|
|
|
|
case S5P_MFC_R2H_CMD_INIT_BUFFERS_RET:
|
|
s5p_mfc_handle_init_buffers(ctx, reason, err);
|
|
break;
|
|
|
|
case S5P_MFC_R2H_CMD_COMPLETE_SEQ_RET:
|
|
s5p_mfc_hw_call(dev->mfc_ops, clear_int_flags, dev);
|
|
ctx->int_type = reason;
|
|
ctx->int_err = err;
|
|
s5p_mfc_handle_stream_complete(ctx);
|
|
break;
|
|
|
|
case S5P_MFC_R2H_CMD_DPB_FLUSH_RET:
|
|
ctx->state = MFCINST_RUNNING;
|
|
goto irq_cleanup_hw;
|
|
|
|
default:
|
|
mfc_debug(2, "Unknown int reason\n");
|
|
s5p_mfc_hw_call(dev->mfc_ops, clear_int_flags, dev);
|
|
}
|
|
spin_unlock(&dev->irqlock);
|
|
mfc_debug_leave();
|
|
return IRQ_HANDLED;
|
|
irq_cleanup_hw:
|
|
s5p_mfc_hw_call(dev->mfc_ops, clear_int_flags, dev);
|
|
ctx->int_type = reason;
|
|
ctx->int_err = err;
|
|
ctx->int_cond = 1;
|
|
if (test_and_clear_bit(0, &dev->hw_lock) == 0)
|
|
mfc_err("Failed to unlock hw\n");
|
|
|
|
s5p_mfc_clock_off();
|
|
clear_work_bit(ctx);
|
|
wake_up(&ctx->queue);
|
|
|
|
s5p_mfc_hw_call(dev->mfc_ops, try_run, dev);
|
|
spin_unlock(&dev->irqlock);
|
|
mfc_debug(2, "Exit via irq_cleanup_hw\n");
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/* Open an MFC node */
|
|
static int s5p_mfc_open(struct file *file)
|
|
{
|
|
struct video_device *vdev = video_devdata(file);
|
|
struct s5p_mfc_dev *dev = video_drvdata(file);
|
|
struct s5p_mfc_ctx *ctx = NULL;
|
|
struct vb2_queue *q;
|
|
int ret = 0;
|
|
|
|
mfc_debug_enter();
|
|
if (mutex_lock_interruptible(&dev->mfc_mutex))
|
|
return -ERESTARTSYS;
|
|
dev->num_inst++; /* It is guarded by mfc_mutex in vfd */
|
|
/* Allocate memory for context */
|
|
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
|
|
if (!ctx) {
|
|
ret = -ENOMEM;
|
|
goto err_alloc;
|
|
}
|
|
init_waitqueue_head(&ctx->queue);
|
|
v4l2_fh_init(&ctx->fh, vdev);
|
|
file->private_data = &ctx->fh;
|
|
v4l2_fh_add(&ctx->fh);
|
|
ctx->dev = dev;
|
|
INIT_LIST_HEAD(&ctx->src_queue);
|
|
INIT_LIST_HEAD(&ctx->dst_queue);
|
|
ctx->src_queue_cnt = 0;
|
|
ctx->dst_queue_cnt = 0;
|
|
/* Get context number */
|
|
ctx->num = 0;
|
|
while (dev->ctx[ctx->num]) {
|
|
ctx->num++;
|
|
if (ctx->num >= MFC_NUM_CONTEXTS) {
|
|
mfc_debug(2, "Too many open contexts\n");
|
|
ret = -EBUSY;
|
|
goto err_no_ctx;
|
|
}
|
|
}
|
|
/* Mark context as idle */
|
|
clear_work_bit_irqsave(ctx);
|
|
dev->ctx[ctx->num] = ctx;
|
|
if (vdev == dev->vfd_dec) {
|
|
ctx->type = MFCINST_DECODER;
|
|
ctx->c_ops = get_dec_codec_ops();
|
|
s5p_mfc_dec_init(ctx);
|
|
/* Setup ctrl handler */
|
|
ret = s5p_mfc_dec_ctrls_setup(ctx);
|
|
if (ret) {
|
|
mfc_err("Failed to setup mfc controls\n");
|
|
goto err_ctrls_setup;
|
|
}
|
|
} else if (vdev == dev->vfd_enc) {
|
|
ctx->type = MFCINST_ENCODER;
|
|
ctx->c_ops = get_enc_codec_ops();
|
|
/* only for encoder */
|
|
INIT_LIST_HEAD(&ctx->ref_queue);
|
|
ctx->ref_queue_cnt = 0;
|
|
s5p_mfc_enc_init(ctx);
|
|
/* Setup ctrl handler */
|
|
ret = s5p_mfc_enc_ctrls_setup(ctx);
|
|
if (ret) {
|
|
mfc_err("Failed to setup mfc controls\n");
|
|
goto err_ctrls_setup;
|
|
}
|
|
} else {
|
|
ret = -ENOENT;
|
|
goto err_bad_node;
|
|
}
|
|
ctx->fh.ctrl_handler = &ctx->ctrl_handler;
|
|
ctx->inst_no = MFC_NO_INSTANCE_SET;
|
|
/* Load firmware if this is the first instance */
|
|
if (dev->num_inst == 1) {
|
|
dev->watchdog_timer.expires = jiffies +
|
|
msecs_to_jiffies(MFC_WATCHDOG_INTERVAL);
|
|
add_timer(&dev->watchdog_timer);
|
|
ret = s5p_mfc_power_on();
|
|
if (ret < 0) {
|
|
mfc_err("power on failed\n");
|
|
goto err_pwr_enable;
|
|
}
|
|
s5p_mfc_clock_on();
|
|
ret = s5p_mfc_load_firmware(dev);
|
|
if (ret) {
|
|
s5p_mfc_clock_off();
|
|
goto err_load_fw;
|
|
}
|
|
/* Init the FW */
|
|
ret = s5p_mfc_init_hw(dev);
|
|
s5p_mfc_clock_off();
|
|
if (ret)
|
|
goto err_init_hw;
|
|
}
|
|
/* Init videobuf2 queue for CAPTURE */
|
|
q = &ctx->vq_dst;
|
|
q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
|
|
q->drv_priv = &ctx->fh;
|
|
q->lock = &dev->mfc_mutex;
|
|
if (vdev == dev->vfd_dec) {
|
|
q->io_modes = VB2_MMAP;
|
|
q->ops = get_dec_queue_ops();
|
|
} else if (vdev == dev->vfd_enc) {
|
|
q->io_modes = VB2_MMAP | VB2_USERPTR;
|
|
q->ops = get_enc_queue_ops();
|
|
} else {
|
|
ret = -ENOENT;
|
|
goto err_queue_init;
|
|
}
|
|
/*
|
|
* We'll do mostly sequential access, so sacrifice TLB efficiency for
|
|
* faster allocation.
|
|
*/
|
|
q->dma_attrs = DMA_ATTR_ALLOC_SINGLE_PAGES;
|
|
q->mem_ops = &vb2_dma_contig_memops;
|
|
q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
|
|
ret = vb2_queue_init(q);
|
|
if (ret) {
|
|
mfc_err("Failed to initialize videobuf2 queue(capture)\n");
|
|
goto err_queue_init;
|
|
}
|
|
/* Init videobuf2 queue for OUTPUT */
|
|
q = &ctx->vq_src;
|
|
q->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
|
|
q->drv_priv = &ctx->fh;
|
|
q->lock = &dev->mfc_mutex;
|
|
if (vdev == dev->vfd_dec) {
|
|
q->io_modes = VB2_MMAP;
|
|
q->ops = get_dec_queue_ops();
|
|
} else if (vdev == dev->vfd_enc) {
|
|
q->io_modes = VB2_MMAP | VB2_USERPTR;
|
|
q->ops = get_enc_queue_ops();
|
|
} else {
|
|
ret = -ENOENT;
|
|
goto err_queue_init;
|
|
}
|
|
/* One way to indicate end-of-stream for MFC is to set the
|
|
* bytesused == 0. However by default videobuf2 handles bytesused
|
|
* equal to 0 as a special case and changes its value to the size
|
|
* of the buffer. Set the allow_zero_bytesused flag so that videobuf2
|
|
* will keep the value of bytesused intact.
|
|
*/
|
|
q->allow_zero_bytesused = 1;
|
|
|
|
/*
|
|
* We'll do mostly sequential access, so sacrifice TLB efficiency for
|
|
* faster allocation.
|
|
*/
|
|
q->dma_attrs = DMA_ATTR_ALLOC_SINGLE_PAGES;
|
|
q->mem_ops = &vb2_dma_contig_memops;
|
|
q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
|
|
ret = vb2_queue_init(q);
|
|
if (ret) {
|
|
mfc_err("Failed to initialize videobuf2 queue(output)\n");
|
|
goto err_queue_init;
|
|
}
|
|
mutex_unlock(&dev->mfc_mutex);
|
|
mfc_debug_leave();
|
|
return ret;
|
|
/* Deinit when failure occurred */
|
|
err_queue_init:
|
|
if (dev->num_inst == 1)
|
|
s5p_mfc_deinit_hw(dev);
|
|
err_init_hw:
|
|
err_load_fw:
|
|
err_pwr_enable:
|
|
if (dev->num_inst == 1) {
|
|
if (s5p_mfc_power_off() < 0)
|
|
mfc_err("power off failed\n");
|
|
del_timer_sync(&dev->watchdog_timer);
|
|
}
|
|
err_ctrls_setup:
|
|
s5p_mfc_dec_ctrls_delete(ctx);
|
|
err_bad_node:
|
|
dev->ctx[ctx->num] = NULL;
|
|
err_no_ctx:
|
|
v4l2_fh_del(&ctx->fh);
|
|
v4l2_fh_exit(&ctx->fh);
|
|
kfree(ctx);
|
|
err_alloc:
|
|
dev->num_inst--;
|
|
mutex_unlock(&dev->mfc_mutex);
|
|
mfc_debug_leave();
|
|
return ret;
|
|
}
|
|
|
|
/* Release MFC context */
|
|
static int s5p_mfc_release(struct file *file)
|
|
{
|
|
struct s5p_mfc_ctx *ctx = fh_to_ctx(file->private_data);
|
|
struct s5p_mfc_dev *dev = ctx->dev;
|
|
|
|
/* if dev is null, do cleanup that doesn't need dev */
|
|
mfc_debug_enter();
|
|
if (dev)
|
|
mutex_lock(&dev->mfc_mutex);
|
|
vb2_queue_release(&ctx->vq_src);
|
|
vb2_queue_release(&ctx->vq_dst);
|
|
if (dev) {
|
|
s5p_mfc_clock_on();
|
|
|
|
/* Mark context as idle */
|
|
clear_work_bit_irqsave(ctx);
|
|
/*
|
|
* If instance was initialised and not yet freed,
|
|
* return instance and free resources
|
|
*/
|
|
if (ctx->state != MFCINST_FREE && ctx->state != MFCINST_INIT) {
|
|
mfc_debug(2, "Has to free instance\n");
|
|
s5p_mfc_close_mfc_inst(dev, ctx);
|
|
}
|
|
/* hardware locking scheme */
|
|
if (dev->curr_ctx == ctx->num)
|
|
clear_bit(0, &dev->hw_lock);
|
|
dev->num_inst--;
|
|
if (dev->num_inst == 0) {
|
|
mfc_debug(2, "Last instance\n");
|
|
s5p_mfc_deinit_hw(dev);
|
|
del_timer_sync(&dev->watchdog_timer);
|
|
s5p_mfc_clock_off();
|
|
if (s5p_mfc_power_off() < 0)
|
|
mfc_err("Power off failed\n");
|
|
} else {
|
|
mfc_debug(2, "Shutting down clock\n");
|
|
s5p_mfc_clock_off();
|
|
}
|
|
}
|
|
if (dev)
|
|
dev->ctx[ctx->num] = NULL;
|
|
s5p_mfc_dec_ctrls_delete(ctx);
|
|
v4l2_fh_del(&ctx->fh);
|
|
/* vdev is gone if dev is null */
|
|
if (dev)
|
|
v4l2_fh_exit(&ctx->fh);
|
|
kfree(ctx);
|
|
mfc_debug_leave();
|
|
if (dev)
|
|
mutex_unlock(&dev->mfc_mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Poll */
|
|
static __poll_t s5p_mfc_poll(struct file *file,
|
|
struct poll_table_struct *wait)
|
|
{
|
|
struct s5p_mfc_ctx *ctx = fh_to_ctx(file->private_data);
|
|
struct s5p_mfc_dev *dev = ctx->dev;
|
|
struct vb2_queue *src_q, *dst_q;
|
|
struct vb2_buffer *src_vb = NULL, *dst_vb = NULL;
|
|
__poll_t rc = 0;
|
|
unsigned long flags;
|
|
|
|
mutex_lock(&dev->mfc_mutex);
|
|
src_q = &ctx->vq_src;
|
|
dst_q = &ctx->vq_dst;
|
|
/*
|
|
* There has to be at least one buffer queued on each queued_list, which
|
|
* means either in driver already or waiting for driver to claim it
|
|
* and start processing.
|
|
*/
|
|
if ((!src_q->streaming || list_empty(&src_q->queued_list))
|
|
&& (!dst_q->streaming || list_empty(&dst_q->queued_list))) {
|
|
rc = EPOLLERR;
|
|
goto end;
|
|
}
|
|
mutex_unlock(&dev->mfc_mutex);
|
|
poll_wait(file, &ctx->fh.wait, wait);
|
|
poll_wait(file, &src_q->done_wq, wait);
|
|
poll_wait(file, &dst_q->done_wq, wait);
|
|
mutex_lock(&dev->mfc_mutex);
|
|
if (v4l2_event_pending(&ctx->fh))
|
|
rc |= EPOLLPRI;
|
|
spin_lock_irqsave(&src_q->done_lock, flags);
|
|
if (!list_empty(&src_q->done_list))
|
|
src_vb = list_first_entry(&src_q->done_list, struct vb2_buffer,
|
|
done_entry);
|
|
if (src_vb && (src_vb->state == VB2_BUF_STATE_DONE
|
|
|| src_vb->state == VB2_BUF_STATE_ERROR))
|
|
rc |= EPOLLOUT | EPOLLWRNORM;
|
|
spin_unlock_irqrestore(&src_q->done_lock, flags);
|
|
spin_lock_irqsave(&dst_q->done_lock, flags);
|
|
if (!list_empty(&dst_q->done_list))
|
|
dst_vb = list_first_entry(&dst_q->done_list, struct vb2_buffer,
|
|
done_entry);
|
|
if (dst_vb && (dst_vb->state == VB2_BUF_STATE_DONE
|
|
|| dst_vb->state == VB2_BUF_STATE_ERROR))
|
|
rc |= EPOLLIN | EPOLLRDNORM;
|
|
spin_unlock_irqrestore(&dst_q->done_lock, flags);
|
|
end:
|
|
mutex_unlock(&dev->mfc_mutex);
|
|
return rc;
|
|
}
|
|
|
|
/* Mmap */
|
|
static int s5p_mfc_mmap(struct file *file, struct vm_area_struct *vma)
|
|
{
|
|
struct s5p_mfc_ctx *ctx = fh_to_ctx(file->private_data);
|
|
unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
|
|
int ret;
|
|
|
|
if (offset < DST_QUEUE_OFF_BASE) {
|
|
mfc_debug(2, "mmaping source\n");
|
|
ret = vb2_mmap(&ctx->vq_src, vma);
|
|
} else { /* capture */
|
|
mfc_debug(2, "mmaping destination\n");
|
|
vma->vm_pgoff -= (DST_QUEUE_OFF_BASE >> PAGE_SHIFT);
|
|
ret = vb2_mmap(&ctx->vq_dst, vma);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* v4l2 ops */
|
|
static const struct v4l2_file_operations s5p_mfc_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = s5p_mfc_open,
|
|
.release = s5p_mfc_release,
|
|
.poll = s5p_mfc_poll,
|
|
.unlocked_ioctl = video_ioctl2,
|
|
.mmap = s5p_mfc_mmap,
|
|
};
|
|
|
|
/* DMA memory related helper functions */
|
|
static void s5p_mfc_memdev_release(struct device *dev)
|
|
{
|
|
of_reserved_mem_device_release(dev);
|
|
}
|
|
|
|
static struct device *s5p_mfc_alloc_memdev(struct device *dev,
|
|
const char *name, unsigned int idx)
|
|
{
|
|
struct device *child;
|
|
int ret;
|
|
|
|
child = devm_kzalloc(dev, sizeof(*child), GFP_KERNEL);
|
|
if (!child)
|
|
return NULL;
|
|
|
|
device_initialize(child);
|
|
dev_set_name(child, "%s:%s", dev_name(dev), name);
|
|
child->parent = dev;
|
|
child->coherent_dma_mask = dev->coherent_dma_mask;
|
|
child->dma_mask = dev->dma_mask;
|
|
child->release = s5p_mfc_memdev_release;
|
|
|
|
/*
|
|
* The memdevs are not proper OF platform devices, so in order for them
|
|
* to be treated as valid DMA masters we need a bit of a hack to force
|
|
* them to inherit the MFC node's DMA configuration.
|
|
*/
|
|
of_dma_configure(child, dev->of_node, true);
|
|
|
|
if (device_add(child) == 0) {
|
|
ret = of_reserved_mem_device_init_by_idx(child, dev->of_node,
|
|
idx);
|
|
if (ret == 0)
|
|
return child;
|
|
device_del(child);
|
|
}
|
|
|
|
put_device(child);
|
|
return NULL;
|
|
}
|
|
|
|
static int s5p_mfc_configure_2port_memory(struct s5p_mfc_dev *mfc_dev)
|
|
{
|
|
struct device *dev = &mfc_dev->plat_dev->dev;
|
|
void *bank2_virt;
|
|
dma_addr_t bank2_dma_addr;
|
|
unsigned long align_size = 1 << MFC_BASE_ALIGN_ORDER;
|
|
int ret;
|
|
|
|
/*
|
|
* Create and initialize virtual devices for accessing
|
|
* reserved memory regions.
|
|
*/
|
|
mfc_dev->mem_dev[BANK_L_CTX] = s5p_mfc_alloc_memdev(dev, "left",
|
|
BANK_L_CTX);
|
|
if (!mfc_dev->mem_dev[BANK_L_CTX])
|
|
return -ENODEV;
|
|
mfc_dev->mem_dev[BANK_R_CTX] = s5p_mfc_alloc_memdev(dev, "right",
|
|
BANK_R_CTX);
|
|
if (!mfc_dev->mem_dev[BANK_R_CTX]) {
|
|
device_unregister(mfc_dev->mem_dev[BANK_L_CTX]);
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* Allocate memory for firmware and initialize both banks addresses */
|
|
ret = s5p_mfc_alloc_firmware(mfc_dev);
|
|
if (ret) {
|
|
device_unregister(mfc_dev->mem_dev[BANK_R_CTX]);
|
|
device_unregister(mfc_dev->mem_dev[BANK_L_CTX]);
|
|
return ret;
|
|
}
|
|
|
|
mfc_dev->dma_base[BANK_L_CTX] = mfc_dev->fw_buf.dma;
|
|
|
|
bank2_virt = dma_alloc_coherent(mfc_dev->mem_dev[BANK_R_CTX],
|
|
align_size, &bank2_dma_addr, GFP_KERNEL);
|
|
if (!bank2_virt) {
|
|
mfc_err("Allocating bank2 base failed\n");
|
|
s5p_mfc_release_firmware(mfc_dev);
|
|
device_unregister(mfc_dev->mem_dev[BANK_R_CTX]);
|
|
device_unregister(mfc_dev->mem_dev[BANK_L_CTX]);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Valid buffers passed to MFC encoder with LAST_FRAME command
|
|
* should not have address of bank2 - MFC will treat it as a null frame.
|
|
* To avoid such situation we set bank2 address below the pool address.
|
|
*/
|
|
mfc_dev->dma_base[BANK_R_CTX] = bank2_dma_addr - align_size;
|
|
|
|
dma_free_coherent(mfc_dev->mem_dev[BANK_R_CTX], align_size, bank2_virt,
|
|
bank2_dma_addr);
|
|
|
|
vb2_dma_contig_set_max_seg_size(mfc_dev->mem_dev[BANK_L_CTX],
|
|
DMA_BIT_MASK(32));
|
|
vb2_dma_contig_set_max_seg_size(mfc_dev->mem_dev[BANK_R_CTX],
|
|
DMA_BIT_MASK(32));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void s5p_mfc_unconfigure_2port_memory(struct s5p_mfc_dev *mfc_dev)
|
|
{
|
|
device_unregister(mfc_dev->mem_dev[BANK_L_CTX]);
|
|
device_unregister(mfc_dev->mem_dev[BANK_R_CTX]);
|
|
vb2_dma_contig_clear_max_seg_size(mfc_dev->mem_dev[BANK_L_CTX]);
|
|
vb2_dma_contig_clear_max_seg_size(mfc_dev->mem_dev[BANK_R_CTX]);
|
|
}
|
|
|
|
static int s5p_mfc_configure_common_memory(struct s5p_mfc_dev *mfc_dev)
|
|
{
|
|
struct device *dev = &mfc_dev->plat_dev->dev;
|
|
unsigned long mem_size = SZ_4M;
|
|
unsigned int bitmap_size;
|
|
|
|
if (IS_ENABLED(CONFIG_DMA_CMA) || exynos_is_iommu_available(dev))
|
|
mem_size = SZ_8M;
|
|
|
|
if (mfc_mem_size)
|
|
mem_size = memparse(mfc_mem_size, NULL);
|
|
|
|
bitmap_size = BITS_TO_LONGS(mem_size >> PAGE_SHIFT) * sizeof(long);
|
|
|
|
mfc_dev->mem_bitmap = kzalloc(bitmap_size, GFP_KERNEL);
|
|
if (!mfc_dev->mem_bitmap)
|
|
return -ENOMEM;
|
|
|
|
mfc_dev->mem_virt = dma_alloc_coherent(dev, mem_size,
|
|
&mfc_dev->mem_base, GFP_KERNEL);
|
|
if (!mfc_dev->mem_virt) {
|
|
kfree(mfc_dev->mem_bitmap);
|
|
dev_err(dev, "failed to preallocate %ld MiB for the firmware and context buffers\n",
|
|
(mem_size / SZ_1M));
|
|
return -ENOMEM;
|
|
}
|
|
mfc_dev->mem_size = mem_size;
|
|
mfc_dev->dma_base[BANK_L_CTX] = mfc_dev->mem_base;
|
|
mfc_dev->dma_base[BANK_R_CTX] = mfc_dev->mem_base;
|
|
|
|
/*
|
|
* MFC hardware cannot handle 0 as a base address, so mark first 128K
|
|
* as used (to keep required base alignment) and adjust base address
|
|
*/
|
|
if (mfc_dev->mem_base == (dma_addr_t)0) {
|
|
unsigned int offset = 1 << MFC_BASE_ALIGN_ORDER;
|
|
|
|
bitmap_set(mfc_dev->mem_bitmap, 0, offset >> PAGE_SHIFT);
|
|
mfc_dev->dma_base[BANK_L_CTX] += offset;
|
|
mfc_dev->dma_base[BANK_R_CTX] += offset;
|
|
}
|
|
|
|
/* Firmware allocation cannot fail in this case */
|
|
s5p_mfc_alloc_firmware(mfc_dev);
|
|
|
|
mfc_dev->mem_dev[BANK_L_CTX] = mfc_dev->mem_dev[BANK_R_CTX] = dev;
|
|
vb2_dma_contig_set_max_seg_size(dev, DMA_BIT_MASK(32));
|
|
|
|
dev_info(dev, "preallocated %ld MiB buffer for the firmware and context buffers\n",
|
|
(mem_size / SZ_1M));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void s5p_mfc_unconfigure_common_memory(struct s5p_mfc_dev *mfc_dev)
|
|
{
|
|
struct device *dev = &mfc_dev->plat_dev->dev;
|
|
|
|
dma_free_coherent(dev, mfc_dev->mem_size, mfc_dev->mem_virt,
|
|
mfc_dev->mem_base);
|
|
kfree(mfc_dev->mem_bitmap);
|
|
vb2_dma_contig_clear_max_seg_size(dev);
|
|
}
|
|
|
|
static int s5p_mfc_configure_dma_memory(struct s5p_mfc_dev *mfc_dev)
|
|
{
|
|
struct device *dev = &mfc_dev->plat_dev->dev;
|
|
|
|
if (exynos_is_iommu_available(dev) || !IS_TWOPORT(mfc_dev))
|
|
return s5p_mfc_configure_common_memory(mfc_dev);
|
|
else
|
|
return s5p_mfc_configure_2port_memory(mfc_dev);
|
|
}
|
|
|
|
static void s5p_mfc_unconfigure_dma_memory(struct s5p_mfc_dev *mfc_dev)
|
|
{
|
|
struct device *dev = &mfc_dev->plat_dev->dev;
|
|
|
|
s5p_mfc_release_firmware(mfc_dev);
|
|
if (exynos_is_iommu_available(dev) || !IS_TWOPORT(mfc_dev))
|
|
s5p_mfc_unconfigure_common_memory(mfc_dev);
|
|
else
|
|
s5p_mfc_unconfigure_2port_memory(mfc_dev);
|
|
}
|
|
|
|
/* MFC probe function */
|
|
static int s5p_mfc_probe(struct platform_device *pdev)
|
|
{
|
|
struct s5p_mfc_dev *dev;
|
|
struct video_device *vfd;
|
|
struct resource *res;
|
|
int ret;
|
|
|
|
pr_debug("%s++\n", __func__);
|
|
dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
|
|
if (!dev)
|
|
return -ENOMEM;
|
|
|
|
spin_lock_init(&dev->irqlock);
|
|
spin_lock_init(&dev->condlock);
|
|
dev->plat_dev = pdev;
|
|
if (!dev->plat_dev) {
|
|
dev_err(&pdev->dev, "No platform data specified\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
dev->variant = of_device_get_match_data(&pdev->dev);
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
dev->regs_base = devm_ioremap_resource(&pdev->dev, res);
|
|
if (IS_ERR(dev->regs_base))
|
|
return PTR_ERR(dev->regs_base);
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
|
|
if (!res) {
|
|
dev_err(&pdev->dev, "failed to get irq resource\n");
|
|
return -ENOENT;
|
|
}
|
|
dev->irq = res->start;
|
|
ret = devm_request_irq(&pdev->dev, dev->irq, s5p_mfc_irq,
|
|
0, pdev->name, dev);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Failed to install irq (%d)\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = s5p_mfc_configure_dma_memory(dev);
|
|
if (ret < 0) {
|
|
dev_err(&pdev->dev, "failed to configure DMA memory\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = s5p_mfc_init_pm(dev);
|
|
if (ret < 0) {
|
|
dev_err(&pdev->dev, "failed to get mfc clock source\n");
|
|
goto err_dma;
|
|
}
|
|
|
|
/*
|
|
* Load fails if fs isn't mounted. Try loading anyway.
|
|
* _open() will load it, it it fails now. Ignore failure.
|
|
*/
|
|
s5p_mfc_load_firmware(dev);
|
|
|
|
mutex_init(&dev->mfc_mutex);
|
|
init_waitqueue_head(&dev->queue);
|
|
dev->hw_lock = 0;
|
|
INIT_WORK(&dev->watchdog_work, s5p_mfc_watchdog_worker);
|
|
atomic_set(&dev->watchdog_cnt, 0);
|
|
timer_setup(&dev->watchdog_timer, s5p_mfc_watchdog, 0);
|
|
|
|
ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev);
|
|
if (ret)
|
|
goto err_v4l2_dev_reg;
|
|
|
|
/* decoder */
|
|
vfd = video_device_alloc();
|
|
if (!vfd) {
|
|
v4l2_err(&dev->v4l2_dev, "Failed to allocate video device\n");
|
|
ret = -ENOMEM;
|
|
goto err_dec_alloc;
|
|
}
|
|
vfd->fops = &s5p_mfc_fops;
|
|
vfd->ioctl_ops = get_dec_v4l2_ioctl_ops();
|
|
vfd->release = video_device_release;
|
|
vfd->lock = &dev->mfc_mutex;
|
|
vfd->v4l2_dev = &dev->v4l2_dev;
|
|
vfd->vfl_dir = VFL_DIR_M2M;
|
|
vfd->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING;
|
|
set_bit(V4L2_FL_QUIRK_INVERTED_CROP, &vfd->flags);
|
|
snprintf(vfd->name, sizeof(vfd->name), "%s", S5P_MFC_DEC_NAME);
|
|
dev->vfd_dec = vfd;
|
|
video_set_drvdata(vfd, dev);
|
|
|
|
/* encoder */
|
|
vfd = video_device_alloc();
|
|
if (!vfd) {
|
|
v4l2_err(&dev->v4l2_dev, "Failed to allocate video device\n");
|
|
ret = -ENOMEM;
|
|
goto err_enc_alloc;
|
|
}
|
|
vfd->fops = &s5p_mfc_fops;
|
|
vfd->ioctl_ops = get_enc_v4l2_ioctl_ops();
|
|
vfd->release = video_device_release;
|
|
vfd->lock = &dev->mfc_mutex;
|
|
vfd->v4l2_dev = &dev->v4l2_dev;
|
|
vfd->vfl_dir = VFL_DIR_M2M;
|
|
vfd->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING;
|
|
snprintf(vfd->name, sizeof(vfd->name), "%s", S5P_MFC_ENC_NAME);
|
|
dev->vfd_enc = vfd;
|
|
video_set_drvdata(vfd, dev);
|
|
platform_set_drvdata(pdev, dev);
|
|
|
|
/* Initialize HW ops and commands based on MFC version */
|
|
s5p_mfc_init_hw_ops(dev);
|
|
s5p_mfc_init_hw_cmds(dev);
|
|
s5p_mfc_init_regs(dev);
|
|
|
|
/* Register decoder and encoder */
|
|
ret = video_register_device(dev->vfd_dec, VFL_TYPE_GRABBER, 0);
|
|
if (ret) {
|
|
v4l2_err(&dev->v4l2_dev, "Failed to register video device\n");
|
|
goto err_dec_reg;
|
|
}
|
|
v4l2_info(&dev->v4l2_dev,
|
|
"decoder registered as /dev/video%d\n", dev->vfd_dec->num);
|
|
|
|
ret = video_register_device(dev->vfd_enc, VFL_TYPE_GRABBER, 0);
|
|
if (ret) {
|
|
v4l2_err(&dev->v4l2_dev, "Failed to register video device\n");
|
|
goto err_enc_reg;
|
|
}
|
|
v4l2_info(&dev->v4l2_dev,
|
|
"encoder registered as /dev/video%d\n", dev->vfd_enc->num);
|
|
|
|
pr_debug("%s--\n", __func__);
|
|
return 0;
|
|
|
|
/* Deinit MFC if probe had failed */
|
|
err_enc_reg:
|
|
video_unregister_device(dev->vfd_dec);
|
|
err_dec_reg:
|
|
video_device_release(dev->vfd_enc);
|
|
err_enc_alloc:
|
|
video_device_release(dev->vfd_dec);
|
|
err_dec_alloc:
|
|
v4l2_device_unregister(&dev->v4l2_dev);
|
|
err_v4l2_dev_reg:
|
|
s5p_mfc_final_pm(dev);
|
|
err_dma:
|
|
s5p_mfc_unconfigure_dma_memory(dev);
|
|
|
|
pr_debug("%s-- with error\n", __func__);
|
|
return ret;
|
|
|
|
}
|
|
|
|
/* Remove the driver */
|
|
static int s5p_mfc_remove(struct platform_device *pdev)
|
|
{
|
|
struct s5p_mfc_dev *dev = platform_get_drvdata(pdev);
|
|
struct s5p_mfc_ctx *ctx;
|
|
int i;
|
|
|
|
v4l2_info(&dev->v4l2_dev, "Removing %s\n", pdev->name);
|
|
|
|
/*
|
|
* Clear ctx dev pointer to avoid races between s5p_mfc_remove()
|
|
* and s5p_mfc_release() and s5p_mfc_release() accessing ctx->dev
|
|
* after s5p_mfc_remove() is run during unbind.
|
|
*/
|
|
mutex_lock(&dev->mfc_mutex);
|
|
for (i = 0; i < MFC_NUM_CONTEXTS; i++) {
|
|
ctx = dev->ctx[i];
|
|
if (!ctx)
|
|
continue;
|
|
/* clear ctx->dev */
|
|
ctx->dev = NULL;
|
|
}
|
|
mutex_unlock(&dev->mfc_mutex);
|
|
|
|
del_timer_sync(&dev->watchdog_timer);
|
|
flush_work(&dev->watchdog_work);
|
|
|
|
video_unregister_device(dev->vfd_enc);
|
|
video_unregister_device(dev->vfd_dec);
|
|
video_device_release(dev->vfd_enc);
|
|
video_device_release(dev->vfd_dec);
|
|
v4l2_device_unregister(&dev->v4l2_dev);
|
|
s5p_mfc_unconfigure_dma_memory(dev);
|
|
|
|
s5p_mfc_final_pm(dev);
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
|
|
static int s5p_mfc_suspend(struct device *dev)
|
|
{
|
|
struct s5p_mfc_dev *m_dev = dev_get_drvdata(dev);
|
|
int ret;
|
|
|
|
if (m_dev->num_inst == 0)
|
|
return 0;
|
|
|
|
if (test_and_set_bit(0, &m_dev->enter_suspend) != 0) {
|
|
mfc_err("Error: going to suspend for a second time\n");
|
|
return -EIO;
|
|
}
|
|
|
|
/* Check if we're processing then wait if it necessary. */
|
|
while (test_and_set_bit(0, &m_dev->hw_lock) != 0) {
|
|
/* Try and lock the HW */
|
|
/* Wait on the interrupt waitqueue */
|
|
ret = wait_event_interruptible_timeout(m_dev->queue,
|
|
m_dev->int_cond, msecs_to_jiffies(MFC_INT_TIMEOUT));
|
|
if (ret == 0) {
|
|
mfc_err("Waiting for hardware to finish timed out\n");
|
|
clear_bit(0, &m_dev->enter_suspend);
|
|
return -EIO;
|
|
}
|
|
}
|
|
|
|
ret = s5p_mfc_sleep(m_dev);
|
|
if (ret) {
|
|
clear_bit(0, &m_dev->enter_suspend);
|
|
clear_bit(0, &m_dev->hw_lock);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int s5p_mfc_resume(struct device *dev)
|
|
{
|
|
struct s5p_mfc_dev *m_dev = dev_get_drvdata(dev);
|
|
|
|
if (m_dev->num_inst == 0)
|
|
return 0;
|
|
return s5p_mfc_wakeup(m_dev);
|
|
}
|
|
#endif
|
|
|
|
/* Power management */
|
|
static const struct dev_pm_ops s5p_mfc_pm_ops = {
|
|
SET_SYSTEM_SLEEP_PM_OPS(s5p_mfc_suspend, s5p_mfc_resume)
|
|
};
|
|
|
|
static struct s5p_mfc_buf_size_v5 mfc_buf_size_v5 = {
|
|
.h264_ctx = MFC_H264_CTX_BUF_SIZE,
|
|
.non_h264_ctx = MFC_CTX_BUF_SIZE,
|
|
.dsc = DESC_BUF_SIZE,
|
|
.shm = SHARED_BUF_SIZE,
|
|
};
|
|
|
|
static struct s5p_mfc_buf_size buf_size_v5 = {
|
|
.fw = MAX_FW_SIZE,
|
|
.cpb = MAX_CPB_SIZE,
|
|
.priv = &mfc_buf_size_v5,
|
|
};
|
|
|
|
static struct s5p_mfc_variant mfc_drvdata_v5 = {
|
|
.version = MFC_VERSION,
|
|
.version_bit = MFC_V5_BIT,
|
|
.port_num = MFC_NUM_PORTS,
|
|
.buf_size = &buf_size_v5,
|
|
.fw_name[0] = "s5p-mfc.fw",
|
|
.clk_names = {"mfc", "sclk_mfc"},
|
|
.num_clocks = 2,
|
|
.use_clock_gating = true,
|
|
};
|
|
|
|
static struct s5p_mfc_buf_size_v6 mfc_buf_size_v6 = {
|
|
.dev_ctx = MFC_CTX_BUF_SIZE_V6,
|
|
.h264_dec_ctx = MFC_H264_DEC_CTX_BUF_SIZE_V6,
|
|
.other_dec_ctx = MFC_OTHER_DEC_CTX_BUF_SIZE_V6,
|
|
.h264_enc_ctx = MFC_H264_ENC_CTX_BUF_SIZE_V6,
|
|
.other_enc_ctx = MFC_OTHER_ENC_CTX_BUF_SIZE_V6,
|
|
};
|
|
|
|
static struct s5p_mfc_buf_size buf_size_v6 = {
|
|
.fw = MAX_FW_SIZE_V6,
|
|
.cpb = MAX_CPB_SIZE_V6,
|
|
.priv = &mfc_buf_size_v6,
|
|
};
|
|
|
|
static struct s5p_mfc_variant mfc_drvdata_v6 = {
|
|
.version = MFC_VERSION_V6,
|
|
.version_bit = MFC_V6_BIT,
|
|
.port_num = MFC_NUM_PORTS_V6,
|
|
.buf_size = &buf_size_v6,
|
|
.fw_name[0] = "s5p-mfc-v6.fw",
|
|
/*
|
|
* v6-v2 firmware contains bug fixes and interface change
|
|
* for init buffer command
|
|
*/
|
|
.fw_name[1] = "s5p-mfc-v6-v2.fw",
|
|
.clk_names = {"mfc"},
|
|
.num_clocks = 1,
|
|
};
|
|
|
|
static struct s5p_mfc_buf_size_v6 mfc_buf_size_v7 = {
|
|
.dev_ctx = MFC_CTX_BUF_SIZE_V7,
|
|
.h264_dec_ctx = MFC_H264_DEC_CTX_BUF_SIZE_V7,
|
|
.other_dec_ctx = MFC_OTHER_DEC_CTX_BUF_SIZE_V7,
|
|
.h264_enc_ctx = MFC_H264_ENC_CTX_BUF_SIZE_V7,
|
|
.other_enc_ctx = MFC_OTHER_ENC_CTX_BUF_SIZE_V7,
|
|
};
|
|
|
|
static struct s5p_mfc_buf_size buf_size_v7 = {
|
|
.fw = MAX_FW_SIZE_V7,
|
|
.cpb = MAX_CPB_SIZE_V7,
|
|
.priv = &mfc_buf_size_v7,
|
|
};
|
|
|
|
static struct s5p_mfc_variant mfc_drvdata_v7 = {
|
|
.version = MFC_VERSION_V7,
|
|
.version_bit = MFC_V7_BIT,
|
|
.port_num = MFC_NUM_PORTS_V7,
|
|
.buf_size = &buf_size_v7,
|
|
.fw_name[0] = "s5p-mfc-v7.fw",
|
|
.clk_names = {"mfc", "sclk_mfc"},
|
|
.num_clocks = 2,
|
|
};
|
|
|
|
static struct s5p_mfc_buf_size_v6 mfc_buf_size_v8 = {
|
|
.dev_ctx = MFC_CTX_BUF_SIZE_V8,
|
|
.h264_dec_ctx = MFC_H264_DEC_CTX_BUF_SIZE_V8,
|
|
.other_dec_ctx = MFC_OTHER_DEC_CTX_BUF_SIZE_V8,
|
|
.h264_enc_ctx = MFC_H264_ENC_CTX_BUF_SIZE_V8,
|
|
.other_enc_ctx = MFC_OTHER_ENC_CTX_BUF_SIZE_V8,
|
|
};
|
|
|
|
static struct s5p_mfc_buf_size buf_size_v8 = {
|
|
.fw = MAX_FW_SIZE_V8,
|
|
.cpb = MAX_CPB_SIZE_V8,
|
|
.priv = &mfc_buf_size_v8,
|
|
};
|
|
|
|
static struct s5p_mfc_variant mfc_drvdata_v8 = {
|
|
.version = MFC_VERSION_V8,
|
|
.version_bit = MFC_V8_BIT,
|
|
.port_num = MFC_NUM_PORTS_V8,
|
|
.buf_size = &buf_size_v8,
|
|
.fw_name[0] = "s5p-mfc-v8.fw",
|
|
.clk_names = {"mfc"},
|
|
.num_clocks = 1,
|
|
};
|
|
|
|
static struct s5p_mfc_variant mfc_drvdata_v8_5433 = {
|
|
.version = MFC_VERSION_V8,
|
|
.version_bit = MFC_V8_BIT,
|
|
.port_num = MFC_NUM_PORTS_V8,
|
|
.buf_size = &buf_size_v8,
|
|
.fw_name[0] = "s5p-mfc-v8.fw",
|
|
.clk_names = {"pclk", "aclk", "aclk_xiu"},
|
|
.num_clocks = 3,
|
|
};
|
|
|
|
static struct s5p_mfc_buf_size_v6 mfc_buf_size_v10 = {
|
|
.dev_ctx = MFC_CTX_BUF_SIZE_V10,
|
|
.h264_dec_ctx = MFC_H264_DEC_CTX_BUF_SIZE_V10,
|
|
.other_dec_ctx = MFC_OTHER_DEC_CTX_BUF_SIZE_V10,
|
|
.h264_enc_ctx = MFC_H264_ENC_CTX_BUF_SIZE_V10,
|
|
.hevc_enc_ctx = MFC_HEVC_ENC_CTX_BUF_SIZE_V10,
|
|
.other_enc_ctx = MFC_OTHER_ENC_CTX_BUF_SIZE_V10,
|
|
};
|
|
|
|
static struct s5p_mfc_buf_size buf_size_v10 = {
|
|
.fw = MAX_FW_SIZE_V10,
|
|
.cpb = MAX_CPB_SIZE_V10,
|
|
.priv = &mfc_buf_size_v10,
|
|
};
|
|
|
|
static struct s5p_mfc_variant mfc_drvdata_v10 = {
|
|
.version = MFC_VERSION_V10,
|
|
.version_bit = MFC_V10_BIT,
|
|
.port_num = MFC_NUM_PORTS_V10,
|
|
.buf_size = &buf_size_v10,
|
|
.fw_name[0] = "s5p-mfc-v10.fw",
|
|
};
|
|
|
|
static const struct of_device_id exynos_mfc_match[] = {
|
|
{
|
|
.compatible = "samsung,mfc-v5",
|
|
.data = &mfc_drvdata_v5,
|
|
}, {
|
|
.compatible = "samsung,mfc-v6",
|
|
.data = &mfc_drvdata_v6,
|
|
}, {
|
|
.compatible = "samsung,mfc-v7",
|
|
.data = &mfc_drvdata_v7,
|
|
}, {
|
|
.compatible = "samsung,mfc-v8",
|
|
.data = &mfc_drvdata_v8,
|
|
}, {
|
|
.compatible = "samsung,exynos5433-mfc",
|
|
.data = &mfc_drvdata_v8_5433,
|
|
}, {
|
|
.compatible = "samsung,mfc-v10",
|
|
.data = &mfc_drvdata_v10,
|
|
},
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, exynos_mfc_match);
|
|
|
|
static struct platform_driver s5p_mfc_driver = {
|
|
.probe = s5p_mfc_probe,
|
|
.remove = s5p_mfc_remove,
|
|
.driver = {
|
|
.name = S5P_MFC_NAME,
|
|
.pm = &s5p_mfc_pm_ops,
|
|
.of_match_table = exynos_mfc_match,
|
|
},
|
|
};
|
|
|
|
module_platform_driver(s5p_mfc_driver);
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_AUTHOR("Kamil Debski <k.debski@samsung.com>");
|
|
MODULE_DESCRIPTION("Samsung S5P Multi Format Codec V4L2 driver");
|
|
|