linux/drivers/media/platform/s5p-mfc/s5p_mfc.c
Andrzej Hajda 4d0b0ed636 [media] s5p-mfc: use MFC_BUF_FLAG_EOS to identify last buffers in decoder capture queue
MFC driver never delivered EOS event to apps feeding constantly its capture
buffer with fresh buffers. The patch fixes it by marking last buffers
returned by MFC with MFC_BUF_FLAG_EOS flag and firing EOS event on
de-queuing such buffers.

Signed-off-by: Andrzej Hajda <a.hajda@samsung.com>
Signed-off-by: Kamil Debski <k.debski@samsung.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@osg.samsung.com>
2015-10-20 14:40:16 -02:00

1544 lines
42 KiB
C

/*
* Samsung S5P Multi Format Codec v 5.1
*
* Copyright (c) 2011 Samsung Electronics Co., Ltd.
* Kamil Debski, <k.debski@samsung.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/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 <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_opr.h"
#include "s5p_mfc_cmd.h"
#include "s5p_mfc_pm.h"
#define S5P_MFC_NAME "s5p-mfc"
#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");
/* 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);
}
/* 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);
}
static void s5p_mfc_watchdog(unsigned long arg)
{
struct s5p_mfc_dev *dev = (struct s5p_mfc_dev *)arg;
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");
queue_work(dev->watchdog_workqueue, &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_hw_call_void(dev->mfc_ops, cleanup_queue,
&ctx->dst_queue, &ctx->vq_dst);
s5p_mfc_hw_call_void(dev->mfc_ops, 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);
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;
size_t 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 = 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) {
if (vb2_dma_contig_plane_dma_addr(&dst_buf->b->vb2_buf, 0)
== dec_y_addr) {
dst_buf->b->timecode =
src_buf->b->timecode;
dst_buf->b->timestamp =
src_buf->b->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;
size_t dspl_y_addr;
unsigned int frame_type;
dspl_y_addr = 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) {
/* Check if this is the buffer we're looking for */
if (vb2_dma_contig_plane_dma_addr(&dst_buf->b->vb2_buf, 0)
== 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 long flags;
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_void(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_void(dev->mfc_ops, try_run, dev);
return;
}
if (ctx->dpb_flush_flag)
ctx->dpb_flush_flag = 0;
spin_lock_irqsave(&dev->irqlock, flags);
/* 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:
spin_unlock_irqrestore(&dev->irqlock, flags);
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_void(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_void(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)
{
unsigned long flags;
mfc_err("Interrupt Error: %08x\n", err);
if (ctx != NULL) {
/* 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 */
spin_lock_irqsave(&dev->irqlock, flags);
s5p_mfc_hw_call_void(dev->mfc_ops, cleanup_queue,
&ctx->dst_queue, &ctx->vq_dst);
/* Mark all src buffers as having an error */
s5p_mfc_hw_call_void(dev->mfc_ops, cleanup_queue,
&ctx->src_queue, &ctx->vq_src);
spin_unlock_irqrestore(&dev->irqlock, flags);
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_void(dev->mfc_ops, clear_int_flags, dev);
s5p_mfc_clock_off();
wake_up_dev(dev, reason, err);
return;
}
/* 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 == NULL)
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_void(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 (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_void(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_void(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;
unsigned long flags;
if (ctx == NULL)
return;
dev = ctx->dev;
s5p_mfc_hw_call_void(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) {
spin_lock_irqsave(&dev->irqlock, flags);
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);
}
spin_unlock_irqrestore(&dev->irqlock, flags);
} 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_void(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;
spin_lock(&dev->irqlock);
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);
}
spin_unlock(&dev->irqlock);
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_void(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);
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)
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_void(dev->mfc_ops, clear_int_flags, dev);
s5p_mfc_handle_stream_complete(ctx);
break;
}
s5p_mfc_hw_call_void(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_void(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;
clear_work_bit(ctx);
wake_up(&ctx->queue);
goto irq_cleanup_hw;
case S5P_MFC_R2H_CMD_CLOSE_INSTANCE_RET:
clear_work_bit(ctx);
ctx->inst_no = MFC_NO_INSTANCE_SET;
ctx->state = MFCINST_FREE;
wake_up(&ctx->queue);
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_void(dev->mfc_ops, clear_int_flags, dev);
wake_up_dev(dev, reason, err);
clear_bit(0, &dev->hw_lock);
clear_bit(0, &dev->enter_suspend);
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_void(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:
clear_work_bit(ctx);
ctx->state = MFCINST_RUNNING;
wake_up(&ctx->queue);
goto irq_cleanup_hw;
default:
mfc_debug(2, "Unknown int reason\n");
s5p_mfc_hw_call_void(dev->mfc_ops, clear_int_flags, dev);
}
mfc_debug_leave();
return IRQ_HANDLED;
irq_cleanup_hw:
s5p_mfc_hw_call_void(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();
s5p_mfc_hw_call_void(dev->mfc_ops, try_run, dev);
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) {
mfc_err("Not enough memory\n");
ret = -ENOMEM;
goto err_alloc;
}
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_err("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;
}
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->io_modes = VB2_MMAP;
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;
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;
}
init_waitqueue_head(&ctx->queue);
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;
mfc_debug_enter();
mutex_lock(&dev->mfc_mutex);
s5p_mfc_clock_on();
vb2_queue_release(&ctx->vq_src);
vb2_queue_release(&ctx->vq_dst);
/* 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);
if (s5p_mfc_power_off() < 0)
mfc_err("Power off failed\n");
}
mfc_debug(2, "Shutting down clock\n");
s5p_mfc_clock_off();
dev->ctx[ctx->num] = NULL;
s5p_mfc_dec_ctrls_delete(ctx);
v4l2_fh_del(&ctx->fh);
v4l2_fh_exit(&ctx->fh);
kfree(ctx);
mfc_debug_leave();
mutex_unlock(&dev->mfc_mutex);
return 0;
}
/* Poll */
static unsigned int 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;
unsigned int 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 = POLLERR;
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 |= POLLPRI;
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 |= POLLOUT | POLLWRNORM;
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 |= POLLIN | POLLRDNORM;
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);
struct s5p_mfc_dev *dev = ctx->dev;
unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
int ret;
if (mutex_lock_interruptible(&dev->mfc_mutex))
return -ERESTARTSYS;
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);
}
mutex_unlock(&dev->mfc_mutex);
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,
};
static int match_child(struct device *dev, void *data)
{
if (!dev_name(dev))
return 0;
return !strcmp(dev_name(dev), (char *)data);
}
static void *mfc_get_drv_data(struct platform_device *pdev);
static int s5p_mfc_alloc_memdevs(struct s5p_mfc_dev *dev)
{
unsigned int mem_info[2] = { };
dev->mem_dev_l = devm_kzalloc(&dev->plat_dev->dev,
sizeof(struct device), GFP_KERNEL);
if (!dev->mem_dev_l) {
mfc_err("Not enough memory\n");
return -ENOMEM;
}
device_initialize(dev->mem_dev_l);
of_property_read_u32_array(dev->plat_dev->dev.of_node,
"samsung,mfc-l", mem_info, 2);
if (dma_declare_coherent_memory(dev->mem_dev_l, mem_info[0],
mem_info[0], mem_info[1],
DMA_MEMORY_MAP | DMA_MEMORY_EXCLUSIVE) == 0) {
mfc_err("Failed to declare coherent memory for\n"
"MFC device\n");
return -ENOMEM;
}
dev->mem_dev_r = devm_kzalloc(&dev->plat_dev->dev,
sizeof(struct device), GFP_KERNEL);
if (!dev->mem_dev_r) {
mfc_err("Not enough memory\n");
return -ENOMEM;
}
device_initialize(dev->mem_dev_r);
of_property_read_u32_array(dev->plat_dev->dev.of_node,
"samsung,mfc-r", mem_info, 2);
if (dma_declare_coherent_memory(dev->mem_dev_r, mem_info[0],
mem_info[0], mem_info[1],
DMA_MEMORY_MAP | DMA_MEMORY_EXCLUSIVE) == 0) {
pr_err("Failed to declare coherent memory for\n"
"MFC device\n");
return -ENOMEM;
}
return 0;
}
/* 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) {
dev_err(&pdev->dev, "Not enough memory for MFC device\n");
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 = mfc_get_drv_data(pdev);
ret = s5p_mfc_init_pm(dev);
if (ret < 0) {
dev_err(&pdev->dev, "failed to get mfc clock source\n");
return ret;
}
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 == NULL) {
dev_err(&pdev->dev, "failed to get irq resource\n");
ret = -ENOENT;
goto err_res;
}
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);
goto err_res;
}
if (pdev->dev.of_node) {
ret = s5p_mfc_alloc_memdevs(dev);
if (ret < 0)
goto err_res;
} else {
dev->mem_dev_l = device_find_child(&dev->plat_dev->dev,
"s5p-mfc-l", match_child);
if (!dev->mem_dev_l) {
mfc_err("Mem child (L) device get failed\n");
ret = -ENODEV;
goto err_res;
}
dev->mem_dev_r = device_find_child(&dev->plat_dev->dev,
"s5p-mfc-r", match_child);
if (!dev->mem_dev_r) {
mfc_err("Mem child (R) device get failed\n");
ret = -ENODEV;
goto err_res;
}
}
dev->alloc_ctx[0] = vb2_dma_contig_init_ctx(dev->mem_dev_l);
if (IS_ERR(dev->alloc_ctx[0])) {
ret = PTR_ERR(dev->alloc_ctx[0]);
goto err_res;
}
dev->alloc_ctx[1] = vb2_dma_contig_init_ctx(dev->mem_dev_r);
if (IS_ERR(dev->alloc_ctx[1])) {
ret = PTR_ERR(dev->alloc_ctx[1]);
goto err_mem_init_ctx_1;
}
mutex_init(&dev->mfc_mutex);
ret = s5p_mfc_alloc_firmware(dev);
if (ret)
goto err_alloc_fw;
ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev);
if (ret)
goto err_v4l2_dev_reg;
init_waitqueue_head(&dev->queue);
/* 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;
snprintf(vfd->name, sizeof(vfd->name), "%s", S5P_MFC_DEC_NAME);
dev->vfd_dec = vfd;
ret = video_register_device(vfd, VFL_TYPE_GRABBER, 0);
if (ret) {
v4l2_err(&dev->v4l2_dev, "Failed to register video device\n");
video_device_release(vfd);
goto err_dec_reg;
}
v4l2_info(&dev->v4l2_dev,
"decoder registered as /dev/video%d\n", vfd->num);
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;
snprintf(vfd->name, sizeof(vfd->name), "%s", S5P_MFC_ENC_NAME);
dev->vfd_enc = vfd;
ret = video_register_device(vfd, VFL_TYPE_GRABBER, 0);
if (ret) {
v4l2_err(&dev->v4l2_dev, "Failed to register video device\n");
video_device_release(vfd);
goto err_enc_reg;
}
v4l2_info(&dev->v4l2_dev,
"encoder registered as /dev/video%d\n", vfd->num);
video_set_drvdata(vfd, dev);
platform_set_drvdata(pdev, dev);
dev->hw_lock = 0;
dev->watchdog_workqueue = create_singlethread_workqueue(S5P_MFC_NAME);
INIT_WORK(&dev->watchdog_work, s5p_mfc_watchdog_worker);
atomic_set(&dev->watchdog_cnt, 0);
init_timer(&dev->watchdog_timer);
dev->watchdog_timer.data = (unsigned long)dev;
dev->watchdog_timer.function = s5p_mfc_watchdog;
/* 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);
pr_debug("%s--\n", __func__);
return 0;
/* Deinit MFC if probe had failed */
err_enc_reg:
video_device_release(dev->vfd_enc);
err_enc_alloc:
video_unregister_device(dev->vfd_dec);
err_dec_reg:
video_device_release(dev->vfd_dec);
err_dec_alloc:
v4l2_device_unregister(&dev->v4l2_dev);
err_v4l2_dev_reg:
s5p_mfc_release_firmware(dev);
err_alloc_fw:
vb2_dma_contig_cleanup_ctx(dev->alloc_ctx[1]);
err_mem_init_ctx_1:
vb2_dma_contig_cleanup_ctx(dev->alloc_ctx[0]);
err_res:
s5p_mfc_final_pm(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);
v4l2_info(&dev->v4l2_dev, "Removing %s\n", pdev->name);
del_timer_sync(&dev->watchdog_timer);
flush_workqueue(dev->watchdog_workqueue);
destroy_workqueue(dev->watchdog_workqueue);
video_unregister_device(dev->vfd_enc);
video_unregister_device(dev->vfd_dec);
v4l2_device_unregister(&dev->v4l2_dev);
s5p_mfc_release_firmware(dev);
vb2_dma_contig_cleanup_ctx(dev->alloc_ctx[0]);
vb2_dma_contig_cleanup_ctx(dev->alloc_ctx[1]);
if (pdev->dev.of_node) {
put_device(dev->mem_dev_l);
put_device(dev->mem_dev_r);
}
s5p_mfc_final_pm(dev);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int s5p_mfc_suspend(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct s5p_mfc_dev *m_dev = platform_get_drvdata(pdev);
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 platform_device *pdev = to_platform_device(dev);
struct s5p_mfc_dev *m_dev = platform_get_drvdata(pdev);
if (m_dev->num_inst == 0)
return 0;
return s5p_mfc_wakeup(m_dev);
}
#endif
#ifdef CONFIG_PM
static int s5p_mfc_runtime_suspend(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct s5p_mfc_dev *m_dev = platform_get_drvdata(pdev);
atomic_set(&m_dev->pm.power, 0);
return 0;
}
static int s5p_mfc_runtime_resume(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct s5p_mfc_dev *m_dev = platform_get_drvdata(pdev);
atomic_set(&m_dev->pm.power, 1);
return 0;
}
#endif
/* Power management */
static const struct dev_pm_ops s5p_mfc_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(s5p_mfc_suspend, s5p_mfc_resume)
SET_RUNTIME_PM_OPS(s5p_mfc_runtime_suspend, s5p_mfc_runtime_resume,
NULL)
};
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_buf_align mfc_buf_align_v5 = {
.base = MFC_BASE_ALIGN_ORDER,
};
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,
.buf_align = &mfc_buf_align_v5,
.fw_name[0] = "s5p-mfc.fw",
};
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_buf_align mfc_buf_align_v6 = {
.base = 0,
};
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,
.buf_align = &mfc_buf_align_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",
};
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_buf_align mfc_buf_align_v7 = {
.base = 0,
};
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,
.buf_align = &mfc_buf_align_v7,
.fw_name[0] = "s5p-mfc-v7.fw",
};
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_buf_align mfc_buf_align_v8 = {
.base = 0,
};
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,
.buf_align = &mfc_buf_align_v8,
.fw_name[0] = "s5p-mfc-v8.fw",
};
static const struct platform_device_id mfc_driver_ids[] = {
{
.name = "s5p-mfc",
.driver_data = (unsigned long)&mfc_drvdata_v5,
}, {
.name = "s5p-mfc-v5",
.driver_data = (unsigned long)&mfc_drvdata_v5,
}, {
.name = "s5p-mfc-v6",
.driver_data = (unsigned long)&mfc_drvdata_v6,
}, {
.name = "s5p-mfc-v7",
.driver_data = (unsigned long)&mfc_drvdata_v7,
}, {
.name = "s5p-mfc-v8",
.driver_data = (unsigned long)&mfc_drvdata_v8,
},
{},
};
MODULE_DEVICE_TABLE(platform, mfc_driver_ids);
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,
},
{},
};
MODULE_DEVICE_TABLE(of, exynos_mfc_match);
static void *mfc_get_drv_data(struct platform_device *pdev)
{
struct s5p_mfc_variant *driver_data = NULL;
if (pdev->dev.of_node) {
const struct of_device_id *match;
match = of_match_node(exynos_mfc_match,
pdev->dev.of_node);
if (match)
driver_data = (struct s5p_mfc_variant *)match->data;
} else {
driver_data = (struct s5p_mfc_variant *)
platform_get_device_id(pdev)->driver_data;
}
return driver_data;
}
static struct platform_driver s5p_mfc_driver = {
.probe = s5p_mfc_probe,
.remove = s5p_mfc_remove,
.id_table = mfc_driver_ids,
.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");