summaryrefslogblamecommitdiffstats
path: root/drivers/media/video/s5p-mfc/s5p_mfc.c
blob: 76008549b3f19c78de3a0dd1ef033faa2068e278 (plain) (tree)
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941



















                                                                       






















































































































































































                                                                                     
                                                                                 












































                                                                               
                                                                                  

































































































































































































































                                                                                       
                                                                















































































































































































































































































































































































































































































                                                                                
                        
                                                      










































































































                                                                                   



                                                                          























                                                                              

                                                    

































































































































































                                                                                 
                                                








                                              
                                       




                                                                 
/*
 * 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 <linux/workqueue.h>
#include <media/videobuf2-core.h>
#include "regs-mfc.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_pm.h"
#include "s5p_mfc_shm.h"

#define S5P_MFC_NAME		"s5p-mfc"
#define S5P_MFC_DEC_NAME	"s5p-mfc-dec"
#define S5P_MFC_ENC_NAME	"s5p-mfc-enc"

int debug;
module_param(debug, 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 */
static 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);
}

/* 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_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_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_FIMV_R2H_CMD_ERR_RET, 0);
	}
	clear_bit(0, &dev->hw_lock);
	spin_unlock_irqrestore(&dev->irqlock, flags);
	/* 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_reload_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 enum s5p_mfc_node_type s5p_mfc_get_node_type(struct file *file)
{
	struct video_device *vdev = video_devdata(file);

	if (!vdev) {
		mfc_err("failed to get video_device");
		return MFCNODE_INVALID;
	}
	if (vdev->index == 0)
		return MFCNODE_DECODER;
	else if (vdev->index == 1)
		return MFCNODE_ENCODER;
	return MFCNODE_INVALID;
}

static void s5p_mfc_clear_int_flags(struct s5p_mfc_dev *dev)
{
	mfc_write(dev, 0, S5P_FIMV_RISC_HOST_INT);
	mfc_write(dev, 0, S5P_FIMV_RISC2HOST_CMD);
	mfc_write(dev, 0xffff, S5P_FIMV_SI_RTN_CHID);
}

static void s5p_mfc_handle_frame_all_extracted(struct s5p_mfc_ctx *ctx)
{
	struct s5p_mfc_buf *dst_buf;

	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->v4l2_buf.index);
		vb2_set_plane_payload(dst_buf->b, 0, 0);
		vb2_set_plane_payload(dst_buf->b, 1, 0);
		list_del(&dst_buf->list);
		ctx->dst_queue_cnt--;
		dst_buf->b->v4l2_buf.sequence = (ctx->sequence++);

		if (s5p_mfc_read_shm(ctx, PIC_TIME_TOP) ==
			s5p_mfc_read_shm(ctx, PIC_TIME_BOT))
			dst_buf->b->v4l2_buf.field = V4L2_FIELD_NONE;
		else
			dst_buf->b->v4l2_buf.field = V4L2_FIELD_INTERLACED;

		ctx->dec_dst_flag &= ~(1 << dst_buf->b->v4l2_buf.index);
		vb2_buffer_done(dst_buf->b, 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 = s5p_mfc_get_dec_y_adr();
	unsigned int frame_type = s5p_mfc_get_frame_type();

	/* Copy timestamp / timecode from decoded src to dst and set
	   appropraite 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, 0) == dec_y_addr) {
			memcpy(&dst_buf->b->v4l2_buf.timecode,
				&src_buf->b->v4l2_buf.timecode,
				sizeof(struct v4l2_timecode));
			memcpy(&dst_buf->b->v4l2_buf.timestamp,
				&src_buf->b->v4l2_buf.timestamp,
				sizeof(struct timeval));
			switch (frame_type) {
			case S5P_FIMV_DECODE_FRAME_I_FRAME:
				dst_buf->b->v4l2_buf.flags |=
						V4L2_BUF_FLAG_KEYFRAME;
				break;
			case S5P_FIMV_DECODE_FRAME_P_FRAME:
				dst_buf->b->v4l2_buf.flags |=
						V4L2_BUF_FLAG_PFRAME;
				break;
			case S5P_FIMV_DECODE_FRAME_B_FRAME:
				dst_buf->b->v4l2_buf.flags |=
						V4L2_BUF_FLAG_BFRAME;
				break;
			}
			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 = s5p_mfc_get_dspl_y_adr();
	unsigned int frame_type = s5p_mfc_get_frame_type();
	unsigned int index;

	/* 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, 0) == dspl_y_addr) {
			list_del(&dst_buf->list);
			ctx->dst_queue_cnt--;
			dst_buf->b->v4l2_buf.sequence = ctx->sequence;
			if (s5p_mfc_read_shm(ctx, PIC_TIME_TOP) ==
				s5p_mfc_read_shm(ctx, PIC_TIME_BOT))
				dst_buf->b->v4l2_buf.field = V4L2_FIELD_NONE;
			else
				dst_buf->b->v4l2_buf.field =
							V4L2_FIELD_INTERLACED;
			vb2_set_plane_payload(dst_buf->b, 0, ctx->luma_size);
			vb2_set_plane_payload(dst_buf->b, 1, ctx->chroma_size);
			clear_bit(dst_buf->b->v4l2_buf.index,
							&ctx->dec_dst_flag);

			vb2_buffer_done(dst_buf->b,
				err ? VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE);

			index = dst_buf->b->v4l2_buf.index;
			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;
	struct s5p_mfc_buf *src_buf;
	unsigned long flags;
	unsigned int res_change;

	unsigned int index;

	dst_frame_status = s5p_mfc_get_dspl_status()
				& S5P_FIMV_DEC_STATUS_DECODING_STATUS_MASK;
	res_change = s5p_mfc_get_dspl_status()
				& S5P_FIMV_DEC_STATUS_RESOLUTION_MASK;
	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) {
		ctx->state = MFCINST_RES_CHANGE_INIT;
		s5p_mfc_clear_int_flags(dev);
		wake_up_ctx(ctx, reason, err);
		if (test_and_clear_bit(0, &dev->hw_lock) == 0)
			BUG();
		s5p_mfc_clock_off();
		s5p_mfc_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) {
			s5p_mfc_handle_frame_all_extracted(ctx);
			ctx->state = MFCINST_RES_CHANGE_END;
			goto leave_handle_frame;
		} else {
			s5p_mfc_handle_frame_all_extracted(ctx);
		}
	}

	if (dst_frame_status == S5P_FIMV_DEC_STATUS_DECODING_DISPLAY ||
		dst_frame_status == S5P_FIMV_DEC_STATUS_DECODING_ONLY)
		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_get_consumed_stream();
		if (ctx->codec_mode != S5P_FIMV_CODEC_H264_DEC &&
			s5p_mfc_get_frame_type() == S5P_FIMV_DECODE_FRAME_P_FRAME
					&& ctx->consumed_stream + STUFF_BYTE <
					src_buf->b->v4l2_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 {
			index = src_buf->b->v4l2_buf.index;
			mfc_debug(2, "MFC needs next buffer\n");
			ctx->consumed_stream = 0;
			list_del(&src_buf->list);
			ctx->src_queue_cnt--;
			if (s5p_mfc_err_dec(err) > 0)
				vb2_buffer_done(src_buf->b, VB2_BUF_STATE_ERROR);
			else
				vb2_buffer_done(src_buf->b, 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->dpb_count)
		clear_work_bit(ctx);
	s5p_mfc_clear_int_flags(dev);
	wake_up_ctx(ctx, reason, err);
	if (test_and_clear_bit(0, &dev->hw_lock) == 0)
		BUG();
	s5p_mfc_clock_off();
	s5p_mfc_try_run(dev);
}

/* Error handling for interrupt */
static void s5p_mfc_handle_error(struct s5p_mfc_ctx *ctx,
				 unsigned int reason, unsigned int err)
{
	struct s5p_mfc_dev *dev;
	unsigned long flags;

	/* If no context is available then all necessary
	 * processing has been done. */
	if (ctx == 0)
		return;

	dev = ctx->dev;
	mfc_err("Interrupt Error: %08x\n", err);
	s5p_mfc_clear_int_flags(dev);
	wake_up_dev(dev, reason, err);

	/* Error recovery is dependent on the state of context */
	switch (ctx->state) {
	case MFCINST_INIT:
		/* This error had to happen while acquireing instance */
	case MFCINST_GOT_INST:
		/* This error had to happen while parsing the header */
	case MFCINST_HEAD_PARSED:
		/* This error had to happen while setting dst buffers */
	case MFCINST_RETURN_INST:
		/* This error had to happen while releasing instance */
		clear_work_bit(ctx);
		wake_up_ctx(ctx, reason, err);
		if (test_and_clear_bit(0, &dev->hw_lock) == 0)
			BUG();
		s5p_mfc_clock_off();
		ctx->state = MFCINST_ERROR;
		break;
	case MFCINST_FINISHING:
	case MFCINST_FINISHED:
	case MFCINST_RUNNING:
		/* It is higly probable that an error occured
		 * 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_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);
		spin_unlock_irqrestore(&dev->irqlock, flags);
		if (test_and_clear_bit(0, &dev->hw_lock) == 0)
			BUG();
		s5p_mfc_clock_off();
		break;
	default:
		mfc_err("Encountered an error interrupt which had not been handled\n");
		break;
	}
	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;
	unsigned int guard_width, guard_height;

	if (ctx == 0)
		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_get_img_width();
		ctx->img_height = s5p_mfc_get_img_height();

		ctx->buf_width = ALIGN(ctx->img_width,
						S5P_FIMV_NV12MT_HALIGN);
		ctx->buf_height = ALIGN(ctx->img_height,
						S5P_FIMV_NV12MT_VALIGN);
		mfc_debug(2, "SEQ Done: Movie dimensions %dx%d, "
			"buffer dimensions: %dx%d\n", ctx->img_width,
				ctx->img_height, ctx->buf_width,
						ctx->buf_height);
		if (ctx->codec_mode == S5P_FIMV_CODEC_H264_DEC) {
			ctx->luma_size = ALIGN(ctx->buf_width *
				ctx->buf_height, S5P_FIMV_DEC_BUF_ALIGN);
			ctx->chroma_size = ALIGN(ctx->buf_width *
					 ALIGN((ctx->img_height >> 1),
					       S5P_FIMV_NV12MT_VALIGN),
					       S5P_FIMV_DEC_BUF_ALIGN);
			ctx->mv_size = ALIGN(ctx->buf_width *
					ALIGN((ctx->buf_height >> 2),
					S5P_FIMV_NV12MT_VALIGN),
					S5P_FIMV_DEC_BUF_ALIGN);
		} else {
			guard_width = ALIGN(ctx->img_width + 24,
					S5P_FIMV_NV12MT_HALIGN);
			guard_height = ALIGN(ctx->img_height + 16,
						S5P_FIMV_NV12MT_VALIGN);
			ctx->luma_size = ALIGN(guard_width *
				guard_height, S5P_FIMV_DEC_BUF_ALIGN);
			guard_width = ALIGN(ctx->img_width + 16,
						S5P_FIMV_NV12MT_HALIGN);
			guard_height = ALIGN((ctx->img_height >> 1) + 4,
						S5P_FIMV_NV12MT_VALIGN);
			ctx->chroma_size = ALIGN(guard_width *
				guard_height, S5P_FIMV_DEC_BUF_ALIGN);
			ctx->mv_size = 0;
		}
		ctx->dpb_count = s5p_mfc_get_dpb_count();
		if (ctx->img_width == 0 || ctx->img_height == 0)
			ctx->state = MFCINST_ERROR;
		else
			ctx->state = MFCINST_HEAD_PARSED;
	}
	s5p_mfc_clear_int_flags(dev);
	clear_work_bit(ctx);
	if (test_and_clear_bit(0, &dev->hw_lock) == 0)
		BUG();
	s5p_mfc_clock_off();
	s5p_mfc_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 == 0)
		return;
	dev = ctx->dev;
	s5p_mfc_clear_int_flags(dev);
	ctx->int_type = reason;
	ctx->int_err = err;
	ctx->int_cond = 1;
	spin_lock(&dev->condlock);
	clear_bit(ctx->num, &dev->ctx_work_bits);
	spin_unlock(&dev->condlock);
	if (err == 0) {
		ctx->state = MFCINST_RUNNING;
		if (!ctx->dpb_flush_flag) {
			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_STATE_DONE);
			}
			spin_unlock_irqrestore(&dev->irqlock, flags);
		} else {
			ctx->dpb_flush_flag = 0;
		}
		if (test_and_clear_bit(0, &dev->hw_lock) == 0)
			BUG();

		s5p_mfc_clock_off();

		wake_up(&ctx->queue);
		s5p_mfc_try_run(dev);
	} else {
		if (test_and_clear_bit(0, &dev->hw_lock) == 0)
			BUG();

		s5p_mfc_clock_off();

		wake_up(&ctx->queue);
	}
}

/* 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_get_int_reason();
	err = s5p_mfc_get_int_err();
	mfc_debug(1, "Int reason: %d (err: %08x)\n", reason, err);
	switch (reason) {
	case S5P_FIMV_R2H_CMD_ERR_RET:
		/* An error has occured */
		if (ctx->state == MFCINST_RUNNING &&
			s5p_mfc_err_dec(err) >= S5P_FIMV_ERR_WARNINGS_START)
			s5p_mfc_handle_frame(ctx, reason, err);
		else
			s5p_mfc_handle_error(ctx, reason, err);
		clear_bit(0, &dev->enter_suspend);
		break;

	case S5P_FIMV_R2H_CMD_SLICE_DONE_RET:
	case S5P_FIMV_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");
			s5p_mfc_clear_int_flags(dev);
			wake_up_ctx(ctx, reason, err);
			if (test_and_clear_bit(0, &dev->hw_lock) == 0)
				BUG();
			s5p_mfc_clock_off();
			s5p_mfc_try_run(dev);
		} else {
			s5p_mfc_handle_frame(ctx, reason, err);
		}
		break;

	case S5P_FIMV_R2H_CMD_SEQ_DONE_RET:
		s5p_mfc_handle_seq_done(ctx, reason, err);
		break;

	case S5P_FIMV_R2H_CMD_OPEN_INSTANCE_RET:
		ctx->inst_no = s5p_mfc_get_inst_no();
		ctx->state = MFCINST_GOT_INST;
		clear_work_bit(ctx);
		wake_up(&ctx->queue);
		goto irq_cleanup_hw;

	case S5P_FIMV_R2H_CMD_CLOSE_INSTANCE_RET:
		clear_work_bit(ctx);
		ctx->state = MFCINST_FREE;
		wake_up(&ctx->queue);
		goto irq_cleanup_hw;

	case S5P_FIMV_R2H_CMD_SYS_INIT_RET:
	case S5P_FIMV_R2H_CMD_FW_STATUS_RET:
	case S5P_FIMV_R2H_CMD_SLEEP_RET:
	case S5P_FIMV_R2H_CMD_WAKEUP_RET:
		if (ctx)
			clear_work_bit(ctx);
		s5p_mfc_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_FIMV_R2H_CMD_INIT_BUFFERS_RET:
		s5p_mfc_handle_init_buffers(ctx, reason, err);
		break;
	default:
		mfc_debug(2, "Unknown int reason\n");
		s5p_mfc_clear_int_flags(dev);
	}
	mfc_debug_leave();
	return IRQ_HANDLED;
irq_cleanup_hw:
	s5p_mfc_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_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 s5p_mfc_dev *dev = video_drvdata(file);
	struct s5p_mfc_ctx *ctx = NULL;
	struct vb2_queue *q;
	unsigned long flags;
	int ret = 0;

	mfc_debug_enter();
	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, video_devdata(file));
	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 */
	spin_lock_irqsave(&dev->condlock, flags);
	clear_bit(ctx->num, &dev->ctx_work_bits);
	spin_unlock_irqrestore(&dev->condlock, flags);
	dev->ctx[ctx->num] = ctx;
	if (s5p_mfc_get_node_type(file) == MFCNODE_DECODER) {
		ctx->type = MFCINST_DECODER;
		ctx->c_ops = get_dec_codec_ops();
		/* 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 (s5p_mfc_get_node_type(file) == MFCNODE_ENCODER) {
		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;
		/* 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 = -1;
	/* 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_alloc_and_load_firmware(dev);
		if (ret)
			goto err_alloc_fw;
		/* Init the FW */
		ret = s5p_mfc_init_hw(dev);
		if (ret)
			goto err_init_hw;
		s5p_mfc_clock_off();
	}
	/* Init videobuf2 queue for CAPTURE */
	q = &ctx->vq_dst;
	q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
	q->drv_priv = &ctx->fh;
	if (s5p_mfc_get_node_type(file) == MFCNODE_DECODER) {
		q->io_modes = VB2_MMAP;
		q->ops = get_dec_queue_ops();
	} else if (s5p_mfc_get_node_type(file) == MFCNODE_ENCODER) {
		q->io_modes = VB2_MMAP | VB2_USERPTR;
		q->ops = get_enc_queue_ops();
	} else {
		ret = -ENOENT;
		goto err_queue_init;
	}
	q->mem_ops = (struct vb2_mem_ops *)&vb2_dma_contig_memops;
	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;
	if (s5p_mfc_get_node_type(file) == MFCNODE_DECODER) {
		q->io_modes = VB2_MMAP;
		q->ops = get_dec_queue_ops();
	} else if (s5p_mfc_get_node_type(file) == MFCNODE_ENCODER) {
		q->io_modes = VB2_MMAP | VB2_USERPTR;
		q->ops = get_enc_queue_ops();
	} else {
		ret = -ENOENT;
		goto err_queue_init;
	}
	q->mem_ops = (struct vb2_mem_ops *)&vb2_dma_contig_memops;
	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);
	mfc_debug_leave();
	return ret;
	/* Deinit when failure occured */
err_queue_init:
err_init_hw:
	s5p_mfc_release_firmware(dev);
err_alloc_fw:
	dev->ctx[ctx->num] = 0;
	del_timer_sync(&dev->watchdog_timer);
	s5p_mfc_clock_off();
err_pwr_enable:
	if (dev->num_inst == 1) {
		if (s5p_mfc_power_off() < 0)
			mfc_err("power off failed\n");
		s5p_mfc_release_firmware(dev);
	}
err_ctrls_setup:
	s5p_mfc_dec_ctrls_delete(ctx);
err_bad_node:
err_no_ctx:
	v4l2_fh_del(&ctx->fh);
	v4l2_fh_exit(&ctx->fh);
	kfree(ctx);
err_alloc:
	dev->num_inst--;
	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;
	unsigned long flags;

	mfc_debug_enter();
	s5p_mfc_clock_on();
	vb2_queue_release(&ctx->vq_src);
	vb2_queue_release(&ctx->vq_dst);
	/* Mark context as idle */
	spin_lock_irqsave(&dev->condlock, flags);
	clear_bit(ctx->num, &dev->ctx_work_bits);
	spin_unlock_irqrestore(&dev->condlock, flags);
	/* If instance was initialised then
	 * return instance and free reosurces */
	if (ctx->inst_no != MFC_NO_INSTANCE_SET) {
		mfc_debug(2, "Has to free instance\n");
		ctx->state = MFCINST_RETURN_INST;
		spin_lock_irqsave(&dev->condlock, flags);
		set_bit(ctx->num, &dev->ctx_work_bits);
		spin_unlock_irqrestore(&dev->condlock, flags);
		s5p_mfc_clean_ctx_int_flags(ctx);
		s5p_mfc_try_run(dev);
		/* Wait until instance is returned or timeout occured */
		if (s5p_mfc_wait_for_done_ctx
		    (ctx, S5P_FIMV_R2H_CMD_CLOSE_INSTANCE_RET, 0)) {
			s5p_mfc_clock_off();
			mfc_err("Err returning instance\n");
		}
		mfc_debug(2, "After free instance\n");
		/* Free resources */
		s5p_mfc_release_codec_buffers(ctx);
		s5p_mfc_release_instance_buffer(ctx);
		if (ctx->type == MFCINST_DECODER)
			s5p_mfc_release_dec_desc_buffer(ctx);

		ctx->inst_no = MFC_NO_INSTANCE_SET;
	}
	/* 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 - release firmware\n");
		/* reset <-> F/W release */
		s5p_mfc_reset(dev);
		s5p_mfc_release_firmware(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] = 0;
	s5p_mfc_dec_ctrls_delete(ctx);
	v4l2_fh_del(&ctx->fh);
	v4l2_fh_exit(&ctx->fh);
	kfree(ctx);
	mfc_debug_leave();
	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;

	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, &src_q->done_wq, wait);
	poll_wait(file, &dst_q->done_wq, wait);
	mutex_lock(&dev->mfc_mutex);
	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:
	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,
};

static int match_child(struct device *dev, void *data)
{
	if (!dev_name(dev))
		return 0;
	return !strcmp(dev_name(dev), (char *)data);
}

/* 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 = kzalloc(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");
		ret = -ENODEV;
		goto err_dev;
	}

	ret = s5p_mfc_init_pm(dev);
	if (ret < 0) {
		dev_err(&pdev->dev, "failed to get mfc clock source\n");
		goto err_clk;
	}

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (res == NULL) {
		dev_err(&pdev->dev, "failed to get memory region resource\n");
		ret = -ENOENT;
		goto err_res;
	}

	dev->mfc_mem = request_mem_region(res->start, resource_size(res),
					  pdev->name);
	if (dev->mfc_mem == NULL) {
		dev_err(&pdev->dev, "failed to get memory region\n");
		ret = -ENOENT;
		goto err_mem_reg;
	}
	dev->regs_base = ioremap(dev->mfc_mem->start, resource_size(dev->mfc_mem));
	if (dev->regs_base == NULL) {
		dev_err(&pdev->dev, "failed to ioremap address region\n");
		ret = -ENOENT;
		goto err_ioremap;
	}

	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_get_res;
	}
	dev->irq = res->start;
	ret = request_irq(dev->irq, s5p_mfc_irq, IRQF_DISABLED, pdev->name,
									dev);
	if (ret) {
		dev_err(&pdev->dev, "Failed to install irq (%d)\n", ret);
		goto err_req_irq;
	}

	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_find_child;
	}
	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_find_child;
	}

	dev->alloc_ctx[0] = vb2_dma_contig_init_ctx(dev->mem_dev_l);
	if (IS_ERR_OR_NULL(dev->alloc_ctx[0])) {
		ret = PTR_ERR(dev->alloc_ctx[0]);
		goto err_mem_init_ctx_0;
	}
	dev->alloc_ctx[1] = vb2_dma_contig_init_ctx(dev->mem_dev_r);
	if (IS_ERR_OR_NULL(dev->alloc_ctx[1])) {
		ret = PTR_ERR(dev->alloc_ctx[1]);
		goto err_mem_init_ctx_1;
	}

	mutex_init(&dev->mfc_mutex);

	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;
	/* Locking in file operations other than ioctl should be done
	   by the driver, not the V4L2 core.
	   This driver needs auditing so that this flag can be removed. */
	set_bit(V4L2_FL_LOCK_ALL_FOPS, &vfd->flags);
	vfd->v4l2_dev	= &dev->v4l2_dev;
	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;
	/* This should not be necessary */
	set_bit(V4L2_FL_LOCK_ALL_FOPS, &vfd->flags);
	vfd->v4l2_dev	= &dev->v4l2_dev;
	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;

	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:
	vb2_dma_contig_cleanup_ctx(dev->alloc_ctx[1]);
err_mem_init_ctx_1:
	vb2_dma_contig_cleanup_ctx(dev->alloc_ctx[0]);
err_mem_init_ctx_0:
err_find_child:
	free_irq(dev->irq, dev);
err_req_irq:
err_get_res:
	iounmap(dev->regs_base);
	dev->regs_base = NULL;
err_ioremap:
	release_resource(dev->mfc_mem);
	kfree(dev->mfc_mem);
err_mem_reg:
err_res:
	s5p_mfc_final_pm(dev);
err_clk:
err_dev:
	kfree(dev);
	pr_debug("%s-- with error\n", __func__);
	return ret;

}

/* Remove the driver */
static int __devexit 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);
	vb2_dma_contig_cleanup_ctx(dev->alloc_ctx[0]);
	vb2_dma_contig_cleanup_ctx(dev->alloc_ctx[1]);

	free_irq(dev->irq, dev);
	iounmap(dev->regs_base);
	if (dev->mfc_mem) {
		release_resource(dev->mfc_mem);
		kfree(dev->mfc_mem);
		dev->mfc_mem = NULL;
	}
	s5p_mfc_final_pm(dev);
	kfree(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;
	return s5p_mfc_sleep(m_dev);
	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 || m_dev->ctx[m_dev->curr_ctx]->int_cond,
			msecs_to_jiffies(MFC_INT_TIMEOUT));

		if (ret == 0) {
			mfc_err("Waiting for hardware to finish timed out\n");
			return -EIO;
		}
	}
	return 0;
}

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_RUNTIME
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);
	int pre_power;

	if (!m_dev->alloc_ctx)
		return 0;
	pre_power = atomic_read(&m_dev->pm.power);
	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 platform_driver s5p_mfc_driver = {
	.probe	= s5p_mfc_probe,
	.remove	= __devexit_p(s5p_mfc_remove),
	.driver	= {
		.name	= S5P_MFC_NAME,
		.owner	= THIS_MODULE,
		.pm	= &s5p_mfc_pm_ops
	},
};

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");