tuner: add maru_tuner
authorTaeHyeong Lee <time.lee@samsung.com>
Thu, 14 Jul 2016 01:34:45 +0000 (10:34 +0900)
committerSeokYeon Hwang <syeon.hwang@samsung.com>
Thu, 28 Jul 2016 05:21:00 +0000 (14:21 +0900)
Change-Id: Icaf347d674bf8e9893ebcb3a521fbd3dcf007c5a
Signed-off-by: TaeHyeong Lee <time.lee@samsung.com>
13 files changed:
tizen/src/ecs/Makefile.objs
tizen/src/ecs/ecs_msg_device.c
tizen/src/ecs/ecs_tuner.h [new file with mode: 0644]
tizen/src/hw/maru_device_ids.h
tizen/src/hw/pci/Makefile.objs
tizen/src/hw/pci/maru_dtv_audio.c [new file with mode: 0644]
tizen/src/hw/pci/maru_dtv_audio.h [new file with mode: 0644]
tizen/src/hw/pci/maru_external_input_pci.c [new file with mode: 0644]
tizen/src/hw/pci/maru_external_input_pci.h [new file with mode: 0644]
tizen/src/hw/pci/maru_tuner.c [new file with mode: 0644]
tizen/src/hw/pci/maru_tuner.h [new file with mode: 0644]
tizen/src/hw/pci/maru_tuner_decoder.c [new file with mode: 0644]
tizen/src/hw/pci/maru_tuner_decoder.h [new file with mode: 0644]

index b43f474..b0a65ae 100644 (file)
@@ -5,4 +5,4 @@ obj-y += ecs_eventcast.o
 obj-y += ecs_sensor.o
 obj-y += ecs_hds.o
 obj-y += ecs_nfc.o
-obj-y += ecs_sdcard.o
+obj-y += ecs_sdcard.o
\ No newline at end of file
index 3a68274..d9919e2 100644 (file)
 #include "debug_ch.h"
 #include "ecs_sdcard.h"
 
+#if defined(CONFIG_LIBAV)
+#include "ecs_tuner.h"
+#endif
+
+
 MULTI_DEBUG_CHANNEL(qemu, ecs);
 
 static void msgproc_device_ans(ECS_Client *ccli, const char *category, bool succeed, const char *data)
@@ -304,6 +309,90 @@ static void msgproc_device_req_nfc(ECS_Client *ccli, ECS__DeviceReq *msg)
     g_free(data);
 }
 
+#if defined(CONFIG_LIBAV)
+static void send_tuner_info(struct tune_info *info)
+{
+    char data[TUNER_DATA_SIZE] = {0, };
+    char cmd[10] = "TUNER_DTV";
+
+    if (strlen(info->map_table_path) < MAX_PATH_LEN) {
+        sprintf(data, "%d:%d:%d:%d:%d:%d:%s", info->is_streaming, info->frequency, info->modulation,
+                info->system, info->country, info->playmode, info->map_table_path);
+    }
+
+    make_send_device_ntf(cmd, MSG_GROUP_STATUS, STATUS_TUNER, data);
+}
+
+static bool msgproc_device_req_tuner(ECS_Client* ccli, ECS__DeviceReq* msg, char * cmd)
+{
+    char* data = NULL;
+    struct tune_info info;
+    type_group group = (type_group) (msg->group & 0xff);
+    type_action action = (type_action) (msg->action & 0xff);
+
+    if (msg->has_data && msg->data.len > 0)
+    {
+        data = (char*) g_malloc0(msg->data.len + 1);
+        memcpy(data, msg->data.data, msg->data.len);
+    }
+
+    LOG_INFO("tuner group: %d, action : %d\n", group, action);
+
+    if (action == TUNER_GET_INFO) {
+        if (marutuner_get_tuned_info(&info) < 0) {
+            LOG_TRACE("tuner : Not tuned yet\n");
+            info.is_streaming = false;
+            info.frequency = 0;
+            info.modulation = 13;
+            info.playmode = 1;
+        } else {
+            LOG_TRACE(" tuner : current tune - freq:%ld  mod:%d  is_stream:%s playmode:%d\n",
+                    (long int)info.frequency, info.modulation, info.is_streaming?"on":"off",
+                    info.playmode);
+        }
+        LOG_TRACE("ts_path:%s\n",info.map_table_path);
+        send_tuner_info(&info);
+    } else if (action == TUNER_STILL_INFO) {
+        char token[] = ":";
+        char *section = strtok(data, token);
+        int still_on = atoi(section);
+
+        LOG_TRACE("tuner : Still image : %d \n", still_on);
+        marutuner_switch_playmode(0, still_on);
+    } else if (action == TUNER_CHANGE_CONF) {
+        //call change file function
+        char *path = data;
+        marutuner_set_table_file(path);
+        LOG_TRACE("tuner : change configuration file : %s \n", data);
+    } else {
+        char token[] = ":";
+        char *section = strtok(data, token);
+        info.is_streaming = atoi(section);
+
+        section = strtok(NULL, token);
+        info.frequency = (uint32_t)atoi(section);
+
+        section = strtok(NULL, token);
+        info.modulation = (fe_modulation_t)atoi(section);
+        LOG_TRACE("tuner : antenna=%d, freq=%d, mod=%d",
+                info.is_streaming, info.frequency, info.modulation);
+
+        if (action == TUNER_CHANGE_INFO) {
+            //FIXME use proper devnum value after implementing dual tuner configure is done
+            marutuner_toggle_stream(0, &info, info.is_streaming);
+        } else if (action == TUNER_CHANGE_FILE) {
+            //FIXME use proper devnum value after implementing dual tuner configure is done
+            marutuner_switch_ts(0, &info);
+        }
+    }
+
+    if (data) {
+        g_free(data);
+    }
+    return true;
+}
+#endif
+
 #ifndef CONFIG_EXTENSION_PATH
 bool msgproc_device_req_ext(ECS_Client *ccli, ECS__DeviceReq *msg)
 {
@@ -345,6 +434,10 @@ bool msgproc_device_req(ECS_Client *ccli, ECS__DeviceReq *msg)
         msgproc_device_req_nfc(ccli, msg);
     } else if (!strcmp(cmd, "sdcard")) {
         handle_sdcard((char *)msg->data.data, msg->data.len);
+    #if defined(CONFIG_LIBAV)
+    } else if (!strcmp(cmd, CMD_TUNER)) {
+        msgproc_device_req_tuner(ccli, msg, cmd);
+    #endif
     } else if (msgproc_device_req_ext(ccli, msg)) {
         LOG_INFO("Extension request. cmd [%s]\n", cmd);
     } else {
diff --git a/tizen/src/ecs/ecs_tuner.h b/tizen/src/ecs/ecs_tuner.h
new file mode 100644 (file)
index 0000000..8e17e8e
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Emulator Control Server Extension
+ *
+ * Copyright (c) 2015 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Contact:
+ *  Jinhyung choi   <jinh0.choi@samsung.com>
+ *  Chulho Song     <ch81.song@samsung.com>
+ *  HakHyun Kim     <haken.kim@samsung.com>
+ *  Sangho Park     <sangho.p@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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ * Contributors:
+ * - S-Core Co., Ltd
+ *
+ */
+
+#include "hw/pci/maru_tuner.h"
+
+#define CMD_TUNER "tuner"
+
+#define TUNER_MSG_SIZE  4140
+#define TUNER_DATA_SIZE 4125
+
+#define ECS_TUNER_MSG_GROUP_ECP                     1
+
+// Tuner message action
+#define TUNER_CHANGE_INFO       1
+#define TUNER_GET_INFO          2
+#define TUNER_CHANGE_FILE       3
+#define TUNER_SYSTEM_INFO       4
+#define TUNER_STILL_INFO        5
+#define TUNER_CHANGE_CONF       6
+#define STATUS_TUNER            221
+
+static bool msgproc_device_req_tuner(ECS_Client* ccli, ECS__DeviceReq* msg, char * cmd);
+
index b1766c7..2809ddb 100644 (file)
 
 
 /* PCI */
-#define PCI_VENDOR_ID_TIZEN                0xC9B5
-#define PCI_DEVICE_ID_VIRTUAL_BRIGHTNESS   0x1014
-#define PCI_DEVICE_ID_VIRTUAL_CAMERA       0x1018
-#define PCI_DEVICE_ID_VIRTUAL_CODEC        0x101C
+#define PCI_VENDOR_ID_TIZEN                     0xC9B5
+#define PCI_DEVICE_ID_VIRTUAL_BRIGHTNESS        0x1014
+#define PCI_DEVICE_ID_VIRTUAL_CAMERA            0x1018
+#define PCI_DEVICE_ID_VIRTUAL_CODEC             0x101C
 /* Device ID 0x1000 through 0x103F inclusive is a virtio device */
 #define PCI_DEVICE_ID_VIRTIO_MARU_TOUCHSCREEN   0x101D
 #define PCI_DEVICE_ID_VIRTIO_MARU_KEYBOARD      0x1020
 
 #define PCI_DEVICE_ID_VIRTUAL_BRILL_CODEC       0x1040
 
+#define PCI_DEVICE_ID_VIRTUAL_TUNER             0x1044
+#define PCI_DEVICE_ID_VIRTUAL_TUNER_DECODER     0x1048
+#define PCI_DEVICE_ID_VIRTUAL_DTV_AUDIO         0x104C
+#define PCI_DEVICE_ID_VIRTUAL_EXTERNAL_INPUT    0x1019
+
 /* Virtio */
 /*
 +----------------------+--------------------+---------------+
index 809cb97..93873c5 100644 (file)
@@ -1,5 +1,8 @@
 obj-$(CONFIG_LIBAV) += maru_brillcodec_device.o
 obj-$(CONFIG_LIBAV) += maru_brillcodec.o
+obj-$(CONFIG_LIBAV) += maru_tuner.o maru_tuner_decoder.o maru_dtv_audio.o
+obj-y += maru_external_input_pci.o
+
 ifdef CONFIG_LINUX
 LIBS += -ldl
 endif
@@ -38,6 +41,9 @@ maru_brillcodec_device.o-libs := $(LIBAV_LIBS)
 maru_brillcodec.o-cflags := $(LIBAV_CFLAGS)
 maru_brillcodec.o-libs := $(LIBAV_LIBS)
 ifdef CONFIG_LIBAV
+maru_tuner.o-cflags := $(LIBAV_CFLAGS)
+maru_tuner_decoder.o-cflags := $(LIBAV_CFLAGS)
+maru_dtv_audio.o-cflags := $(LIBAV_CFLAGS)
 ifdef CONFIG_DXVA2
 maru_dxva2_plugin.o-cflags := $(LIBAV_CFLAGS)
 endif
diff --git a/tizen/src/hw/pci/maru_dtv_audio.c b/tizen/src/hw/pci/maru_dtv_audio.c
new file mode 100644 (file)
index 0000000..077885a
--- /dev/null
@@ -0,0 +1,1552 @@
+/*
+ * Maru virtual DTV audio device
+ *
+ * Copyright (C) 2014 Samsung Electronics Co., Ltd. All rights reserved.
+ *
+ * Contact:
+ * Byeongki Shin <bk0121.shin@samsung.com>
+ * Sangho Park <sangho1206.park@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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA  02110-1301, USA.
+ *
+ * Contributors:
+ * - S-Core Co., Ltd
+ *
+ */
+
+#include "tizen/src/emulator.h"
+#include "qemu-common.h"
+#include "hw/maru_device_ids.h"
+#include "maru_dtv_audio.h"
+#include "util/new_debug_ch.h"
+
+/*
+ * WORKING NOTICE(THIS COMMENT SHOULD BE REMOVED)
+ *
+ * The 'Comment-out' codes will be required later.(eg. by mixer)
+ * The #ifdef-#endif clause may have to be removed.
+ */
+
+DECLARE_DEBUG_CHANNEL(dtv_audio);
+
+enum {
+    AC97_Reset                     = 0x00,
+    AC97_Master_Volume_Mute        = 0x02,
+    AC97_Headphone_Volume_Mute     = 0x04,
+    AC97_Master_Volume_Mono_Mute   = 0x06,
+    AC97_Master_Tone_RL            = 0x08,
+    AC97_PC_BEEP_Volume_Mute       = 0x0A,
+    AC97_Phone_Volume_Mute         = 0x0C,
+    AC97_Mic_Volume_Mute           = 0x0E,
+    AC97_Line_In_Volume_Mute       = 0x10,
+    AC97_CD_Volume_Mute            = 0x12,
+    AC97_Video_Volume_Mute         = 0x14,
+    AC97_Aux_Volume_Mute           = 0x16,
+    AC97_PCM_Out_Volume_Mute       = 0x18,
+    AC97_Record_Select             = 0x1A,
+    AC97_Record_Gain_Mute          = 0x1C,
+    AC97_Record_Gain_Mic_Mute      = 0x1E,
+    AC97_General_Purpose           = 0x20,
+    AC97_3D_Control                = 0x22,
+    AC97_AC_97_RESERVED            = 0x24,
+    AC97_Powerdown_Ctrl_Stat       = 0x26,
+    AC97_Extended_Audio_ID         = 0x28,
+    AC97_Extended_Audio_Ctrl_Stat  = 0x2A,
+    AC97_PCM_Front_DAC_Rate        = 0x2C,
+    AC97_PCM_Surround_DAC_Rate     = 0x2E,
+    AC97_PCM_LFE_DAC_Rate          = 0x30,
+    AC97_PCM_LR_ADC_Rate           = 0x32,
+    AC97_MIC_ADC_Rate              = 0x34,
+    AC97_6Ch_Vol_C_LFE_Mute        = 0x36,
+    AC97_6Ch_Vol_L_R_Surround_Mute = 0x38,
+    AC97_Vendor_Reserved           = 0x58,
+    AC97_Sigmatel_Analog           = 0x6c, /* We emulate a Sigmatel codec */
+    AC97_Sigmatel_Dac2Invert       = 0x6e, /* We emulate a Sigmatel codec */
+    AC97_Vendor_ID1                = 0x7c,
+    AC97_Vendor_ID2                = 0x7e
+};
+
+#define SOFT_VOLUME
+#define SR_FIFOE 16             /* rwc */
+#define SR_BCIS  8              /* rwc */
+#define SR_LVBCI 4              /* rwc */
+#define SR_CELV  2              /* ro */
+#define SR_DCH   1              /* ro */
+#define SR_VALID_MASK ((1 << 5) - 1)
+#define SR_WCLEAR_MASK (SR_FIFOE | SR_BCIS | SR_LVBCI)
+#define SR_RO_MASK (SR_DCH | SR_CELV)
+#define SR_INT_MASK (SR_FIFOE | SR_BCIS | SR_LVBCI)
+
+#define CR_IOCE  16             /* rw */
+#define CR_FEIE  8              /* rw */
+#define CR_LVBIE 4              /* rw */
+#define CR_RR    2              /* rw */
+#define CR_RPBM  1              /* rw */
+#define CR_VALID_MASK ((1 << 5) - 1)
+#define CR_DONT_CLEAR_MASK (CR_IOCE | CR_FEIE | CR_LVBIE)
+
+#define GC_WR    4              /* rw */
+#define GC_CR    2              /* rw */
+#define GC_VALID_MASK ((1 << 6) - 1)
+
+#define GS_MD3   (1<<17)        /* rw */
+#define GS_AD3   (1<<16)        /* rw */
+#define GS_RCS   (1<<15)        /* rwc */
+#define GS_B3S12 (1<<14)        /* ro */
+#define GS_B2S12 (1<<13)        /* ro */
+#define GS_B1S12 (1<<12)        /* ro */
+#define GS_S1R1  (1<<11)        /* rwc */
+#define GS_S0R1  (1<<10)        /* rwc */
+#define GS_S1CR  (1<<9)         /* ro */
+#define GS_S0CR  (1<<8)         /* ro */
+#define GS_MINT  (1<<7)         /* ro */
+#define GS_POINT (1<<6)         /* ro */
+#define GS_PIINT (1<<5)         /* ro */
+#define GS_RSRVD ((1<<4)|(1<<3))
+#define GS_MOINT (1<<2)         /* ro */
+#define GS_MIINT (1<<1)         /* ro */
+#define GS_GSCI  1              /* rwc */
+#define GS_RO_MASK (GS_B3S12|                   \
+                    GS_B2S12|                   \
+                    GS_B1S12|                   \
+                    GS_S1CR|                    \
+                    GS_S0CR|                    \
+                    GS_MINT|                    \
+                    GS_POINT|                   \
+                    GS_PIINT|                   \
+                    GS_RSRVD|                   \
+                    GS_MOINT|                   \
+                    GS_MIINT)
+#define GS_VALID_MASK ((1 << 18) - 1)
+#define GS_WCLEAR_MASK (GS_RCS|GS_S1R1|GS_S0R1|GS_GSCI)
+
+#define BD_IOC (1<<31)
+#define BD_BUP (1<<30)
+
+#define EACS_VRA 1
+#define EACS_VRM 8
+
+#define MUTE_SHIFT 15
+
+#define REC_MASK 7
+
+#define VOL_LAD 3
+
+enum {
+    REC_MIC = 0,
+    REC_CD,
+    REC_VIDEO,
+    REC_AUX,
+    REC_LINE_IN,
+    REC_STEREO_MIX,
+    REC_MONO_MIX,
+    REC_PHONE
+};
+
+enum {
+    BUP_SET = 1,
+    BUP_LAST = 2
+};
+
+#define MKREGS(prefix, start)                   \
+enum {                                          \
+    prefix ## _BDBAR = start,                   \
+    prefix ## _CIV = start + 4,                 \
+    prefix ## _LVI = start + 5,                 \
+    prefix ## _SR = start + 6,                  \
+    prefix ## _PICB = start + 8,                \
+    prefix ## _PIV = start + 10,                \
+    prefix ## _CR = start + 11                  \
+}
+
+enum {
+    PI_INDEX = 0,
+    PO_INDEX,
+    MC_INDEX,
+    LAST_INDEX
+};
+
+MKREGS (PI, PI_INDEX * 16);
+MKREGS (PO, PO_INDEX * 16);
+MKREGS (MC, MC_INDEX * 16);
+
+enum {
+    GLOB_CNT = 0x2c,
+    GLOB_STA = 0x30,
+    CAS      = 0x34
+};
+
+#define GET_BM(index) (((index) >> 4) & 3)
+
+//static void po_callback (void *opaque, int free);
+//static void pi_callback (void *opaque, int avail);
+//static void mc_callback (void *opaque, int avail);
+
+static void warm_reset (MaruDtvAudioState *s)
+{
+    (void) s;
+}
+
+static void cold_reset (MaruDtvAudioState *s)
+{
+    (void) s;
+}
+
+
+
+static void fetch_bd (MaruDtvAudioState *s, AC97BusMasterRegs *r)
+{
+    uint8_t b[8];
+
+    pci_dma_read (&s->dev, r->bdbar + r->civ * 8, b, 8);
+    r->bd_valid = 1;
+    r->bd.addr = le32_to_cpu (*(uint32_t *) &b[0]) & ~3;
+    r->bd.ctl_len = le32_to_cpu (*(uint32_t *) &b[4]);
+    r->picb = r->bd.ctl_len & 0xffff;
+    LOG_TRACE("bd %2d addr=%#x ctl=%#06x len=%#x(%d bytes)\n",
+           r->civ, r->bd.addr, r->bd.ctl_len >> 16,
+           r->bd.ctl_len & 0xffff,
+           (r->bd.ctl_len & 0xffff) << 1);
+}
+
+static void update_sr (MaruDtvAudioState *s, AC97BusMasterRegs *r, uint32_t new_sr)
+{
+    int event = 0;
+    int level = 0;
+    uint32_t new_mask = new_sr & SR_INT_MASK;
+    uint32_t old_mask = r->sr & SR_INT_MASK;
+    uint32_t masks[] = {GS_PIINT, GS_POINT, GS_MINT};
+
+    if (new_mask ^ old_mask) {
+        /** @todo is IRQ deasserted when only one of status bits is cleared? */
+        if (!new_mask) {
+            event = 1;
+            level = 0;
+        }
+        else {
+            if ((new_mask & SR_LVBCI) && (r->cr & CR_LVBIE)) {
+                event = 1;
+                level = 1;
+            }
+            if ((new_mask & SR_BCIS) && (r->cr & CR_IOCE)) {
+                event = 1;
+                level = 1;
+            }
+        }
+    }
+
+    r->sr = new_sr;
+
+    LOG_TRACE("IOC%d LVB%d sr=%#x event=%d level=%d\n",
+           r->sr & SR_BCIS, r->sr & SR_LVBCI,
+           r->sr,
+           event, level);
+
+    if (!event)
+        return;
+
+    if (level) {
+        s->glob_sta |= masks[r - s->bm_regs];
+        LOG_TRACE("set irq level=1\n");
+        pci_irq_assert(&s->dev);
+    }
+    else {
+        s->glob_sta &= ~masks[r - s->bm_regs];
+        LOG_TRACE("set irq level=0\n");
+        pci_irq_deassert(&s->dev);
+    }
+}
+
+static void voice_set_active (MaruDtvAudioState *s, int bm_index, int on)
+{
+    switch (bm_index) {
+    case PI_INDEX:
+        AUD_set_active_in (s->voice_pi, on);
+        break;
+
+    case PO_INDEX:
+        AUD_set_active_out (s->voice_po, on);
+        break;
+
+    case MC_INDEX:
+        AUD_set_active_in (s->voice_mc, on);
+        break;
+
+    default:
+        AUD_log ("ac97", "invalid bm_index(%d) in voice_set_active", bm_index);
+        break;
+    }
+}
+
+static void reset_bm_regs (MaruDtvAudioState *s, AC97BusMasterRegs *r)
+{
+    LOG_TRACE("reset_bm_regs\n");
+    r->bdbar = 0;
+    r->civ = 0;
+    r->lvi = 0;
+    /** todo do we need to do that? */
+    update_sr (s, r, SR_DCH);
+    r->picb = 0;
+    r->piv = 0;
+    r->cr = r->cr & CR_DONT_CLEAR_MASK;
+    r->bd_valid = 0;
+
+    voice_set_active (s, r - s->bm_regs, 0);
+    memset (s->silence, 0, sizeof (s->silence));
+}
+
+static void mixer_store (MaruDtvAudioState *s, uint32_t i, uint16_t v)
+{
+    if (i + 2 > sizeof (s->mixer_data)) {
+        LOG_TRACE("mixer_store: index %d out of bounds %zd\n",
+               i, sizeof (s->mixer_data));
+        return;
+    }
+
+    s->mixer_data[i + 0] = v & 0xff;
+    s->mixer_data[i + 1] = v >> 8;
+}
+
+static uint16_t mixer_load (MaruDtvAudioState *s, uint32_t i)
+{
+    uint16_t val = 0xffff;
+
+    if (i + 2 > sizeof (s->mixer_data)) {
+        LOG_TRACE("mixer_load: index %d out of bounds %zd\n",
+               i, sizeof (s->mixer_data));
+    }
+    else {
+        val = s->mixer_data[i + 0] | (s->mixer_data[i + 1] << 8);
+    }
+
+    return val;
+}
+
+static void open_voice (MaruDtvAudioState *s, int index, int freq)
+{
+//    struct audsettings as;
+
+//    as.freq = freq;
+//    as.nchannels = 2;
+//    as.fmt = AUD_FMT_S16;
+//    as.endianness = 0;
+
+    if (freq > 0) {
+        s->invalid_freq[index] = 0;
+        switch (index) {
+        case PI_INDEX:
+#if 0
+            s->voice_pi = AUD_open_in (
+                &s->card,
+                s->voice_pi,
+                "ac97.pi",
+                s,
+                pi_callback,
+                &as
+                );
+#endif
+            break;
+
+        case PO_INDEX:
+#if 0
+            s->voice_po = AUD_open_out (
+                &s->card,
+                s->voice_po,
+                "ac97.po",
+                s,
+                po_callback,
+                &as
+                );
+#endif
+            break;
+
+        case MC_INDEX:
+#if 0
+            s->voice_mc = AUD_open_in (
+                &s->card,
+                s->voice_mc,
+                "ac97.mc",
+                s,
+                mc_callback,
+                &as
+                );
+#endif
+            break;
+        }
+    }
+    else {
+        s->invalid_freq[index] = freq;
+        switch (index) {
+        case PI_INDEX:
+      //      AUD_close_in (&s->card, s->voice_pi);
+      //      s->voice_pi = NULL;
+            break;
+
+        case PO_INDEX:
+      //      AUD_close_out (&s->card, s->voice_po);
+      //      s->voice_po = NULL;
+            break;
+
+        case MC_INDEX:
+       //     AUD_close_in (&s->card, s->voice_mc);
+       //     s->voice_mc = NULL;
+            break;
+        }
+    }
+}
+
+static void reset_voices (MaruDtvAudioState *s, uint8_t active[LAST_INDEX])
+{
+    uint16_t freq;
+
+    freq = mixer_load (s, AC97_PCM_LR_ADC_Rate);
+    open_voice (s, PI_INDEX, freq);
+//    AUD_set_active_in (s->voice_pi, active[PI_INDEX]);
+
+    freq = mixer_load (s, AC97_PCM_Front_DAC_Rate);
+    open_voice (s, PO_INDEX, freq);
+//    AUD_set_active_out (s->voice_po, active[PO_INDEX]);
+
+    freq = mixer_load (s, AC97_MIC_ADC_Rate);
+    open_voice (s, MC_INDEX, freq);
+//    AUD_set_active_in (s->voice_mc, active[MC_INDEX]);
+}
+
+static void get_volume (uint16_t vol, uint16_t mask, int inverse,
+                        int *mute, uint8_t *lvol, uint8_t *rvol)
+{
+    *mute = (vol >> MUTE_SHIFT) & 1;
+    *rvol = (255 * (vol & mask)) / mask;
+    *lvol = (255 * ((vol >> 8) & mask)) / mask;
+
+    if (inverse) {
+        *rvol = 255 - *rvol;
+        *lvol = 255 - *lvol;
+    }
+}
+
+static void update_combined_volume_out (MaruDtvAudioState *s)
+{
+    uint8_t lvol, rvol, plvol, prvol;
+    int mute, pmute;
+
+    get_volume (mixer_load (s, AC97_Master_Volume_Mute), 0x3f, 1,
+                &mute, &lvol, &rvol);
+    get_volume (mixer_load (s, AC97_PCM_Out_Volume_Mute), 0x1f, 1,
+                &pmute, &plvol, &prvol);
+
+    mute = mute | pmute;
+    lvol = (lvol * plvol) / 255;
+    rvol = (rvol * prvol) / 255;
+
+    AUD_set_volume_out (s->voice_po, mute, lvol, rvol);
+}
+
+static void update_volume_in (MaruDtvAudioState *s)
+{
+    uint8_t lvol, rvol;
+    int mute;
+
+    get_volume (mixer_load (s, AC97_Record_Gain_Mute), 0x0f, 0,
+                &mute, &lvol, &rvol);
+
+    AUD_set_volume_in (s->voice_pi, mute, lvol, rvol);
+}
+
+
+static void set_volume (MaruDtvAudioState *s, int index, uint32_t val)
+{
+    LOG_TRACE("(%s):index=%#x, val=%#x\n", __func__, index, val);
+    switch (index) {
+    case AC97_Master_Volume_Mute:
+        val &= 0xbf3f;
+        mixer_store (s, index, val);
+        update_combined_volume_out (s);
+        break;
+    case AC97_PCM_Out_Volume_Mute:
+        val &= 0x9f1f;
+        mixer_store (s, index, val);
+        update_combined_volume_out (s);
+        break;
+    case AC97_Record_Gain_Mute:
+        val &= 0x8f0f;
+        mixer_store (s, index, val);
+        update_volume_in (s);
+        break;
+    }
+}
+
+static void record_select (MaruDtvAudioState *s, uint32_t val)
+{
+    uint8_t rs = val & REC_MASK;
+    uint8_t ls = (val >> 8) & REC_MASK;
+    mixer_store (s, AC97_Record_Select, rs | (ls << 8));
+}
+
+static void mixer_reset (MaruDtvAudioState *s)
+{
+    uint8_t active[LAST_INDEX];
+
+    LOG_TRACE ("mixer_reset\n");
+    memset (s->mixer_data, 0, sizeof (s->mixer_data));
+    memset (active, 0, sizeof (active));
+    mixer_store (s, AC97_Reset                   , 0x0000); /* 6940 */
+    mixer_store (s, AC97_Headphone_Volume_Mute   , 0x0000);
+    mixer_store (s, AC97_Master_Volume_Mono_Mute , 0x0000);
+    mixer_store (s, AC97_Master_Tone_RL,           0x0000);
+    mixer_store (s, AC97_PC_BEEP_Volume_Mute     , 0x0000);
+    mixer_store (s, AC97_Phone_Volume_Mute       , 0x0000);
+    mixer_store (s, AC97_Mic_Volume_Mute         , 0x0000);
+    mixer_store (s, AC97_Line_In_Volume_Mute     , 0x0000);
+    mixer_store (s, AC97_CD_Volume_Mute          , 0x0000);
+    mixer_store (s, AC97_Video_Volume_Mute       , 0x0000);
+    mixer_store (s, AC97_Aux_Volume_Mute         , 0x0000);
+    mixer_store (s, AC97_Record_Gain_Mic_Mute    , 0x0000);
+    mixer_store (s, AC97_General_Purpose         , 0x0000);
+    mixer_store (s, AC97_3D_Control              , 0x0000);
+    mixer_store (s, AC97_Powerdown_Ctrl_Stat     , 0x000f);
+
+    /*
+     * Sigmatel 9700 (STAC9700)
+     */
+    mixer_store (s, AC97_Vendor_ID1              , 0x8384);
+    mixer_store (s, AC97_Vendor_ID2              , 0x7600); /* 7608 */
+
+    mixer_store (s, AC97_Extended_Audio_ID       , 0x0809);
+    mixer_store (s, AC97_Extended_Audio_Ctrl_Stat, 0x0009);
+    mixer_store (s, AC97_PCM_Front_DAC_Rate      , 0xbb80);
+    mixer_store (s, AC97_PCM_Surround_DAC_Rate   , 0xbb80);
+    mixer_store (s, AC97_PCM_LFE_DAC_Rate        , 0xbb80);
+    mixer_store (s, AC97_PCM_LR_ADC_Rate         , 0xbb80);
+    mixer_store (s, AC97_MIC_ADC_Rate            , 0xbb80);
+
+    record_select (s, 0);
+    set_volume (s, AC97_Master_Volume_Mute, 0x8000);
+    set_volume (s, AC97_PCM_Out_Volume_Mute, 0x8808);
+    set_volume (s, AC97_Record_Gain_Mute, 0x8808);
+
+    reset_voices (s, active);
+}
+
+/**
+ * Native audio mixer
+ * I/O Reads
+ */
+static uint32_t nam_readb (void *opaque, uint32_t addr)
+{
+    MaruDtvAudioState *s = opaque;
+    LOG_TRACE("U nam readb %#x\n", addr);
+    s->cas = 0;
+    return ~0U;
+}
+
+static uint32_t nam_readw (void *opaque, uint32_t addr)
+{
+    MaruDtvAudioState *s = opaque;
+    uint32_t val = ~0U;
+    uint32_t index = addr;
+    s->cas = 0;
+    val = mixer_load (s, index);
+    return val;
+}
+
+static uint32_t nam_readl (void *opaque, uint32_t addr)
+{
+    MaruDtvAudioState *s = opaque;
+    LOG_TRACE("U nam readl %#x\n", addr);
+    s->cas = 0;
+    return ~0U;
+}
+
+/**
+ * Native audio mixer
+ * I/O Writes
+ */
+static void nam_writeb (void *opaque, uint32_t addr, uint32_t val)
+{
+    MaruDtvAudioState *s = opaque;
+    LOG_TRACE("U nam writeb %#x <- %#x\n", addr, val);
+    s->cas = 0;
+}
+
+static void nam_writew (void *opaque, uint32_t addr, uint32_t val)
+{
+    MaruDtvAudioState *s = opaque;
+    uint32_t index = addr;
+    s->cas = 0;
+
+    LOG_TRACE("(%s)index=%#x\n", __func__, index);
+    switch (index) {
+    case AC97_Reset:
+        mixer_reset (s);
+        break;
+    case AC97_Powerdown_Ctrl_Stat:
+        val &= ~0x800f;
+        val |= mixer_load (s, index) & 0xf;
+        mixer_store (s, index, val);
+        break;
+    case AC97_PCM_Out_Volume_Mute:
+    case AC97_Master_Volume_Mute:
+    case AC97_Record_Gain_Mute:
+        set_volume (s, index, val);
+        break;
+    case AC97_Record_Select:
+        record_select (s, val);
+        break;
+    case AC97_Vendor_ID1:
+    case AC97_Vendor_ID2:
+        LOG_TRACE("Attempt to write vendor ID to %#x\n", val);
+        break;
+    case AC97_Extended_Audio_ID:
+        LOG_TRACE("Attempt to write extended audio ID to %#x\n", val);
+        break;
+    case AC97_Extended_Audio_Ctrl_Stat:
+        if (!(val & EACS_VRA)) {
+            mixer_store (s, AC97_PCM_Front_DAC_Rate, 0xbb80);
+            mixer_store (s, AC97_PCM_LR_ADC_Rate,    0xbb80);
+            open_voice (s, PI_INDEX, 48000);
+            open_voice (s, PO_INDEX, 48000);
+        }
+        if (!(val & EACS_VRM)) {
+            mixer_store (s, AC97_MIC_ADC_Rate, 0xbb80);
+            open_voice (s, MC_INDEX, 48000);
+        }
+        LOG_TRACE("Setting extended audio control to %#x\n", val);
+        mixer_store (s, AC97_Extended_Audio_Ctrl_Stat, val);
+        break;
+    case AC97_PCM_Front_DAC_Rate:
+        if (mixer_load (s, AC97_Extended_Audio_Ctrl_Stat) & EACS_VRA) {
+            mixer_store (s, index, val);
+            LOG_TRACE("Set front DAC rate to %d\n", val);
+            open_voice (s, PO_INDEX, val);
+        }
+        else {
+            LOG_TRACE("Attempt to set front DAC rate to %d, "
+                   "but VRA is not set\n",
+                   val);
+        }
+        break;
+    case AC97_MIC_ADC_Rate:
+        if (mixer_load (s, AC97_Extended_Audio_Ctrl_Stat) & EACS_VRM) {
+            mixer_store (s, index, val);
+            LOG_TRACE("Set MIC ADC rate to %d\n", val);
+            open_voice (s, MC_INDEX, val);
+        }
+        else {
+            LOG_TRACE("Attempt to set MIC ADC rate to %d, "
+                   "but VRM is not set\n",
+                   val);
+        }
+        break;
+    case AC97_PCM_LR_ADC_Rate:
+        if (mixer_load (s, AC97_Extended_Audio_Ctrl_Stat) & EACS_VRA) {
+            mixer_store (s, index, val);
+            LOG_TRACE("Set front LR ADC rate to %d\n", val);
+            open_voice (s, PI_INDEX, val);
+        }
+        else {
+            LOG_TRACE("Attempt to set LR ADC rate to %d, but VRA is not set\n",
+                    val);
+        }
+        break;
+    case AC97_Headphone_Volume_Mute:
+    case AC97_Master_Volume_Mono_Mute:
+    case AC97_Master_Tone_RL:
+    case AC97_PC_BEEP_Volume_Mute:
+    case AC97_Phone_Volume_Mute:
+    case AC97_Mic_Volume_Mute:
+    case AC97_Line_In_Volume_Mute:
+    case AC97_CD_Volume_Mute:
+    case AC97_Video_Volume_Mute:
+    case AC97_Aux_Volume_Mute:
+    case AC97_Record_Gain_Mic_Mute:
+    case AC97_General_Purpose:
+    case AC97_3D_Control:
+    case AC97_Sigmatel_Analog:
+    case AC97_Sigmatel_Dac2Invert:
+        /* None of the features in these regs are emulated, so they are RO */
+        break;
+    case 0x100:
+    default:
+        LOG_TRACE("U nam writew %#x <- %#x\n", addr, val);
+        mixer_store (s, index, val);
+        break;
+    }
+}
+
+static void nam_writel (void *opaque, uint32_t addr, uint32_t val)
+{
+    MaruDtvAudioState *s = opaque;
+    LOG_TRACE("U nam writel %#x <- %#x\n", addr, val);
+    s->cas = 0;
+}
+
+/**
+ * Native audio bus master
+ * I/O Reads
+ */
+static uint32_t nabm_readb (void *opaque, uint32_t addr)
+{
+    MaruDtvAudioState *s = opaque;
+    AC97BusMasterRegs *r = NULL;
+    uint32_t index = addr;
+    uint32_t val = ~0U;
+
+    switch (index) {
+    case CAS:
+        LOG_TRACE("CAS %d\n", s->cas);
+        val = s->cas;
+        s->cas = 1;
+        break;
+    case PI_CIV:
+    case PO_CIV:
+    case MC_CIV:
+        r = &s->bm_regs[GET_BM (index)];
+        val = r->civ;
+        LOG_TRACE("CIV[%d] -> %#x\n", GET_BM (index), val);
+        break;
+    case PI_LVI:
+    case PO_LVI:
+    case MC_LVI:
+        r = &s->bm_regs[GET_BM (index)];
+        val = r->lvi;
+        LOG_TRACE("LVI[%d] -> %#x\n", GET_BM (index), val);
+        break;
+    case PI_PIV:
+    case PO_PIV:
+    case MC_PIV:
+        r = &s->bm_regs[GET_BM (index)];
+        val = r->piv;
+        LOG_TRACE("PIV[%d] -> %#x\n", GET_BM (index), val);
+        break;
+    case PI_CR:
+    case PO_CR:
+    case MC_CR:
+        r = &s->bm_regs[GET_BM (index)];
+        val = r->cr;
+        LOG_TRACE("CR[%d] -> %#x\n", GET_BM (index), val);
+        break;
+    case PI_SR:
+    case PO_SR:
+    case MC_SR:
+        r = &s->bm_regs[GET_BM (index)];
+        val = r->sr & 0xff;
+        LOG_TRACE("SRb[%d] -> %#x\n", GET_BM (index), val);
+        break;
+    default:
+        LOG_TRACE("U nabm readb %#x -> %#x\n", addr, val);
+        break;
+    }
+    return val;
+}
+
+static uint32_t nabm_readw (void *opaque, uint32_t addr)
+{
+    MaruDtvAudioState *s = opaque;
+    AC97BusMasterRegs *r = NULL;
+    uint32_t index = addr;
+    uint32_t val = ~0U;
+
+    switch (index) {
+    case PI_SR:
+    case PO_SR:
+    case MC_SR:
+        r = &s->bm_regs[GET_BM (index)];
+        val = r->sr;
+        LOG_TRACE("SR[%d] -> %#x\n", GET_BM (index), val);
+        break;
+    case PI_PICB:
+    case PO_PICB:
+    case MC_PICB:
+        r = &s->bm_regs[GET_BM (index)];
+        val = r->picb;
+        LOG_TRACE("PICB[%d] -> %#x\n", GET_BM (index), val);
+        break;
+    default:
+        LOG_TRACE("U nabm readw %#x -> %#x\n", addr, val);
+        break;
+    }
+    return val;
+}
+
+static uint32_t nabm_readl (void *opaque, uint32_t addr)
+{
+    MaruDtvAudioState *s = opaque;
+    AC97BusMasterRegs *r = NULL;
+    uint32_t index = addr;
+    uint32_t val = ~0U;
+
+    switch (index) {
+    case PI_BDBAR:
+    case PO_BDBAR:
+    case MC_BDBAR:
+        r = &s->bm_regs[GET_BM (index)];
+        val = r->bdbar;
+        LOG_TRACE("BMADDR[%d] -> %#x\n", GET_BM (index), val);
+        break;
+    case PI_CIV:
+    case PO_CIV:
+    case MC_CIV:
+        r = &s->bm_regs[GET_BM (index)];
+        val = r->civ | (r->lvi << 8) | (r->sr << 16);
+        LOG_TRACE("CIV LVI SR[%d] -> %#x, %#x, %#x\n", GET_BM (index),
+               r->civ, r->lvi, r->sr);
+        break;
+    case PI_PICB:
+    case PO_PICB:
+    case MC_PICB:
+        r = &s->bm_regs[GET_BM (index)];
+        val = r->picb | (r->piv << 16) | (r->cr << 24);
+        LOG_TRACE("PICB PIV CR[%d] -> %#x %#x %#x %#x\n", GET_BM (index),
+               val, r->picb, r->piv, r->cr);
+        break;
+    case GLOB_CNT:
+        val = s->glob_cnt;
+        LOG_TRACE("glob_cnt -> %#x\n", val);
+        break;
+    case GLOB_STA:
+        val = s->glob_sta | GS_S0CR;
+        //LOG_TRACE("glob_sta -> %#x\n", val);
+        break;
+    default:
+        LOG_TRACE("U nabm readl %#x -> %#x\n", addr, val);
+        break;
+    }
+    return val;
+}
+
+/**
+ * Native audio bus master
+ * I/O Writes
+ */
+static void nabm_writeb (void *opaque, uint32_t addr, uint32_t val)
+{
+    MaruDtvAudioState *s = opaque;
+    AC97BusMasterRegs *r = NULL;
+    uint32_t index = addr;
+    switch (index) {
+    case PI_LVI:
+    case PO_LVI:
+    case MC_LVI:
+        r = &s->bm_regs[GET_BM (index)];
+        if ((r->cr & CR_RPBM) && (r->sr & SR_DCH)) {
+            r->sr &= ~(SR_DCH | SR_CELV);
+            r->civ = r->piv;
+            r->piv = (r->piv + 1) % 32;
+            fetch_bd (s, r);
+        }
+        r->lvi = val % 32;
+        LOG_TRACE("LVI[%d] <- %#x\n", GET_BM (index), val);
+        break;
+    case PI_CR:
+    case PO_CR:
+    case MC_CR:
+        LOG_TRACE("CR start\n");
+        r = &s->bm_regs[GET_BM (index)];
+        if (val & CR_RR) {
+            reset_bm_regs (s, r);
+        }
+        else {
+            LOG_TRACE("CR step 1\n");
+            r->cr = val & CR_VALID_MASK;
+            if (!(r->cr & CR_RPBM)) {
+                LOG_TRACE("CR step 2\n");
+                voice_set_active (s, r - s->bm_regs, 0);
+                LOG_TRACE("CR step 3\n");
+                r->sr |= SR_DCH;
+            }
+            else {
+                LOG_TRACE("CR step 4\n");
+                r->civ = r->piv;
+                r->piv = (r->piv + 1) % 32;
+                fetch_bd (s, r);
+                r->sr &= ~SR_DCH;
+                voice_set_active (s, r - s->bm_regs, 1);
+                LOG_TRACE("CR step 5\n");
+            }
+        }
+        LOG_TRACE("CR[%d] <- %#x (cr %#x)\n", GET_BM (index), val, r->cr);
+        break;
+    case PI_SR:
+    case PO_SR:
+    case MC_SR:
+        r = &s->bm_regs[GET_BM (index)];
+        r->sr |= val & ~(SR_RO_MASK | SR_WCLEAR_MASK);
+        update_sr (s, r, r->sr & ~(val & SR_WCLEAR_MASK));
+        LOG_TRACE("SR[%d] <- %#x (sr %#x)\n", GET_BM (index), val, r->sr);
+        break;
+    default:
+        LOG_TRACE("U nabm writeb %#x <- %#x\n", addr, val);
+        break;
+    }
+}
+
+static void nabm_writew (void *opaque, uint32_t addr, uint32_t val)
+{
+    MaruDtvAudioState *s = opaque;
+    AC97BusMasterRegs *r = NULL;
+    uint32_t index = addr;
+    switch (index) {
+    case PI_SR:
+    case PO_SR:
+    case MC_SR:
+        r = &s->bm_regs[GET_BM (index)];
+        r->sr |= val & ~(SR_RO_MASK | SR_WCLEAR_MASK);
+        update_sr (s, r, r->sr & ~(val & SR_WCLEAR_MASK));
+        LOG_TRACE("SR[%d] <- %#x (sr %#x)\n", GET_BM (index), val, r->sr);
+        break;
+    default:
+        LOG_TRACE("U nabm writew %#x <- %#x\n", addr, val);
+        break;
+    }
+}
+
+static void nabm_writel (void *opaque, uint32_t addr, uint32_t val)
+{
+    MaruDtvAudioState *s = opaque;
+    AC97BusMasterRegs *r = NULL;
+    uint32_t index = addr;
+    switch (index) {
+    case PI_BDBAR:
+    case PO_BDBAR:
+    case MC_BDBAR:
+        r = &s->bm_regs[GET_BM (index)];
+        r->bdbar = val & ~3;
+        LOG_TRACE("BDBAR[%d] <- %#x (bdbar %#x)\n",
+               GET_BM (index), val, r->bdbar);
+        break;
+    case GLOB_CNT:
+        if (val & GC_WR)
+            warm_reset (s);
+        if (val & GC_CR)
+            cold_reset (s);
+        if (!(val & (GC_WR | GC_CR)))
+            s->glob_cnt = val & GC_VALID_MASK;
+        LOG_TRACE("glob_cnt <- %#x (glob_cnt %#x)\n", val, s->glob_cnt);
+        break;
+    case GLOB_STA:
+        s->glob_sta &= ~(val & GS_WCLEAR_MASK);
+        s->glob_sta |= (val & ~(GS_WCLEAR_MASK | GS_RO_MASK)) & GS_VALID_MASK;
+        //LOG_TRACE("glob_sta <- %#x (glob_sta %#x)\n", val, s->glob_sta);
+        break;
+    default:
+        LOG_TRACE("U nabm writel %#x <- %#x\n", addr, val);
+        break;
+    }
+}
+
+static int write_audio(MaruDtvAudioState *s, int len)
+{
+    int total = 0;
+    int pos = s->rem_pos;
+    int written;
+
+    while (len) {
+        LOG_TRACE("(%s) len(%d), pos(%d), total(%d)\n", __func__, len, pos, total);
+        written = AUD_write(s->voice_po,
+                          (char *)s->remainder->samples + pos,
+                          len);
+        if (written == 0) {
+            LOG_TRACE("(%s) nothing written\n", __func__);
+            break;
+        }
+
+        len -= written;
+        pos += written;
+        total += written;
+    }
+
+    return total;
+}
+
+#if 0
+static void write_bup (MaruDtvAudioState *s, int elapsed)
+{
+    LOG_TRACE ("write_bup\n");
+    if (!(s->bup_flag & BUP_SET)) {
+        if (s->bup_flag & BUP_LAST) {
+            int i;
+            uint8_t *p = s->silence;
+            for (i = 0; i < sizeof (s->silence) / 4; i++, p += 4) {
+                *(uint32_t *) p = s->last_samp;
+            }
+        }
+        else {
+            memset (s->silence, 0, sizeof (s->silence));
+        }
+        s->bup_flag |= BUP_SET;
+    }
+
+    while (elapsed) {
+        int temp = audio_MIN (elapsed, sizeof (s->silence));
+        while (temp) {
+            int copied = AUD_write (s->voice_po, s->silence, temp);
+            if (!copied)
+                return;
+            temp -= copied;
+            elapsed -= copied;
+        }
+    }
+}
+
+static int read_audio (MaruDtvAudioState *s, AC97BusMasterRegs *r,
+                       int max, int *stop)
+{
+    uint8_t tmpbuf[4096];
+    uint32_t addr = r->bd.addr;
+    uint32_t temp = r->picb << 1;
+    uint32_t nread = 0;
+    int to_copy = 0;
+    SWVoiceIn *voice = (r - s->bm_regs) == MC_INDEX ? s->voice_mc : s->voice_pi;
+
+    temp = audio_MIN (temp, max);
+
+    if (!temp) {
+        *stop = 1;
+        return 0;
+    }
+
+    while (temp) {
+        int acquired;
+        to_copy = audio_MIN (temp, sizeof (tmpbuf));
+        acquired = AUD_read (voice, tmpbuf, to_copy);
+        if (!acquired) {
+            *stop = 1;
+            break;
+        }
+        pci_dma_write (&s->dev, addr, tmpbuf, acquired);
+        temp -= acquired;
+        addr += acquired;
+        nread += acquired;
+    }
+
+    r->bd.addr = addr;
+    return nread;
+}
+
+static void transfer_audio (MaruDtvAudioState *s, int index, int elapsed)
+{
+    AC97BusMasterRegs *r = &s->bm_regs[index];
+    int stop = 0;
+
+    if (s->invalid_freq[index]) {
+        AUD_log ("ac97", "attempt to use voice %d with invalid frequency %d\n",
+                 index, s->invalid_freq[index]);
+        return;
+    }
+
+    if (r->sr & SR_DCH) {
+        if (r->cr & CR_RPBM) {
+            switch (index) {
+            case PO_INDEX:
+                write_bup (s, elapsed);
+                break;
+            }
+        }
+        return;
+    }
+
+    while ((elapsed >> 1) && !stop) {
+        int temp;
+
+        if (!r->bd_valid) {
+            LOG_TRACE ("invalid bd\n");
+            fetch_bd (s, r);
+        }
+
+        if (!r->picb) {
+            LOG_TRACE ("fresh bd %d is empty %#x %#x\n",
+                   r->civ, r->bd.addr, r->bd.ctl_len);
+            if (r->civ == r->lvi) {
+                r->sr |= SR_DCH; /* CELV? */
+                s->bup_flag = 0;
+                break;
+            }
+            r->sr &= ~SR_CELV;
+            r->civ = r->piv;
+            r->piv = (r->piv + 1) % 32;
+            fetch_bd (s, r);
+            return;
+        }
+
+        switch (index) {
+        case PO_INDEX:
+            //temp = write_audio (s, r, elapsed, &stop);
+            //elapsed -= temp;
+            //r->picb -= (temp >> 1);
+            break;
+
+        case PI_INDEX:
+        case MC_INDEX:
+            temp = read_audio (s, r, elapsed, &stop);
+            elapsed -= temp;
+            r->picb -= (temp >> 1);
+            break;
+        }
+
+        if (!r->picb) {
+            uint32_t new_sr = r->sr & ~SR_CELV;
+
+            if (r->bd.ctl_len & BD_IOC) {
+                new_sr |= SR_BCIS;
+            }
+
+            if (r->civ == r->lvi) {
+                LOG_TRACE ("Underrun civ (%d) == lvi (%d)\n", r->civ, r->lvi);
+
+                new_sr |= SR_LVBCI | SR_DCH | SR_CELV;
+                stop = 1;
+                s->bup_flag = (r->bd.ctl_len & BD_BUP) ? BUP_LAST : 0;
+            }
+            else {
+                r->civ = r->piv;
+                r->piv = (r->piv + 1) % 32;
+                fetch_bd (s, r);
+            }
+
+            update_sr (s, r, new_sr);
+        }
+    }
+}
+
+static void pi_callback (void *opaque, int avail)
+{
+    transfer_audio (opaque, PI_INDEX, avail);
+}
+
+static void mc_callback (void *opaque, int avail)
+{
+    transfer_audio (opaque, MC_INDEX, avail);
+}
+
+static void po_callback (void *opaque, int free)
+{
+    transfer_audio (opaque, PO_INDEX, free);
+}
+#endif
+static const VMStateDescription vmstate_ac97_bm_regs = {
+    .name = "maru_dtv_ac97_bm_regs",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .fields      = (VMStateField []) {
+        VMSTATE_UINT32 (bdbar, AC97BusMasterRegs),
+        VMSTATE_UINT8 (civ, AC97BusMasterRegs),
+        VMSTATE_UINT8 (lvi, AC97BusMasterRegs),
+        VMSTATE_UINT16 (sr, AC97BusMasterRegs),
+        VMSTATE_UINT16 (picb, AC97BusMasterRegs),
+        VMSTATE_UINT8 (piv, AC97BusMasterRegs),
+        VMSTATE_UINT8 (cr, AC97BusMasterRegs),
+        VMSTATE_UINT32 (bd_valid, AC97BusMasterRegs),
+        VMSTATE_UINT32 (bd.addr, AC97BusMasterRegs),
+        VMSTATE_UINT32 (bd.ctl_len, AC97BusMasterRegs),
+        VMSTATE_END_OF_LIST ()
+    }
+};
+
+static int ac97_post_load (void *opaque, int version_id)
+{
+    uint8_t active[LAST_INDEX];
+    MaruDtvAudioState *s = opaque;
+
+    record_select (s, mixer_load (s, AC97_Record_Select));
+    set_volume (s, AC97_Master_Volume_Mute,
+                mixer_load (s, AC97_Master_Volume_Mute));
+    set_volume (s, AC97_PCM_Out_Volume_Mute,
+                mixer_load (s, AC97_PCM_Out_Volume_Mute));
+    set_volume (s, AC97_Record_Gain_Mute,
+                mixer_load (s, AC97_Record_Gain_Mute));
+
+    active[PI_INDEX] = !!(s->bm_regs[PI_INDEX].cr & CR_RPBM);
+    active[PO_INDEX] = !!(s->bm_regs[PO_INDEX].cr & CR_RPBM);
+    active[MC_INDEX] = !!(s->bm_regs[MC_INDEX].cr & CR_RPBM);
+    reset_voices (s, active);
+
+    s->bup_flag = 0;
+    s->last_samp = 0;
+    return 0;
+}
+
+static bool is_version_2 (void *opaque, int version_id)
+{
+    return version_id == 2;
+}
+
+static const VMStateDescription vmstate_maru_dtv_audio = {
+    .name = "maru_dtv_ac97",
+    .version_id = 3,
+    .minimum_version_id = 2,
+    .minimum_version_id_old = 2,
+    .post_load = ac97_post_load,
+    .fields      = (VMStateField []) {
+        VMSTATE_PCI_DEVICE (dev, MaruDtvAudioState),
+        VMSTATE_UINT32 (glob_cnt, MaruDtvAudioState),
+        VMSTATE_UINT32 (glob_sta, MaruDtvAudioState),
+        VMSTATE_UINT32 (cas, MaruDtvAudioState),
+        VMSTATE_STRUCT_ARRAY (bm_regs, MaruDtvAudioState, 3, 1,
+                              vmstate_ac97_bm_regs, AC97BusMasterRegs),
+        VMSTATE_BUFFER (mixer_data, MaruDtvAudioState),
+        VMSTATE_UNUSED_TEST (is_version_2, 3),
+        VMSTATE_END_OF_LIST ()
+    }
+};
+
+static uint64_t nam_read(void *opaque, hwaddr addr, unsigned size)
+{
+    if ((addr / size) > 256) {
+        return -1;
+    }
+
+    switch (size) {
+    case 1:
+        return nam_readb(opaque, addr);
+    case 2:
+        return nam_readw(opaque, addr);
+    case 4:
+        return nam_readl(opaque, addr);
+    default:
+        return -1;
+    }
+}
+
+static void nam_write(void *opaque, hwaddr addr, uint64_t val,
+                      unsigned size)
+{
+    if ((addr / size) > 256) {
+        return;
+    }
+
+    switch (size) {
+    case 1:
+        nam_writeb(opaque, addr, val);
+        break;
+    case 2:
+        nam_writew(opaque, addr, val);
+        break;
+    case 4:
+        nam_writel(opaque, addr, val);
+        break;
+    }
+}
+
+static const MemoryRegionOps maru_dtv_io_nam_ops = {
+    .read = nam_read,
+    .write = nam_write,
+    .impl = {
+        .min_access_size = 1,
+        .max_access_size = 4,
+    },
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static uint64_t nabm_read(void *opaque, hwaddr addr, unsigned size)
+{
+    if ((addr / size) > 64) {
+        return -1;
+    }
+
+    switch (size) {
+    case 1:
+        return nabm_readb(opaque, addr);
+    case 2:
+        return nabm_readw(opaque, addr);
+    case 4:
+        return nabm_readl(opaque, addr);
+    default:
+        return -1;
+    }
+}
+
+static void nabm_write(void *opaque, hwaddr addr, uint64_t val,
+                      unsigned size)
+{
+    if ((addr / size) > 64) {
+        return;
+    }
+
+    switch (size) {
+    case 1:
+        nabm_writeb(opaque, addr, val);
+        break;
+    case 2:
+        nabm_writew(opaque, addr, val);
+        break;
+    case 4:
+        nabm_writel(opaque, addr, val);
+        break;
+    }
+}
+
+
+static const MemoryRegionOps maru_dtv_io_nabm_ops = {
+    .read = nabm_read,
+    .write = nabm_write,
+    .impl = {
+        .min_access_size = 1,
+        .max_access_size = 4,
+    },
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void ac97_on_reset (void *opaque)
+{
+    MaruDtvAudioState *s = opaque;
+
+    reset_bm_regs (s, &s->bm_regs[0]);
+    reset_bm_regs (s, &s->bm_regs[1]);
+    reset_bm_regs (s, &s->bm_regs[2]);
+
+    /*
+     * Reset the mixer too. The Windows XP driver seems to rely on
+     * this. At least it wants to read the vendor id before it resets
+     * the codec manually.
+     */
+    mixer_reset (s);
+}
+
+/******************************************************************************
+ * Mixer Interfaces
+ *****************************************************************************/
+
+
+/******************************************************************************
+ * PCM Intefaces
+ *****************************************************************************/
+static MaruDtvAudioState *g_state;
+
+static void audio_play(MaruDtvAudioState *s, int to_play, int *free)
+{
+    int written = 0;
+
+    while (to_play) {
+        written = write_audio(s, to_play);
+        if (written == 0) {
+            LOG_TRACE("(%s) nothing written\n", __func__);
+            /* something to do? */
+            break;
+        }
+
+        *free -= written;
+        to_play -= written;
+        s->rem_left -= written;
+        s->rem_pos += written;
+    }
+    LOG_TRACE("(%s) free(%d), to_play(%d), rem_left(%d), rem_pos(%d)\n",
+           __func__, *free, to_play, s->rem_left, s->rem_pos);
+    if (s->remainder && s->rem_left == 0) {
+        av_free(s->remainder->samples);
+        g_free(s->remainder);
+        s->remainder = NULL;
+        s->rem_pos = 0;
+    }
+}
+
+/* once invoked, one element is processed */
+static void playout_cb(void *opaque, int free)
+{
+    MaruDtvAudioState *s = opaque;
+    AudioFrameEntry *elem = NULL;
+    int to_play;
+
+    LOG_TRACE("\n");
+    LOG_TRACE("(%s) rem_left = %d, free = %d\n", __func__, s->rem_left, free);
+
+    /* first, process the remained queue data */
+    to_play = audio_MIN(s->rem_left, free);
+    audio_play(s, to_play, &free);
+
+    /* check that the free is available */
+    if (free == 0) {
+        return;
+    }
+
+    /* next, process a new element from queue */
+    elem = (AudioFrameEntry *)maru_tuner_decoder_pop_queue(NULL, MARUDEC_TYPE_AUDIO);
+    if (elem == NULL) {
+        LOG_TRACE("(%s) there is no queue element.\n", __func__);
+        return;
+    }
+    LOG_TRACE("(%s) elem size(%d), free(%d)\n",
+            __func__, elem->size, free);
+
+    s->remainder = elem;
+    s->rem_left = elem->size;
+    s->rem_pos = 0;
+
+    to_play = audio_MIN(elem->size, free);
+    audio_play(s, to_play, &free);
+}
+
+int maru_dtv_audio_setup(int freq, int nchannels, int fmt, int endians)
+{
+    LOG_TRACE("(%s) freq = %d, nchannels = %d, fmt = %d, endians = %d\n",
+            __func__, freq, nchannels, fmt, endians);
+
+    MaruDtvAudioState *s = (MaruDtvAudioState *)g_state;
+    struct audsettings as;
+
+    if (freq < 0) {
+        return -1;
+    }
+
+    as.freq = freq;
+    as.nchannels = 2;
+    as.fmt = AUD_FMT_S16;
+    as.endianness = 0;
+
+    s->voice_po = AUD_open_out(&s->card, s->voice_po,
+                               "maru-dtv-audio", s,
+                               playout_cb, &as);
+    if (!s->voice_po) {
+        /* something to cleanup */
+        LOG_SEVERE("(%s) audio open for playout failed\n", __func__);
+        return -1;
+    }
+
+    return 0;
+}
+
+void maru_dtv_audio_close(void)
+{
+    LOG_TRACE("(%s)\n", __func__);
+
+    MaruDtvAudioState *s = (MaruDtvAudioState *)g_state;
+    AUD_close_out (&s->card, s->voice_po);
+    s->voice_po = NULL;
+}
+
+void maru_dtv_audio_set_active(bool on)
+{
+    MaruDtvAudioState *s = (MaruDtvAudioState *)g_state;
+
+    if (s->active == on) {
+        return;
+    }
+
+    s->active = on;
+    LOG_TRACE("(%s) active changed to %s\n", __func__, s->active ? "on" : "off");
+
+    AUD_set_active_out(s->voice_po, s->active);
+}
+
+/******************************************************************************
+ * Device Initialization
+ *****************************************************************************/
+static int maru_dtv_audio_initfn (PCIDevice *dev)
+{
+    MaruDtvAudioState *s = DO_UPCAST (MaruDtvAudioState, dev, dev);
+    uint8_t *c = s->dev.config;
+
+    g_state = s;
+
+    /* TODO: no need to override */
+    c[PCI_COMMAND] = 0x00;      /* pcicmd pci command rw, ro */
+    c[PCI_COMMAND + 1] = 0x00;
+
+    /* TODO: */
+    c[PCI_STATUS] = PCI_STATUS_FAST_BACK;      /* pcists pci status rwc, ro */
+    c[PCI_STATUS + 1] = PCI_STATUS_DEVSEL_MEDIUM >> 8;
+
+    c[PCI_CLASS_PROG] = 0x00;      /* pi programming interface ro */
+
+    /* TODO set when bar is registered. no need to override. */
+    /* nabmar native audio mixer base address rw */
+    c[PCI_BASE_ADDRESS_0] = PCI_BASE_ADDRESS_SPACE_IO;
+    c[PCI_BASE_ADDRESS_0 + 1] = 0x00;
+    c[PCI_BASE_ADDRESS_0 + 2] = 0x00;
+    c[PCI_BASE_ADDRESS_0 + 3] = 0x00;
+
+    /* TODO set when bar is registered. no need to override. */
+      /* nabmbar native audio bus mastering base address rw */
+    c[PCI_BASE_ADDRESS_0 + 4] = PCI_BASE_ADDRESS_SPACE_IO;
+    c[PCI_BASE_ADDRESS_0 + 5] = 0x00;
+    c[PCI_BASE_ADDRESS_0 + 6] = 0x00;
+    c[PCI_BASE_ADDRESS_0 + 7] = 0x00;
+
+    if (s->use_broken_id) {
+        c[PCI_SUBSYSTEM_VENDOR_ID] = 0x86;
+        c[PCI_SUBSYSTEM_VENDOR_ID + 1] = 0x80;
+        c[PCI_SUBSYSTEM_ID] = 0x00;
+        c[PCI_SUBSYSTEM_ID + 1] = 0x00;
+    }
+
+    c[PCI_INTERRUPT_LINE] = 0x00;      /* intr_ln interrupt line rw */
+    c[PCI_INTERRUPT_PIN] = 0x01;      /* intr_pn interrupt pin ro */
+
+    memory_region_init_io (&s->io_nam, OBJECT(s), &maru_dtv_io_nam_ops, s,
+                           "dtv-nam", 1024);
+    memory_region_init_io (&s->io_nabm, OBJECT(s), &maru_dtv_io_nabm_ops, s,
+                           "dtv-nabm", 256);
+    pci_register_bar (&s->dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &s->io_nam);
+    pci_register_bar (&s->dev, 1, PCI_BASE_ADDRESS_SPACE_IO, &s->io_nabm);
+    qemu_register_reset(ac97_on_reset, s);
+    AUD_register_card ("maru-dtv-audio", &s->card);
+    ac97_on_reset (s);
+
+    LOG_TRACE("successfully initialized\n");
+
+    return 0;
+}
+
+static void maru_dtv_audio_exitfn (PCIDevice *dev)
+{
+    MaruDtvAudioState *s = DO_UPCAST (MaruDtvAudioState, dev, dev);
+
+    AUD_remove_card(&s->card);
+}
+
+static int maru_dtv_audio_init (PCIBus *bus)
+{
+    pci_create_simple (bus, -1, "maru-dtv-audio");
+    return 0;
+}
+
+static Property maru_dtv_audio_properties[] = {
+    DEFINE_PROP_UINT32 ("use_broken_id", MaruDtvAudioState, use_broken_id, 0),
+    DEFINE_PROP_END_OF_LIST (),
+};
+
+static void maru_dtv_audio_class_init (ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS (klass);
+    PCIDeviceClass *k = PCI_DEVICE_CLASS (klass);
+
+    k->init = maru_dtv_audio_initfn;
+    k->exit = maru_dtv_audio_exitfn;
+    k->vendor_id = PCI_VENDOR_ID_TIZEN;
+    k->device_id = PCI_DEVICE_ID_VIRTUAL_DTV_AUDIO;
+    k->revision = 0x01;
+    k->class_id = PCI_CLASS_MULTIMEDIA_AUDIO;
+    set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
+    dc->desc = "Maru Audio For DTV";
+    dc->vmsd = &vmstate_maru_dtv_audio;
+    dc->props = maru_dtv_audio_properties;
+}
+
+static const TypeInfo maru_dtv_audio_info = {
+    .name          = "maru-dtv-audio",
+    .parent        = TYPE_PCI_DEVICE,
+    .instance_size = sizeof (MaruDtvAudioState),
+    .class_init    = maru_dtv_audio_class_init,
+};
+
+static void maru_dtv_audio_register_types (void)
+{
+    type_register_static (&maru_dtv_audio_info);
+    pci_register_soundhw("maru-dtv-audio", "Maru Audio For DTV", maru_dtv_audio_init);
+}
+
+type_init (maru_dtv_audio_register_types)
diff --git a/tizen/src/hw/pci/maru_dtv_audio.h b/tizen/src/hw/pci/maru_dtv_audio.h
new file mode 100644 (file)
index 0000000..b2be7f3
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * Maru virtual DTV audio device
+ *
+ * Copyright (C) 2014 Samsung Electronics Co., Ltd. All rights reserved.
+ *
+ * Contact:
+ * Byeongki Shin <bk0121.shin@samsung.com>
+ * Sangho Park <sangho1206.park@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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA  02110-1301, USA.
+ *
+ * Contributors:
+ * - S-Core Co., Ltd
+ *
+ */
+
+#ifndef _MARU_DTV_AUDIO_H_
+#define _MARU_DTV_AUDIO_H_
+
+#include "hw/hw.h"
+#include "hw/pci/pci.h"
+#include "hw/audio/audio.h"
+#include "audio/audio.h"
+
+#include "maru_tuner_decoder.h"
+
+typedef struct BD {
+    uint32_t addr;
+    uint32_t ctl_len;
+} BD;
+
+typedef struct AC97BusMasterRegs {
+    uint32_t bdbar;             /* rw 0 */
+    uint8_t civ;                /* ro 0 */
+    uint8_t lvi;                /* rw 0 */
+    uint16_t sr;                /* rw 1 */
+    uint16_t picb;              /* ro 0 */
+    uint8_t piv;                /* ro 0 */
+    uint8_t cr;                 /* rw 0 */
+    unsigned int bd_valid;
+    BD bd;
+} AC97BusMasterRegs;
+
+typedef struct MaruDtvAudioState {
+    PCIDevice           dev;
+    QEMUSoundCard       card;
+
+    uint32_t            use_broken_id;
+    uint32_t            glob_sta;
+    uint32_t            glob_cnt;
+    SWVoiceOut          *voice_po;
+    SWVoiceIn           *voice_pi;
+    SWVoiceIn           *voice_mc;
+    bool                active;
+
+    int                 po_written;
+    AudioFrameEntry     *remainder;
+    int                 rem_pos;
+    int                 rem_left;
+
+    MemoryRegion        io_nam;
+    MemoryRegion        io_nabm;
+
+    AC97BusMasterRegs   bm_regs[3];
+    uint32_t            cas;
+    uint8_t             mixer_data[256];
+    int                 invalid_freq[3];
+    uint32_t            last_samp;
+    int                 bup_flag;
+    uint8_t             silence[128];
+} MaruDtvAudioState;
+
+/* audio device API for decoder */
+/**
+ * make dtv audio setup with given parameters
+ *
+ * return : success 0, fail -1
+ * note : our audio device supports only for 2-channel, S16, and little-endian
+ *        This is same to AC97 setup.
+ */
+int maru_dtv_audio_setup(int freq, int nchannels, int fmt, int endians);
+void maru_dtv_audio_close(void);
+void maru_dtv_audio_set_active(bool on);
+
+#endif  /* _MARU_DTV_AUDIO_H_ */
diff --git a/tizen/src/hw/pci/maru_external_input_pci.c b/tizen/src/hw/pci/maru_external_input_pci.c
new file mode 100644 (file)
index 0000000..eb7428b
--- /dev/null
@@ -0,0 +1,1074 @@
+/*
+ * Common implementation of MARU Virtual External Input device by PCI bus.
+ *
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Contact:
+ * GunSoo Kim <gunsoo83.kim@samsung.com>
+ * HyunJin Lee <hyunjin816.lee@samsung.com>
+ * SangHo Park <sangho1206.park@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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA  02110-1301, USA.
+ *
+ * Contributors:
+ * - S-Core Co., Ltd
+ *
+ */
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <inttypes.h>
+#include <signal.h>
+
+#include "qemu-common.h"
+#include "qemu/main-loop.h"
+#include "qemu/error-report.h"
+#include "exec/cpu-common.h"
+
+#include "hw/maru_device_ids.h"
+#include "util/new_debug_ch.h"
+
+#include "qemu/config-file.h"
+#include "maru_external_input_pci.h"
+#include "hw/vigs/winsys.h"
+#include "hw/vigs/work_queue.h"
+
+DECLARE_DEBUG_CHANNEL(ext_input);
+#define MARU_EXTINPUT_THREAD_NAME    "maru_extinput_worker_thread"
+
+struct maru_extinput_saved_frame {
+    void *data;
+    uint32_t pixelformat;
+    uint32_t width;
+    uint32_t height;
+    uint32_t size;
+};
+
+MaruExtInputState *extinput_state;
+uint32_t onoff_state;
+
+
+static void *rgb_data = NULL;
+static int  rgb_ref = 0;
+
+static struct ext_resolution dst_res[MAX_TYPE] =
+{
+    {ATV_WIDTH, ATV_HEIGHT, "COMPOSITE"}, /* AV */
+    {ATV_WIDTH, ATV_HEIGHT, "ATV"}, /* ATV */
+    {FULLHD_WIDTH, FULLHD_HEIGHT, "COMPONENT"}, /* COMPONENT */
+    {FULLHD_WIDTH, FULLHD_HEIGHT, "SCART"},  /* SCART */
+    {FULLHD_WIDTH, FULLHD_HEIGHT, "HDMI"},   /* HDMI */
+};
+
+static int composite_input = 0;
+static int hdmi_input = 0;
+static int atv_lock = 0;
+
+static struct work_queue *g_render_queue = NULL;
+
+/*
+ * Must only be accessed from 'g_render_queue'.
+ */
+static struct winsys_interface *g_wsi = NULL;
+static struct winsys_surface *g_ws_surface = NULL;
+
+struct maru_extinput_set_output_work_item
+{
+    struct work_queue_item base;
+
+    uint32_t sfc_id;
+};
+
+struct maru_extinput_render_work_item
+{
+    struct work_queue_item base;
+
+    void *data;
+    int width;
+    int height;
+};
+
+static void load_yuv_image(void* fb, enum input_type input, char *path);
+static void* maru_extinput_alloc_rgb(int size, enum input_type input, char *path)
+{
+    assert(atomic_read(&rgb_ref) == 0);
+    //TODO alloc size is fixed to 1920X1080 now, but eventually it should be same as surface size. so alloc & load should be defered right after getting the surface
+
+    rgb_data = (void *)g_malloc0(size);
+    load_yuv_image(rgb_data, input, path);
+    atomic_set(&rgb_ref, 1);
+    return rgb_data;
+}
+
+static void* maru_extinput_get_rgb(void)
+{
+    if(atomic_read(&rgb_ref) == 0)
+        return NULL;
+    atomic_inc(&rgb_ref);
+    return rgb_data;
+}
+
+static int maru_extinput_get_ref(void)
+{
+    return atomic_read(&rgb_ref);
+}
+
+static void maru_extinput_release_rgb(void)
+{
+    assert(atomic_read(&rgb_ref) > 0);
+
+    atomic_dec(&rgb_ref);
+    if (atomic_read(&rgb_ref))
+        return ;
+    g_free(rgb_data);
+    rgb_data = NULL;
+}
+
+static void maru_extinput_set_output_work(struct work_queue_item *wq_item)
+{
+    struct maru_extinput_set_output_work_item *item = (struct maru_extinput_set_output_work_item*)wq_item;
+
+    if (g_ws_surface) {
+        g_ws_surface->release(g_ws_surface);
+        g_ws_surface = NULL;
+    }
+
+    if (item->sfc_id) {
+        g_ws_surface = g_wsi->acquire_surface(g_wsi, item->sfc_id);
+    }
+    g_free(item);
+}
+
+static void maru_extinput_render_work(struct work_queue_item *wq_item)
+{
+    struct maru_extinput_render_work_item *item = (struct maru_extinput_render_work_item*)wq_item;
+
+    if (g_ws_surface) {
+        //assert(g_ws_surface->width == item->width);
+        //assert(g_ws_surface->height == item->height);
+        //assert(item->data == rgb_data);
+        g_ws_surface->draw_pixels(g_ws_surface, item->data);
+    }
+    g_free(item);
+    maru_extinput_release_rgb();
+}
+
+static void maru_extinput_set_output(uint32_t sfc_id)
+{
+    struct maru_extinput_set_output_work_item *item;
+
+    item = g_malloc0(sizeof(*item));
+
+    work_queue_item_init(&item->base, &maru_extinput_set_output_work);
+
+    item->sfc_id = sfc_id;
+
+    work_queue_add_item(g_render_queue, &item->base);
+}
+
+static void maru_extinput_render(void *data, uint32_t width, uint32_t height)
+{
+    struct maru_extinput_render_work_item *item;
+
+    item = g_malloc0(sizeof(*item));
+
+    work_queue_item_init(&item->base, &maru_extinput_render_work);
+
+    item->data = maru_extinput_get_rgb();
+    item->width = width;
+    item->height = height;
+
+    assert(item->data != NULL);
+    work_queue_add_item(g_render_queue, &item->base);
+}
+
+typedef struct tagMaruExtInputConvertFrameInfo {
+    uint32_t width;
+    uint32_t height;
+} MaruExtInputConvertFrameInfo;
+
+static MaruExtInputConvertFrameInfo supported_dst_frames[] = {
+        { 640, 480 },
+        { 1920, 1080 },
+};
+
+static int is_streamon(MaruExtInputState *state)
+{
+    int st;
+    qemu_mutex_lock(&state->thread_mutex);
+    st = state->streamon;
+    qemu_mutex_unlock(&state->thread_mutex);
+    return (st == _MC_THREAD_STREAMON);
+}
+
+static int is_stream_paused(MaruExtInputState *state)
+{
+    int st;
+    qemu_mutex_lock(&state->thread_mutex);
+    st = state->streamon;
+    qemu_mutex_unlock(&state->thread_mutex);
+    return (st == _MC_THREAD_PAUSED);
+}
+/* HDMI device port setting */
+static void maru_hdmi_input_setting(MaruExtInputState *state)
+{
+    MaruExtInputParam *param = state->param;
+    param->top = 0;
+
+    hdmi_input = param->stack[0];
+}
+
+static void load_yuv_image(void* fb, enum input_type input, char *path)
+{
+    int yuv_fd = 0;
+    int nread = 0;
+    int offset = 0;
+    int width, height;
+    char image_path[PATH_MAX] = {0,};
+    const char *file_name;
+    if (path == NULL) {
+        LOG_SEVERE("Failed to get external input image path\n");
+        return;
+    }
+
+    file_name = dst_res[input].filename;
+
+    width = FULLHD_WIDTH;
+    height = FULLHD_HEIGHT;
+    if ((input == ATV) && (atv_lock == 0)) {
+        snprintf(image_path, sizeof(image_path), "%s/%s_%dx%d.rgb", path, "UNLOCKED_ATV", width, height);
+    } else if (input == HDMI) {
+        snprintf(image_path, sizeof(image_path), "%s/%s%d_%dx%d.rgb", path, file_name, hdmi_input + 1, width, height);
+    } else {
+        snprintf(image_path, sizeof(image_path), "%s/%s_%dx%d.rgb", path, file_name, width, height);
+    }
+    LOG_INFO("load %s file\n", image_path);
+    if ((yuv_fd = open(image_path, O_RDONLY | O_BINARY)) == -1)
+    {
+        LOG_SEVERE("Failed to open yuv image file. errstr(%s)\n", strerror(errno));
+        return;
+    }
+
+    do {
+        offset += nread;
+        nread = read(yuv_fd, fb+offset, 128);
+    } while(nread > 0);
+}
+
+static void maru_extinput_device_start_preview(MaruExtInputState *state, enum input_type input)
+{
+    MaruExtInputParam *param = state->param;
+    param->top = 0;
+    if (maru_extinput_get_rgb() == NULL)
+        maru_extinput_alloc_rgb(FULLHD_HEIGHT * FULLHD_WIDTH * 4, input, state->stillimg);
+    else
+        maru_extinput_get_rgb();
+    LOG_TRACE("Starting preview \n");
+    qemu_mutex_lock(&state->thread_mutex);
+    state->streamon = _MC_THREAD_STREAMON;
+    qemu_mutex_unlock(&state->thread_mutex);
+}
+
+static void maru_extinput_device_stop_preview(MaruExtInputState *state)
+{
+    MaruExtInputParam *param = state->param;
+    param->top = 0;
+
+    if (is_streamon(state)) {
+        qemu_mutex_lock(&state->thread_mutex);
+        state->streamon = _MC_THREAD_STREAMOFF;
+        qemu_mutex_unlock(&state->thread_mutex);
+        maru_extinput_release_rgb();
+    }
+
+    LOG_INFO("Stopping preview  %d\n", atomic_read(&rgb_ref));
+}
+
+/* ATV lock status setting */
+static void maru_set_atv_lock(MaruExtInputState *state)
+{
+    MaruExtInputParam *param = state->param;
+    param->top = 0;
+
+    LOG_INFO("lock state is changed : (%d) to (%d)\n", atv_lock, param->stack[0]);
+    atv_lock = param->stack[0];
+
+    if (state->streamon == _MC_THREAD_STREAMON) {
+        maru_extinput_device_stop_preview(state);
+
+        /* wait all frame is cleared */
+        while (maru_extinput_get_ref() != 0)
+            usleep(100);
+
+        maru_extinput_device_start_preview(state, ATV);
+    } else {
+        LOG_TRACE("not streamon state. do nothing.\n");
+    }
+}
+
+static int check_resolution(uint32_t width, uint32_t height)
+{
+    uint32_t index;
+    uint32_t array_size;
+
+    array_size = ARRAY_SIZE(supported_dst_frames);
+    for (index = 0; index < array_size; index++) {
+        if (supported_dst_frames[index].width == width
+                && supported_dst_frames[index].height == height) {
+            break;
+        }
+    }
+    if (index == array_size) {
+        LOG_SEVERE("Not supported width/height: (%d/%d)\n", width, height);
+        return -1;
+    }
+
+    return 0;
+}
+
+static void maru_extinput_device_s_ctrl(MaruExtInputState *state, enum input_type input)
+{
+    uint32_t ctrl_id = 0;
+    MaruExtInputParam *param = state->param;
+    param->top = 0;
+
+    ctrl_id = param->stack[0];
+    switch (ctrl_id) {
+    case V4L2_CID_BRIGHTNESS:
+    case V4L2_CID_CONTRAST:
+    case V4L2_CID_SATURATION:
+    case V4L2_CID_SHARPNESS:
+    case V4L2_CID_HUE:
+        LOG_WARNING("%d is set to the value of the %d, do nothing..\n", param->stack[1], ctrl_id);
+        break;
+    case V4L2_CID_DV_VIDEO_OUTPUT:
+        LOG_INFO("%d is set to the value of the VIDEO_OUTPUT stream:%d\n", param->stack[1], is_streamon(state));
+        if (is_streamon(state)) {
+            maru_extinput_set_output(param->stack[1]);
+            maru_extinput_render(rgb_data, FULLHD_WIDTH, FULLHD_HEIGHT);
+        }
+        break;
+    default:
+        LOG_SEVERE("Our emulator does not support this control: 0x%x\n", ctrl_id);
+        param->errCode = EINVAL;
+        return;
+    }
+}
+
+static void maru_extinput_device_s_ext_ctrls(MaruExtInputState *state, enum input_type input)
+{
+    struct ext_resolution *res = NULL;
+    MaruExtInputParam *param = state->param;
+
+    param->top = 0;
+
+    res = &dst_res[input];
+
+    if (check_resolution(param->stack[0], param->stack[1]) < 0) {
+        LOG_SEVERE("Failed to set video resolution: width:height(%d:%d), errstr(%s)\n",
+                param->stack[0], param->stack[1], strerror(errno));
+        param->errCode = EINVAL;
+        return;
+    }
+
+    res->width = param->stack[0];
+    res->height = param->stack[1];
+
+    LOG_TRACE("Set the control : w:h(%dx%d) %d\n", res->width, res->height, input);
+}
+
+static void maru_extinput_device_g_ext_ctrls(MaruExtInputState *state, enum input_type input)
+{
+    MaruExtInputParam *param = state->param;
+    struct ext_resolution *res = &dst_res[input];
+
+    param->top = 0;
+    param->stack[0] = res->width;
+    param->stack[1] = res->height;
+    LOG_TRACE("Get the control : w:h(%dx%d)\n", res->width, res->height);
+}
+
+static void maru_extinput_device_open(MaruExtInputState *state, enum input_type input)
+{
+    MaruExtInputParam *param = state->param;
+    param->top = 0;
+    LOG_TRACE("open %s device \n", dst_res[input].filename);
+}
+
+static void maru_extinput_device_close(MaruExtInputState *state)
+{
+    if (!is_stream_paused(state))
+        maru_extinput_device_stop_preview(state);
+    LOG_INFO("Closed\n");
+}
+
+/*
+ * HDMI functions
+ */
+static void maru_hdmi_device_s_fmt(MaruExtInputState *state)
+{
+    MaruExtInputParam *param = state->param;
+
+    param->top = 0;
+
+    LOG_TRACE("Set the format: fmt(%c%c%c%C)\n",
+            (char)(param->stack[0]),
+            (char)(param->stack[0] >> 8),
+            (char)(param->stack[0] >> 16),
+            (char)(param->stack[0] >> 24));
+}
+
+static void maru_hdmi_device_g_fmt(MaruExtInputState *state)
+{
+    MaruExtInputParam *param = state->param;
+
+    param->top = 0;
+
+    LOG_TRACE("Get the format\n");
+}
+
+/*
+ * Composite functions
+ */
+static void maru_composite_device_s_input(MaruExtInputState *state)
+{
+    uint32_t input;
+    MaruExtInputParam *param = state->param;
+    param->top = 0;
+
+    input = param->stack[0];
+    if (input == AV || input == ATV) {
+        composite_input = input;
+    } else {
+        LOG_SEVERE("This input(%d) is not supported\n", input);
+        param->errCode = EINVAL;
+    }
+}
+/*
+ *  I/O functions
+ */
+static inline uint32_t
+maru_extinput_mmio_read(void *opaque, hwaddr offset)
+{
+    uint32_t ret = 0;
+    MaruExtInputState *state = (MaruExtInputState *)opaque;
+
+    switch (offset & 0xFF) {
+    case MARU_EXTINPUT_PLATFORM_TYPE:
+        ret = state->platform_type;
+        break;
+    case MARU_EXTINPUT_CMD_ISR:
+        qemu_mutex_lock(&state->thread_mutex);
+        ret = state->isr;
+        if (ret != 0) {
+            pci_set_irq(&state->dev, 0);
+            state->isr = 0;
+        }
+        qemu_mutex_unlock(&state->thread_mutex);
+        break;
+    case MARU_EXTINPUT_CMD_ISR_STATE:
+        qemu_mutex_lock(&state->thread_mutex);
+        ret = state->isr_state;
+        if (ret != 0) {
+            state->isr_state = 0;
+        }
+        qemu_mutex_unlock(&state->thread_mutex);
+        break;
+    case MARU_EXTINPUT_ONOFF_STATE:
+        ret = onoff_state;
+        break;
+    case MARU_EXTINPUT_CMD_G_DATA:
+        ret = state->param->stack[state->param->top++];
+        break;
+    case MARU_HDMI_CMD_OPEN:
+    case MARU_HDMI_CMD_CLOSE:
+    case MARU_HDMI_CMD_START_PREVIEW:
+    case MARU_HDMI_CMD_STOP_PREVIEW:
+    case MARU_HDMI_CMD_S_FMT:
+    case MARU_HDMI_CMD_G_FMT:
+    case MARU_HDMI_CMD_S_CTRL:
+    case MARU_HDMI_CMD_S_EXT_CTRLS:
+    case MARU_HDMI_CMD_G_EXT_CTRLS:
+    case MARU_COMPOSITE_CMD_OPEN:
+    case MARU_COMPOSITE_CMD_CLOSE:
+    case MARU_COMPOSITE_CMD_START_PREVIEW:
+    case MARU_COMPOSITE_CMD_STOP_PREVIEW:
+    case MARU_COMPOSITE_CMD_S_INPUT:
+    case MARU_COMPOSITE_CMD_S_CTRL:
+    case MARU_COMPOSITE_CMD_G_EXT_CTRLS:
+    case MARU_COMPONENT_CMD_OPEN:
+    case MARU_COMPONENT_CMD_CLOSE:
+    case MARU_COMPONENT_CMD_START_PREVIEW:
+    case MARU_COMPONENT_CMD_STOP_PREVIEW:
+    case MARU_COMPONENT_CMD_S_CTRL:
+    case MARU_COMPONENT_CMD_S_EXT_CTRLS:
+    case MARU_COMPONENT_CMD_G_EXT_CTRLS:
+    case MARU_SWITCH_CMD_S_INPUT:
+    case MARU_ATV_LOCK_STATUS:
+        ret = state->param->errCode;
+        state->param->errCode = 0;
+        break;
+    default:
+        LOG_SEVERE("Not supported command: 0x%x\n", offset);
+        ret = EINVAL;
+        break;
+    }
+    return ret;
+}
+
+static inline void
+maru_extinput_mmio_write(void *opaque, hwaddr offset, uint32_t value)
+{
+    MaruExtInputState *state = (MaruExtInputState *)opaque;
+
+    LOG_TRACE("Enter write mmio 0x%x\n", offset & 0xFF);
+
+    switch (offset & 0xFF) {
+    case MARU_EXTINPUT_CMD_S_DATA:
+        state->param->stack[state->param->top++] = value;
+        break;
+    case MARU_EXTINPUT_CMD_DATACLR:
+        memset(state->param, 0, sizeof(MaruExtInputParam));
+        break;
+    case MARU_HDMI_CMD_OPEN:
+        maru_extinput_device_open(state, HDMI);
+        break;
+    case MARU_HDMI_CMD_CLOSE:
+        maru_extinput_device_close(state);
+        break;
+    case MARU_HDMI_CMD_START_PREVIEW:
+        maru_extinput_device_start_preview(state, HDMI);
+        break;
+    case MARU_HDMI_CMD_STOP_PREVIEW:
+        maru_extinput_device_stop_preview(state);
+        memset(state->vaddr, 0, MARU_EXTINPUT_MEM_SIZE);
+        break;
+    case MARU_HDMI_CMD_S_FMT:
+        maru_hdmi_device_s_fmt(state);
+        break;
+    case MARU_HDMI_CMD_G_FMT:
+        maru_hdmi_device_g_fmt(state);
+        break;
+    case MARU_HDMI_CMD_S_CTRL:
+        maru_extinput_device_s_ctrl(state, HDMI);
+        break;
+    case MARU_HDMI_CMD_S_EXT_CTRLS:
+        maru_extinput_device_s_ext_ctrls(state, HDMI);
+        break;
+    case MARU_HDMI_CMD_G_EXT_CTRLS:
+        maru_extinput_device_g_ext_ctrls(state, HDMI);
+        break;
+    case MARU_COMPOSITE_CMD_OPEN:
+        maru_extinput_device_open(state, AV);
+        break;
+    case MARU_COMPOSITE_CMD_CLOSE:
+        maru_extinput_device_close(state);
+        break;
+    case MARU_COMPOSITE_CMD_START_PREVIEW:
+        if (composite_input == AV) {
+            maru_extinput_device_start_preview(state, AV);
+        } else if (composite_input == ATV) {
+            maru_extinput_device_start_preview(state, ATV);
+        }
+        break;
+    case MARU_COMPOSITE_CMD_STOP_PREVIEW:
+        maru_extinput_device_stop_preview(state);
+        memset(state->vaddr, 0, MARU_EXTINPUT_MEM_SIZE);
+        break;
+    case MARU_COMPOSITE_CMD_S_INPUT:
+        maru_composite_device_s_input(state);
+        break;
+    case MARU_COMPOSITE_CMD_S_CTRL:
+        maru_extinput_device_s_ctrl(state, AV);
+        break;
+    case MARU_COMPOSITE_CMD_G_EXT_CTRLS:
+        maru_extinput_device_g_ext_ctrls(state, AV);
+        break;
+    case MARU_COMPONENT_CMD_OPEN:
+        maru_extinput_device_open(state, COMPONENT);
+        break;
+    case MARU_COMPONENT_CMD_CLOSE:
+        maru_extinput_device_close(state);
+        break;
+    case MARU_COMPONENT_CMD_START_PREVIEW:
+        maru_extinput_device_start_preview(state, COMPONENT);
+        break;
+    case MARU_COMPONENT_CMD_STOP_PREVIEW:
+        maru_extinput_device_stop_preview(state);
+        memset(state->vaddr, 0, MARU_EXTINPUT_MEM_SIZE);
+        break;
+    case MARU_COMPONENT_CMD_S_CTRL:
+        maru_extinput_device_s_ctrl(state, COMPONENT);
+        break;
+    case MARU_COMPONENT_CMD_S_EXT_CTRLS:
+        maru_extinput_device_s_ext_ctrls(state, COMPONENT);
+        break;
+    case MARU_COMPONENT_CMD_G_EXT_CTRLS:
+        maru_extinput_device_g_ext_ctrls(state, COMPONENT);
+        break;
+    case MARU_SWITCH_CMD_S_INPUT:
+        maru_hdmi_input_setting(state);
+        break;
+    case MARU_ATV_LOCK_STATUS:
+        maru_set_atv_lock(state);
+        break;
+    default:
+        LOG_SEVERE("Not supported command: 0x%x\n", offset);
+        break;
+    }
+}
+
+static const MemoryRegionOps maru_extinput_mmio_ops = {
+    .old_mmio = {
+        .read = {
+            maru_extinput_mmio_read,
+            maru_extinput_mmio_read,
+            maru_extinput_mmio_read,
+        },
+        .write = {
+            maru_extinput_mmio_write,
+            maru_extinput_mmio_write,
+            maru_extinput_mmio_write,
+        },
+    },
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+/*
+ *  QEMU bottom half funtion
+ */
+static void maru_extinput_tx_bh(void *opaque)
+{
+    MaruExtInputState *state = (MaruExtInputState *)opaque;
+
+    qemu_mutex_lock(&state->thread_mutex);
+    if (state->isr) {
+        pci_set_irq(&state->dev, 1);
+    }
+    qemu_mutex_unlock(&state->thread_mutex);
+}
+
+/*
+ *  send external input state
+ */
+void send_to_extinput(uint32_t change_state, uint32_t bitmap)
+{
+    qemu_mutex_lock(&extinput_state->thread_mutex);
+    onoff_state = 0;
+    onoff_state = change_state << 16;
+    onoff_state |= bitmap;
+    extinput_state->isr |= 0x01;   /* set a flag of rasing a interrupt */
+    extinput_state->isr_state = _ISR_EXTINPUT;
+    qemu_bh_schedule(extinput_state->tx_bh);
+    qemu_mutex_unlock(&extinput_state->thread_mutex);
+}
+
+static int maru_extinput_set_platform_type(MaruExtInputState* s)
+{
+    /* if we use this option, we should check opts validation here */
+
+    s->platform_type = 0;
+    return 0;
+}
+
+/*
+ *  Initialization function
+ */
+static int maru_extinput_initfn(PCIDevice *dev)
+{
+    WorkQueueObject *wqobj;
+    WSIObject *wsiobj;
+    bool ambiguous;
+    uint8_t *pci_conf = NULL;
+    extinput_state = DO_UPCAST(MaruExtInputState, dev, dev);
+    pci_conf = extinput_state->dev.config;
+
+    if( maru_extinput_set_platform_type(extinput_state) != 0)
+        return -1;
+    wqobj = workqueueobject_create(&ambiguous);
+
+    if (ambiguous) {
+        LOG_SEVERE("ambiguous work queue, set 'render_queue' property");
+        return -1;
+    }
+
+    if (!wqobj) {
+        LOG_SEVERE("unable to create work queue");
+        return -1;
+    }
+
+    wsiobj = wsiobject_find(extinput_state->wsi);
+
+    if (!wsiobj) {
+        LOG_SEVERE("winsys interface '%s' not found", extinput_state->wsi);
+        return -1;
+    }
+
+    g_render_queue = wqobj->wq;
+    g_wsi = wsiobj->wsi;
+
+    pci_config_set_interrupt_pin(pci_conf, 0x03);
+
+    memory_region_init_ram(&extinput_state->vram, NULL, "maruextinput.ram", MARU_EXTINPUT_MEM_SIZE, &error_abort);
+    extinput_state->vaddr = memory_region_get_ram_ptr(&extinput_state->vram);
+    memset(extinput_state->vaddr, 0, MARU_EXTINPUT_MEM_SIZE);
+
+    memory_region_init_io(&extinput_state->mmio,
+            NULL,
+            &maru_extinput_mmio_ops,
+            extinput_state,
+            "maru-extinput-mmio",
+            MARU_EXTINPUT_REG_SIZE);
+
+    pci_register_bar(&extinput_state->dev, 0, PCI_BASE_ADDRESS_MEM_PREFETCH, &extinput_state->vram);
+    pci_register_bar(&extinput_state->dev, 1, PCI_BASE_ADDRESS_SPACE_MEMORY, &extinput_state->mmio);
+
+    /* for worker thread */
+    extinput_state->param = (MaruExtInputParam *)g_malloc0(sizeof(MaruExtInputParam));
+
+    qemu_mutex_init(&extinput_state->thread_mutex);
+    extinput_state->streamon = _MC_THREAD_PAUSED;
+
+    extinput_state->tx_bh = qemu_bh_new(maru_extinput_tx_bh, extinput_state);
+    LOG_INFO("[%s] external input device was initialized.\n", __func__);
+
+    return 0;
+}
+
+/*
+ *  Termination function
+ */
+static void maru_extinput_exitfn(PCIDevice *pci_dev)
+{
+    MaruExtInputState *s =
+        OBJECT_CHECK(MaruExtInputState, pci_dev, MARU_PCI_EXTERNAL_INPUT_DEVICE_NAME);
+
+    g_free(s->param);
+    qemu_mutex_destroy(&s->thread_mutex);
+
+    LOG_INFO("[%s] external input device was released.\n", __func__);
+}
+
+int maru_external_input_pci_init(PCIBus *bus)
+{
+    LOG_INFO("[%s] external input device was initialized.\n", __func__);
+    pci_create_simple(bus, -1, MARU_PCI_EXTERNAL_INPUT_DEVICE_NAME);
+
+    return 0;
+}
+
+#ifdef QTEST_TIZEN
+/**
+ * @test  UTC_EXTINPUT_TEST02
+ * @sut   CHECKRESOLUTION
+ * @brief Check invalid resolution. External-Input can support 640X480, 1920x1080
+ * @flow  #02-01 call check_resolution with invalid width
+ *        #02-02 call check_resolution with invalid height
+ *        #02-03 call check_resolution with unsupported width, height
+ * @type  Error Condition
+ * @input #02-01 1280, 480
+ *        #02-02 1920, 1280
+ *        #02-03 720, 640
+ */
+static void qtest_extinput_02(MaruExtInputState *state)
+{
+    int ret = 0;
+
+    /* flow #02-01 */
+    ret = check_resolution(1280, 480);
+    if (ret < 0) {
+        LOG_INFO("[QTEST][EXTERNALINPUT][UTC_EXTINPUT_TEST02] #02-01 : SUCCESS\n");
+    } else {
+        LOG_INFO("[QTEST][EXTERNALINPUT][UTC_EXTINPUT_TEST02] #02-01 : FAIL\n");
+    }
+
+    /* flow #02-02 */
+    ret = check_resolution(1920, 1280);
+    if (ret < 0) {
+        LOG_INFO("[QTEST][EXTERNALINPUT][UTC_EXTINPUT_TEST02] #02-02 : SUCCESS\n");
+    } else {
+        LOG_INFO("[QTEST][EXTERNALINPUT][UTC_EXTINPUT_TEST02] #02-02 : FAIL\n");
+    }
+
+    /* flow #02-03 */
+    ret = check_resolution(720, 640);
+    if (ret < 0) {
+        LOG_INFO("[QTEST][EXTERNALINPUT][UTC_EXTINPUT_TEST02] #02-03 : SUCCESS\n");
+    } else {
+        LOG_INFO("[QTEST][EXTERNALINPUT][UTC_EXTINPUT_TEST02] #02-03 : FAIL\n");
+    }
+}
+
+/**
+ * @test  UTC_EXTINPUT_TEST03
+ * @sut   CHECKRESOLUTION
+ * @brief Check valid resolution. External-Input can support 640X480, 1920x1080
+ * @flow  #03-01 call check_resolution with supported width, height
+ *        #03-02 call check_resolution with supported width, height
+ * @type  Error Condition
+ * @input #03-01 640, 480
+ *        #03-02 1920, 1080
+ */
+static void qtest_extinput_03(MaruExtInputState *state)
+{
+    int ret = 0;
+
+    /* flow #03-01 */
+    ret = check_resolution(640, 480);
+    if (ret < 0) {
+        LOG_INFO("[QTEST][EXTERNALINPUT][UTC_EXTINPUT_TEST03] #03-01 : FAIL\n");
+    } else {
+        LOG_INFO("[QTEST][EXTERNALINPUT][UTC_EXTINPUT_TEST03] #03-01 : SUCCESS\n");
+    }
+
+    /* flow #03-02 */
+    ret = check_resolution(1920, 1080);
+    if (ret < 0) {
+        LOG_INFO("[QTEST][EXTERNALINPUT][UTC_EXTINPUT_TEST03] #03-02 : FAIL\n");
+    } else {
+        LOG_INFO("[QTEST][EXTERNALINPUT][UTC_EXTINPUT_TEST03] #03-02 : SUCCESS\n");
+    }
+}
+
+/**
+ * @test  UTC_EXTINPUT_TEST04
+ * @sut   CHECKSTREAMON
+ * @brief check streaming status after device_open
+ * @flow  #04-01 call is_streamon function after device_open
+ *        #04-02 call is_stream_paused function after device_open
+ * @type  Error Condition
+ * @input #04-01 external input virtual device state
+ *        #04-02 external input virtual device state
+ */
+static void qtest_extinput_04(MaruExtInputState *state)
+{
+    int ret = 0;
+
+    /* open ATV device */
+    maru_extinput_device_open(state, ATV);
+
+    /* flow #04-01 */
+    ret = is_streamon(state);
+    if (ret == 0) {
+        LOG_INFO("[QTEST][EXTERNALINPUT][UTC_EXTINPUT_TEST04] #04-01 : SUCCESS\n");
+    } else {
+        LOG_INFO("[QTEST][EXTERNALINPUT][UTC_EXTINPUT_TEST04] #04-01 : FAIL\n");
+    }
+
+    /* flow #04-02 */
+    ret = is_stream_paused(state);
+    if (ret != 0) {
+        LOG_INFO("[QTEST][EXTERNALINPUT][UTC_EXTINPUT_TEST04] #04-02 : SUCCESS\n");
+    } else {
+        LOG_INFO("[QTEST][EXTERNALINPUT][UTC_EXTINPUT_TEST04] #04-02 : FAIL\n");
+    }
+
+    /* close ATV device */
+    maru_extinput_device_close(state);
+}
+
+/**
+ * @test  UTC_EXTINPUT_TEST05
+ * @sut   CHECKSTREAMON
+ * @brief check streaming status after streaming start
+ * @flow  #04-01 call is_streamon function after start_preview
+ *        #04-02 call is_stream_paused function after start_preview
+ * @type  Error Condition
+ * @input #04-01 external input virtual device state
+ *        #04-02 external input virtual device state
+ */
+static void qtest_extinput_05(MaruExtInputState *state)
+{
+    int ret = 0;
+
+    /* open ATV device */
+    maru_extinput_device_open(state, ATV);
+
+    /* start streaming */
+    maru_extinput_device_start_preview(state, ATV);
+
+    /* flow #05-01 */
+    ret = is_streamon(state);
+    if (ret != 0) {
+        LOG_INFO("[QTEST][EXTERNALINPUT][UTC_EXTINPUT_TEST05] #05-01 : SUCCESS\n");
+    } else {
+        LOG_INFO("[QTEST][EXTERNALINPUT][UTC_EXTINPUT_TEST05] #05-01 : FAIL\n");
+    }
+
+    /* flow #05-02 */
+    ret = is_stream_paused(state);
+    if (ret == 0) {
+        LOG_INFO("[QTEST][EXTERNALINPUT][UTC_EXTINPUT_TEST05] #05-02 : SUCCESS\n");
+    } else {
+        LOG_INFO("[QTEST][EXTERNALINPUT][UTC_EXTINPUT_TEST05] #05-02 : FAIL\n");
+    }
+
+    /* close ATV device */
+    maru_extinput_device_close(state);
+}
+
+/**
+ * @test  UTC_EXTINPUT_TEST06
+ * @sut   CHECKSTREAMON
+ * @brief check streaming status after streaming stop
+ * @flow  #06-01 call is_streamon function after stop_preview
+ *        #06-02 call is_stream_paused function after stop_preview
+ * @type  Error Condition
+ * @input #06-01 external input virtual device state
+ *        #06-02 external input virtual device state
+ */
+static void qtest_extinput_06(MaruExtInputState *state)
+{
+    int ret = 0;
+
+    /* open ATV device */
+    maru_extinput_device_open(state, ATV);
+
+    /* start streaming */
+    maru_extinput_device_start_preview(state, ATV);
+
+    /* stop streaming */
+    maru_extinput_device_stop_preview(state);
+
+    /* flow #06-01 */
+    ret = is_streamon(state);
+    if (ret == 0) {
+        LOG_INFO("[QTEST][EXTERNALINPUT][UTC_EXTINPUT_TEST06] #06-01 : SUCCESS\n");
+    } else {
+        LOG_INFO("[QTEST][EXTERNALINPUT][UTC_EXTINPUT_TEST06] #06-01 : FAIL\n");
+    }
+
+    /* flow #06-02 */
+    ret = is_stream_paused(state);
+    if (ret != 0) {
+        LOG_INFO("[QTEST][EXTERNALINPUT][UTC_EXTINPUT_TEST06] #06-02 : SUCCESS\n");
+    } else {
+        LOG_INFO("[QTEST][EXTERNALINPUT][UTC_EXTINPUT_TEST06] #06-02 : FAIL\n");
+    }
+
+    /* close ATV device */
+    maru_extinput_device_close(state);
+}
+
+/**
+ * @test  UTC_EXTINPUT_TEST07
+ * @sut   CHECKSTREAMON
+ * @brief check streaming status after device_close
+ * @flow  #07-01 call is_streamon function after device_close
+ *        #07-02 call is_stream_paused function after device_close
+ * @type  Error Condition
+ * @input #07-01 external input virtual device state
+ *        #07-02 external input virtual device state
+ */
+static void qtest_extinput_07(MaruExtInputState *state)
+{
+    int ret = 0;
+
+    /* open ATV device */
+    maru_extinput_device_open(state, ATV);
+
+    /* close ATV device */
+    maru_extinput_device_close(state);
+
+    /* flow #07-01 */
+    ret = is_streamon(state);
+    if (ret == 0) {
+        LOG_INFO("[QTEST][EXTERNALINPUT][UTC_EXTINPUT_TEST07] #07-01 : SUCCESS\n");
+    } else {
+        LOG_INFO("[QTEST][EXTERNALINPUT][UTC_EXTINPUT_TEST07] #07-01 : FAIL\n");
+    }
+
+    /* flow #07-02 */
+    ret = is_stream_paused(state);
+    if (ret != 0) {
+        LOG_INFO("[QTEST][EXTERNALINPUT][UTC_EXTINPUT_TEST07] #07-02 : SUCCESS\n");
+    } else {
+        LOG_INFO("[QTEST][EXTERNALINPUT][UTC_EXTINPUT_TEST07] #07-02 : FAIL\n");
+    }
+}
+
+static int maru_extinput_qtest_initfn(PCIDevice *dev)
+{
+    int ret = 0;
+    extinput_state = DO_UPCAST(MaruExtInputState, dev, dev);
+
+    LOG_INFO("initialize external input device for qtest \n");
+    ret = maru_extinput_initfn(dev);
+    if (ret < 0) {
+        LOG_SEVERE("failed to initialize external input device.\n");
+        return ret;
+    }
+
+    /* check resolution */
+    qtest_extinput_02(extinput_state);
+    qtest_extinput_03(extinput_state);
+
+    /* check streaming state */
+    qtest_extinput_04(extinput_state);
+    qtest_extinput_05(extinput_state);
+    qtest_extinput_06(extinput_state);
+    qtest_extinput_07(extinput_state);
+
+    return 0;
+}
+#endif //QTEST_TIZEN
+
+static void maru_extinput_reset(DeviceState *dev)
+{
+    LOG_INFO("extinput reset. \n");
+
+    maru_extinput_device_close(extinput_state);
+}
+
+static Property maru_extinput_props[] = {
+    DEFINE_PROP_STRING("stillimg", MaruExtInputState, stillimg),
+    DEFINE_PROP_STRING("wsi", MaruExtInputState, wsi),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void maru_external_input_pci_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+#ifdef QTEST_TIZEN
+    k->init = maru_extinput_qtest_initfn;
+#else
+    k->init = maru_extinput_initfn;
+#endif //QTEST_TIZEN
+    k->exit = maru_extinput_exitfn;
+    k->vendor_id = PCI_VENDOR_ID_TIZEN;
+    k->device_id = PCI_DEVICE_ID_VIRTUAL_EXTERNAL_INPUT;
+    k->class_id = PCI_CLASS_OTHERS;
+    dc->props = maru_extinput_props;
+    dc->desc = "MARU Virtual External Input device for Tizen emulator";
+    dc->reset = maru_extinput_reset;
+}
+
+static TypeInfo maru_external_input_info = {
+    .name          = MARU_PCI_EXTERNAL_INPUT_DEVICE_NAME,
+    .parent        = TYPE_PCI_DEVICE,
+    .instance_size = sizeof(MaruExtInputState),
+    .class_init    = maru_external_input_pci_class_init,
+};
+
+static void maru_external_input_pci_register_types(void)
+{
+    type_register_static(&maru_external_input_info);
+}
+
+type_init(maru_external_input_pci_register_types)
diff --git a/tizen/src/hw/pci/maru_external_input_pci.h b/tizen/src/hw/pci/maru_external_input_pci.h
new file mode 100644 (file)
index 0000000..937703f
--- /dev/null
@@ -0,0 +1,570 @@
+/*
+ * Common implementation of MARU Virtual External Input device by PCI bus.
+ *
+ * Copyright (c) 2011 - 2013 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Contact:
+ * GunSoo Kim <gunsoo83.kim@samsung.com>
+ * HyunJin Lee <hyunjin816.lee@samsung.com>
+ * SangHo Park <sangho1206.park@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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA  02110-1301, USA.
+ *
+ * Contributors:
+ * - S-Core Co., Ltd
+ *
+ */
+#ifndef _MARU_EXTERNAL_INPUT_PCI_H_
+#define _MARU_EXTERNAL_INPUT_PCI_H_
+
+#include "hw/pci/pci.h"
+#include "qemu/thread.h"
+
+#define CLEAR(x) memset(&(x), 0, sizeof(x))
+
+#define MARU_PCI_EXTERNAL_INPUT_DEVICE_NAME     "maru-external-input-pci"
+
+#define MARU_EXTINPUT_MEM_SIZE    (4 * 1024 * 1024)   /* 4MB */
+#define MARU_EXTINPUT_REG_SIZE    (256)               /* 64 * 4Byte */
+
+#define MARU_EXTINPUT_MAX_PARAM    20
+#define MARU_EXTINPUT_SKIPFRAMES    2
+
+#define MARU_EXTINPUT_CMD_INIT           0x00
+#define MARU_EXTINPUT_CMD_ISR            0x04
+#define MARU_EXTINPUT_CMD_ISR_STATE      0x08
+#define MARU_EXTINPUT_CMD_S_DATA         0x0C
+#define MARU_EXTINPUT_CMD_G_DATA         0x10
+#define MARU_EXTINPUT_CMD_DATACLR        0x14
+#define MARU_EXTINPUT_ONOFF_STATE        0x18
+
+#define MARU_HDMI_CMD_OPEN               0x1C
+#define MARU_HDMI_CMD_CLOSE              0x20
+#define MARU_HDMI_CMD_START_PREVIEW      0x24
+#define MARU_HDMI_CMD_STOP_PREVIEW       0x28
+#define MARU_HDMI_CMD_S_FMT              0x2C
+#define MARU_HDMI_CMD_G_FMT              0x30
+#define MARU_HDMI_CMD_S_CTRL             0x34
+#define MARU_HDMI_CMD_S_EXT_CTRLS        0x38
+#define MARU_HDMI_CMD_G_EXT_CTRLS        0x3C
+#define MARU_COMPOSITE_CMD_OPEN          0x40
+#define MARU_COMPOSITE_CMD_CLOSE         0x44
+#define MARU_COMPOSITE_CMD_START_PREVIEW 0x48
+#define MARU_COMPOSITE_CMD_STOP_PREVIEW  0x4C
+#define MARU_COMPOSITE_CMD_S_INPUT       0x50
+#define MARU_COMPOSITE_CMD_S_CTRL        0x54
+#define MARU_COMPOSITE_CMD_G_EXT_CTRLS   0x58
+
+#define MARU_COMPONENT_CMD_OPEN          0x5C
+#define MARU_COMPONENT_CMD_CLOSE         0x60
+#define MARU_COMPONENT_CMD_START_PREVIEW 0x64
+#define MARU_COMPONENT_CMD_STOP_PREVIEW  0x68
+#define MARU_COMPONENT_CMD_S_CTRL        0x6C
+#define MARU_COMPONENT_CMD_S_EXT_CTRLS   0x70
+#define MARU_COMPONENT_CMD_G_EXT_CTRLS   0x74
+
+#define MARU_SWITCH_CMD_S_INPUT          0x78
+#define MARU_ATV_LOCK_STATUS             0x7C
+
+#define MARU_EXTINPUT_PLATFORM_TYPE      0x80
+
+#define FULLHD_WIDTH  1920
+#define FULLHD_HEIGHT 1080
+
+#define ATV_WIDTH  720
+#define ATV_HEIGHT 480
+
+typedef struct MaruExtInputState MaruExtInputState;
+typedef struct MaruExtInputParam MaruExtInputParam;
+
+struct MaruExtInputParam {
+    uint32_t    top;
+    uint32_t    retVal;
+    uint32_t    errCode;
+    uint32_t    stack[MARU_EXTINPUT_MAX_PARAM];
+};
+
+struct MaruExtInputState {
+    PCIDevice           dev;
+    MaruExtInputParam   *param;
+    QemuThread          thread_id;
+    QemuMutex           thread_mutex;;
+    QEMUBH              *tx_bh;
+
+    char                *stillimg;
+    char                *wsi;
+    bool                destroying;
+    int                 platform_type; /* for platform compatibility */
+    void                *vaddr;     /* vram ptr */
+    uint32_t            isr;
+    uint32_t            isr_state;
+    uint32_t            streamon;
+    uint32_t            req_frame;
+
+    MemoryRegion        vram;
+    MemoryRegion        mmio;
+};
+
+struct ext_resolution {
+    uint32_t width;
+    uint32_t height;
+    const char* filename;
+};
+
+enum {
+    _MC_THREAD_PAUSED,
+    _MC_THREAD_STREAMON,
+    _MC_THREAD_STREAMOFF,
+};
+
+enum {
+    _ISR_FILLBUF = 1,
+    _ISR_EXTINPUT,
+};
+
+enum input_type {
+    AV,
+    ATV,
+    COMPONENT,
+    SCART,
+    HDMI,
+    MAX_TYPE
+};
+
+int maru_external_input_pci_init(PCIBus *bus);
+void send_to_extinput(uint32_t change_state, uint32_t bitmap);
+
+/*
+   These defines, enum, structurs are V4L2 specific in linux/videodev2.h
+   Add these data for compatible with Windows/Mac
+ */
+#define VIDEO_MAX_FRAME               32
+#define VIDEO_MAX_PLANES               8
+
+/*  Four-character-code (FOURCC) */
+#define v4l2_fourcc(a, b, c, d)\
+    ((uint32_t)(a) | ((uint32_t)(b) << 8) | ((uint32_t)(c) << 16) | ((uint32_t)(d) << 24))
+
+/*      Pixel format         FOURCC                          depth  Description  */
+
+/* RGB formats */
+#define V4L2_PIX_FMT_RGB332  v4l2_fourcc('R', 'G', 'B', '1') /*  8  RGB-3-3-2     */
+#define V4L2_PIX_FMT_RGB444  v4l2_fourcc('R', '4', '4', '4') /* 16  xxxxrrrr ggggbbbb */
+#define V4L2_PIX_FMT_RGB555  v4l2_fourcc('R', 'G', 'B', 'O') /* 16  RGB-5-5-5     */
+#define V4L2_PIX_FMT_RGB565  v4l2_fourcc('R', 'G', 'B', 'P') /* 16  RGB-5-6-5     */
+#define V4L2_PIX_FMT_RGB555X v4l2_fourcc('R', 'G', 'B', 'Q') /* 16  RGB-5-5-5 BE  */
+#define V4L2_PIX_FMT_RGB565X v4l2_fourcc('R', 'G', 'B', 'R') /* 16  RGB-5-6-5 BE  */
+#define V4L2_PIX_FMT_BGR666  v4l2_fourcc('B', 'G', 'R', 'H') /* 18  BGR-6-6-6     */
+#define V4L2_PIX_FMT_BGR24   v4l2_fourcc('B', 'G', 'R', '3') /* 24  BGR-8-8-8     */
+#define V4L2_PIX_FMT_RGB24   v4l2_fourcc('R', 'G', 'B', '3') /* 24  RGB-8-8-8     */
+#define V4L2_PIX_FMT_BGR32   v4l2_fourcc('B', 'G', 'R', '4') /* 32  BGR-8-8-8-8   */
+#define V4L2_PIX_FMT_RGB32   v4l2_fourcc('R', 'G', 'B', '4') /* 32  RGB-8-8-8-8   */
+
+/* Grey formats */
+#define V4L2_PIX_FMT_GREY    v4l2_fourcc('G', 'R', 'E', 'Y') /*  8  Greyscale     */
+#define V4L2_PIX_FMT_Y4      v4l2_fourcc('Y', '0', '4', ' ') /*  4  Greyscale     */
+#define V4L2_PIX_FMT_Y6      v4l2_fourcc('Y', '0', '6', ' ') /*  6  Greyscale     */
+#define V4L2_PIX_FMT_Y10     v4l2_fourcc('Y', '1', '0', ' ') /* 10  Greyscale     */
+#define V4L2_PIX_FMT_Y12     v4l2_fourcc('Y', '1', '2', ' ') /* 12  Greyscale     */
+#define V4L2_PIX_FMT_Y16     v4l2_fourcc('Y', '1', '6', ' ') /* 16  Greyscale     */
+
+/* Grey bit-packed formats */
+#define V4L2_PIX_FMT_Y10BPACK    v4l2_fourcc('Y', '1', '0', 'B') /* 10  Greyscale bit-packed */
+
+/* Palette formats */
+#define V4L2_PIX_FMT_PAL8    v4l2_fourcc('P', 'A', 'L', '8') /*  8  8-bit palette */
+
+/* Luminance+Chrominance formats */
+#define V4L2_PIX_FMT_YVU410  v4l2_fourcc('Y', 'V', 'U', '9') /*  9  YVU 4:1:0     */
+#define V4L2_PIX_FMT_YVU420  v4l2_fourcc('Y', 'V', '1', '2') /* 12  YVU 4:2:0     */
+#define V4L2_PIX_FMT_YUYV    v4l2_fourcc('Y', 'U', 'Y', 'V') /* 16  YUV 4:2:2     */
+#define V4L2_PIX_FMT_YYUV    v4l2_fourcc('Y', 'Y', 'U', 'V') /* 16  YUV 4:2:2     */
+#define V4L2_PIX_FMT_YVYU    v4l2_fourcc('Y', 'V', 'Y', 'U') /* 16 YVU 4:2:2 */
+#define V4L2_PIX_FMT_UYVY    v4l2_fourcc('U', 'Y', 'V', 'Y') /* 16  YUV 4:2:2     */
+#define V4L2_PIX_FMT_VYUY    v4l2_fourcc('V', 'Y', 'U', 'Y') /* 16  YUV 4:2:2     */
+#define V4L2_PIX_FMT_YUV422P v4l2_fourcc('4', '2', '2', 'P') /* 16  YVU422 planar */
+#define V4L2_PIX_FMT_YUV411P v4l2_fourcc('4', '1', '1', 'P') /* 16  YVU411 planar */
+#define V4L2_PIX_FMT_Y41P    v4l2_fourcc('Y', '4', '1', 'P') /* 12  YUV 4:1:1     */
+#define V4L2_PIX_FMT_YUV444  v4l2_fourcc('Y', '4', '4', '4') /* 16  xxxxyyyy uuuuvvvv */
+#define V4L2_PIX_FMT_YUV555  v4l2_fourcc('Y', 'U', 'V', 'O') /* 16  YUV-5-5-5     */
+#define V4L2_PIX_FMT_YUV565  v4l2_fourcc('Y', 'U', 'V', 'P') /* 16  YUV-5-6-5     */
+#define V4L2_PIX_FMT_YUV32   v4l2_fourcc('Y', 'U', 'V', '4') /* 32  YUV-8-8-8-8   */
+#define V4L2_PIX_FMT_YUV410  v4l2_fourcc('Y', 'U', 'V', '9') /*  9  YUV 4:1:0     */
+#define V4L2_PIX_FMT_YUV420  v4l2_fourcc('Y', 'U', '1', '2') /* 12  YUV 4:2:0     */
+#define V4L2_PIX_FMT_HI240   v4l2_fourcc('H', 'I', '2', '4') /*  8  8-bit color   */
+#define V4L2_PIX_FMT_HM12    v4l2_fourcc('H', 'M', '1', '2') /*  8  YUV 4:2:0 16x16 macroblocks */
+#define V4L2_PIX_FMT_M420    v4l2_fourcc('M', '4', '2', '0') /* 12  YUV 4:2:0 2 lines y, 1 line uv interleaved */
+
+/* two planes -- one Y, one Cr + Cb interleaved  */
+#define V4L2_PIX_FMT_NV12    v4l2_fourcc('N', 'V', '1', '2') /* 12  Y/CbCr 4:2:0  */
+#define V4L2_PIX_FMT_NV21    v4l2_fourcc('N', 'V', '2', '1') /* 12  Y/CrCb 4:2:0  */
+#define V4L2_PIX_FMT_NV16    v4l2_fourcc('N', 'V', '1', '6') /* 16  Y/CbCr 4:2:2  */
+#define V4L2_PIX_FMT_NV61    v4l2_fourcc('N', 'V', '6', '1') /* 16  Y/CrCb 4:2:2  */
+
+/* two non contiguous planes - one Y, one Cr + Cb interleaved  */
+#define V4L2_PIX_FMT_NV12M   v4l2_fourcc('N', 'M', '1', '2') /* 12  Y/CbCr 4:2:0  */
+#define V4L2_PIX_FMT_NV12MT  v4l2_fourcc('T', 'M', '1', '2') /* 12  Y/CbCr 4:2:0 64x32 macroblocks */
+
+/* three non contiguous planes - Y, Cb, Cr */
+#define V4L2_PIX_FMT_YUV420M v4l2_fourcc('Y', 'M', '1', '2') /* 12  YUV420 planar */
+
+/* Bayer formats - see http://www.siliconimaging.com/RGB%20Bayer.htm */
+#define V4L2_PIX_FMT_SBGGR8  v4l2_fourcc('B', 'A', '8', '1') /*  8  BGBG.. GRGR.. */
+#define V4L2_PIX_FMT_SGBRG8  v4l2_fourcc('G', 'B', 'R', 'G') /*  8  GBGB.. RGRG.. */
+#define V4L2_PIX_FMT_SGRBG8  v4l2_fourcc('G', 'R', 'B', 'G') /*  8  GRGR.. BGBG.. */
+#define V4L2_PIX_FMT_SRGGB8  v4l2_fourcc('R', 'G', 'G', 'B') /*  8  RGRG.. GBGB.. */
+#define V4L2_PIX_FMT_SBGGR10 v4l2_fourcc('B', 'G', '1', '0') /* 10  BGBG.. GRGR.. */
+#define V4L2_PIX_FMT_SGBRG10 v4l2_fourcc('G', 'B', '1', '0') /* 10  GBGB.. RGRG.. */
+#define V4L2_PIX_FMT_SGRBG10 v4l2_fourcc('B', 'A', '1', '0') /* 10  GRGR.. BGBG.. */
+#define V4L2_PIX_FMT_SRGGB10 v4l2_fourcc('R', 'G', '1', '0') /* 10  RGRG.. GBGB.. */
+#define V4L2_PIX_FMT_SBGGR12 v4l2_fourcc('B', 'G', '1', '2') /* 12  BGBG.. GRGR.. */
+#define V4L2_PIX_FMT_SGBRG12 v4l2_fourcc('G', 'B', '1', '2') /* 12  GBGB.. RGRG.. */
+#define V4L2_PIX_FMT_SGRBG12 v4l2_fourcc('B', 'A', '1', '2') /* 12  GRGR.. BGBG.. */
+#define V4L2_PIX_FMT_SRGGB12 v4l2_fourcc('R', 'G', '1', '2') /* 12  RGRG.. GBGB.. */
+    /* 10bit raw bayer DPCM compressed to 8 bits */
+#define V4L2_PIX_FMT_SGRBG10DPCM8 v4l2_fourcc('B', 'D', '1', '0')
+    /*
+     * 10bit raw bayer, expanded to 16 bits
+     * xxxxrrrrrrrrrrxxxxgggggggggg xxxxggggggggggxxxxbbbbbbbbbb...
+     */
+#define V4L2_PIX_FMT_SBGGR16 v4l2_fourcc('B', 'Y', 'R', '2') /* 16  BGBG.. GRGR.. */
+
+/* compressed formats */
+#define V4L2_PIX_FMT_MJPEG    v4l2_fourcc('M', 'J', 'P', 'G') /* Motion-JPEG   */
+#define V4L2_PIX_FMT_JPEG     v4l2_fourcc('J', 'P', 'E', 'G') /* JFIF JPEG     */
+#define V4L2_PIX_FMT_DV       v4l2_fourcc('d', 'v', 's', 'd') /* 1394          */
+#define V4L2_PIX_FMT_MPEG     v4l2_fourcc('M', 'P', 'E', 'G') /* MPEG-1/2/4 Multiplexed */
+#define V4L2_PIX_FMT_H264     v4l2_fourcc('H', '2', '6', '4') /* H264 with start codes */
+#define V4L2_PIX_FMT_H264_NO_SC v4l2_fourcc('A', 'V', 'C', '1') /* H264 without start codes */
+#define V4L2_PIX_FMT_H263     v4l2_fourcc('H', '2', '6', '3') /* H263          */
+#define V4L2_PIX_FMT_MPEG1    v4l2_fourcc('M', 'P', 'G', '1') /* MPEG-1 ES     */
+#define V4L2_PIX_FMT_MPEG2    v4l2_fourcc('M', 'P', 'G', '2') /* MPEG-2 ES     */
+#define V4L2_PIX_FMT_MPEG4    v4l2_fourcc('M', 'P', 'G', '4') /* MPEG-4 ES     */
+#define V4L2_PIX_FMT_XVID     v4l2_fourcc('X', 'V', 'I', 'D') /* Xvid           */
+#define V4L2_PIX_FMT_VC1_ANNEX_G v4l2_fourcc('V', 'C', '1', 'G') /* SMPTE 421M Annex G compliant stream */
+#define V4L2_PIX_FMT_VC1_ANNEX_L v4l2_fourcc('V', 'C', '1', 'L') /* SMPTE 421M Annex L compliant stream */
+
+/*  Vendor-specific formats   */
+#define V4L2_PIX_FMT_CPIA1    v4l2_fourcc('C', 'P', 'I', 'A') /* cpia1 YUV */
+#define V4L2_PIX_FMT_WNVA     v4l2_fourcc('W', 'N', 'V', 'A') /* Winnov hw compress */
+#define V4L2_PIX_FMT_SN9C10X  v4l2_fourcc('S', '9', '1', '0') /* SN9C10x compression */
+#define V4L2_PIX_FMT_SN9C20X_I420 v4l2_fourcc('S', '9', '2', '0') /* SN9C20x YUV 4:2:0 */
+#define V4L2_PIX_FMT_PWC1     v4l2_fourcc('P', 'W', 'C', '1') /* pwc older webcam */
+#define V4L2_PIX_FMT_PWC2     v4l2_fourcc('P', 'W', 'C', '2') /* pwc newer webcam */
+#define V4L2_PIX_FMT_ET61X251 v4l2_fourcc('E', '6', '2', '5') /* ET61X251 compression */
+#define V4L2_PIX_FMT_SPCA501  v4l2_fourcc('S', '5', '0', '1') /* YUYV per line */
+#define V4L2_PIX_FMT_SPCA505  v4l2_fourcc('S', '5', '0', '5') /* YYUV per line */
+#define V4L2_PIX_FMT_SPCA508  v4l2_fourcc('S', '5', '0', '8') /* YUVY per line */
+#define V4L2_PIX_FMT_SPCA561  v4l2_fourcc('S', '5', '6', '1') /* compressed GBRG bayer */
+#define V4L2_PIX_FMT_PAC207   v4l2_fourcc('P', '2', '0', '7') /* compressed BGGR bayer */
+#define V4L2_PIX_FMT_MR97310A v4l2_fourcc('M', '3', '1', '0') /* compressed BGGR bayer */
+#define V4L2_PIX_FMT_SN9C2028 v4l2_fourcc('S', 'O', 'N', 'X') /* compressed GBRG bayer */
+#define V4L2_PIX_FMT_SQ905C   v4l2_fourcc('9', '0', '5', 'C') /* compressed RGGB bayer */
+#define V4L2_PIX_FMT_PJPG     v4l2_fourcc('P', 'J', 'P', 'G') /* Pixart 73xx JPEG */
+#define V4L2_PIX_FMT_OV511    v4l2_fourcc('O', '5', '1', '1') /* ov511 JPEG */
+#define V4L2_PIX_FMT_OV518    v4l2_fourcc('O', '5', '1', '8') /* ov518 JPEG */
+#define V4L2_PIX_FMT_STV0680  v4l2_fourcc('S', '6', '8', '0') /* stv0680 bayer */
+#define V4L2_PIX_FMT_TM6000   v4l2_fourcc('T', 'M', '6', '0') /* tm5600/tm60x0 */
+#define V4L2_PIX_FMT_CIT_YYVYUY v4l2_fourcc('C', 'I', 'T', 'V') /* one line of Y then 1 line of VYUY */
+#define V4L2_PIX_FMT_KONICA420  v4l2_fourcc('K', 'O', 'N', 'I') /* YUV420 planar in blocks of 256 pixels */
+#define V4L2_PIX_FMT_JPGL   v4l2_fourcc('J', 'P', 'G', 'L') /* JPEG-Lite */
+#define V4L2_PIX_FMT_SE401      v4l2_fourcc('S', '4', '0', '1') /* se401 janggu compressed rgb */
+
+/*  Values for ctrl_class field */
+#define V4L2_CTRL_CLASS_USER 0x00980000 /* Old-style 'user' controls */
+#define V4L2_CTRL_CLASS_MPEG 0x00990000 /* MPEG-compression controls */
+#define V4L2_CTRL_CLASS_CAMERA 0x009a0000   /* Camera class controls */
+#define V4L2_CTRL_CLASS_FM_TX 0x009b0000    /* FM Modulator control class */
+#define V4L2_CTRL_CLASS_FLASH 0x009c0000    /* Camera flash controls */
+
+#define V4L2_CTRL_ID_MASK         (0x0fffffff)
+#define V4L2_CTRL_ID2CLASS(id)    ((id) & 0x0fff0000UL)
+#define V4L2_CTRL_DRIVER_PRIV(id) (((id) & 0xffff) >= 0x1000)
+
+/*  Control flags  */
+#define V4L2_CTRL_FLAG_DISABLED     0x0001
+#define V4L2_CTRL_FLAG_GRABBED      0x0002
+#define V4L2_CTRL_FLAG_READ_ONLY    0x0004
+#define V4L2_CTRL_FLAG_UPDATE       0x0008
+#define V4L2_CTRL_FLAG_INACTIVE     0x0010
+#define V4L2_CTRL_FLAG_SLIDER       0x0020
+#define V4L2_CTRL_FLAG_WRITE_ONLY   0x0040
+#define V4L2_CTRL_FLAG_VOLATILE     0x0080
+
+/*  Query flag, to be ORed with the control ID */
+#define V4L2_CTRL_FLAG_NEXT_CTRL    0x80000000
+
+/*  User-class control IDs defined by V4L2 */
+#define V4L2_CID_MAX_CTRLS      1024
+#define V4L2_CID_BASE           (V4L2_CTRL_CLASS_USER | 0x900)
+#define V4L2_CID_USER_BASE      V4L2_CID_BASE
+/*  IDs reserved for driver specific controls */
+#define V4L2_CID_PRIVATE_BASE       0x08000000
+
+#define V4L2_CID_USER_CLASS         (V4L2_CTRL_CLASS_USER | 1)
+#define V4L2_CID_BRIGHTNESS     (V4L2_CID_BASE+0)
+#define V4L2_CID_CONTRAST       (V4L2_CID_BASE+1)
+#define V4L2_CID_SATURATION     (V4L2_CID_BASE+2)
+#define V4L2_CID_HUE            (V4L2_CID_BASE+3)
+#define V4L2_CID_AUDIO_VOLUME       (V4L2_CID_BASE+5)
+#define V4L2_CID_AUDIO_BALANCE      (V4L2_CID_BASE+6)
+#define V4L2_CID_AUDIO_BASS     (V4L2_CID_BASE+7)
+#define V4L2_CID_AUDIO_TREBLE       (V4L2_CID_BASE+8)
+#define V4L2_CID_AUDIO_MUTE     (V4L2_CID_BASE+9)
+#define V4L2_CID_AUDIO_LOUDNESS     (V4L2_CID_BASE+10)
+#define V4L2_CID_BLACK_LEVEL        (V4L2_CID_BASE+11) /* Deprecated */
+#define V4L2_CID_AUTO_WHITE_BALANCE (V4L2_CID_BASE+12)
+#define V4L2_CID_DO_WHITE_BALANCE   (V4L2_CID_BASE+13)
+#define V4L2_CID_RED_BALANCE        (V4L2_CID_BASE+14)
+#define V4L2_CID_BLUE_BALANCE       (V4L2_CID_BASE+15)
+#define V4L2_CID_GAMMA          (V4L2_CID_BASE+16)
+#define V4L2_CID_WHITENESS      (V4L2_CID_GAMMA) /* Deprecated */
+#define V4L2_CID_EXPOSURE       (V4L2_CID_BASE+17)
+#define V4L2_CID_AUTOGAIN       (V4L2_CID_BASE+18)
+#define V4L2_CID_GAIN           (V4L2_CID_BASE+19)
+#define V4L2_CID_HFLIP          (V4L2_CID_BASE+20)
+#define V4L2_CID_VFLIP          (V4L2_CID_BASE+21)
+/* Deprecated; use V4L2_CID_PAN_RESET and V4L2_CID_TILT_RESET */
+#define V4L2_CID_HCENTER        (V4L2_CID_BASE+22)
+#define V4L2_CID_VCENTER        (V4L2_CID_BASE+23)
+#define V4L2_CID_POWER_LINE_FREQUENCY   (V4L2_CID_BASE+24)
+#define V4L2_CID_HUE_AUTO           (V4L2_CID_BASE+25)
+#define V4L2_CID_WHITE_BALANCE_TEMPERATURE  (V4L2_CID_BASE+26)
+#define V4L2_CID_SHARPNESS          (V4L2_CID_BASE+27)
+#define V4L2_CID_BACKLIGHT_COMPENSATION     (V4L2_CID_BASE+28)
+#define V4L2_CID_CHROMA_AGC                     (V4L2_CID_BASE+29)
+#define V4L2_CID_COLOR_KILLER                   (V4L2_CID_BASE+30)
+#define V4L2_CID_COLORFX            (V4L2_CID_BASE+31)
+
+#define V4L2_CTRL_CLASS_DV 0x00a00000 /* Digital Video controls */
+#define V4L2_CID_DV_CLASS_BASE (V4L2_CTRL_CLASS_DV | 0x900)
+#define V4L2_CID_DV_VIDEO_OUTPUT (V4L2_CID_DV_CLASS_BASE + 200)
+
+/*
+ *  E N U M S
+ */
+enum v4l2_field {
+    V4L2_FIELD_ANY           = 0, /* driver can choose from none,
+                     top, bottom, interlaced
+                     depending on whatever it thinks
+                     is approximate ... */
+    V4L2_FIELD_NONE          = 1, /* this device has no fields ... */
+    V4L2_FIELD_TOP           = 2, /* top field only */
+    V4L2_FIELD_BOTTOM        = 3, /* bottom field only */
+    V4L2_FIELD_INTERLACED    = 4, /* both fields interlaced */
+    V4L2_FIELD_SEQ_TB        = 5, /* both fields sequential into one
+                     buffer, top-bottom order */
+    V4L2_FIELD_SEQ_BT        = 6, /* same as above + bottom-top order */
+    V4L2_FIELD_ALTERNATE     = 7, /* both fields alternating into
+                     separate buffers */
+    V4L2_FIELD_INTERLACED_TB = 8, /* both fields interlaced, top field
+                     first and the top field is
+                     transmitted first */
+    V4L2_FIELD_INTERLACED_BT = 9, /* both fields interlaced, top field
+                     first and the bottom field is
+                     transmitted first */
+};
+
+/* see also http://vektor.theorem.ca/graphics/ycbcr/ */
+enum v4l2_colorspace {
+    /* ITU-R 601 -- broadcast NTSC/PAL */
+    V4L2_COLORSPACE_SMPTE170M     = 1,
+
+    /* 1125-Line (US) HDTV */
+    V4L2_COLORSPACE_SMPTE240M     = 2,
+
+    /* HD and modern captures. */
+    V4L2_COLORSPACE_REC709        = 3,
+
+    /* broken BT878 extents (601, luma range 16-253 instead of 16-235) */
+    V4L2_COLORSPACE_BT878         = 4,
+
+    /* These should be useful.  Assume 601 extents. */
+    V4L2_COLORSPACE_470_SYSTEM_M  = 5,
+    V4L2_COLORSPACE_470_SYSTEM_BG = 6,
+
+    /* I know there will be cameras that send this.  So, this is
+     * unspecified chromaticities and full 0-255 on each of the
+     * Y'CbCr components
+     */
+    V4L2_COLORSPACE_JPEG          = 7,
+
+    /* For RGB colourspaces, this is probably a good start. */
+    V4L2_COLORSPACE_SRGB          = 8,
+};
+
+enum v4l2_buf_type {
+    V4L2_BUF_TYPE_VIDEO_CAPTURE        = 1,
+    V4L2_BUF_TYPE_VIDEO_OUTPUT         = 2,
+    V4L2_BUF_TYPE_VIDEO_OVERLAY        = 3,
+    V4L2_BUF_TYPE_VBI_CAPTURE          = 4,
+    V4L2_BUF_TYPE_VBI_OUTPUT           = 5,
+    V4L2_BUF_TYPE_SLICED_VBI_CAPTURE   = 6,
+    V4L2_BUF_TYPE_SLICED_VBI_OUTPUT    = 7,
+#if 1
+    /* Experimental */
+    V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY = 8,
+#endif
+    V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE = 9,
+    V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE  = 10,
+    V4L2_BUF_TYPE_PRIVATE              = 0x80,
+};
+
+struct v4l2_control {
+    uint32_t             id;
+    int32_t              value;
+};
+
+/*
+ *  V I D E O   I M A G E   F O R M A T
+ */
+struct v4l2_pix_format {
+    uint32_t                width;
+    uint32_t            height;
+    uint32_t            pixelformat;
+    enum v4l2_field     field;
+    uint32_t                bytesperline;   /* for padding, zero if unused */
+    uint32_t                sizeimage;
+    enum v4l2_colorspace    colorspace;
+    uint32_t            priv;       /* private data, depends on pixelformat */
+};
+
+struct v4l2_rect {
+    int32_t   left;
+    int32_t   top;
+    int32_t   width;
+    int32_t   height;
+};
+
+struct v4l2_clip {
+    struct v4l2_rect        c;
+    struct v4l2_clip    *next;
+};
+
+struct v4l2_window {
+    struct v4l2_rect        w;
+    enum v4l2_field     field;
+    uint32_t            chromakey;
+    struct v4l2_clip    *clips;
+    uint32_t            clipcount;
+    void            *bitmap;
+    uint32_t           global_alpha;
+};
+
+/* Raw VBI */
+struct v4l2_vbi_format {
+    uint32_t    sampling_rate;      /* in 1 Hz */
+    uint32_t    offset;
+    uint32_t    samples_per_line;
+    uint32_t    sample_format;      /* V4L2_PIX_FMT_* */
+    int32_t     start[2];
+    uint32_t    count[2];
+    uint32_t    flags;          /* V4L2_VBI_* */
+    uint32_t    reserved[2];        /* must be zero */
+};
+
+struct v4l2_sliced_vbi_format {
+    uint16_t   service_set;
+    /* service_lines[0][...] specifies lines 0-23 (1-23 used) of the first field
+       service_lines[1][...] specifies lines 0-23 (1-23 used) of the second field
+                 (equals frame lines 313-336 for 625 line video
+                  standards, 263-286 for 525 line standards) */
+    uint16_t   service_lines[2][24];
+    uint32_t   io_size;
+    uint32_t   reserved[2];            /* must be zero */
+};
+
+/**
+ * struct v4l2_plane_pix_format - additional, per-plane format definition
+ * @sizeimage:      maximum size in bytes required for data, for which
+ *          this plane will be used
+ * @bytesperline:   distance in bytes between the leftmost pixels in two
+ *          adjacent lines
+ */
+struct v4l2_plane_pix_format {
+    uint32_t        sizeimage;
+    uint16_t        bytesperline;
+    uint16_t        reserved[7];
+} __attribute__ ((packed));
+
+/**
+ * struct v4l2_pix_format_mplane - multiplanar format definition
+ * @width:      image width in pixels
+ * @height:     image height in pixels
+ * @pixelformat:    little endian four character code (fourcc)
+ * @field:      field order (for interlaced video)
+ * @colorspace:     supplemental to pixelformat
+ * @plane_fmt:      per-plane information
+ * @num_planes:     number of planes for this format
+ */
+struct v4l2_pix_format_mplane {
+    uint32_t                width;
+    uint32_t                height;
+    uint32_t                pixelformat;
+    enum v4l2_field         field;
+    enum v4l2_colorspace        colorspace;
+
+    struct v4l2_plane_pix_format    plane_fmt[VIDEO_MAX_PLANES];
+    uint8_t             num_planes;
+    uint8_t             reserved[11];
+} __attribute__ ((packed));
+
+/**
+ * struct v4l2_format - stream data format
+ * @type:   type of the data stream
+ * @pix:    definition of an image format
+ * @pix_mp: definition of a multiplanar image format
+ * @win:    definition of an overlaid image
+ * @vbi:    raw VBI capture or output parameters
+ * @sliced: sliced VBI capture or output parameters
+ * @raw_data:   placeholder for future extensions and custom formats
+ */
+struct v4l2_format {
+    enum v4l2_buf_type type;
+    union {
+        struct v4l2_pix_format      pix;     /* V4L2_BUF_TYPE_VIDEO_CAPTURE */
+        struct v4l2_pix_format_mplane   pix_mp;  /* V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE */
+        struct v4l2_window      win;     /* V4L2_BUF_TYPE_VIDEO_OVERLAY */
+        struct v4l2_vbi_format      vbi;     /* V4L2_BUF_TYPE_VBI_CAPTURE */
+        struct v4l2_sliced_vbi_format   sliced;  /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */
+        uint8_t raw_data[200];                   /* user-defined */
+    } fmt;
+};
+
+enum v4l2_ctrl_type {
+    V4L2_CTRL_TYPE_INTEGER       = 1,
+    V4L2_CTRL_TYPE_BOOLEAN       = 2,
+    V4L2_CTRL_TYPE_MENU      = 3,
+    V4L2_CTRL_TYPE_BUTTON        = 4,
+    V4L2_CTRL_TYPE_INTEGER64     = 5,
+    V4L2_CTRL_TYPE_CTRL_CLASS    = 6,
+    V4L2_CTRL_TYPE_STRING        = 7,
+    V4L2_CTRL_TYPE_BITMASK       = 8,
+};
+
+/*  Used in the VIDIOC_QUERYCTRL ioctl for querying controls */
+struct v4l2_queryctrl {
+    uint32_t             id;
+    enum v4l2_ctrl_type  type;
+    uint8_t          name[32];  /* Whatever */
+    int32_t          minimum;   /* Note signedness */
+    int32_t          maximum;
+    int32_t          step;
+    int32_t          default_value;
+    uint32_t                flags;
+    uint32_t             reserved[2];
+};
+#endif
diff --git a/tizen/src/hw/pci/maru_tuner.c b/tizen/src/hw/pci/maru_tuner.c
new file mode 100644 (file)
index 0000000..af700b8
--- /dev/null
@@ -0,0 +1,1872 @@
+/* * Maru virtual tuner device
+ *
+ * Copyright (C) 2011 - 2014 Samsung Electronics Co., Ltd. All rights reserved.
+ *
+ * Contact:
+ * Byeongki Shin <bk0121.shin@samsung.com>
+ * Sangho Park <sangho.p@samsung.com>
+ * Ningjia Fan <ningjia.fan@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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA  02110-1301, USA.
+ *
+ * Contributors:
+ * - S-Core Co., Ltd
+ *
+ */
+
+#include <stdio.h>
+#include <stddef.h>
+#include <pthread.h>
+#include "exec/memory.h"
+#include "exec/memory-internal.h"
+#include "exec/ram_addr.h"
+#include "tizen/src/emulator.h"
+#include "qemu/config-file.h"
+#include "qemu/main-loop.h"
+#include "qemu-common.h"
+#include "hw/maru_device_ids.h"
+#include "maru_tuner.h"
+#include "ecs/ecs.h"
+#include "util/new_debug_ch.h"
+
+DECLARE_DEBUG_CHANNEL(tuner);
+
+#define MARUTUNER_DEV_NAME      "maru-virtual-tuner"
+#define MARUTUNER_TS_PACKETS    1024
+//#define MARUTUNER_MEM_SIZE    (188 * MARUTUNER_TS_PACKETS)    /* size of a TS packet is 188 byte */
+#define MARUTUNER_MEM_SIZE      0x2000
+#define MARUTUNER_REG_SIZE      256
+
+
+#define REPLAY_ON_EOF   0
+
+#define print_tuner_state(s) LOG_INFO("(%s:%d) tsfile:[%s] freq:%ld, mod:%x\n", \
+                                  __func__, __LINE__, s->fe.ts_filename?s->fe.ts_filename:"null", s->fe.frequency, s->fe.modulation);
+
+#ifdef QTEST_TIZEN
+#include <libgen.h>
+
+static int qtest_tuner_initfn(PCIDevice *dev);
+static void qtest_tuner_02(MaruTunerState *s);
+static void qtest_tuner_03(MaruTunerState *s);
+static void qtest_tuner_04(MaruTunerState *s);
+#endif
+
+static int g_tuners_num = 0;
+
+//TODO global tune info should be N-tuner based
+static char ts_mapping_table_path[MAX_PATH_LEN] = {0};
+//static char still_img_path[MAX_PATH_LEN] = {0};
+static struct tune_info g_current_tune = {0, 0, MAX_MODULATION, -1, -1};
+
+static pthread_mutex_t g_tuners_lck = PTHREAD_MUTEX_INITIALIZER;
+static QSIMPLEQ_HEAD(, MaruTunerState) g_tuners = QSIMPLEQ_HEAD_INITIALIZER(g_tuners);
+
+/* MaruTunerState handling utilities
+ * add_tuner_device, del_tuner_device , find_tuner_device
+ */
+static void add_tuner_device(MaruTunerState* s)
+{
+    pthread_mutex_lock(&g_tuners_lck);
+    QSIMPLEQ_INSERT_TAIL(&g_tuners, s, list);
+    pthread_mutex_unlock(&g_tuners_lck);
+}
+
+static void del_tuner_device(MaruTunerState* s)
+{
+    pthread_mutex_lock(&g_tuners_lck);
+    QSIMPLEQ_REMOVE(&g_tuners, s, MaruTunerState, list);
+    pthread_mutex_unlock(&g_tuners_lck);
+}
+
+static MaruTunerState* find_tuner_device(int dev_num)
+{
+    // g_tuners_lck should be held before enter
+    LOG_TRACE("(%s) %d\n", __func__, dev_num);
+    MaruTunerState* s = NULL;
+    if (QSIMPLEQ_EMPTY(&g_tuners))
+        return NULL;
+    QSIMPLEQ_FOREACH(s, &g_tuners, list) {
+        if (s->id == dev_num)
+            return s;
+    }
+    return NULL;
+}
+
+
+/* Should be called after acquiring tsdemux mutex */
+static void dump_filter_list(MaruTunerState *s)
+{
+    struct pid_filter *filter;
+    LOG_TRACE("(%s) :\n", __func__);
+    QLIST_FOREACH(filter, &s->filters_head, entries) {
+        LOG_TRACE("    pid = %d(0x%x), stream_idx = %d, enabled = %d\n",
+                filter->pid, filter->pid, filter->stream_idx, filter->enabled);
+    }
+
+    if (QLIST_EMPTY(&s->filters_head)) {
+        LOG_TRACE("    filter list is empty\n", __func__);
+    }
+}
+
+
+struct param_setting {
+    const char *name;
+    unsigned int value;
+};
+
+#define MAX_ONAIR_LEN      10
+#define MAX_MODULATION_LEN 10
+#define MAX_CARRIER_LEN 128
+static struct param_setting atsc_modulation_list[] = {
+    { "QPSK",    QPSK   },
+    { "PSK8",    PSK_8  },
+    { "QAM32",   QAM_32 },
+    { "QAM16",   QAM_16 },
+    { "QAM128",  QAM_128},
+    { "8VSB",    VSB_8  },
+    { "16VSB",   VSB_16 },
+    { "QAM64",   QAM_64 },
+    { "QAM256",  QAM_256},
+    { NULL,      -1 }
+};
+
+static struct param_setting config_system_list[] = {
+    { "ATSC", MARUTUNER_CONFIG_SYSTEM_ATSC },
+    { "DVB",  MARUTUNER_CONFIG_SYSTEM_DVB  },
+    { NULL, -1}
+};
+
+static struct param_setting config_country_list[] = {
+    { "KOR", MARUTUNER_CONFIG_COUNTRY_KOR },
+    { "USA", MARUTUNER_CONFIG_COUNTRY_USA },
+    { NULL, -1}
+};
+
+static struct param_setting config_playmode_list[] = {
+    { "ts", MARUTUNER_CONFIG_PLAYMODE_TS },
+    { "still", MARUTUNER_CONFIG_PLAYMODE_STILL },
+    { NULL, -1}
+};
+
+static int get_config_value(struct param_setting *list, const char *str)
+{
+    while (list->name) {
+        if (strcmp(list->name, str) == 0) {
+            break;
+        }
+        list++;
+    }
+    return list->value;
+}
+
+
+#define CONFIG_TABLE_NR_COLUMN 7
+#define CONFIG_TABLE_NR_COLUMN_DVB 12
+static inline int get_table_entry_params_atsc(char *buf, unsigned int *streaming, char* carrier,
+                                         unsigned int *freq, char *mod,
+                                         char *path)
+{
+    int cnt;
+    unsigned int ptc = 0;
+    char onair[MAX_ONAIR_LEN] = {0};
+    assert(buf);
+    cnt = sscanf(buf, "%u %s %s %s %u %u %[^\n]", streaming, onair, carrier, mod, &ptc, freq, path);
+    return cnt;
+}
+
+static inline int get_table_entry_params_dvb(char *buf, unsigned int *streaming, char* carrier,
+                                         unsigned int *freq, char *mod,
+                                         char *path)
+{
+    int cnt;
+    unsigned int sym_rate = 0;
+    unsigned int cell_id = 0;
+    unsigned int bit_error = 0;
+    unsigned int sig_strength = 0;
+    unsigned int sig_quality = 0;
+    unsigned int ptc = 0;
+    char onair[MAX_ONAIR_LEN] = {0};
+    assert(buf);
+    // The DVB specific params, which are from sym_rate to sig_quality, have no real operation now
+    // Reserved for future use
+    cnt = sscanf(buf, "%u %s %s %s %u %u %u %u %u %u %u %[^\n]", streaming, onair, carrier, mod, &ptc, freq,
+                &sym_rate, &cell_id, &bit_error, &sig_strength, &sig_quality, path);
+    return cnt;
+}
+
+/*
+ * Table spec :
+ * <Antena> <OnAir> <PTC> <Frequency> <Modulation> <TS-file>
+ *
+ * returned argument :
+ * antena, frequency, modulation, file path
+ * none used : ptc, onair
+ */
+//<Antena> <OnAir> <Carrier> <Modulation> <PTC> <Frequency> <TS-file>
+static inline int get_table_entry_params(MaruTunerState *s, char *buf, unsigned int *streaming, char* carrier,
+                                         unsigned int *freq, char *mod,
+                                         char *path)
+{
+    int ret = 0;
+    if (s->cfg.system == MARUTUNER_CONFIG_SYSTEM_ATSC) {
+        ret = (CONFIG_TABLE_NR_COLUMN == get_table_entry_params_atsc(buf, streaming, carrier, freq, mod, path));
+    } else if (s->cfg.system == MARUTUNER_CONFIG_SYSTEM_DVB) {
+        ret = (CONFIG_TABLE_NR_COLUMN_DVB == get_table_entry_params_dvb(buf, streaming, carrier, freq, mod, path));
+    } else {
+        ret = 0;
+    }
+    return ret;
+}
+
+// This marutuner_tune_atv function checks whether the number of columns is (CONFIG_TABLE_NR_COLUMN - 1),
+// because the condition of ATV is existence of every parameters without only tsfile column.
+// To sum up, in case of DTV must have CONFIG_TABLE_NR_COLUMN, and in case of ATV must have (CONFIG_TABLE_NR_COLUMN-1).
+static inline int get_table_entry_params_atv(MaruTunerState *s, char *buf, unsigned int *streaming, char* carrier,
+                                         unsigned int *freq, char *mod,
+                                         char *path)
+{
+    int ret = 0;
+    if (s->cfg.system == MARUTUNER_CONFIG_SYSTEM_ATSC) {
+        ret = ((CONFIG_TABLE_NR_COLUMN - 1) == get_table_entry_params_atsc(buf, streaming, carrier, freq, mod, path));
+    } else if (s->cfg.system == MARUTUNER_CONFIG_SYSTEM_DVB) {
+        ret = ((CONFIG_TABLE_NR_COLUMN_DVB - 1) == get_table_entry_params_dvb(buf, streaming, carrier, freq, mod, path));
+    } else {
+        ret = 0;
+    }
+    return ret;
+}
+
+/*
+ * simple table searching for atv
+ */
+static void marutuner_tune_atv(MaruTunerState *s, unsigned int freq)
+{
+    char line_buf[MAX_PATH_LEN];       // is this enough?
+    char tpath[MAX_PATH_LEN];
+    char tmod_str[MAX_MODULATION_LEN];
+    char carrier[MAX_CARRIER_LEN];
+    size_t line_size = MAX_PATH_LEN;
+
+    s->fe.frequency = freq;
+    s->fe_status = MARUTUNER_FE_TUNE_FAILED;
+
+    s->table = fopen(ts_mapping_table_path, "rb");
+    if (!s->table) {
+        LOG_SEVERE("(%s) table file open failed(%d)\n", __func__, errno);
+        return;
+    }
+
+    /* for win32 compatibility, we can't use getline */
+    while ((fgets(line_buf, line_size, s->table)) != NULL) {
+        unsigned int is_streaming = 0;
+        unsigned int tfreq = 0;
+        memset(tpath, 0x0, MAX_PATH_LEN);
+        memset(tmod_str, 0x0, MAX_MODULATION_LEN);
+        memset(carrier, 0x0, MAX_CARRIER_LEN);
+
+        if (!get_table_entry_params_atv(s, line_buf, &is_streaming, carrier, &tfreq, tmod_str, tpath)) {
+            LOG_TRACE("(%s) invalid entry(%s). go to next entry.\n", __func__, line_buf);
+            continue;
+        }
+
+        /*
+         * check frequency existence and no file
+         * don't care modulation
+         */
+        if (s->fe.frequency == tfreq) {
+            if (tpath[0] == '\0') {
+                LOG_TRACE("(%s) frequency(%u) matched, atv channel\n", __func__, tfreq);
+                s->fe_status = MARUTUNER_FE_HAS_ONLY_PARAM;
+                break;
+            } else {
+                LOG_TRACE("(%s) frequency(%u) matched, but dtv channel\n", __func__, tfreq);
+                break;
+            }
+        }
+    }
+
+    if (s->table) {
+        fclose(s->table);
+        s->table = NULL;
+    }
+}
+
+/*
+ * TS mapping table parser
+ *
+ * return tuned status.
+ *        0 : failed. no entry or error
+ *        1 : failed. has only frequency and modulation, but no ts file
+ *        2 : success
+ */
+static int get_mapped_ts_file(MaruTunerState *s, FILE *table, struct fe_param *fe)
+{
+    char line_buf[MAX_PATH_LEN];       // is this enough?
+    char tmod_str[MAX_MODULATION_LEN];
+    char tpath[MAX_PATH_LEN];
+    char carrier[MAX_CARRIER_LEN];
+
+
+    size_t line_size = MAX_PATH_LEN;
+    int ret = MARUTUNER_FE_TUNE_FAILED;
+
+    /* for win32 compatibility, we can't use getline */
+    while ((fgets(line_buf, line_size, table)) != NULL) {
+        unsigned int is_streaming = 0;
+        unsigned int tfreq = 0;
+        fe_modulation_t tmod;
+        memset(tpath, 0x0, MAX_PATH_LEN);
+        memset(tmod_str, 0x0, MAX_MODULATION_LEN);
+        memset(carrier, 0x0, MAX_CARRIER_LEN);
+
+        struct param_setting *mod_list = atsc_modulation_list;
+        if (!get_table_entry_params(s, line_buf, &is_streaming, carrier, &tfreq, tmod_str, tpath)) {
+            //LOG_TRACE("(%s) missing params(entry: %s). go to next entry.\n", __func__, line_buf);
+            continue;
+        }
+        //LOG_TRACE("(%s) parsing success(freq=%u, mod=%s, path=%s)\n", __func__, tfreq, tmod_str, tpath);
+
+        /* get matched modulation value */
+        while (mod_list->name) {
+            if (strcmp(tmod_str, mod_list->name) == 0) {
+                tmod = mod_list->value;
+                goto modulation_converted;
+            }
+            mod_list++;
+        }
+        LOG_INFO("(%s) unsupported modulation. go to next entry\n", __func__);
+        continue;
+
+modulation_converted:
+        /* compare parameters */
+        if ((fe->frequency == tfreq) && (fe->modulation == tmod)) {
+            fe->ts_filename = strdup(tpath);
+            if (!fe->ts_filename) {
+                LOG_SEVERE("(%s) strdup error\n", __func__);
+                goto out;
+            }
+
+            LOG_INFO("(%s) frequency(%u) and modulation(%s) file(%s) matched\n",
+                    __func__, tfreq, tmod_str, fe->ts_filename);
+            /*
+             * the 'b' mode of fopen is required by non-unix system
+             */
+            fe->tsfile = fopen(fe->ts_filename, "rb");
+            if (!fe->tsfile) {
+                LOG_SEVERE("(%s) ts file open failed\n", __func__);
+                ret = MARUTUNER_FE_HAS_ONLY_PARAM;
+                goto out;
+            }
+            fe->is_streaming = is_streaming;
+            ret = MARUTUNER_FE_HAS_TS;
+            goto out;
+        }
+    }
+    LOG_TRACE("(%s) there is no matched list.\n", __func__);
+
+out:
+    return ret;
+}
+
+static int get_stream_idx_from_pid(MaruTunerState *s, int pid)
+{
+    AVStream *st;
+    int i;
+
+    for (i = 0; i < s->fmt_ctx->nb_streams; i++) {
+        st = s->fmt_ctx->streams[i];
+        if (st->id == pid)
+            return i;
+    }
+
+    return -1;
+}
+
+/* Should be called after acquiring tsdemux mutex */
+static int add_pid_filter(MaruTunerState *s, int pid)
+{
+    struct pid_filter *filter;
+
+    LOG_TRACE("(%s)\n", __func__);
+
+    /* check that a filter already has this pid */
+    QLIST_FOREACH(filter, &s->filters_head, entries) {
+        if (filter->pid == pid) {
+            LOG_TRACE("(%s) filter(pid = %d) already exists\n", __func__, pid);
+            return 0;
+        }
+    }
+
+    /* new one */
+    filter = calloc(1, sizeof(struct pid_filter));
+    if (!filter) {
+        LOG_SEVERE("(%s) not enough memory\n", __func__);
+        return -1;
+    }
+
+    filter->pid = pid;
+    filter->stream_idx = -1; //stream_idx could be 0
+    QLIST_INSERT_HEAD(&s->filters_head, filter, entries);
+
+    return 0;
+}
+
+/* Should be called after acquiring tsdemux mutex
+ * return value:
+ *  -1 , if any error
+ *  otherwise, # of enabled filters
+ */
+static int enable_filters_and_decoder(MaruTunerState *s)
+{
+    struct pid_filter *filter;
+#ifndef QTEST_TIZEN
+    AVStream *st;
+#endif
+    int idx;
+    int ret = 0;
+
+    LOG_TRACE("(%s)\n", __func__);
+
+    QLIST_FOREACH(filter, &s->filters_head, entries) {
+        if (filter->enabled == 0) {
+            idx = get_stream_idx_from_pid(s, filter->pid);
+            if (idx < 0) {
+                LOG_TRACE("(%s) failed to get the matched stream idx(pid:%d)\n", __func__, filter->pid);
+                continue;
+            }
+
+#ifndef QTEST_TIZEN
+            st = s->fmt_ctx->streams[idx];
+            if (maru_tuner_dec_init_ctx(s->decoder_state, st) < 0) {
+                /* TODO : unsupported format handling */
+                LOG_SEVERE("(%s) Failed to initialize decoder context. pid(%d), stream idx(%d)\n",
+                        __func__, filter->pid, filter->stream_idx);
+                ret = -1;
+                break;
+            }
+#endif
+
+            filter->stream_idx = idx;
+            filter->enabled = 1;
+            ret++;
+            LOG_TRACE("(%s) pid(%d) matched stream idx = %d\n",
+                    __func__, filter->pid, filter->stream_idx);
+        }
+    }
+
+    return ret;
+}
+
+/* Should be called after acquiring tsdemux mutex */
+static int init_avstream(MaruTunerState *s)
+{
+    /* open input file, and allocate format context */
+    if (avformat_open_input(&s->fmt_ctx, s->fe.ts_filename, NULL, NULL) < 0) {
+        LOG_SEVERE("(%s) avformat_open_input couldn't open ts file %s\n", __func__, s->fe.ts_filename);
+        return -1;
+    }
+    LOG_INFO("init avstream :fmt_ctx: %p\n", s->fmt_ctx);
+
+    /* retrieve stream information */
+    if (avformat_find_stream_info(s->fmt_ctx, NULL) < 0) {
+        LOG_SEVERE("(%s) av_find_stream_info couldn't find stream information\n", __func__);
+        goto error;
+    }
+
+    LOG_TRACE("(%s) nb_programs = %d, nb_streams = %d\n",
+            __func__, s->fmt_ctx->nb_programs, s->fmt_ctx->nb_streams);
+
+    return 0;
+
+error:
+    avformat_close_input(&s->fmt_ctx);
+    LOG_SEVERE("(%s) init failed. avstream closed\n", __func__);
+    return -1;
+}
+
+static void disable_filters_and_decoder(MaruTunerState *s)
+{
+    AVStream *st;
+    struct pid_filter *filter, *next;
+
+    if (!s->fmt_ctx)
+        return;
+
+    QLIST_FOREACH_SAFE(filter, &s->filters_head, entries, next) {
+        if (filter->stream_idx == -1) {
+            LOG_TRACE("(%s) filter(%p): pid(%d) is not enabled, enabled=%d\n",
+                    __func__, filter, filter->pid, filter->enabled);
+            continue;
+        }
+
+        st = s->fmt_ctx->streams[filter->stream_idx];
+        maru_tuner_dec_deinit_ctx(s->decoder_state, st);
+        filter->enabled = 0;
+        filter->stream_idx = -1;
+        LOG_TRACE("(%s) disabled filter(%p): pid(%d), enabled(%d), stream_idx(%d)\n",
+                __func__, filter, filter->pid, filter->enabled, filter->stream_idx);
+    }
+}
+
+/* Should be called after acquiring tsdemux mutex */
+static void free_avstream(MaruTunerState *s, bool isfree)
+{
+    if (!s->fmt_ctx)
+        return;
+
+    disable_filters_and_decoder(s);
+
+    LOG_TRACE("(%s) fmt_ctx: %p\n", __func__, s->fmt_ctx);
+    avformat_close_input(&s->fmt_ctx);
+    s->fe.is_streaming = false;
+    /* don't need to assign null to fmt_ctx */
+    //LOG_TRACE("(%s) after avformat close() :fmt_ctx: %p\n", __func__, s->fmt_ctx);
+}
+
+static int switch_avstream(MaruTunerState *s)
+{
+    int ret;
+
+    free_avstream(s, false);
+
+    s->table = fopen(ts_mapping_table_path, "rb");
+    if (!s->table) {
+        LOG_SEVERE("(%s) table file open failed\n", __func__);
+        return MARUTUNER_FE_TUNE_FAILED;
+    }
+    //TODO  mem leak here , before call get_mapped_ts_file
+    // tsfile_name, tsfile (FILE*) should be closed.
+    // at the same time, couldn't do that, because of section mutex
+    ret = get_mapped_ts_file(s, s->table, &s->fe);
+
+    if (ret != MARUTUNER_FE_HAS_TS)
+        goto close;
+
+    if (init_avstream(s) < 0) {
+        LOG_SEVERE("(%s) failed to switch avstream\n", __func__);
+        ret = MARUTUNER_FE_TUNE_FAILED;
+        goto close;
+    }
+
+    ret = enable_filters_and_decoder(s);
+    if (ret < 0) {
+        LOG_SEVERE("(%s) failed to enable fileter and decoder\n", __func__);
+        ret = MARUTUNER_FE_TUNE_FAILED;
+        free_avstream(s, false);
+        goto close;
+    } else if (ret == 0) {
+        s->doing_filtering = 0;
+    }
+
+close:
+    if (s->table) {
+        fclose (s->table);
+        s->table = NULL;
+    }
+    return ret;
+}
+
+#if 1
+static void cleanup_filters(MaruTunerState *s)
+{
+    struct pid_filter *filter, *next;
+    AVStream *st;
+
+    LOG_TRACE("(%s)\n", __func__);
+
+    qemu_mutex_lock(&s->tsdemux_mutex);
+    QLIST_FOREACH_SAFE(filter, &s->filters_head, entries, next) {
+        st = s->fmt_ctx->streams[filter->stream_idx];
+        maru_tuner_dec_deinit_ctx(s->decoder_state, st);
+        QLIST_REMOVE(filter, entries);
+        free(filter);
+    }
+    qemu_mutex_unlock(&s->tsdemux_mutex);
+}
+#endif
+
+/* Should be called after acquires section mutex */
+static void marutuner_raise_intr(MaruTunerState *s, uint32_t irq)
+{
+    s->irq = irq;
+    qemu_bh_schedule(s->qemu_bh);
+}
+
+/* thread for section */
+static void *marutuner_section_th(void *thread_param)
+{
+    MaruTunerState *s = (MaruTunerState *)thread_param;
+#ifdef CONFIG_LINUX
+    QemuThread thread;
+#endif
+
+    qemu_mutex_lock(&s->section_mutex);
+    LOG_INFO("section thread starting...\n");
+#ifdef CONFIG_LINUX
+    qemu_thread_get_self(&thread);
+    LOG_TRACE("(%s) thread id : %x\n", __func__, thread.thread);
+#endif
+
+    while (1) {
+        //LOG_TRACE("(%s) waiting...\n", __func__);
+        qemu_cond_wait(&s->section_th_cond, &s->section_mutex);
+        //LOG_TRACE("(%s) woke up\n", __func__);
+
+        if (s->destroying == true) {
+            LOG_TRACE("(%s) destroying...\n", __func__);
+            s->hw_ptr_offset = 0;
+#ifndef QTEST_TIZEN
+            memset(s->vaddr, 0, MARUTUNER_MEM_SIZE);
+#endif
+            break;
+        }
+
+        /* stop dma after the interrupt thread of kernel wakes up section thread */
+        if (s->fe.is_streaming == false || s->doing_dma == 0) {
+            s->hw_ptr_offset = 0;
+            memset(s->vaddr, 0, MARUTUNER_MEM_SIZE);
+            LOG_INFO("(%s) thread stopped\n", __func__);
+            continue;
+        }
+
+        /*
+         * XXX : re-starting workaround
+         * When gstreamer demux plugin works with frontend plugin independently or faster,
+         * second time play already has previous frontend status like MARUTUNER_FE_HAS_TS.
+         * So, this is processed as error, but it is no problem as demux send start cmd again.
+         * Of course, we can handle to do not close ts file, this is not our goal.
+         */
+        if (s->doing_dma && !(s->fe.tsfile)) {
+            LOG_TRACE("(%s) there is no opened ts file\n", __func__);
+            marutuner_raise_intr(s, MARUTUNER_ERR_INT);
+            continue;
+        }
+
+        int read_cnt = 0;
+        int total_cnt = 0;
+        int todo = MARUTUNER_MEM_SIZE;
+        while (todo > 0) {
+            read_cnt = fread(s->vaddr + read_cnt, 1, todo, s->fe.tsfile);
+            if (read_cnt < todo) {
+                if (feof(s->fe.tsfile)) {
+                    LOG_TRACE("(%s) End of TS file\n", __func__);
+                    if (fseek(s->fe.tsfile, 0, SEEK_SET) < 0) {
+                        LOG_TRACE("(%s) Failed to fseek\n", __func__);
+                    }
+                }
+            }
+            todo -= read_cnt;
+            total_cnt += read_cnt;
+            //LOG_TRACE("(%s) total_cnt = %d, todo = %d\n", __func__, total_cnt, todo);
+        }
+
+        s->hw_ptr_offset = total_cnt;
+        qemu_mutex_unlock(&s->section_mutex);
+        usleep(5000); // temporary time
+        qemu_mutex_lock(&s->section_mutex);
+        marutuner_raise_intr(s, MARUTUNER_DMA_COMPLETE_INT);
+    }
+    qemu_mutex_unlock(&s->section_mutex);
+
+    LOG_TRACE("(%s) finished\n", __func__);
+    return NULL;
+}
+
+static void *marutuner_tsdemux_th(void *thread_param)
+{
+    MaruTunerState *s = (MaruTunerState *)thread_param;
+    struct pid_filter *filter;
+    AVStream *st;
+    AVPacket pkt;
+    int ret;
+
+    qemu_mutex_lock(&s->tsdemux_mutex);
+
+    LOG_INFO("tsdemux thread starting...\n");
+    av_init_packet(&pkt);
+    pkt.data = NULL;
+    pkt.size = 0;
+
+    while (1) {
+        LOG_TRACE("(%s) waiting...\n", __func__);
+        qemu_cond_wait(&s->tsdemux_th_cond, &s->tsdemux_mutex);
+        qemu_mutex_unlock(&s->tsdemux_mutex);
+        LOG_TRACE("(%s) woke up\n", __func__);
+
+        if (s->destroying == true) {
+            LOG_TRACE("(%s) destroying...\n", __func__);
+            break;
+        }
+
+        while (1) {
+            if (maru_tuner_dec_queue_is_full(s->decoder_state)) {
+                /* we should find optimized sleep time */
+                usleep(10000);
+                continue;
+            }
+
+            qemu_mutex_lock(&s->tsdemux_mutex);
+            if (s->fe.is_streaming  == false || s->doing_filtering == 0) {
+                LOG_TRACE("(%s) filtering stop\n", __func__);
+                break;
+            }
+
+
+            ret = av_read_frame(s->fmt_ctx, &pkt);
+            if (ret< 0) {
+                if (ret == AVERROR(EAGAIN)) {
+                    LOG_SEVERE("(%s) av_read_frame EAGAIN error\n", __func__);
+                    break;
+                }
+
+                if (ret == AVERROR_EOF) {
+                    LOG_INFO("(%s) End of TS file\n", __func__);
+#if REPLAY_ON_EOF
+                    ret = avformat_seek_file(s->fmt_ctx, -1, INT64_MIN, 0, INT64_MAX, 0);
+                    if (ret < 0) {
+                        //FIXME: infinite loop??
+                        LOG_SEVERE("(%s) failted to seek to start position\n");
+                        break;
+                    }
+                    qemu_mutex_unlock(&s->tsdemux_mutex);
+                    continue;
+#endif
+                }
+                s->doing_filtering = 0;
+                break;
+            }
+
+            /*
+             * filtering packet for PID
+             */
+            QLIST_FOREACH(filter, &s->filters_head, entries) {
+                //LOG_TRACE("(%s:%d) start decode for filter->pid:%d filter->sid = %d pkt.streamidx:%d\n", __func__, __LINE__, filter->pid, filter->stream_idx, pkt.stream_index);
+                if (filter->enabled == 1 && filter->stream_idx == pkt.stream_index) {
+                    //LOG_TRACE("(%s:%d) start decode for filter->pid:%d\n", __func__, __LINE__, filter->pid);
+                    st = s->fmt_ctx->streams[filter->stream_idx];
+                    maru_tuner_dec_decode_pkt(s->decoder_state, st, &pkt);
+                }
+            }
+
+            qemu_mutex_unlock(&s->tsdemux_mutex);
+        }
+#if 0
+        /* flush cached frames */
+        pkt.data = NULL;
+        pkt.size = 0;
+        do {
+            maru_tuner_dec_decode_pkt(&got_frame);
+        } while (got_frame);
+#endif
+    }
+
+    LOG_INFO("(%s) finished\n", __func__);
+
+    return NULL;
+}
+
+static void marutuner_threads_init(MaruTunerState *s)
+{
+    s->destroying = false;
+    qemu_thread_create(&s->section_th, "marutuner_section_th", marutuner_section_th, (void *)s,
+            QEMU_THREAD_JOINABLE);
+    qemu_thread_create(&s->tsdemux_th, "marutuner_tsdemux_th", marutuner_tsdemux_th, (void *)s,
+            QEMU_THREAD_JOINABLE);
+}
+
+int marutuner_get_tuned_info(struct tune_info *info)
+{
+    assert(info != NULL);
+    // TODO have to lock with some lock
+    // otherwise, gettor will get imperfect data
+
+    info->is_tuned = g_current_tune.is_tuned;
+    info->frequency = g_current_tune.frequency;
+    info->modulation = g_current_tune.modulation;
+    info->is_streaming = g_current_tune.is_streaming;
+    info->system = g_current_tune.system;
+    info->country = g_current_tune.country;
+    info->playmode = g_current_tune.playmode;
+    strcpy(info->map_table_path, ts_mapping_table_path);
+
+    return info->is_tuned? 0 : -1;
+}
+
+static int marutuner_set_tuned_info(MaruTunerState *s, bool is_tuned)
+{
+    // TODO have to lock with some lock
+    if (s->id != 0)
+        return 0;
+
+    g_current_tune.is_tuned = is_tuned;
+    g_current_tune.is_streaming= s->fe.is_streaming;
+    g_current_tune.frequency = s->fe.frequency;
+    g_current_tune.modulation = s->fe.modulation;
+    g_current_tune.system = s->cfg.system;
+    g_current_tune.country = s->cfg.country;
+    g_current_tune.playmode = s->cfg.playmode;
+
+    qemu_bh_schedule(s->set_tune_info_bh);
+    LOG_TRACE("(%s) set g_current_tune info freq:%lld, mod:%lld\n",
+            __func__, g_current_tune.frequency, g_current_tune.modulation);
+    return 0;
+}
+
+int marutuner_toggle_stream(int devnum, struct tune_info *info, bool on)
+{
+    MaruTunerState *s = NULL;
+    assert(info != NULL);
+    assert(info->modulation < MAX_MODULATION);
+    //TODO devnum should be get as parameters
+    assert(devnum == 0);
+    LOG_INFO("(%s) toggle : %s on current[stream on?:%s freq:%lld mod:%d]\n"
+         , __func__, on? "on":"off"
+         , info->is_streaming ? "on": "off"
+         , info->frequency, info->modulation);
+
+    pthread_mutex_lock(&g_tuners_lck);
+    s = find_tuner_device(devnum);
+    if (s == NULL || s->destroying == true)
+        goto out;
+
+    qemu_mutex_lock(&s->tsdemux_mutex);
+    if (s->fe.frequency == info->frequency &&
+            s->fe.modulation == info->modulation) {
+        s->fe.is_streaming = on;
+        marutuner_set_tuned_info(s, true);
+    }
+    if (s->doing_filtering == 1)
+        qemu_cond_signal(&s->tsdemux_th_cond);
+    qemu_mutex_unlock(&s->tsdemux_mutex);
+
+    qemu_mutex_lock(&s->section_mutex);
+    if (s->doing_dma == 1)
+        qemu_cond_signal(&s->section_th_cond);
+    qemu_mutex_unlock(&s->section_mutex);
+
+out:
+    pthread_mutex_unlock(&g_tuners_lck);
+    return 0;
+}
+
+int marutuner_switch_ts(int devnum, struct tune_info *info)
+{
+    MaruTunerState *s = NULL;
+    LOG_TRACE("(%s) reload avstream \n",__func__);
+
+    assert(info != NULL);
+    assert(info->modulation < MAX_MODULATION);
+    //TODO devnum should be get as parameters
+    assert(devnum == 0);
+
+    pthread_mutex_lock(&g_tuners_lck);
+    s = find_tuner_device(devnum);
+    if (s == NULL ||s->destroying == true)
+        goto out;
+
+    //section thread , demux thread have to be stopped at some point
+    qemu_mutex_lock(&s->tsdemux_mutex);
+    if (s->fe.frequency == info->frequency &&
+            s->fe.modulation == info->modulation) {
+        switch_avstream(s);
+        marutuner_set_tuned_info(s, true);
+    }
+
+    if (s->doing_filtering)
+        qemu_cond_signal(&s->tsdemux_th_cond);
+    qemu_mutex_unlock(&s->tsdemux_mutex);
+
+    //TODO  here any new command can come through mmio , will that be any probs??
+
+    qemu_mutex_lock(&s->section_mutex);
+    qemu_cond_signal(&s->section_th_cond);
+    qemu_mutex_unlock(&s->section_mutex);
+
+out:
+    pthread_mutex_unlock(&g_tuners_lck);
+    return 0;
+}
+
+static int playmode_set_to_ts(MaruTunerState *s)
+{
+    int ret;
+
+    assert(!s->fmt_ctx);
+    //print_tuner_state(s);
+    LOG_TRACE("(%s) fe_status:%d, is_tuned:%d\n", __func__, s->fe_status, g_current_tune.is_tuned);
+
+    /* XXX : 'is_tuned' and 'fe_status' is the same thing? */
+    if (s->fe_status != MARUTUNER_FE_HAS_TS) {
+        return 0;
+    }
+
+    if ((ret = init_avstream(s)) < 0) {
+        LOG_SEVERE("(%s) failed to init_avstream\n", __func__);
+        goto out;
+    }
+
+    ret = enable_filters_and_decoder(s);
+    if (ret < 0) {
+        LOG_SEVERE("(%s) failed to enable fileter and decoder\n", __func__);
+        free_avstream(s, false);
+    } else if (ret > 0) {
+        s->doing_filtering = 1;
+        qemu_cond_signal(&s->tsdemux_th_cond);
+    } // don't care ret == 0
+
+out:
+    return ret;
+}
+
+static int playmode_set_to_stillimg(MaruTunerState *s)
+{
+    //print_tuner_state(s);
+
+    free_avstream(s, false);
+    s->fe.is_streaming = true; //section thread should not stop
+    s->doing_filtering = 0; //tsdemux thread should stop
+
+    return 0;
+}
+
+int marutuner_switch_playmode(int devnum, int playmode)
+{
+    MaruTunerState *s = NULL;
+    int ret = 0;
+
+    pthread_mutex_lock(&g_tuners_lck);
+    s = find_tuner_device(devnum);
+    if (s == NULL ||s->destroying == true) {
+        qemu_mutex_unlock(&s->tsdemux_mutex);
+        return -1;
+    }
+
+    assert(playmode == MARUTUNER_CONFIG_PLAYMODE_TS ||
+           playmode == MARUTUNER_CONFIG_PLAYMODE_STILL);
+    LOG_INFO("switching play mode %s -> %s\n",
+         config_playmode_list[s->cfg.playmode].name,
+         config_playmode_list[playmode].name);
+
+    /* if not changed, just ignore */
+    if (s->cfg.playmode == playmode) {
+        goto out;
+    }
+
+    qemu_mutex_lock(&s->tsdemux_mutex);
+    if (playmode == MARUTUNER_CONFIG_PLAYMODE_TS) {
+        ret = playmode_set_to_ts(s);
+    } else {
+        ret = playmode_set_to_stillimg(s);
+    }
+    if (ret < 0) {
+        LOG_INFO("playmode switching failed\n");
+        qemu_mutex_unlock(&s->tsdemux_mutex);
+        goto out;
+    }
+
+    s->cfg.playmode = playmode;
+    marutuner_set_tuned_info(s, g_current_tune.is_tuned);
+    maru_tuner_dec_set_stillimage(s->decoder_state, playmode);
+    qemu_mutex_unlock(&s->tsdemux_mutex);
+
+out:
+    pthread_mutex_unlock(&g_tuners_lck);
+    return ret;
+}
+
+int marutuner_set_table_file(const char* path)
+{//FIXME SAFE?
+    strncpy(ts_mapping_table_path, path, MAX_PATH_LEN);
+    LOG_TRACE("(%s) ts_mapping_table_path : %s\n", __func__, ts_mapping_table_path);
+    return 0;
+}
+
+static uint64_t marutuner_reg_read(void *opaque,
+        hwaddr addr,
+        unsigned size)
+{
+    uint32_t ret = 0;
+    MaruTunerState *s = (MaruTunerState *)opaque;
+
+    switch (addr & 0xFF) {
+    case MARUTUNER_FE_STATUS:
+        //LOG_TRACE("(%s) MARUTUNER_FE_STATUS\n", __func__);
+        ret = s->fe_status;
+        break;
+
+    case MARUTUNER_FE_FREQ:
+        LOG_TRACE("(%s) MARUTUNER_FE_FREQ\n", __func__);
+        ret = s->fe.frequency;
+        break;
+
+    case MARUTUNER_FE_MOD:
+        LOG_TRACE("(%s) MARUTUNER_FE_MOD\n", __func__);
+        ret = s->fe.modulation;
+        break;
+
+    case MARUTUNER_FE_GET_SYSTEM:
+        LOG_TRACE("(%s) MARUTUNER_FE_GET_SYSTEM\n", __func__);
+        ret = s->cfg.system;
+        break;
+
+    case MARUTUNER_FE_GET_ID:
+        LOG_TRACE("(%s) MARUTUNER_FE_GET_ID\n", __func__);
+        ret = s->id;
+        break;
+
+    case MARUTUNER_START:
+        //LOG_TRACE("(%s) MARUTUNER_START\n", __func__);
+        ret = s->doing_dma;
+        break;
+
+    case MARUTUNER_INT:
+        //LOG_TRACE("(%s) MARUTUNER_INT\n");
+        qemu_mutex_lock(&s->section_mutex);
+        ret = s->irq;
+        if (ret) {
+            pci_set_irq(&s->dev, 0);
+            s->irq = 0;
+        }
+        qemu_mutex_unlock(&s->section_mutex);
+        break;
+
+    case MARUTUNER_HWPTR:
+        ret = s->hw_ptr_offset;
+        //LOG_TRACE("(%s) MARUTUNER_HWPTR, hw_ptr_offset = %d\n", __func__, s->hw_ptr_offset);
+        break;
+
+    case MARUTUNER_ATV_GET_STATUS:
+        //LOG_TRACE("(%s) MARUTUNER_ATV_GET_STATUS\n", __func__);
+        ret = s->fe_status;
+        break;
+
+    case MARUTUNER_FE_RESET:
+    case MARUTUNER_FE_TUNE:
+    case MARUTUNER_SETDMA:
+        break;
+
+    default:
+        LOG_SEVERE("(%s) invalid cmd. register addr = %d\n", __func__, (int)addr);
+        break;
+    }
+
+    return ret;
+}
+
+static void marutuner_fe_reset(MaruTunerState *s)
+{
+    qemu_mutex_lock(&s->section_mutex);
+    s->fe.frequency = 0;
+    s->fe.modulation = MAX_MODULATION;
+    s->fe_status = MARUTUNER_FE_TUNE_FAILED;
+    if (s->fe.tsfile) {
+        fclose(s->fe.tsfile);
+        s->fe.tsfile = NULL;
+    }
+    s->doing_dma = 0;
+    qemu_mutex_unlock(&s->section_mutex);
+
+    qemu_mutex_lock(&s->tsdemux_mutex);
+    //free_avstream(s, true);
+    free_avstream(s, false);
+    if (QLIST_EMPTY(&s->filters_head)) {
+        s->doing_filtering = 0;
+    }
+    if (s->fe.ts_filename) {
+        free(s->fe.ts_filename);
+        s->fe.ts_filename = NULL;
+    }
+    dump_filter_list(s);
+    marutuner_set_tuned_info(s, false);
+    qemu_mutex_unlock(&s->tsdemux_mutex);
+}
+
+static void marutuner_fe_freq(MaruTunerState *s, uint64_t val)
+{
+    s->fe.frequency = val;
+}
+
+static void marutuner_fe_mod(MaruTunerState *s, uint64_t val)
+{
+    s->fe.modulation = val;
+}
+
+static void marutuner_fe_tune(MaruTunerState *s)
+{
+    int ret;
+
+    /* because marutuner opens the table for each time, don't need sync with ECP */
+    s->table = fopen(ts_mapping_table_path, "rb");
+    if (!s->table) {
+        s->fe_status = MARUTUNER_FE_TUNE_FAILED;
+        LOG_TRACE("(%s) table file open failed\n", __func__);
+        return;
+    }
+
+    qemu_mutex_lock(&s->section_mutex);
+    if (s->fe.tsfile) {
+        fclose(s->fe.tsfile);
+        s->fe.tsfile = NULL;
+    }
+    qemu_mutex_unlock(&s->section_mutex);
+
+    qemu_mutex_lock(&s->tsdemux_mutex);
+    s->doing_filtering = 0;
+    free_avstream(s, false);
+
+    s->fe_status = get_mapped_ts_file(s, s->table, &s->fe);
+    if (s->fe_status != MARUTUNER_FE_HAS_TS) {
+        qemu_mutex_unlock(&s->tsdemux_mutex);
+        goto out;
+    }
+
+    /* libav and decoder init for each playmode */
+    if (s->cfg.playmode == MARUTUNER_CONFIG_PLAYMODE_TS) {
+        if (maru_tuner_dec_set_stillimage(s->decoder_state, false) < 0) {
+            LOG_SEVERE("(%s) stillimage setting failed(playmode:%s)\n",
+                    __func__,
+                    s->cfg.playmode == MARUTUNER_CONFIG_PLAYMODE_TS ? "TS" : "STILL");
+            qemu_mutex_unlock(&s->tsdemux_mutex);
+            goto out;
+        }
+
+        if (init_avstream(s) < 0) {
+            LOG_SEVERE("(%s) failed to init avstream\n", __func__);
+            s->fe_status = MARUTUNER_FE_TUNE_FAILED;
+            qemu_mutex_unlock(&s->tsdemux_mutex);
+            goto out;
+        }
+
+        if ((ret = enable_filters_and_decoder(s)) < 0) {
+            LOG_SEVERE("(%s) failed to enable fileter and decoder\n", __func__);
+            s->fe_status = MARUTUNER_FE_TUNE_FAILED;
+            free_avstream(s, false);
+            qemu_mutex_unlock(&s->tsdemux_mutex);
+            goto out;
+        }
+        if (ret > 0) {
+            LOG_TRACE("(%s) wakeup tsdemux thread\n", __func__);
+            s->doing_filtering = 1;
+            qemu_cond_signal(&s->tsdemux_th_cond);
+        }
+    } else if (s->cfg.playmode == MARUTUNER_CONFIG_PLAYMODE_STILL) {
+        if (maru_tuner_dec_set_stillimage(s->decoder_state, true) < 0) {
+            LOG_SEVERE("(%s) stillimage setting failed(playmode:%s)\n",
+                    __func__,
+                    s->cfg.playmode == MARUTUNER_CONFIG_PLAYMODE_TS ? "TS" : "STILL");
+            qemu_mutex_unlock(&s->tsdemux_mutex);
+            goto out;
+        }
+    } else { //undefined playmode
+        LOG_SEVERE("(%s) undefined playmode\n", __func__);
+        qemu_mutex_unlock(&s->tsdemux_mutex);
+        goto out;
+    }
+
+    marutuner_set_tuned_info(s, true);
+    qemu_mutex_unlock(&s->tsdemux_mutex);
+
+    /* if demux already started, threads should wakeup */
+    qemu_mutex_lock(&s->section_mutex);
+    if (s->doing_dma == 1) {
+        LOG_TRACE("(%s) wakeup section thread\n", __func__);
+        qemu_cond_signal(&s->section_th_cond);
+    }
+    qemu_mutex_unlock(&s->section_mutex);
+
+out:
+    fclose(s->table);
+    s->table = NULL;
+}
+
+static void marutuner_setdma(MaruTunerState *s, uint64_t val)
+{
+    // XXX : is 'val' ok? should init memory_region_init?
+    s->vaddr = qemu_get_ram_ptr((ram_addr_t)val);
+    memset(s->vaddr, 0, MARUTUNER_MEM_SIZE);
+}
+
+static int marutuner_start_section(MaruTunerState *s, uint64_t val)
+{
+    int ret = -1;
+
+    qemu_mutex_lock(&s->section_mutex);
+    if (val != s->doing_dma) {
+        LOG_TRACE("(%s) section %s\n", __func__, (val == 1 ? "START" : "STOP"));
+    }
+    s->doing_dma = val;
+    if (s->doing_dma) {
+        if (s->fe_status == MARUTUNER_FE_HAS_TS) {
+            //LOG_TRACE("(%s) wakeup section thread\n", __func__);
+#ifndef QTEST_TIZEN
+            qemu_cond_signal(&s->section_th_cond);
+#endif
+            ret = 0;
+        } else {
+            LOG_TRACE("(%s) thread will be woken up when frontend locked\n", __func__);
+        }
+    }
+    qemu_mutex_unlock(&s->section_mutex);
+
+    return ret;
+}
+
+static int marutuner_start_pid_filter(MaruTunerState *s, uint64_t val)
+{
+    int ret = 0;
+
+    qemu_mutex_lock(&s->tsdemux_mutex);
+    if (add_pid_filter(s, val) < 0) {
+        qemu_mutex_unlock(&s->tsdemux_mutex);
+        return -1;
+    }
+
+    /* if the frontend is already tuned */
+    if (s->fe_status == MARUTUNER_FE_HAS_TS) {
+        LOG_TRACE("(%s) frontend already tuned. playmode:%s\n",
+                __func__,
+                s->cfg.playmode == MARUTUNER_CONFIG_PLAYMODE_TS ? "TS" : "STILL");
+        if (s->cfg.playmode == MARUTUNER_CONFIG_PLAYMODE_TS) {
+            /* decoder playmode is already set */
+            if ((ret = enable_filters_and_decoder(s)) < 0) {
+                qemu_mutex_unlock(&s->tsdemux_mutex);
+                return ret;
+            }
+            if (ret > 0) {
+                s->doing_filtering = 1;
+                LOG_TRACE("(%s) wakeup ts demux thread\n", __func__);
+#ifndef QTEST_TIZEN
+                qemu_cond_signal(&s->tsdemux_th_cond);
+#endif
+            } else {
+                LOG_TRACE("(%s) start filtering with pid:%d but there is no valid stream\n", __func__, val);
+            }
+        } else if (s->cfg.playmode == MARUTUNER_CONFIG_PLAYMODE_STILL) {
+            /* A new pid filtering adding don't need to wakeup tsdemux thread,
+             * therefore, we ignore this filtering request.
+             *
+             * And because the playmode is already set when it is tuned,
+             * don't need anything to do here.
+             */
+            ;
+        } else { //undefined playmode
+            LOG_SEVERE("(%s) undefined playmode\n", __func__);
+            ret = -1;
+        }
+    } else {
+        /*
+         * If frontend is not tuned yet, we can't get any stream info
+         * In this case, we only add a filter for the pid
+         */
+        LOG_TRACE("(%s) erontend does not tuned yet\n", __func__);
+        ret = -1;
+
+        dump_filter_list(s);
+    }
+    qemu_mutex_unlock(&s->tsdemux_mutex);
+
+    return ret;
+}
+
+static void marutuner_stop_pid_filter(MaruTunerState *s, uint64_t val)
+{
+    struct pid_filter *filter, *next;
+    AVStream *st;
+
+    qemu_mutex_lock(&s->tsdemux_mutex);
+    QLIST_FOREACH_SAFE(filter, &s->filters_head, entries, next) {
+        if (filter->pid == val) {
+            LOG_TRACE("(%s) MARUTUNER_STOP_PID_FILTER: find matched filter(pid=%d, stream_idx=%d)\n",
+                    __func__, filter->pid, filter->stream_idx);
+            if (s->cfg.playmode == MARUTUNER_CONFIG_PLAYMODE_TS) {
+                if (s->fmt_ctx && filter->stream_idx != -1) {
+                    st = s->fmt_ctx->streams[filter->stream_idx];
+                    maru_tuner_dec_deinit_ctx(s->decoder_state, st);
+                }
+            }
+            QLIST_REMOVE(filter, entries);
+            free(filter);
+        }
+    }
+
+    /*
+     * we just stop the tsdemux thread here.
+     * fmt_ctx is freed on frontend reset.
+     */
+    if (QLIST_EMPTY(&s->filters_head)) {
+        s->doing_filtering = 0;
+    }
+    dump_filter_list(s);
+    qemu_mutex_unlock(&s->tsdemux_mutex);
+}
+
+static void marutuner_reg_write(void *opaque,
+                            hwaddr addr,
+                            uint64_t val,
+                            unsigned size)
+{
+    MaruTunerState *s = (MaruTunerState *)opaque;
+
+    switch (addr & 0xFF) {
+    case MARUTUNER_FE_RESET:
+        LOG_TRACE("(%s) MARUTUNER_FE_RESET: val = %lld\n", __func__, val);
+        marutuner_fe_reset(s);
+        break;
+
+    case MARUTUNER_FE_FREQ:
+        LOG_TRACE("(%s) MARUTUNER_FE_FREQ: val = %lld\n", __func__, val);
+        marutuner_fe_freq(s, val);
+        break;
+
+    case MARUTUNER_FE_MOD:
+        LOG_TRACE("(%s) MARUTUNER_FE_MOD: val = %lld\n", __func__, val);
+        marutuner_fe_mod(s, val);
+        break;
+
+    case MARUTUNER_FE_TUNE:
+        LOG_TRACE("(%s) MARUTUNER_FE_TUNE: val = %lld\n", __func__, val);
+        marutuner_fe_tune(s);
+        break;
+
+    case MARUTUNER_SETDMA:
+        LOG_TRACE("(%s) MARUTUNER_SETDMA: val = %lld\n", __func__, val);
+        marutuner_setdma(s, val);
+        break;
+
+    case MARUTUNER_START:
+        marutuner_start_section(s, val);
+        break;
+
+    case MARUTUNER_START_PID_FILTER:
+        LOG_TRACE("(%s) MARUTUNER_START_PID_FILTER: val = %lld\n", __func__, val);
+        marutuner_start_pid_filter(s, val);
+        break;
+
+    case MARUTUNER_STOP_PID_FILTER:
+        LOG_TRACE("(%s) MARUTUNER_STOP_PID_FILTER: val = %lld\n", __func__, val);
+        marutuner_stop_pid_filter(s, val);
+        break;
+
+    case MARUTUNER_ATV_SET_TUNE:
+        LOG_TRACE("(%s) MARUTUNER_ATV_SET_TUNE: val = %lld\n", __func__, val);
+        marutuner_tune_atv(s, (unsigned int)val);
+        break;
+
+    case MARUTUNER_FE_STATUS:
+    case MARUTUNER_INT:
+    case MARUTUNER_HWPTR:
+    case MARUTUNER_FE_GET_SYSTEM:
+        LOG_TRACE("(%s) ETC(0x%02llx): val = %lld\n", __func__, addr, val);
+        break;
+
+    default:
+        LOG_SEVERE("(%s) invalid cmd. register addr = %d\n", __func__, (int)addr);
+        break;
+    }
+}
+
+static int set_tuner_info(MaruTunerState* s)
+{
+    if (!s->prop_table) {
+        LOG_SEVERE("No table file path\n");
+        return -1;
+    }
+
+    strncpy(ts_mapping_table_path, s->prop_table, MAX_PATH_LEN);
+
+    if (s->prop_system)
+        s->cfg.system = get_config_value(config_system_list, s->prop_system);
+    else //default system
+        s->cfg.system = MARUTUNER_CONFIG_SYSTEM_ATSC;
+
+    if (s->cfg.system == MARUTUNER_CONFIG_SYSTEM_ATSC) {
+        if (s->prop_country == NULL) {
+            LOG_SEVERE("invalid tuner configure: country has not specified even system is ATSC\n", __func__);
+            return -1;
+        }
+        s->cfg.country = get_config_value(config_country_list, s->prop_country);
+    } else {
+        s->cfg.country = -1;
+    }
+
+    if (s->prop_playmode)
+        s->cfg.system = get_config_value(config_playmode_list, s->prop_playmode);
+    else //default playmode
+        s->cfg.playmode = MARUTUNER_CONFIG_PLAYMODE_STILL;
+
+    marutuner_set_tuned_info(s, false);
+
+    LOG_INFO("tuner configurations: sys:%s country:%s playmode:%s table:%s\n",
+         config_system_list[s->cfg.system].name,
+         (s->cfg.country != -1) ? config_country_list[s->cfg.country].name : "-",
+         config_playmode_list[s->cfg.playmode].name,
+         ts_mapping_table_path);
+
+    return 0;
+}
+
+static const MemoryRegionOps marutuner_mmio_ops = {
+    .read = marutuner_reg_read,
+    .write = marutuner_reg_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+/*  for sake of no lock , copy to local variable.
+ *
+ * XXX : need not check initial status
+ * When the frontend is reset, the parameters initialized.
+ * And, finally, marutuner_get_tuned_info check this initialized modulation
+ */
+#define TUNER_DATA_SIZE 4125
+static void marutuner_deliver_tuned_info(void *opaque)
+{
+    char data[TUNER_DATA_SIZE];
+    char cmd[10] = {0};
+
+    strcpy(cmd, "TUNER_DTV");
+
+    if (strlen(g_current_tune.map_table_path) < MAX_PATH_LEN) {
+        sprintf(data, "%d:%d:%d:%d:%d:%d:%s",
+                g_current_tune.is_streaming, g_current_tune.frequency,
+                g_current_tune.modulation, g_current_tune.system,
+                g_current_tune.country, g_current_tune. playmode,
+                g_current_tune.map_table_path);
+    }
+
+    make_send_device_ntf(cmd, MSG_GROUP_STATUS, 221, data);
+}
+
+static void marutuner_irq(void *opaque)
+{
+        MaruTunerState *s = (MaruTunerState *)opaque;
+
+        qemu_mutex_lock(&s->section_mutex);
+        if (s->irq) {
+                pci_set_irq(&s->dev, 1);
+        }
+        qemu_mutex_unlock(&s->section_mutex);
+}
+
+static void pci_create_marutuner_decoder(PCIBus *bus, MaruTunerState *s)
+{
+    PCIDevice *dev = NULL;
+    MaruTunerDecoderState *decoder_state = NULL;
+
+    g_stillimg_dir = s->prop_stillimg;
+    g_wsi_name = s->prop_wsi;
+    dev = pci_create_simple(bus, -1, TUNER_DECODER_DEVICE_NAME);
+    decoder_state = DO_UPCAST(MaruTunerDecoderState, dev, dev);
+    s->decoder_state = decoder_state;
+}
+
+static int marutuner_initfn(PCIDevice *dev)
+{
+    PCIBus *bus = PCI_BUS(dev->bus);
+    MaruTunerState *s = DO_UPCAST(MaruTunerState, dev, dev);
+    uint8_t *pci_conf = s->dev.config;
+
+    LOG_INFO("device initializing...\n");
+
+    pci_create_marutuner_decoder(bus, s);
+
+    pci_config_set_interrupt_pin(pci_conf, 2);
+
+    memory_region_init_io(&s->mmio, OBJECT(s), &marutuner_mmio_ops, s,
+                            "marutuner-mmio", MARUTUNER_REG_SIZE);
+
+    //pci_register_bar(&s->dev, 0, PCI_BASE_ADDRESS_MEM_PREFETCH, &s->vram);
+    pci_register_bar(&s->dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->mmio);
+
+    QLIST_INIT(&s->filters_head);
+
+    qemu_mutex_init(&s->section_mutex);
+    qemu_cond_init(&s->section_th_cond);
+
+    qemu_mutex_init(&s->tsdemux_mutex);
+    qemu_cond_init(&s->tsdemux_th_cond);
+
+    marutuner_threads_init(s);
+
+    s->destroying = false;
+    s->qemu_bh = qemu_bh_new(marutuner_irq, s);
+    s->set_tune_info_bh = qemu_bh_new(marutuner_deliver_tuned_info, s);
+
+    s->id = g_tuners_num++;
+    /* register all libav formats and codecs */
+    av_register_all();
+
+    if (set_tuner_info(s) < 0)
+        goto error;
+
+    add_tuner_device(s);
+    LOG_INFO("device initialized successfully\n");
+
+    return 0;
+
+error:
+    qemu_bh_delete(s->qemu_bh);
+    qemu_bh_delete(s->set_tune_info_bh);
+    s->destroying = true;
+
+    qemu_mutex_lock(&s->section_mutex);
+    qemu_cond_signal(&s->section_th_cond);
+    qemu_mutex_unlock(&s->section_mutex);
+    qemu_thread_join(&s->section_th);
+    qemu_cond_destroy(&s->section_th_cond);
+    qemu_mutex_destroy(&s->section_mutex);
+
+    qemu_mutex_lock(&s->tsdemux_mutex);
+    qemu_cond_signal(&s->tsdemux_th_cond);
+    qemu_mutex_unlock(&s->tsdemux_mutex);
+    qemu_thread_join(&s->tsdemux_th);
+    qemu_cond_destroy(&s->tsdemux_th_cond);
+    qemu_mutex_destroy(&s->tsdemux_mutex);
+
+    LOG_INFO("device initialization failed\n");
+
+    return -1;
+}
+
+static void marutuner_exitfn(PCIDevice *dev)
+{
+    MaruTunerState *s = DO_UPCAST(MaruTunerState, dev, dev);
+
+    cleanup_filters(s);
+    qemu_bh_delete(s->qemu_bh);
+    qemu_bh_delete(s->set_tune_info_bh);
+    s->destroying = true;
+
+    qemu_mutex_lock(&s->section_mutex);
+    qemu_cond_signal(&s->section_th_cond);
+    qemu_mutex_unlock(&s->section_mutex);
+    qemu_thread_join(&s->section_th);
+    qemu_cond_destroy(&s->section_th_cond);
+    qemu_mutex_destroy(&s->section_mutex);
+
+    qemu_mutex_lock(&s->tsdemux_mutex);
+    qemu_cond_signal(&s->tsdemux_th_cond);
+    qemu_mutex_unlock(&s->tsdemux_mutex);
+    qemu_thread_join(&s->tsdemux_th);
+    qemu_cond_destroy(&s->tsdemux_th_cond);
+    qemu_mutex_destroy(&s->tsdemux_mutex);
+
+    del_tuner_device(s);
+
+    LOG_INFO("tuner device was released.\n");
+}
+
+static Property maru_tuner_props[] = {
+    DEFINE_PROP_STRING("system", MaruTunerState, prop_system),
+    DEFINE_PROP_STRING("country", MaruTunerState, prop_country),
+    DEFINE_PROP_STRING("playmode", MaruTunerState, prop_playmode),
+    DEFINE_PROP_STRING("stillimg", MaruTunerState, prop_stillimg),
+    DEFINE_PROP_STRING("table", MaruTunerState, prop_table),
+    DEFINE_PROP_STRING("wsi", MaruTunerState, prop_wsi),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void marutuner_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+#ifdef QTEST_TIZEN
+    k->init = qtest_tuner_initfn;
+#else
+    k->init = marutuner_initfn;
+#endif
+    k->exit = marutuner_exitfn;
+    k->vendor_id = PCI_VENDOR_ID_TIZEN;
+    k->device_id = PCI_DEVICE_ID_VIRTUAL_TUNER;
+    k->class_id = PCI_CLASS_OTHERS;
+    dc->props = maru_tuner_props;
+    dc->desc = "MARU virtual tuner device for Tizen emulator";
+}
+
+static TypeInfo marutuner_info = {
+    .name = MARUTUNER_DEV_NAME,
+    .parent = TYPE_PCI_DEVICE,
+    .instance_size = sizeof(MaruTunerState),
+    .class_init = marutuner_class_init,
+};
+
+static void tuner_register_types(void)
+{
+    type_register_static(&marutuner_info);
+}
+
+type_init(tuner_register_types);
+
+
+
+/******************************************************************************
+  functions for only unit test
+******************************************************************************/
+#ifdef QTEST_TIZEN
+#define QTEST_DTV_FREQ1 57000000
+#define QTEST_DTV_FREQ2 63000000
+#define QTEST_DTV_FREQ3 69000000
+#define QTEST_ATV_FREQ 79000000
+#define QTEST_MOD VSB_8
+
+#define QTEST_DTV_VPID 0x11
+#define QTEST_DTV_APID 0x14
+
+/**
+ * @test  UTC_TUNER_TEST02
+ * @sut   UTC_TUNER
+ * @brief Check frequency and modulation setting
+ * @flow  #02-01 set frequency and read the value by mmio command
+ *        #02-02 set modulation and read the value by mmio command
+ * @type  Right
+ * @input tuner context structure, frequency, modulation
+ */
+static void qtest_tuner_02(MaruTunerState *s)
+{
+    /* flow #02-01 */
+    marutuner_reg_write(s, MARUTUNER_FE_FREQ, QTEST_DTV_FREQ1, 4);
+    LOG_INFO("[QTEST][UTC_TUNER][UTC_TUNER_TEST02] #02-01 : %s\n",
+            marutuner_reg_read(s, MARUTUNER_FE_FREQ, 4) == QTEST_DTV_FREQ1 ? "SUCCESS" : "FAIL");
+
+    /* flow #02-02 */
+    marutuner_reg_write(s, MARUTUNER_FE_MOD, QTEST_MOD, 4);
+    LOG_INFO("[QTEST][UTC_TUNER][UTC_TUNER_TEST02] #02-02 : %s\n",
+            marutuner_reg_read(s, MARUTUNER_FE_MOD, 4) == QTEST_MOD ? "SUCCESS" : "FAIL");
+}
+
+/**
+ * @test  UTC_TUNER_TEST03
+ * @sut   UTC_TUNER
+ * @brief Check frequency and modulation setting
+ * @flow  #03-01 call get_mapped_ts_file with proper freq, mod and ts file
+ *        #03-02 call get_mapped_ts_file with proper freq and mod but nonexistant file
+ *        #03-03 call get_mapped_ts_file with improper freq and mod
+ *        #03-04 call marutuner_tune_atv with proper freq
+ * @type  Right
+ * @input tuner context structure
+ */
+static void qtest_tuner_03(MaruTunerState *s)
+{
+    int ret;
+
+    /* flow #03-01 */
+    s->table = fopen(ts_mapping_table_path, "rb");
+    if (!s->table) {
+        LOG_SEVERE("[QTEST][UTC_TUNER:%s] test error! tizen_tuner-test.cfg is required. test will be stopped\n", __func__);
+        return;
+    }
+    s->fe.frequency = QTEST_DTV_FREQ1;
+    s->fe.modulation = QTEST_MOD;
+
+    ret = get_mapped_ts_file(s, s->table, &s->fe);
+    if (ret == MARUTUNER_FE_HAS_TS) {
+        LOG_INFO("[QTEST][UTC_TUNER][UTC_TUNER_TEST03] #03-01 : SUCCESS\n");
+    } else {
+        LOG_INFO("[QTEST][UTC_TUNER][UTC_TUNER_TEST03] #03-01 : FAIL\n");
+    }
+    fclose(s->table);
+
+    /* flow #03-02 */
+    s->table = fopen(ts_mapping_table_path, "rb");
+    if (!s->table) {
+        LOG_SEVERE("[QTEST][UTC_TUNER:%s] test error! tizen_tuner-test.cfg is required. test will be stopped\n", __func__);
+        return;
+    }
+    s->fe.frequency = QTEST_DTV_FREQ2;
+    s->fe.modulation = QTEST_MOD;
+
+    ret = get_mapped_ts_file(s, s->table, &s->fe);
+    if (ret == MARUTUNER_FE_HAS_ONLY_PARAM) {
+        LOG_INFO("[QTEST][UTC_TUNER][UTC_TUNER_TEST03] #03-02 : SUCCESS\n");
+    } else {
+        LOG_INFO("[QTEST][UTC_TUNER][UTC_TUNER_TEST03] #03-02 : FAIL\n");
+    }
+    fclose(s->table);
+
+    /* flow #03-03 */
+    s->table = fopen(ts_mapping_table_path, "rb");
+    if (!s->table) {
+        LOG_SEVERE("[QTEST][UTC_TUNER:%s] test error! tizen_tuner-test.cfg is required. test will be stopped\n", __func__);
+        return;
+    }
+    s->fe.frequency = QTEST_DTV_FREQ3;
+    s->fe.modulation = QTEST_MOD;
+
+    ret = get_mapped_ts_file(s, s->table, &s->fe);
+    if (ret == MARUTUNER_FE_TUNE_FAILED) {
+        LOG_INFO("[QTEST][UTC_TUNER][UTC_TUNER_TEST03] #03-03 : SUCCESS\n");
+    } else {
+        LOG_INFO("[QTEST][UTC_TUNER][UTC_TUNER_TEST03] #03-03 : FAIL\n");
+    }
+    fclose(s->table);
+
+    /* flow #03-04 */
+    s->fe.frequency = QTEST_ATV_FREQ;
+    marutuner_tune_atv(s, QTEST_ATV_FREQ);
+    if (s->fe_status == MARUTUNER_FE_HAS_ONLY_PARAM) {
+        LOG_INFO("[QTEST][UTC_TUNER][UTC_TUNER_TEST03] #03-04 : SUCCESS\n");
+    } else {
+        LOG_INFO("[QTEST][UTC_TUNER][UTC_TUNER_TEST03] #03-04 : FAIL\n");
+    }
+}
+
+/**
+ * @test  UTC_TUNER_TEST04
+ * @sut   UTC_TUNER
+ * @brief Test start section after a channel is tuned
+ * @flow  #04-01 call marutuner_start_section
+ * @type  Right
+ * @input tuner context structure
+ */
+static void qtest_tuner_04(MaruTunerState *s)
+{
+    int ret;
+
+    s->fe_status = MARUTUNER_FE_HAS_TS;
+
+    ret = marutuner_start_section(s, 1);
+    if (ret < 0) {
+        LOG_INFO("[QTEST][UTC_TUNER][UTC_TUNER_TEST04] #04-01 : FAIL\n", __func__);
+    } else {
+        LOG_INFO("[QTEST][UTC_TUNER][UTC_TUNER_TEST04] #04-01 : SUCCESS\n", __func__);
+    }
+}
+
+/**
+ * @test  UTC_TUNER_TEST05
+ * @sut   UTC_TUNER
+ * @brief Test start pid filtering for each playmode after a channel is tuned
+ * @flow  #05-01 call marutuner_start_pid_filter with STILL mode
+ *        #05-02 call marutuner_start_pid_filter with TS mode
+ * @type  Right
+ * @input tuner context structure
+ */
+static void qtest_tuner_05(MaruTunerState *s)
+{
+    char tsfile_path[4096] = {0};
+    char *cpath, *tpath, *bname;
+    int ret = 0;
+
+    cpath = getenv("PWD");
+    tpath = strdup(cpath);
+    bname = basename(tpath);
+    if (strcmp(bname, "qemu") != 0) {
+        LOG_SEVERE("[QTEST][UTC_TUNER:%s] test error! test must be executed in the qemu directory\n", __func__);
+    }
+
+    sprintf(tsfile_path, "%s/%s/tizen_tuner-test-ts.ts", cpath, "tests");
+    //LOG_INFO("[QTEST][UTC_TUNER:%s] tsfile : %s\n", __func__, tsfile_path);
+
+    s->fe.ts_filename = strdup(tsfile_path);
+    if (init_avstream(s) < 0) {
+        LOG_SEVERE("[QTEST][UTC_TUNER:%s] test error! failed libav\n", __func__);
+        return;
+    }
+
+    s->fe_status = MARUTUNER_FE_HAS_TS;
+    s->cfg.playmode = MARUTUNER_CONFIG_PLAYMODE_STILL;
+    ret = marutuner_start_pid_filter(s, QTEST_DTV_VPID);
+    if (ret < 0) {
+        LOG_INFO("[QTEST][UTC_TUNER][UTC_TUNER_TEST05] #05-01 : FAIL\n", __func__);
+    } else {
+        LOG_INFO("[QTEST][UTC_TUNER][UTC_TUNER_TEST05] #05-01 : SUCCESS\n", __func__);
+    }
+
+    s->cfg.playmode = MARUTUNER_CONFIG_PLAYMODE_TS;
+    marutuner_start_pid_filter(s, QTEST_DTV_VPID);
+    if (ret < 0) {
+        LOG_INFO("[QTEST][UTC_TUNER][UTC_TUNER_TEST05] #05-02 : FAIL\n", __func__);
+    } else {
+        LOG_INFO("[QTEST][UTC_TUNER][UTC_TUNER_TEST05] #05-02 : SUCCESS\n", __func__);
+    }
+
+    free(s->fe.ts_filename);
+    free(tpath);
+}
+
+static int qtest_tuner_initfn(PCIDevice *dev)
+{
+    MaruTunerState *s = NULL;
+
+    /* INIT TEST */
+    LOG_INFO("[QTEST][UTC_TUNER][UTC_TUNER_TEST01] tuner initializing...\n");
+    if (marutuner_initfn(dev) < 0) {
+        LOG_INFO("[QTEST][UTC_TUNER][UTC_TUNER_TEST01] failed to initialize tuner device.\n");
+        return 0;
+    } else {
+        LOG_INFO("[QTEST][UTC_TUNER][UTC_TUNER_TEST01] tuner initialized\n");
+    }
+
+    s = DO_UPCAST(MaruTunerState, dev, dev);
+
+    /* FUNCTION TESTS */
+    qtest_tuner_02(s);
+    qtest_tuner_03(s);
+    qtest_tuner_04(s);
+    qtest_tuner_05(s);
+
+    return 0;
+}
+#endif
+
+
+/*#####################################################
+#######################################################
+#######DEBUGGING AND TESTING FUNCTIONS#################
+######SHOULD BE REPLACE WITH ECS/ECP###################
+#####################################################*/
+
+#include "monitor/monitor.h"
+void tuner_info(Monitor *mon, const QDict *qdict)
+{
+    struct tune_info info;
+
+    marutuner_get_tuned_info(&info);
+    monitor_printf(mon, "tuned:%d freq: %ld mod:%d is_stream:%s system:%d country:%d table:%s\n",
+                   info.is_tuned, (long int)info.frequency, info.modulation, info.is_streaming?"on":"off", info.system, info.country, info.map_table_path);
+}
+
+
+void tuner_switch(Monitor *mon, const QDict *qdict)
+{
+    int ret = 0 ;
+    struct tune_info info;
+    const char *tsfile = qdict_get_try_str(qdict, "file");
+
+    marutuner_get_tuned_info(&info);
+
+    if (info.is_tuned != 1) {
+        monitor_printf(mon, "not tuned yet\n");
+        return ;
+    }
+    monitor_printf(mon, "current tune: freq: %ld mod:%d is_stream:%s\n",
+                   (long int)info.frequency, info.modulation, info.is_streaming?"on":"off");
+    if (tsfile)
+    {
+        monitor_printf (mon, "switch ts file to [%s]\n", tsfile);
+        ret = marutuner_switch_ts(0, &info);
+    } else {
+        monitor_printf (mon, "toggle is_stream: %s -> %s\n"
+                        , info.is_streaming?"on": "off"
+                        , !info.is_streaming?"on": "off");
+
+        ret = marutuner_toggle_stream(0, &info, !info.is_streaming);
+    }
+
+    monitor_printf (mon, "returned : %d\n", ret);
+}
+
diff --git a/tizen/src/hw/pci/maru_tuner.h b/tizen/src/hw/pci/maru_tuner.h
new file mode 100644 (file)
index 0000000..02ceffe
--- /dev/null
@@ -0,0 +1,194 @@
+/* * Common header of MARU Virtual Tuner device.
+ *
+ * Copyright (c) 2011 - 2014 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Contact:
+ * Byeongki Shin <bk0121.shin@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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA  02110-1301, USA.
+ *
+ * Contributors:
+ * - S-Core Co., Ltd
+ *
+ */
+
+#ifndef _MARU_TUNER_H_
+#define _MARU_TUNER_H_
+
+#include <stdio.h>
+#include "hw/pci/pci.h"
+#include "qemu/thread.h"
+#include "libavformat/avformat.h"
+#include "maru_tuner_decoder.h"
+
+#define MAX_PATH_LEN            4096
+/* frontend modulation */
+typedef enum fe_modulation {
+    QPSK,
+    QAM_16,
+    QAM_32,
+    QAM_64,
+    QAM_128,
+    QAM_256,
+    QAM_AUTO,
+    VSB_8,
+    VSB_16,
+    PSK_8,
+    APSK_16,
+    APSK_32,
+    DQPSK,
+    MAX_MODULATION,
+} fe_modulation_t;
+
+
+/* tuner information struct used when communicating with ecp */
+
+typedef struct tune_info {
+    bool                is_tuned;
+    bool                is_streaming;
+    uint32_t            frequency;
+    fe_modulation_t     modulation;
+    int                 system;
+    int                 country;
+    int                 playmode;
+    char                map_table_path[MAX_PATH_LEN];
+}tune_info;
+
+/*
+ * PCI register definition
+ * kernel and qemu must be synchronized.
+ */
+enum marutuner_cmd_reg {
+    MARUTUNER_FE_RESET          = 0x00,
+    MARUTUNER_FE_STATUS         = 0x04,
+    MARUTUNER_FE_FREQ           = 0x08,
+    MARUTUNER_FE_MOD            = 0x0c,
+    MARUTUNER_FE_TUNE           = 0x10,
+    MARUTUNER_FE_GET_SYSTEM     = 0x14,
+    MARUTUNER_FE_GET_ID         = 0x18,
+
+    MARUTUNER_SETDMA            = 0x20,
+    MARUTUNER_START             = 0x24,
+    MARUTUNER_INT               = 0x28,
+    MARUTUNER_HWPTR             = 0x2c,
+    MARUTUNER_START_PID_FILTER  = 0x30,
+    MARUTUNER_STOP_PID_FILTER   = 0x34,
+
+    MARUTUNER_ATV_SET_TUNE      = 0x38,
+    MARUTUNER_ATV_GET_STATUS    = 0x3c,
+};
+
+/* frontend status */
+#define MARUTUNER_FE_TUNE_FAILED    0x00
+#define MARUTUNER_FE_HAS_ONLY_PARAM 0x01
+#define MARUTUNER_FE_HAS_TS         0x02
+
+/* demux interrupt */
+#define MARUTUNER_DMA_COMPLETE_INT  0x01
+#define MARUTUNER_ERR_INT           0x08
+
+struct config {
+#define MARUTUNER_CONFIG_SYSTEM_ATSC 0
+#define MARUTUNER_CONFIG_SYSTEM_DVB  1
+    int system;
+#define MARUTUNER_CONFIG_COUNTRY_KOR 0
+#define MARUTUNER_CONFIG_COUNTRY_USA 1
+    int country;
+#define MARUTUNER_CONFIG_PLAYMODE_TS 0
+#define MARUTUNER_CONFIG_PLAYMODE_STILL 1
+    int playmode;
+};
+
+struct fe_param {
+    bool                is_streaming;
+    uint32_t            frequency;
+    fe_modulation_t     modulation;
+    FILE                *tsfile;
+    char                *ts_filename;   /* tuned ts file */
+};
+
+struct pid_filter {
+    int pid;
+    int stream_idx; // init value is '-1'
+    int enabled;
+    QLIST_ENTRY(pid_filter) entries;
+};
+
+typedef struct MaruTunerState {
+    PCIDevice           dev;
+
+    char                *prop_system;
+    char                *prop_country;
+    char                *prop_playmode;
+    char                *prop_stillimg;
+    char                *prop_table;
+    char                *prop_wsi;
+    FILE                *table;
+    struct config       cfg;
+    struct fe_param     fe;
+
+    /* libav info */
+    AVFormatContext     *fmt_ctx;
+    QLIST_HEAD(, pid_filter) filters_head;  /* filter list */
+
+    QemuThread          section_th;
+    QemuMutex           section_mutex;
+    QemuCond            section_th_cond;
+    QEMUBH              *qemu_bh;           /* for irq raise */
+    QEMUBH              *set_tune_info_bh;  /* for set tuner info for ecs*/
+    QemuThread          tsdemux_th;
+    QemuMutex           tsdemux_mutex;      /* tsdemux and pid list mutex */
+    QemuCond            tsdemux_th_cond;
+
+    uint32_t            id;
+    uint32_t            fe_status;
+    uint32_t            doing_dma;
+    uint32_t            doing_filtering;
+    uint32_t            irq;
+    uint32_t            hw_ptr_offset;
+    bool                destroying;
+    void                *vaddr;         /* vram ptr */
+
+    //MemoryRegion        vram;
+    MemoryRegion        mmio;
+    QSIMPLEQ_ENTRY(MaruTunerState) list;
+
+    MaruTunerDecoderState *decoder_state;
+}MaruTunerState;
+
+/* ------------------------------------------------------------------------- */
+/* Fucntion prototype                                                        */
+/* ------------------------------------------------------------------------- */
+void marutuner_thread_init(MaruTunerState *s);
+void pci_marutuner_init(PCIBus *bus);
+
+
+int marutuner_get_tuned_info(struct tune_info *info);
+int marutuner_toggle_stream(int devnum, struct tune_info *info, bool onoff);
+int marutuner_switch_ts(int devnum, struct tune_info *info);
+int marutuner_switch_playmode(int devnum, int playmode);
+int marutuner_set_table_file(const char* path);
+
+
+
+
+//for debugging & testing, qemu monitor command functions
+void tuner_info(Monitor *mon, const QDict *qdict);
+void tuner_switch(Monitor *mon, const QDict *qdict);
+
+
+
+#endif  /* _MARU_TUNER_H_ */
diff --git a/tizen/src/hw/pci/maru_tuner_decoder.c b/tizen/src/hw/pci/maru_tuner_decoder.c
new file mode 100644 (file)
index 0000000..221a349
--- /dev/null
@@ -0,0 +1,2431 @@
+/*
+ * Virtual Tuner Decoder Device
+ *
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Contact:
+ *  GunSoo Kim <gunsoo83.kim@samsung.com>
+ *  SangHo Park <sangho1206.park@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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ *
+ * Contributors:
+ * - S-Core Co., Ltd
+ *
+ */
+
+#include "qemu-common.h"
+#include "qemu/main-loop.h"
+#include "qemu/error-report.h"
+
+//#include "maru_overlay.h"
+#include "maru_tuner_decoder.h"
+#include "hw/vigs/winsys.h"
+#include "hw/vigs/work_queue.h"
+
+#include "maru_dtv_audio.h"
+
+#include <limits.h>
+
+/* define debug channel */
+DECLARE_DEBUG_CHANNEL(tuner_dec);
+
+// device
+#define TUNER_DECODER_VERSION       1
+
+#define CC_PKT_SIZE             (128)
+#define MAX_CC_ENTRY            (128)
+
+#define TUNER_DECODER_MEM_SIZE         (CC_PKT_SIZE * MAX_CC_ENTRY)
+#define TUNER_DECODER_REG_SIZE          (256)
+
+#define SEND_VIDEOFRAME_TO_KERNEL   0
+#define MAX_AUDIO_ENTRY             5
+#define MAX_VIDEO_ENTRY             20
+
+// for debugging
+#define TUNER_DECODER_FILE_DUMP     0 // dump file path : ~/tuner_test.xxx
+#define TUNER_COMPOSITE_TO_SDLFB    0
+#define TUNER_COMPOSITE_TO_WSI      1
+#define TUNER_PROFILE_FPS           0
+
+#define AV_SYNC_TOLERANCE          1000 // 1000 micro seconds
+#define TUNER_DEC_MAX_USLEEP       1000000
+
+#if SEND_VIDEOFRAME_TO_KERNEL
+typedef struct VideoDeviceMemEntry {
+    uint8_t *buf;
+    uint32_t buf_size;
+    uint32_t buf_id;
+
+    QTAILQ_ENTRY(VideoDeviceMemEntry) node;
+} VideoDeviceMemEntry;
+#endif
+
+#if SEND_VIDEOFRAME_TO_KERNEL
+#define TUNER_DECODER_VIDEO_ENTRY_MAX           1024
+static VideoDeviceMemEntry *video_mem_entry[TUNER_DECODER_VIDEO_ENTRY_MAX];
+#endif
+
+static void *maru_tuner_video_transfer_threads(void *opaque);
+static QemuThread video_transfer_thread;
+
+static int audio_decode_packet(MaruTunerDecoderState* state, AVStream *stream, AVPacket *pkt);
+static int video_decode_packet(MaruTunerDecoderState* state, AVStream *stream, AVPacket *pkt);
+typedef int (*TunerDecodeFunc)(MaruTunerDecoderState* ,AVStream *, AVPacket *);
+static TunerDecodeFunc decode_func[] = {
+    audio_decode_packet,
+    video_decode_packet,
+};
+
+MaruTunerDecoderState *g_main_decoder_state = NULL;
+
+#if TUNER_DECODER_FILE_DUMP
+#include <inttypes.h>
+//static const char *pcm_filename = "tuner_test.pcm";
+static const char *pgm_filename = "tuner_test.pgm";
+static const char *ppm_filename = "tuner_test.ppm";
+static const char *yuv_filename = "tuner_test.yuv";
+#if TUNER_CAPTION_CONFIG
+static const char *cc_filename = "tuner_test.cc";
+#endif
+
+static FILE *audio_outfile = NULL;
+static int audio_dump_size = 0;
+
+static FILE *yuv_outfile = NULL;
+static int dump_frame_count = 0;
+#endif
+
+uint8_t tuner_video_power = 0;
+uint8_t tuner_video_image_valid = 0;
+uint16_t tuner_video_left = 0;
+uint16_t tuner_video_top = 0;
+uint16_t tuner_video_width = 0;
+uint16_t tuner_video_height = 0;
+uint16_t tuner_video_width_back = 0;
+uint16_t tuner_video_height_back = 0;
+pixman_image_t *tuner_video_image = NULL;
+AVFrame *tuner_video_image_f = NULL;
+pixman_image_t *tuner_video_image_back = NULL;
+AVFrame *tuner_video_image_back_f = NULL;
+QemuMutex tuner_dec_video_frame_mutex;
+
+static qemu_timeval g_tuner_dec_first_tv;
+
+char *g_stillimg_dir;
+static AVFrame *stillimage = NULL;
+
+static struct work_queue *g_render_queue = NULL;
+
+/*
+ * Must only be accessed from 'g_render_queue'.
+ */
+char *g_wsi_name;
+static struct winsys_interface *g_wsi = NULL;
+
+#if TUNER_COMPOSITE_TO_WSI
+/*
+ * Must only be accessed from 'g_render_queue'.
+ */
+static struct winsys_surface *g_ws_surface_y = NULL;
+static struct winsys_surface *g_ws_surface_u = NULL;
+static struct winsys_surface *g_ws_surface_v = NULL;
+
+struct maru_tuner_decoder_set_output_work_item
+{
+    struct work_queue_item base;
+
+    int sfc_index;
+    uint32_t sfc_id;
+};
+
+struct maru_tuner_decoder_render_work_item
+{
+    struct work_queue_item base;
+
+    AVFrame *frame;
+    int width;
+    int height;
+    bool free_frame;
+};
+
+static void maru_tuner_decoder_set_output_work(struct work_queue_item *wq_item)
+{
+    struct maru_tuner_decoder_set_output_work_item *item = (struct maru_tuner_decoder_set_output_work_item*)wq_item;
+    struct winsys_surface **ws_surface;
+
+    switch (item->sfc_index) {
+    case 0:
+        ws_surface = &g_ws_surface_y;
+        break;
+    case 1:
+        ws_surface = &g_ws_surface_u;
+        break;
+    case 2:
+        ws_surface = &g_ws_surface_v;
+        break;
+    default:
+        assert(0);
+        ws_surface = &g_ws_surface_y;
+        break;
+    }
+
+    if (*ws_surface) {
+        (*ws_surface)->release(*ws_surface);
+        *ws_surface = NULL;
+    }
+
+    if (item->sfc_id) {
+        *ws_surface = g_wsi->acquire_surface(g_wsi, item->sfc_id);
+    }
+
+    g_free(item);
+}
+
+static void maru_tuner_decoder_render_work(struct work_queue_item *wq_item)
+{
+    struct maru_tuner_decoder_render_work_item *item = (struct maru_tuner_decoder_render_work_item*)wq_item;
+
+    if (g_ws_surface_y && g_ws_surface_u && g_ws_surface_v) {
+        g_ws_surface_y->draw_pixels_scaled(g_ws_surface_y,
+                                           item->frame->data[0],
+                                           item->width / 4,
+                                           item->height);
+        g_ws_surface_u->draw_pixels_scaled(g_ws_surface_u,
+                                           item->frame->data[1],
+                                           item->width / 4,
+                                           item->height / 4);
+        g_ws_surface_v->draw_pixels_scaled(g_ws_surface_v,
+                                           item->frame->data[2],
+                                           item->width / 4,
+                                           item->height / 4);
+    }
+
+    if (item->free_frame) {
+        av_free(item->frame->data[0]);
+        av_free(item->frame);
+    }
+    g_free(item);
+}
+
+static void set_tuner_output(int sfc_index, uint32_t sfc_id)
+{
+    struct maru_tuner_decoder_set_output_work_item *item;
+
+    item = g_malloc0(sizeof(*item));
+
+    work_queue_item_init(&item->base, &maru_tuner_decoder_set_output_work);
+
+    item->sfc_index = sfc_index;
+    item->sfc_id = sfc_id;
+
+    work_queue_add_item(g_render_queue, &item->base);
+}
+
+static void write_tuner_image(AVFrame* frame, int width, int height, bool free_frame)
+{
+    struct maru_tuner_decoder_render_work_item *item;
+
+    item = g_malloc0(sizeof(*item));
+
+    work_queue_item_init(&item->base, &maru_tuner_decoder_render_work);
+
+    item->frame = frame;
+    item->width = width;
+    item->height = height;
+    item->free_frame = free_frame;
+
+    work_queue_add_item(g_render_queue, &item->base);
+}
+#endif
+
+static void maru_tuner_decoder_push_videoqueue(MaruTunerDecoderState* s, AVStream* stream, AVFrame *frame, int width, int height);
+static void maru_tuner_decoder_push_audioqueue(MaruTunerDecoderState* s, uint8_t *samples, int size);
+static void maru_tuner_decoder_flush_queue(MaruTunerDecoderState* s, enum AVMediaType media_type);
+#if TUNER_CAPTION_CONFIG
+static void maru_tuner_decoder_push_ccqueue(MaruTunerDecoderState* s, AVStream *stream, AVFrameSideData *sd, int64_t pts_64);
+#endif
+
+static void maru_tuner_dec_irq_raise(MaruTunerDecoderState *s, uint32_t reason)
+{
+    qemu_mutex_lock(&s->state_mutex);
+    s->isr |= reason;
+
+    if (s->irq_raised == 0) {
+        qemu_bh_schedule(s->bh);
+        s->irq_raised = 1;
+    }
+    qemu_mutex_unlock(&s->state_mutex);
+}
+
+static void gen_decoder_event(MaruTunerDecoderState *s, int event_type)
+{
+    uint32_t reason = 0;
+
+    LOG_TRACE("enter: %s\n", __func__);
+    LOG_TRACE("event type: 0x%x\n", event_type);
+
+    reason |= event_type << 4;
+    reason |= TUNER_DECODER_ISR_EVENT;
+    maru_tuner_dec_irq_raise(s, reason);
+
+    LOG_TRACE("leave: %s\n", __func__);
+}
+
+static void maru_tuner_transfer_threads_create(MaruTunerDecoderState *s)
+{
+    LOG_TRACE("enter: %s\n", __func__);
+
+    qemu_thread_create(&video_transfer_thread, "maru_tuner_video_transfer_threads",
+        maru_tuner_video_transfer_threads, (void *)s, QEMU_THREAD_JOINABLE);
+
+    LOG_TRACE("leave: %s\n", __func__);
+}
+
+static void maru_tuner_init_queue(MaruTunerDecoderState *s)
+{
+    LOG_TRACE("enter: %s\n", __func__);
+
+    s->dec_q[MARUDEC_TYPE_AUDIO].queue_head.aq.tqh_first = NULL;
+    s->dec_q[MARUDEC_TYPE_AUDIO].queue_head.aq.tqh_last =
+        &s->dec_q[MARUDEC_TYPE_AUDIO].queue_head.aq.tqh_first;
+
+    s->dec_q[MARUDEC_TYPE_VIDEO].queue_head.vq.tqh_first = NULL;
+    s->dec_q[MARUDEC_TYPE_VIDEO].queue_head.vq.tqh_last =
+        &s->dec_q[MARUDEC_TYPE_VIDEO].queue_head.vq.tqh_first;
+
+    s->dec_q[MARUDEC_TYPE_AUDIO].elem_cnt = 0;
+    s->dec_q[MARUDEC_TYPE_VIDEO].elem_cnt = 0;
+
+#if TUNER_CAPTION_CONFIG
+    s->cc_q.front_idx = 0;
+    s->cc_q.rear_idx = 0;
+    qemu_mutex_init(&s->cc_q.queue_mutex);
+#endif
+
+    qemu_mutex_init(&s->dec_q[MARUDEC_TYPE_VIDEO].queue_mutex);
+    qemu_mutex_init(&s->dec_q[MARUDEC_TYPE_AUDIO].queue_mutex);
+
+    qemu_sem_init(&s->dec_q[MARUDEC_TYPE_VIDEO].queue_sem_p, 0);
+
+    LOG_TRACE("leave: %s\n", __func__);
+}
+
+static int convert_sample_fmt(enum AVSampleFormat sample_fmt)
+{
+    int ret_fmt = -1;
+
+    switch (sample_fmt) {
+    case AV_SAMPLE_FMT_U8:
+        ret_fmt = AUD_FMT_U8;
+        break;
+    case AV_SAMPLE_FMT_S16:
+        ret_fmt = AUD_FMT_S16;
+        break;
+    case AV_SAMPLE_FMT_S32:
+        ret_fmt = AUD_FMT_S32;
+        break;
+    default:
+        ret_fmt = -1;
+        LOG_SEVERE("not supported sample format=%d\n", sample_fmt);
+    }
+
+    return ret_fmt;
+}
+
+static enum PixelFormat maru_tuner_dec_get_format( AVCodecContext *dec_ctx,
+                                          const enum PixelFormat *pi_fmt)
+{
+    CodecContext *context = (CodecContext *)dec_ctx->opaque;
+    MaruTunerDecoderState *s = (MaruTunerDecoderState *)context->state;
+    const AVPixFmtDescriptor *dsc = NULL;
+    bool can_hwaccel = false;
+    int i = 0;
+    uint8_t flag_hwaccel = 0;
+
+    if (!s->hwaccel_plugin ||
+        !check_av_hw_accel(dec_ctx->codec_id)) {
+        goto end;
+    }
+
+    /* Enumerate available formats */
+    for(i = 0; pi_fmt[i] != PIX_FMT_NONE; i++)
+    {
+        dsc = av_pix_fmt_desc_get(pi_fmt[i]);
+        if (dsc == NULL) {
+            continue;
+        }
+
+        flag_hwaccel = (dsc->flags & PIX_FMT_HWACCEL) != 0;
+        if (flag_hwaccel) {
+            can_hwaccel = true;
+        }
+        LOG_TRACE("available decoder output format %d (%s)\n", pi_fmt[i], dsc->name);
+    }
+
+    if (!can_hwaccel) {
+        goto end;
+    }
+
+    void *plugin_context = s->hwaccel_plugin->setup(dec_ctx, dec_ctx->width, dec_ctx->height);
+    if (!plugin_context) {
+        LOG_SEVERE("Failed to setup DXVA module\n");
+        goto end;
+    }
+    set_plugin_context(dec_ctx, plugin_context);
+
+    for (i = 0; pi_fmt[i] != PIX_FMT_NONE; ++i) {
+        if (pi_fmt[i] == s->hwaccel_plugin->pix_fmt) {
+            break;
+        }
+    }
+
+    if (pi_fmt[i] == PIX_FMT_NONE) {
+        goto end;
+    }
+
+    LOG_INFO("HW_ACCEL is enabled with pix_fmt [%s]\n", av_get_pix_fmt_name(pi_fmt[i]));
+    context->is_hwaccel = true;
+    return pi_fmt[i];
+
+end:
+    LOG_INFO("HW_ACCEL is disabled\n");
+    context->is_hwaccel = false;
+    /* Fallback to default behaviour */
+    return avcodec_default_get_format(dec_ctx, pi_fmt);
+}
+
+static int maru_tuner_dec_get_buffer(struct AVCodecContext *dec_ctx, AVFrame *frame) {
+    CodecContext *context = (CodecContext *)dec_ctx->opaque;
+    MaruTunerDecoderState *s = (MaruTunerDecoderState *)context->state;
+
+    if (context->is_hwaccel) {
+        return s->hwaccel_plugin->get_buffer(dec_ctx, frame);
+    }
+
+    return avcodec_default_get_buffer(dec_ctx, frame);
+}
+
+static void maru_tuner_dec_release_buffer(struct AVCodecContext *dec_ctx, AVFrame *frame) {
+    CodecContext *context = (CodecContext *)dec_ctx->opaque;
+    MaruTunerDecoderState *s = (MaruTunerDecoderState *)context->state;
+
+    if (context->is_hwaccel) {
+        return s->hwaccel_plugin->release_buffer(dec_ctx, frame);
+    }
+
+    return avcodec_default_release_buffer(dec_ctx, frame);
+}
+
+int maru_tuner_dec_init_ctx(MaruTunerDecoderState* s, AVStream* stream)
+{
+    AVCodecContext *dec_ctx = NULL;
+    AVCodec *dec = NULL;
+    int ret = 0;
+
+    LOG_TRACE("enter: %s\n", __func__);
+
+    if (s == NULL || stream == NULL) {
+        LOG_SEVERE("Invalid parameter.\n");
+        return -1;
+    }
+
+    dec_ctx = stream->codec;
+    if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO) {
+        dec_ctx->get_format = maru_tuner_dec_get_format;
+        dec_ctx->get_buffer = maru_tuner_dec_get_buffer;
+        dec_ctx->reget_buffer = avcodec_default_reget_buffer;
+        dec_ctx->release_buffer = maru_tuner_dec_release_buffer;
+
+
+        /* FIXME:
+        remove the brillcodec dependency in CodecContext. */
+        s->context.state = (MaruBrillCodecState *)s;
+        s->context.frame = avcodec_alloc_frame();
+        dec_ctx->opaque = (void *)&s->context;
+
+#if TUNER_COMPOSITE_TO_SDLFB
+        tuner_video_power = 1;
+#endif
+        LOG_TRACE("MPEG video pix_fmt=%d\n", dec_ctx->pix_fmt);
+
+        /* init video state */
+        qemu_mutex_lock(&s->av_state.av_mutex);
+        s->av_state.video_time = 0;
+        s->av_state.video_last_pts = AV_NOPTS_VALUE;
+        s->av_state.video_start_pts = AV_NOPTS_VALUE;
+        s->av_state.av_diff_pts = AV_NOPTS_VALUE;
+        s->av_state.is_first = 1;
+        qemu_mutex_unlock(&s->av_state.av_mutex);
+
+        /* for decoder event state */
+        qemu_mutex_lock(&s->video_event.ve_mutex);
+        s->video_event.vlock = 0;
+        s->video_event.vinfo = 0;
+        s->video_event.unmute = 0;
+        s->video_event.first_avsync = 0;
+        qemu_mutex_unlock(&s->video_event.ve_mutex);
+    }
+
+    if (dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) {
+        /* init audio state */
+        qemu_mutex_lock(&s->av_state.av_mutex);
+        s->av_state.sample_rate = dec_ctx->sample_rate;
+        s->av_state.channels = dec_ctx->channels;
+        s->av_state.pop_size = 0;
+        s->av_state.audio_start_pts = AV_NOPTS_VALUE;
+        s->av_state.sample_fmt = convert_sample_fmt(dec_ctx->sample_fmt);
+        qemu_mutex_unlock(&s->av_state.av_mutex);
+
+#ifndef QTEST_TIZEN
+        maru_dtv_audio_setup(s->av_state.sample_rate, s->av_state.channels, s->av_state.sample_fmt, 0);
+        maru_dtv_audio_set_active(1);
+#endif
+    }
+
+    /* find decoder for the stream */
+    LOG_TRACE ("codec id is %d\n", dec_ctx->codec_id);
+    if (dec_ctx->codec_id == AV_CODEC_ID_MPEG1VIDEO) {
+        LOG_INFO ("Change AV_CODEC_ID_MPEG1VIDEO to AV_CODEC_ID_MPEG2VIDEO\n");
+        dec_ctx->codec_id = AV_CODEC_ID_MPEG2VIDEO;
+    } else if (dec_ctx->codec_id == AV_CODEC_ID_MP3) {
+        LOG_INFO ("Change AV_CODEC_ID_MP3 to AV_CODEC_ID_MP2\n");
+        dec_ctx->codec_id = AV_CODEC_ID_MP2;
+    }
+    dec = avcodec_find_decoder(dec_ctx->codec_id);
+    if (!dec) {
+        LOG_SEVERE("Failed to find decoder(%d)\n", dec_ctx->codec_id);
+        return -1;
+    }
+    LOG_TRACE("decoder name : %s\n", dec->long_name);
+
+    if (check_av_hw_accel(dec_ctx->codec_id)) {
+        dec_ctx->thread_type &= ~FF_THREAD_SLICE;
+    }
+
+    /* init the decoder */
+    ret = avcodec_open2(dec_ctx, dec, NULL);
+    if (ret < 0) {
+        LOG_SEVERE("Failed to open decoder(%s)\n", dec->name);
+        return -1;
+    }
+    LOG_TRACE("avcodec_open() success!\n");
+
+    LOG_TRACE("leave: %s\n", __func__);
+
+    return ret;
+}
+
+#if TUNER_DECODER_FILE_DUMP
+#if 0
+static void pcm_save(int16_t *samples, int size)
+{
+    char *home_path = NULL;
+    char pcm_file_path[PATH_MAX] = {0,};
+
+    home_path = getenv("HOME");
+    sprintf(pcm_file_path, "%s/%s", home_path, pcm_filename);
+    LOG_TRACE("pcm_file_path: %s\n", pcm_file_path);
+
+    LOG_TRACE("pcm_save : size=%d\n", size);
+
+    if (audio_outfile == NULL) {
+        audio_outfile = fopen(pcm_file_path,"w");
+        if (audio_outfile == NULL) {
+            LOG_SEVERE("file open fail, %s: %s\n", pcm_file_path, strerror(errno));
+            return;
+        }
+    }
+
+    fwrite(samples, 1, size, audio_outfile);
+}
+#endif
+#endif
+
+static uint8_t *resample_audio(AVCodecContext * avctx, AVFrame *samples, int *out_size)
+{
+    AVAudioResampleContext *avr = NULL;
+    uint8_t *resample_audio = NULL;
+    int buffer_size = 0, out_linesize = 0;
+    int nb_samples = samples->nb_samples;
+    int out_sample_fmt = AV_SAMPLE_FMT_S16;
+
+#if 0
+    LOG_TRACE("channel_layout=%d\n", avctx->channel_layout);
+    LOG_TRACE("sample_fmt=%d\n", avctx->sample_fmt);
+    LOG_TRACE("channels=%d\n", avctx->channels);
+    LOG_TRACE("sample_rate=%d\n", avctx->sample_rate);
+#endif
+
+    avr = avresample_alloc_context();
+    if (!avr) {
+        LOG_SEVERE("failed to allocate avresample context\n");
+        return NULL;
+    }
+
+    av_opt_set_int(avr, "in_channel_layout", avctx->channel_layout, 0);
+    av_opt_set_int(avr, "in_sample_fmt", avctx->sample_fmt, 0);
+    av_opt_set_int(avr, "in_sample_rate", avctx->sample_rate, 0);
+    av_opt_set_int(avr, "out_channel_layout", avctx->channel_layout, 0);
+    av_opt_set_int(avr, "out_sample_fmt", out_sample_fmt, 0);
+    av_opt_set_int(avr, "out_sample_rate", avctx->sample_rate, 0);
+
+    LOG_TRACE("open avresample context\n");
+    if (avresample_open(avr) < 0) {
+        LOG_SEVERE("failed to open avresample context\n");
+        avresample_free(&avr);
+        return NULL;
+    }
+
+    *out_size =
+        av_samples_get_buffer_size(&out_linesize, avctx->channels,
+                                    nb_samples, out_sample_fmt, 0);
+
+    resample_audio = av_mallocz(*out_size);
+    if (!resample_audio) {
+        LOG_SEVERE("failed to allocate resample buffer\n");
+        avresample_close(avr);
+        avresample_free(&avr);
+        return NULL;
+    }
+
+    buffer_size = avresample_convert(avr, &resample_audio,
+        out_linesize, nb_samples,
+        samples->data, samples->linesize[0],
+        samples->nb_samples);
+
+    LOG_TRACE("resample_audio out_size %d buffer_size %d\n", *out_size, buffer_size);
+
+    avresample_close(avr);
+    avresample_free(&avr);
+
+    return resample_audio;
+}
+
+static int audio_decode_packet(MaruTunerDecoderState* s, AVStream *stream, AVPacket *pkt)
+{
+    AVCodecContext *dec_ctx = NULL;
+    int ret = 0;
+    AVFrame *decoded_frame = NULL;
+    int got_frame = 0;
+
+    LOG_TRACE("enter: %s\n", __func__);
+
+    dec_ctx = stream->codec;
+
+    decoded_frame = avcodec_alloc_frame();
+
+    /* decode audio frame */
+    if (!decoded_frame) {
+        LOG_SEVERE("failed to allocate an outbuf of audio.\n");
+        return -1;
+    } else {
+        ret = avcodec_decode_audio4(dec_ctx, decoded_frame, &got_frame, pkt);
+        if (ret < 0) {
+            LOG_SEVERE("Error decoding audio frame\n");
+            av_freep(&decoded_frame);
+            return -1;
+        }
+        ret = FFMIN(ret, pkt->size);
+        LOG_TRACE("decoding audio. ret %d, out_size %d\n", ret, decoded_frame->linesize[0]);
+
+        if (got_frame && (decoded_frame->linesize[0] > 0)) {
+            uint8_t *dup_samples = NULL;
+            int buffer_size = 0;
+
+            qemu_mutex_lock(&s->av_state.av_mutex);
+            if (s->av_state.audio_start_pts == AV_NOPTS_VALUE) {
+                s->av_state.audio_start_pts = pkt->pts;
+                LOG_TRACE("audio_start pts=%"PRIu64"\n", s->av_state.audio_start_pts);
+            }
+            qemu_mutex_unlock(&s->av_state.av_mutex);
+
+            dup_samples = resample_audio(dec_ctx, decoded_frame, &buffer_size);
+            if (dup_samples == NULL) {
+                LOG_SEVERE("decode_audio. failed to allocate memory\n");
+                av_freep(&decoded_frame);
+                return -1;
+            }
+
+            maru_tuner_decoder_push_audioqueue(s, dup_samples, buffer_size);
+        }
+    }
+
+    av_freep(&decoded_frame);
+
+    LOG_TRACE("leave: %s\n", __func__);
+
+    return ret;
+}
+
+#if TUNER_DECODER_FILE_DUMP
+static void pgm_save(AVPicture *frame, int xsize, int ysize)
+{
+    FILE *f;
+    int i;
+    char *home_path = NULL;
+    char pgm_file_path[PATH_MAX] = {0,};
+
+    home_path = getenv("HOME");
+    sprintf(pgm_file_path, "%s/%s", home_path, pgm_filename);
+    LOG_TRACE("pgm_file_path: %s\n", pgm_file_path);
+
+    LOG_TRACE("pgm_save : linesize=%d, xsize=%d, ysize=%d\n", frame->linesize[0], xsize, ysize);
+
+    f = fopen(pgm_file_path,"w");
+    if (f == NULL) {
+        LOG_SEVERE("file open fail, %s: %s\n", pgm_file_path, strerror(errno));
+        return;
+    }
+
+    fprintf(f,"P5\n%d %d\n%d\n",xsize,ysize,255);
+    for(i=0;i<ysize;i++)
+        fwrite(frame->data[0] + i * frame->linesize[0],1,xsize,f);
+
+    fclose(f);
+}
+
+static void ppm_save(AVPicture* RGB_frame, int width, int height)
+{
+    FILE *f;
+    int i, j;
+    int linesize;
+    char *home_path = NULL;
+    char ppm_file_path[PATH_MAX] = {0,};
+
+    home_path = getenv("HOME");
+    sprintf(ppm_file_path, "%s/%s", home_path, ppm_filename);
+    LOG_TRACE("ppm_file_path: %s\n", ppm_file_path);
+
+    linesize = RGB_frame->linesize[0];
+    LOG_TRACE("ppm_save : linesize=%d, width=%d, height=%d\n", linesize, width, height);
+
+    f = fopen(ppm_file_path,"w");
+    if (f == NULL) {
+        LOG_SEVERE("file open fail, %s: %s\n", ppm_file_path, strerror(errno));
+        return;
+    }
+
+    fprintf(f,"%c%c\n%d %d\n%d\n",'P','6',width,height,255);
+    for(i=0; i<height; i++) {
+        for(j=0; j<linesize; j+=4) {
+            fwrite(RGB_frame->data[0]+ i*linesize+ j+2, 1, 1, f);
+            fwrite(RGB_frame->data[0]+ i*linesize+ j+1, 1, 1, f);
+            fwrite(RGB_frame->data[0]+ i*linesize+ j, 1, 1, f);
+        }
+    }
+
+    fclose(f);
+}
+
+static void yuv_save(AVPicture* frame, int width, int height)
+{
+    int pic_size;
+    uint8_t * out_buf = NULL;
+    char *home_path = NULL;
+    char yuv_file_path[PATH_MAX] = {0,};
+
+    home_path = getenv("HOME");
+    sprintf(yuv_file_path, "%s/%s", home_path, yuv_filename);
+    LOG_TRACE("yuv_file_path: %s\n", yuv_file_path);
+
+    pic_size = avpicture_get_size(PIX_FMT_YUV420P, width, height);
+
+    LOG_TRACE("yuv_save : pic_size=%d, width=%d, height=%d\n", pic_size, width, height);
+    out_buf = av_mallocz(pic_size);
+
+    if (yuv_outfile == NULL) {
+        yuv_outfile = fopen(yuv_file_path,"w");
+        if (yuv_outfile == NULL) {
+            LOG_SEVERE("file open fail, %s: %s\n", yuv_file_path, strerror(errno));
+            return;
+        }
+    }
+
+    avpicture_layout(frame, PIX_FMT_YUV420P, width, height, out_buf, pic_size);
+    fwrite(out_buf, pic_size, 1, yuv_outfile);
+
+    av_free(out_buf);
+}
+
+#if TUNER_CAPTION_CONFIG
+static void cc_save(AVFrameSideData *sd)
+{
+    char *home_path = NULL;
+    char cc_file_path[PATH_MAX] = {0,};
+    static FILE *cc_outfile = NULL;
+
+    home_path = getenv("HOME");
+    sprintf(cc_file_path, "%s/%s", home_path, cc_filename);
+    LOG_TRACE("cc_file_path: %s\n", cc_file_path);
+
+    if (cc_outfile == NULL) {
+        cc_outfile = fopen(cc_file_path,"w");
+        if (cc_outfile == NULL) {
+            LOG_SEVERE("file open fail, %s: %s\n", cc_file_path, strerror(errno));
+            return;
+        }
+    }
+
+    fwrite(sd->data, sd->size, 1, cc_outfile);
+}
+#endif /* TUNER_CAPTION_CONFIG */
+#endif
+
+#if TUNER_COMPOSITE_TO_SDLFB
+#if 1
+static void tuner_do_pixman_scale(void *src_data, int src_w, int src_h,
+        void *dst_data, int dst_w, int dst_h)
+{
+    pixman_image_t *src = NULL;
+    pixman_image_t *dst = NULL;
+    double sx = 0;
+    double sy = 0;
+    pixman_transform_t matrix;
+    struct pixman_f_transform matrix_f;
+
+    src = pixman_image_create_bits(PIXMAN_a8r8g8b8,
+            src_w, src_h, src_data, src_w * 4);
+    dst = pixman_image_create_bits(PIXMAN_a8r8g8b8,
+            dst_w, dst_h, dst_data, dst_w * 4);
+
+    sx = (double)src_w / (double)dst_w;
+    sy = (double)src_h / (double)dst_h;
+    pixman_f_transform_init_identity(&matrix_f);
+    pixman_f_transform_scale(&matrix_f, NULL, sx, sy);
+    pixman_transform_from_pixman_f_transform(&matrix, &matrix_f);
+    pixman_image_set_transform(src, &matrix);
+    pixman_image_set_filter(src, PIXMAN_FILTER_BILINEAR, NULL, 0);
+    pixman_image_composite(PIXMAN_OP_SRC, src, NULL, dst,
+            0, 0, 0, 0, 0, 0,
+            dst_w, dst_h);
+
+    pixman_image_unref(src);
+    pixman_image_unref(dst);
+
+    return;
+}
+
+static void write_tuner_image(AVFrame* frame, int width, int height, bool free_frame)
+{
+    /* scale & composite to overlay */
+    qemu_mutex_lock(&tuner_dec_video_frame_mutex);
+    if (overlay0_power) {
+        tuner_do_pixman_scale((void *)frame->data[0],
+                width, height,
+                overlay_ptr,
+                overlay0_width, overlay0_height);
+    }
+    if (overlay1_power) {
+        tuner_do_pixman_scale((void *)frame->data[0],
+                width, height,
+                overlay_ptr + OVERLAY1_REG_OFFSET,
+                overlay1_width, overlay1_height);
+    }
+    qemu_mutex_unlock(&tuner_dec_video_frame_mutex);
+
+    /* free */
+    if (free_frame) {
+        av_free(frame->data[0]);
+        av_free(frame);
+    }
+}
+#else
+static void write_tuner_image(AVFrame* frame, int width, int height)
+{
+    qemu_mutex_lock(&tuner_dec_video_frame_mutex);
+    if (tuner_video_image_back != NULL) {
+        pixman_image_unref(tuner_video_image_back);
+        tuner_video_image_back = NULL;
+        av_free(tuner_video_image_back_f->data[0]);
+        av_free(tuner_video_image_back_f);
+        LOG_TRACE("release the pixman image for tuner video stream.\n");
+    }
+
+    if (tuner_video_image != NULL) {
+        tuner_video_image_back = tuner_video_image;
+        tuner_video_image_back_f = tuner_video_image_f;
+        tuner_video_image_valid = 0;
+        tuner_video_image = NULL;
+        tuner_video_width_back = tuner_video_width;
+        tuner_video_height_back = tuner_video_height;
+    }
+    qemu_mutex_unlock(&tuner_dec_video_frame_mutex);
+
+    tuner_video_width = width;
+    tuner_video_height = height;
+    tuner_video_image = pixman_image_create_bits(PIXMAN_x8r8g8b8,
+                    width, height,(uint32_t *)frame->data[0], frame->linesize[0]);
+
+    LOG_TRACE("create the pixman image for tuner video stream.\n");
+    tuner_video_image_valid = 1;
+    tuner_video_image_f = frame;
+    tuner_video_image_f->data[0] = frame->data[0];
+}
+#endif
+#endif
+
+static int video_decode_packet(MaruTunerDecoderState* s, AVStream *stream, AVPacket *pkt)
+{
+    AVCodecContext *dec_ctx = NULL;
+    AVFrame *frame = NULL;
+#if TUNER_CAPTION_CONFIG
+    AVFrameSideData *sd = NULL;
+#endif
+
+    int ret = 0;
+    int got_frame = 0;
+
+
+    LOG_TRACE("enter: %s\n", __func__);
+
+    /* generate vlock decoder event */
+    /* MUST check the lock order.
+        video event mutex lock
+        -> device state lock */
+    qemu_mutex_lock(&s->video_event.ve_mutex);
+    if (!s->video_event.vlock) {
+        s->video_event.vlock = 1;
+        gen_decoder_event(s, TUNER_DECODER_VLOCK_EVENT);
+    }
+    qemu_mutex_unlock(&s->video_event.ve_mutex);
+
+    dec_ctx = stream->codec;
+    frame = s->context.frame;
+    avcodec_get_frame_defaults(frame);
+    got_frame = 0;
+
+    /* decode video frame */
+    ret = avcodec_decode_video2(dec_ctx, frame, &got_frame, pkt);
+    if (ret < 0) {
+        LOG_SEVERE("Error decoding video frame\n");
+    }
+
+    if (got_frame) {
+        AVFrame *copied_frame = NULL;
+        int width = frame->width;
+        int height = frame->height;
+        uint32_t i;
+
+        LOG_TRACE("got frame.\n");
+
+        /* generate video info decoder event */
+        /* MUST check the lock order.
+            video event mutex lock
+            -> device state lock */
+        qemu_mutex_lock(&s->video_event.ve_mutex);
+        if (!s->video_event.vinfo) {
+            s->video_event.vinfo = 1;
+            gen_decoder_event(s, TUNER_DECODER_VINFO_EVENT);
+        }
+        qemu_mutex_unlock(&s->video_event.ve_mutex);
+
+        qemu_mutex_lock(&s->av_state.av_mutex);
+        if (s->av_state.video_start_pts == AV_NOPTS_VALUE) {
+            s->av_state.video_start_pts = pkt->pts;
+            LOG_TRACE("video_start pts=%"PRIu64"\n", s->av_state.video_start_pts);
+        }
+        qemu_mutex_unlock(&s->av_state.av_mutex);
+
+#if TUNER_DECODER_FILE_DUMP
+        dump_frame_count++;
+        if (dump_frame_count == 10) {
+            pgm_save((AVPicture *)frame, dec_ctx->width, dec_ctx->height);
+        }
+        if (dump_frame_count >= 50 && dump_frame_count <= 100) {
+            yuv_save((AVPicture *)frame, dec_ctx->width, dec_ctx->height);
+        }
+#endif
+        copied_frame = avcodec_alloc_frame();
+
+        copied_frame->data[0] = av_malloc(width * height + (width * height / 2));
+        copied_frame->data[1] = copied_frame->data[0] + width * height;
+        copied_frame->data[2] = copied_frame->data[1] + width * height / 4;
+
+        if (s->context.is_hwaccel) {
+            s->hwaccel_plugin->get_picture(copied_frame->data[0], frame);
+        } else {
+            for (i = 0; i < height; ++i) {
+                memcpy(copied_frame->data[0] + width * i,
+                        frame->data[0] + frame->linesize[0] * i,
+                        width);
+            }
+
+            for (i = 0; i < height / 2; ++i) {
+                memcpy(copied_frame->data[1] + (width / 2) * i,
+                        frame->data[1] + frame->linesize[1] * i,
+                        (width / 2));
+                memcpy(copied_frame->data[2] + (width / 2) * i,
+                        frame->data[2] + frame->linesize[2] * i,
+                        (width / 2));
+            }
+        }
+
+        copied_frame->pkt_pts = frame->pkt_pts;
+        copied_frame->repeat_pict = frame->repeat_pict;
+
+        maru_tuner_decoder_push_videoqueue(s, stream, copied_frame, width, height);
+
+#if TUNER_CAPTION_CONFIG
+        /* get AVFrameSideData for CC(closed caption) data. */
+        sd = av_frame_get_side_data(frame, AV_FRAME_DATA_A53_CC);
+        if (sd != NULL) {
+#if TUNER_DECODER_FILE_DUMP
+            cc_save(sd);
+#endif
+            /* push cc data to queue */
+            maru_tuner_decoder_push_ccqueue(s, stream, sd, RGB_frame->pkt_pts);
+        }
+#endif
+    }
+
+    LOG_TRACE("leave: %s\n", __func__);
+
+    return ret;
+}
+
+int maru_tuner_dec_decode_pkt(MaruTunerDecoderState* s, AVStream *stream, AVPacket *pkt)
+{
+    AVPacket orig_pkt;
+    int media_type = 0;
+    int decode_type = 0;
+    int ret = 0;
+    int decoded_size = 0;
+
+    LOG_TRACE("enter: %s\n", __func__);
+
+    if (s == NULL || stream == NULL || pkt == NULL) {
+        LOG_SEVERE("Invalid parameter.\n");
+        if (pkt != NULL) {
+            av_free_packet(pkt);
+        }
+        return -1;
+    }
+
+    media_type = stream->codec->codec_type;
+    if (media_type == AVMEDIA_TYPE_AUDIO) {
+        decode_type = MARUDEC_TYPE_AUDIO;
+    } else if (media_type == AVMEDIA_TYPE_VIDEO) {
+        decode_type = MARUDEC_TYPE_VIDEO;
+    } else {
+        LOG_SEVERE("Unknown media type(%d)\n", media_type);
+        return -1;
+    }
+
+    orig_pkt = *pkt;
+
+    do {
+        decoded_size = decode_func[decode_type](s, stream, pkt);
+        LOG_TRACE("decode packet: AVPacket->size=%d, decoded_size=%d\n", pkt->size, decoded_size);
+        if (decoded_size < 0) {
+            ret = -1;
+            break;
+        }
+        pkt->data += decoded_size;
+        pkt->size -= decoded_size;
+    } while(pkt->size > 0);
+
+    av_free_packet(&orig_pkt);
+
+    LOG_TRACE("leave: %s\n", __func__);
+
+    return ret;
+}
+
+int maru_tuner_dec_deinit_ctx(MaruTunerDecoderState* s, AVStream* stream)
+{
+    int ret = 0;
+    AVCodecContext *dec_ctx = NULL;
+
+    LOG_TRACE("enter: %s\n", __func__);
+
+    if (s == NULL || stream == NULL) {
+        LOG_SEVERE("Invalid parameter.\n");
+        return -1;
+    }
+
+    dec_ctx = stream->codec;
+
+    /* flush queue */
+    maru_tuner_decoder_flush_queue(s, dec_ctx->codec_type);
+
+    /* free avframe struct. */
+    if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO) {
+        qemu_mutex_lock(&s->av_state.av_mutex);
+        s->av_state.video_time = 0;
+        s->av_state.video_last_pts = AV_NOPTS_VALUE;
+        s->av_state.video_start_pts = AV_NOPTS_VALUE;
+        s->av_state.av_diff_pts = AV_NOPTS_VALUE;
+        s->av_state.is_first = 0;
+        qemu_mutex_unlock(&s->av_state.av_mutex);
+
+        /* for decoder event state */
+        qemu_mutex_lock(&s->video_event.ve_mutex);
+        s->video_event.vlock = 0;
+        s->video_event.vinfo = 0;
+        qemu_mutex_unlock(&s->video_event.ve_mutex);
+
+        if (s->context.is_hwaccel) {
+            s->hwaccel_plugin->cleanup(get_plugin_context(dec_ctx));
+        }
+
+        av_free(s->context.frame);
+        memset(&s->context, 0x00, sizeof(CodecContext));
+
+#if TUNER_COMPOSITE_TO_SDLFB
+        /* free pixman image */
+        qemu_mutex_lock(&tuner_dec_video_frame_mutex);
+        tuner_video_power = 0;
+        if (tuner_video_image_back != NULL) {
+            pixman_image_unref(tuner_video_image_back);
+            tuner_video_image_back = NULL;
+        }
+        if (tuner_video_image != NULL) {
+            pixman_image_unref(tuner_video_image);
+            tuner_video_image = NULL;
+        }
+        qemu_mutex_unlock(&tuner_dec_video_frame_mutex);
+#endif
+
+#if TUNER_DECODER_FILE_DUMP
+        /* release video dump file */
+        if (yuv_outfile != NULL) {
+            fclose(yuv_outfile);
+            yuv_outfile = NULL;
+        }
+        dump_frame_count = 0;
+#endif
+    }
+
+    if (dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) {
+        qemu_mutex_lock(&s->av_state.av_mutex);
+        s->av_state.sample_rate = 0;
+        s->av_state.channels = 0;
+        s->av_state.sample_fmt = 0;
+        s->av_state.pop_size = 0;
+        s->av_state.audio_start_pts = AV_NOPTS_VALUE;
+        qemu_mutex_unlock(&s->av_state.av_mutex);
+#ifndef QTEST_TIZEN
+        maru_dtv_audio_set_active(0);
+        maru_dtv_audio_close();
+#endif
+
+#if TUNER_DECODER_FILE_DUMP
+        /* release audio dump file */
+        if (audio_outfile != NULL) {
+            fclose(audio_outfile);
+            audio_outfile = NULL;
+            audio_dump_size = 0;
+        }
+        dump_frame_count = 0;
+#endif
+    }
+
+    /* close decoder context */
+    avcodec_close(dec_ctx);
+
+    LOG_TRACE("leave: %s\n", __func__);
+
+    return ret;
+}
+
+static inline bool _is_videoq_full(MaruTunerDecoderState* s)
+{
+    if (s->dec_q[MARUDEC_TYPE_VIDEO].elem_cnt >= MAX_VIDEO_ENTRY) {
+        LOG_TRACE("video queue is FULL\n");
+        return true;
+    }
+    return false;
+}
+
+static inline bool _is_audioq_full(MaruTunerDecoderState* s)
+{
+    if (s->dec_q[MARUDEC_TYPE_AUDIO].elem_cnt >= MAX_AUDIO_ENTRY) {
+        LOG_TRACE("audio queue is FULL\n");
+        return true;
+    }
+    return false;
+}
+
+bool maru_tuner_dec_queue_is_full(MaruTunerDecoderState* s)
+{
+    /* If no audio, the criterion is video queue.
+     * If there are both of audio and video, the criterion is audio queue.
+     */
+    if (s->av_state.audio_start_pts == AV_NOPTS_VALUE) {
+        return _is_videoq_full(s);
+    }
+
+    return _is_audioq_full(s);
+}
+
+static void load_still_image(void)
+{
+    int width = 1920;
+    int height = 1080;
+    int fd = 0;
+    int nread = 0;
+    int offset = 0;
+    int ret = 0;
+    char image_path[PATH_MAX] = {0,};
+
+    if (g_stillimg_dir == NULL) {
+        LOG_SEVERE("Failed to dtv image path\n");
+        return;
+    }
+
+    stillimage = avcodec_alloc_frame();
+    ret = avpicture_alloc((AVPicture *)stillimage, AV_PIX_FMT_YUV420P, width, height);
+    if (ret < 0) {
+        LOG_SEVERE("Error allocate AVPicture for still image\n");
+        av_free(stillimage);
+        stillimage = NULL;
+        return;
+    }
+
+    stillimage->width = width;
+    stillimage->height = height;
+
+    snprintf(image_path, sizeof(image_path), "%s/DTV_%dx%d.yuv420p", g_stillimg_dir, width, height);
+
+    LOG_INFO("load %s file\n", image_path);
+    if ((fd = open(image_path, O_RDONLY | O_BINARY)) == -1)
+    {
+        LOG_SEVERE("Failed to open dtv still image file. errstr(%s)\n", strerror(errno));
+        av_free(stillimage->data[0]);
+        av_free(stillimage);
+        stillimage = NULL;
+        return;
+    }
+
+    do {
+        offset += nread;
+        nread = read(fd, stillimage->data[0]+offset, 128);
+    } while(nread > 0);
+}
+
+int maru_tuner_dec_set_stillimage(MaruTunerDecoderState* s, bool on)
+{
+    int ret = 0;
+
+    if (s == NULL) {
+        LOG_SEVERE("Error : Invalid decoder state\n");
+        ret = -1;
+        return ret;
+    }
+
+    s->stillimage_on = on;
+
+    if (!on) {
+        LOG_TRACE("turn off still image\n");
+
+        /* for decoder event state */
+        qemu_mutex_lock(&s->video_event.ve_mutex);
+        s->video_event.vlock = 0;
+        s->video_event.vinfo = 0;
+        qemu_mutex_unlock(&s->video_event.ve_mutex);
+        return ret;
+    }
+
+    LOG_TRACE("turn on still image\n");
+
+    /* for decoder event state */
+    qemu_mutex_lock(&s->video_event.ve_mutex);
+    s->video_event.vlock = 1;
+    s->video_event.vinfo = 1;
+    s->video_event.unmute = 1;
+    qemu_mutex_unlock(&s->video_event.ve_mutex);
+
+    gen_decoder_event(s, TUNER_DECODER_VLOCK_EVENT
+                        |TUNER_DECODER_VINFO_EVENT
+                        |TUNER_DECODER_UNMUTE_EVENT);
+
+
+    if (stillimage != NULL) {
+        maru_tuner_decoder_push_videoqueue(s, NULL, stillimage,
+                                        stillimage->width, stillimage->height);
+    }
+
+    return ret;
+}
+
+static int64_t update_video_time(MaruTunerDecoderState *s, AVFrame *frame, int time_base_num, int time_base_den)
+{
+    AVRational time_base;
+    int64_t play_pts = 0;
+    int64_t diff_pts;
+    int64_t play_time = 0;
+    int64_t video_time = AV_NOPTS_VALUE;
+
+    time_base.num = time_base_num;
+    time_base.den = time_base_den;
+
+    qemu_mutex_lock(&s->av_state.av_mutex);
+
+    if ((s->av_state.av_diff_pts == AV_NOPTS_VALUE || s->av_state.av_diff_pts == 0)
+        && s->av_state.video_start_pts != AV_NOPTS_VALUE) {
+        if (s->av_state.audio_start_pts == AV_NOPTS_VALUE)
+            diff_pts = 0;
+        else
+            diff_pts = s->av_state.video_start_pts - s->av_state.audio_start_pts;
+
+        s->av_state.av_diff_pts = diff_pts;
+    }
+
+    if (s->av_state.av_diff_pts != AV_NOPTS_VALUE) {
+        s->av_state.video_last_pts = frame->pkt_pts;
+
+        play_pts = s->av_state.video_last_pts - s->av_state.video_start_pts + s->av_state.av_diff_pts;
+        play_time = av_rescale_q(play_pts, time_base, AV_TIME_BASE_Q);
+        LOG_TRACE("avsync last=%"PRIu64", start=%"PRIu64", av_diff=%"PRIu64"\n",
+                s->av_state.video_last_pts, s->av_state.video_start_pts, s->av_state.av_diff_pts);
+        LOG_TRACE("avsync play_pts=%"PRIu64", play_time=%"PRIu64"\n", play_pts, play_time);
+
+        s->av_state.video_time = play_time;
+
+        video_time = s->av_state.video_time;
+    }
+    qemu_mutex_unlock(&s->av_state.av_mutex);
+
+#if 0
+    /* for MPEG2, the frame can be repeated, so we update the
+       clock accordingly */
+    LOG_TRACE("repeat_pict=%d\n", frame->repeat_pict);
+    s->av_state.video_time += frame->repeat_pict * (int64_t)(fps* 0.5);
+#endif
+
+    return video_time;
+}
+
+static int64_t get_audio_write_buf_size(MaruTunerDecoderState *s)
+{
+    TunerDecQueueInfo* q = NULL;
+    int64_t audio_pop_size = 0;
+
+    q = &s->dec_q[MARUDEC_TYPE_AUDIO];
+
+    qemu_mutex_lock(&q->queue_mutex);
+    audio_pop_size = s->av_state.pop_size;
+    qemu_mutex_unlock(&q->queue_mutex);
+
+    return audio_pop_size;
+}
+
+static int64_t get_audio_time(MaruTunerDecoderState *s)
+{
+    int64_t write_buf_size = 0;
+    int64_t ret = 0;
+    double audio_time = 0;
+
+    write_buf_size = get_audio_write_buf_size(s);
+    LOG_TRACE("audio write_buf_size=%d\n", write_buf_size);
+
+    if (write_buf_size > 0 && s->av_state.sample_rate != 0 && s->av_state.channels != 0) {
+        qemu_mutex_lock(&s->av_state.av_mutex);
+        audio_time = ((double)write_buf_size) / ((double)(s->av_state.sample_rate * s->av_state.channels * 2));
+        qemu_mutex_unlock(&s->av_state.av_mutex);
+
+        ret = (int64_t)(audio_time*1000000);
+    }
+
+    return ret;
+}
+
+
+static int maru_tuner_dec_set_delay(MaruTunerDecoderState *s,
+                                    int64_t video_time, int64_t ref, uint32_t *delay)
+{
+    int64_t delay_64 = 0;
+
+    if (video_time < ref) {
+        LOG_TRACE("avsync This frame will be dropped\n");
+        return -1;
+    }
+
+    delay_64 = video_time - ref;
+    if (delay_64 > UINT_MAX) {
+        *delay = UINT_MAX;
+    } else {
+        *delay = delay_64;
+    }
+    LOG_TRACE("avsync delay=%u micro sec\n", *delay);
+
+    qemu_mutex_lock(&s->video_event.ve_mutex);
+    if (!s->video_event.unmute) {
+        s->video_event.first_avsync = 1;
+    }
+    qemu_mutex_unlock(&s->video_event.ve_mutex);
+
+    return 0;
+}
+
+static int maru_tuner_dec_av_sync(MaruTunerDecoderState *s, AVFrame *frame,
+                            int time_base_num, int time_base_den, uint32_t *pdelay)
+{
+    int64_t audio_time = 0;
+    int64_t video_time = 0;
+    int64_t ref_time= 0;
+    qemu_timeval curr_tv;
+    qemu_timeval delta_tv;
+    int ret = 0;
+#if TUNER_PROFILE_FPS
+    static int total_frame = 0;
+    static int drop_frame = 0;
+#endif
+
+    if (s->av_state.is_first) {
+        if (qemu_gettimeofday(&g_tuner_dec_first_tv) < 0) {
+            LOG_WARNING("(%s) avsync gettimeofday error. errno=%d\n", __func__, errno);
+        }
+        s->av_state.is_first = 0;
+        LOG_TRACE("(%s) avsync tv_sec=%d, tv_usec=%d\n",
+                    __func__, g_tuner_dec_first_tv.tv_sec, g_tuner_dec_first_tv.tv_usec);
+    }
+
+    qemu_mutex_lock(&s->av_state.av_mutex);
+    LOG_TRACE("avsync video_last_pts=%"PRIu64", pkt_pts=%"PRIu64", video_start_pts=%"PRIu64"\n",
+               s->av_state.video_last_pts, frame->pkt_pts, s->av_state.video_start_pts);
+    if (s->av_state.video_last_pts == AV_NOPTS_VALUE || s->av_state.video_last_pts > frame->pkt_pts) {
+        s->av_state.video_last_pts = frame->pkt_pts;
+        s->av_state.video_start_pts = s->av_state.video_last_pts;
+        qemu_mutex_unlock(&s->av_state.av_mutex);
+        *pdelay = 0;
+        return ret;
+    }
+    qemu_mutex_unlock(&s->av_state.av_mutex);
+
+    video_time = update_video_time(s, frame, time_base_num, time_base_den);
+    audio_time = get_audio_time(s);
+
+    LOG_TRACE("avsync video_time=%"PRIu64", audio_time=%"PRIu64"\n", video_time, audio_time);
+
+    if (audio_time == 0) {
+        if (qemu_gettimeofday(&curr_tv) < 0) {
+            LOG_WARNING("(%s) avsync gettimeofday error. errno=%d\n", __func__, errno);
+        }
+        delta_tv.tv_sec = curr_tv.tv_sec - g_tuner_dec_first_tv.tv_sec;
+        delta_tv.tv_usec = curr_tv.tv_usec - g_tuner_dec_first_tv.tv_usec;
+        ref_time = delta_tv.tv_sec * TUNER_DEC_MAX_USLEEP + delta_tv.tv_usec;
+    } else {
+        ref_time = audio_time;
+    }
+    LOG_TRACE("(%s) avsync audio_time = %d, ref_time = %d\n", __func__, audio_time, ref_time);
+
+    ret = maru_tuner_dec_set_delay(s, video_time, ref_time, pdelay);
+    if (ret < 0) {
+#if TUNER_PROFILE_FPS
+        drop_frame++;
+#endif
+    }
+
+#if TUNER_PROFILE_FPS
+    total_frame++;
+    if (total_frame%100 == 0) {
+        LOG_INFO("drop rate : %d/%d\n", drop_frame, total_frame);
+    }
+#endif
+
+    return ret;
+}
+
+static void *maru_tuner_video_transfer_threads(void *opaque)
+{
+    MaruTunerDecoderState *s = (MaruTunerDecoderState *)opaque;
+    VideoFrameEntry *elem = NULL;
+    AVFrame *frame = NULL;
+    int width = 0;
+    int height = 0;
+    uint32_t delay = 0;
+    int ret = 0;
+
+    LOG_TRACE("enter: %s\n", __func__);
+
+    while (1) {
+        elem = (VideoFrameEntry *)maru_tuner_decoder_pop_queue(s, MARUDEC_TYPE_VIDEO);
+        if (elem == NULL) {
+            LOG_WARNING("video queue is empty.\n");
+            continue;
+        }
+
+        frame = elem->frame;
+        width = elem->width;
+        height = elem->height;
+
+        LOG_TRACE("VideoFrameEntry. width=%d, height=%d\n", width, height);
+
+        /* stillimage frame */
+        if (elem->is_stillimage) {
+            write_tuner_image(frame, width, height, false);
+            g_free(elem);
+            continue;
+        }
+
+        /* TS frame */
+        ret = maru_tuner_dec_av_sync(s, frame, elem->time_base_num, elem->time_base_den, &delay);
+        if (ret < 0) {
+            av_free(frame->data[0]);
+            av_free(frame);
+        } else {
+            if (delay > AV_SYNC_TOLERANCE) {
+                if (delay > TUNER_DEC_MAX_USLEEP) {
+                    delay = TUNER_DEC_MAX_USLEEP;
+                }
+                usleep(delay);
+            }
+
+            /* first audio/video sync */
+            /* MUST check the lock order.
+                video event mutex lock
+                -> device state lock */
+            qemu_mutex_lock(&s->video_event.ve_mutex);
+            if (s->video_event.first_avsync) {
+                /* generate unmute decoder event */
+                s->video_event.unmute = 1;
+                s->video_event.first_avsync = 0;
+                gen_decoder_event(s, TUNER_DECODER_UNMUTE_EVENT);
+            }
+            qemu_mutex_unlock(&s->video_event.ve_mutex);
+
+            write_tuner_image(frame, width, height, true);
+        }
+        g_free(elem);
+    }
+
+    LOG_TRACE("leave: %s\n", __func__);
+    return NULL;
+}
+
+#if TUNER_CAPTION_CONFIG
+static void maru_tuner_decoder_push_ccqueue(MaruTunerDecoderState* s, AVStream* stream, AVFrameSideData *sd, int64_t pts_64)
+{
+    int64_t pts_ms = 0;
+    uint32_t pts = 0;
+
+    LOG_TRACE("enter: %s\n", __func__);
+
+    if (s == NULL) {
+        LOG_WARNING("Decoder devices is not initialized.\n");
+        return;
+    }
+
+    qemu_mutex_lock(&s->cc_q.queue_mutex);
+
+    if (s->cc_q.elem_cnt < MAX_CC_ENTRY) {
+        uint32_t offset = 0;
+        int cc_idx = 0;
+
+        /* get memory index */
+        cc_idx = s->cc_q.front_idx;
+        s->cc_q.front_idx++;
+        if(s->cc_q.front_idx == MAX_CC_ENTRY) {
+            s->cc_q.front_idx = 0;
+        }
+
+        pts_ms = av_rescale_q(pts_64, stream->time_base, AV_TIME_BASE_Q);
+        pts = ((pts_ms * 45) / 1000)& 0xFFFFFFFF;
+
+        LOG_TRACE("cc data memory index=%d\n", cc_idx);
+        LOG_TRACE("cc data pts=%u, size=%d\n", pts, sd->size);
+
+        /* push ccdata item */
+        offset = cc_idx * CC_PKT_SIZE;
+        memcpy(s->vaddr + offset, &pts, sizeof(pts));
+        offset += sizeof(pts);
+        memcpy(s->vaddr + offset, &sd->size, sizeof(sd->size));
+        offset += sizeof(sd->size);
+        memcpy(s->vaddr + offset, sd->data, sd->size);
+
+        /* MUST check the lock order.
+            cc queue lock
+            -> device state lock */
+        maru_tuner_dec_irq_raise(s, TUNER_DECODER_ISR_CCDATA);
+
+        s->cc_q.elem_cnt++;
+    } else {
+        LOG_WARNING("cc queue entry is FULL\n");
+    }
+
+    LOG_TRACE("(cc)push: cc entry count=%d\n", s->cc_q.elem_cnt);
+    qemu_mutex_unlock(&s->cc_q.queue_mutex);
+}
+#endif
+
+static void maru_tuner_decoder_push_videoqueue(MaruTunerDecoderState* s, AVStream *stream, AVFrame *frame, int width, int height)
+{
+    VideoFrameEntry *elem = NULL;
+    TunerDecQueueInfo* q = NULL;
+
+    q = &s->dec_q[MARUDEC_TYPE_VIDEO];
+
+    qemu_mutex_lock(&q->queue_mutex);
+
+    if (q->elem_cnt < MAX_VIDEO_ENTRY) {
+        elem = g_malloc0(sizeof(VideoFrameEntry));
+
+        elem->frame = frame;
+        elem->width = width;
+        elem->height = height;
+        if (stream != NULL) {
+            /* TS frame */
+            elem->time_base_num = stream->time_base.num;
+            elem->time_base_den = stream->time_base.den;
+            elem->is_stillimage = false;
+        } else {
+            /* stillimage frame */
+            elem->is_stillimage = true;
+        }
+
+        QTAILQ_INSERT_TAIL(&q->queue_head.vq, elem, node);
+        qemu_sem_post(&q->queue_sem_p);
+        q->elem_cnt++;
+    } else {
+        LOG_WARNING("video queue entry is FULL\n");
+#if TUNER_COMPOSITE_TO_SDLFB
+        update_video_time(frame, stream->time_base.num, stream->time_base.den);
+#endif
+        if (stream != NULL) {
+            /* TS frame */
+            av_free(frame->data[0]);
+            av_free(frame);
+        }
+    }
+
+    LOG_TRACE("(video)push: video entry count=%d\n", q->elem_cnt);
+    qemu_mutex_unlock(&q->queue_mutex);
+}
+
+static void maru_tuner_decoder_push_audioqueue(MaruTunerDecoderState* s, uint8_t *samples, int size)
+{
+    AudioFrameEntry *elem = NULL;
+    TunerDecQueueInfo* q = NULL;
+
+    q = &s->dec_q[MARUDEC_TYPE_AUDIO];
+
+    qemu_mutex_lock(&q->queue_mutex);
+
+    if (q->elem_cnt < MAX_AUDIO_ENTRY) {
+        elem = g_malloc0(sizeof(AudioFrameEntry));
+
+        elem->samples = samples;
+        elem->size = size;
+
+        QTAILQ_INSERT_TAIL(&q->queue_head.aq, elem, node);
+        q->elem_cnt++;
+    } else {
+        LOG_WARNING("audio queue entry is FULL\n");
+        av_free(samples);
+    }
+
+    LOG_TRACE("(audio)push: audio entry count=%d, frame\n", q->elem_cnt);
+    qemu_mutex_unlock(&q->queue_mutex);
+}
+
+static void* pop_decode_queue(MaruTunerDecoderState* s, TunerDecQueueInfo* q, enum MaruTunerDecType type)
+{
+    void *ret_elem = NULL;
+    AudioFrameEntry *audio_elem = NULL;
+    VideoFrameEntry *video_elem = NULL;
+
+    if (type == MARUDEC_TYPE_AUDIO) {
+        audio_elem = QTAILQ_FIRST(&q->queue_head.aq);
+        if (audio_elem) {
+            QTAILQ_REMOVE(&q->queue_head.aq, audio_elem, node);
+            ret_elem = (void *)audio_elem;
+            s->av_state.pop_size += audio_elem->size;
+        }
+    } else if (type == MARUDEC_TYPE_VIDEO) {
+        video_elem = QTAILQ_FIRST(&q->queue_head.vq);
+        if (video_elem) {
+            QTAILQ_REMOVE(&q->queue_head.vq, video_elem, node);
+            ret_elem = (void *)video_elem;
+        }
+    } else {
+        LOG_SEVERE("Unknown MaruTunerDecType(%d)\n", type);
+        return NULL;
+    }
+
+    return ret_elem;
+}
+
+void* maru_tuner_decoder_pop_queue(MaruTunerDecoderState* s, enum MaruTunerDecType type)
+{
+    void *ret_elem = NULL;
+    TunerDecQueueInfo* q = NULL;
+
+
+    if (type == MARUDEC_TYPE_AUDIO || type == MARUDEC_TYPE_VIDEO) {
+        //LOG_TRACE("pop queue : MaruTunerDecType(%d)\n", type);
+    } else {
+        LOG_SEVERE("pop queue : Unknown MaruTunerDecType(%d)\n", type);
+        return NULL;
+    }
+
+    if(s == NULL) {
+        /* for dtv audio device */
+        s = g_main_decoder_state;
+    }
+
+    q = &s->dec_q[type];
+
+    /* wait until data entry is provided */
+    if (type == MARUDEC_TYPE_VIDEO) {
+        qemu_sem_wait(&q->queue_sem_p);
+    }
+
+    qemu_mutex_lock(&q->queue_mutex);
+    ret_elem = pop_decode_queue(s, q, type);
+    if (ret_elem == NULL) {
+        //LOG_TRACE("pop: queue entry is empty.\n");
+        qemu_mutex_unlock(&q->queue_mutex);
+        return NULL;
+    }
+
+    q->elem_cnt--;
+    LOG_TRACE("pop: MaruTunerDecType(%d), entry count=%d\n", type, q->elem_cnt);
+    qemu_mutex_unlock(&q->queue_mutex);
+
+    return ret_elem;
+}
+
+static void maru_tuner_decoder_flush_queue(MaruTunerDecoderState* s, enum AVMediaType media_type)
+{
+    AudioFrameEntry *audio_elem = NULL;
+    AudioFrameEntry *audio_elem_next = NULL;
+    VideoFrameEntry *video_elem = NULL;
+    VideoFrameEntry *video_elem_next = NULL;
+    enum MaruTunerDecType type;
+    TunerDecQueueInfo* q = NULL;
+
+    LOG_TRACE("enter: %s\n", __func__);
+
+    if (media_type == AVMEDIA_TYPE_AUDIO) {
+        type = MARUDEC_TYPE_AUDIO;
+    } else if (media_type == AVMEDIA_TYPE_VIDEO) {
+        type = MARUDEC_TYPE_VIDEO;
+    } else {
+        LOG_SEVERE("Unknown AVMediaType(%d)\n", media_type);
+        return;
+    }
+
+    q = &s->dec_q[type];
+
+    switch (type) {
+    case MARUDEC_TYPE_AUDIO:
+        qemu_mutex_lock(&q->queue_mutex);
+        QTAILQ_FOREACH_SAFE(audio_elem, &q->queue_head.aq, node, audio_elem_next) {
+            QTAILQ_REMOVE(&q->queue_head.aq, audio_elem, node);
+            q->elem_cnt--;
+
+            av_free(audio_elem->samples);
+            g_free(audio_elem);
+        }
+        qemu_mutex_unlock(&q->queue_mutex);
+        break;
+    case MARUDEC_TYPE_VIDEO:
+        qemu_mutex_lock(&q->queue_mutex);
+        QTAILQ_FOREACH_SAFE(video_elem, &q->queue_head.vq, node, video_elem_next) {
+            QTAILQ_REMOVE(&q->queue_head.vq, video_elem, node);
+            q->elem_cnt--;
+            if (qemu_sem_timedwait(&q->queue_sem_p, 0) < 0) {
+                LOG_WARNING("flush: video queue entry LOG_SEVEREOR!\n");
+            }
+
+            av_free(video_elem->frame->data[0]);
+            av_free(video_elem->frame);
+            g_free(video_elem);
+        }
+        qemu_mutex_unlock(&q->queue_mutex);
+
+#if TUNER_CAPTION_CONFIG
+        /* flush cc data queue */
+        qemu_mutex_lock(&s->cc_q.queue_mutex);
+        s->cc_q.front_idx = 0;
+        s->cc_q.rear_idx = 0;
+        s->cc_q.elem_cnt = 0;
+        qemu_mutex_unlock(&s->cc_q.queue_mutex);
+#endif
+        break;
+    default:
+        LOG_SEVERE("flush queue : Unknown media type. %d\n", media_type);
+    }
+
+    LOG_TRACE("leave: %s\n", __func__);
+}
+
+//
+static void maru_tuner_decoder_bh_callback(void *opaque)
+{
+    MaruTunerDecoderState *s = (MaruTunerDecoderState *)opaque;
+
+    qemu_mutex_lock(&s->state_mutex);
+
+    if (s->isr){
+        LOG_TRACE("raise irq for shared task.(%p:%d)\n", s, s->isr);
+        pci_set_irq(&s->dev, 1);
+    }
+
+    qemu_mutex_unlock(&s->state_mutex);
+}
+
+/*
+ *  Codec Device APIs
+ */
+static uint64_t maru_tuner_decoder_read(void *opaque,
+                                        hwaddr addr,
+                                        unsigned size)
+{
+    MaruTunerDecoderState *s = (MaruTunerDecoderState *)opaque;
+    uint64_t ret = 0;
+
+    switch (addr) {
+    case TUNER_DECODER_CMD_GET_VERSION:
+        ret = TUNER_DECODER_VERSION;
+        LOG_TRACE("tuner decoder version: %d\n", ret);
+        break;
+
+    case TUNER_DECODER_CMD_GET_VIDMEM_ADDR:
+        LOG_TRACE("video memory address: %d\n", ret);
+        break;
+
+    case TUNER_DECODER_CMD_GET_AUDIO_CLOCK:
+        LOG_TRACE("video memory address: %d\n", ret);
+        break;
+
+    case TUNER_DECODER_CMD_ISR:
+        qemu_mutex_lock(&s->state_mutex);
+        ret = s->isr;
+        if (s->irq_raised) {
+#if TUNER_CAPTION_CONFIG
+            ret |= (s->cc_q.elem_cnt & 0xFFF) << 20;
+#endif
+
+            LOG_TRACE("lower irq.(%d)\n", s->isr);
+            pci_set_irq(&s->dev, 0);
+            s->isr = 0;
+            s->irq_raised = 0;
+        }
+        qemu_mutex_unlock(&s->state_mutex);
+        break;
+
+    case TUNER_DECODER_CMD_CC_INDEX:
+#if TUNER_CAPTION_CONFIG
+        qemu_mutex_lock(&s->cc_q.queue_mutex);
+        if (s->cc_q.elem_cnt == 0) {
+            /* cc data queue is empty. */
+            ret = MAX_CC_ENTRY;
+        } else {
+            ret = s->cc_q.rear_idx;
+        }
+        qemu_mutex_unlock(&s->cc_q.queue_mutex);
+#endif
+        LOG_TRACE("closed caption idx: %d\n", ret);
+        break;
+
+    case TUNER_DECODER_CMD_CC_COMPLETE:
+#if TUNER_CAPTION_CONFIG
+        qemu_mutex_lock(&s->cc_q.queue_mutex);
+        s->cc_q.rear_idx++;
+        if(s->cc_q.rear_idx == MAX_CC_ENTRY) {
+            s->cc_q.rear_idx = 0;
+        }
+
+        if (s->cc_q.elem_cnt > 0) {
+            s->cc_q.elem_cnt--;
+        }
+        ret = s->cc_q.elem_cnt;
+        qemu_mutex_unlock(&s->cc_q.queue_mutex);
+#endif
+        LOG_TRACE("closed caption count: %d\n", ret);
+        break;
+
+    case TUNER_DECODER_CMD_GET_DEVICE_ID:
+        qemu_mutex_lock(&s->state_mutex);
+        ret = s->device_id;
+        qemu_mutex_unlock(&s->state_mutex);
+        LOG_TRACE("tuner decoder device id: %d\n", ret);
+        break;
+
+    case TUNER_DECODER_CMD_GET_VIDEO_EVENT_STATE:
+        /* for decoder event state */
+        qemu_mutex_lock(&s->video_event.ve_mutex);
+        if (s->video_event.vlock) {
+            ret |= TUNER_DECODER_VLOCK_EVENT;
+        }
+        if (s->video_event.vinfo) {
+            ret |= TUNER_DECODER_VINFO_EVENT;
+        }
+        if (s->video_event.unmute
+            && s->video_event.vlock
+            && s->video_event.vinfo) {
+            ret |= TUNER_DECODER_UNMUTE_EVENT;
+        }
+        qemu_mutex_unlock(&s->video_event.ve_mutex);
+        break;
+
+    default:
+        LOG_SEVERE("no available command for read. %x\n", addr);
+    }
+
+    return ret;
+}
+
+static void maru_tuner_decoder_write(void *opaque, hwaddr addr,
+                                    uint64_t value, unsigned size)
+{
+    MaruTunerDecoderState *s = (MaruTunerDecoderState *)opaque;
+
+    switch (addr) {
+    case TUNER_DECODER_CMD_SET_AUDIO_CODEC:
+        LOG_TRACE("set audio codec type: %d\n", value);
+        break;
+
+    case TUNER_DECODER_CMD_SET_VIDEO_CODEC:
+        LOG_TRACE("set video codec type: %d\n", value);
+        break;
+
+    case TUNER_DECODER_CMD_AUDIO_PLAY:
+        LOG_TRACE("play audio stream: %d\n", value);
+        break;
+
+    case TUNER_DECODER_CMD_AUDIO_STOP:
+        LOG_TRACE("stop audio stream: %d\n", value);
+        break;
+
+    case TUNER_DECODER_CMD_VIDEO_PLAY:
+        LOG_TRACE("play video stream: %d\n", value);
+        break;
+
+    case TUNER_DECODER_CMD_VIDEO_STOP:
+        LOG_TRACE("stop video stream: %d\n", value);
+        break;
+
+    case TUNER_DECODER_CMD_VIDEO_SET_OUTPUT_Y:
+        LOG_TRACE("set video output y: %u\n", value);
+#if TUNER_COMPOSITE_TO_WSI
+        set_tuner_output(0, value);
+#endif
+        break;
+
+    case TUNER_DECODER_CMD_VIDEO_SET_OUTPUT_U:
+        LOG_TRACE("set video output u: %u\n", value);
+#if TUNER_COMPOSITE_TO_WSI
+        set_tuner_output(1, value);
+#endif
+        break;
+
+    case TUNER_DECODER_CMD_VIDEO_SET_OUTPUT_V:
+        LOG_TRACE("set video output v: %u\n", value);
+#if TUNER_COMPOSITE_TO_WSI
+        set_tuner_output(2, value);
+#endif
+        if (value && s->stillimage_on && stillimage != NULL) {
+            maru_tuner_decoder_push_videoqueue(s, NULL, stillimage,
+                                             stillimage->width, stillimage->height);
+        }
+        break;
+
+    default:
+        LOG_SEVERE("no available command for write. %d\n", addr);
+    }
+}
+
+static const MemoryRegionOps maru_tuner_decoder_mmio_ops = {
+    .read = maru_tuner_decoder_read,
+    .write = maru_tuner_decoder_write,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+        .unaligned = false
+    },
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static int maru_tuner_decoder_initfn(PCIDevice *dev)
+{
+    WorkQueueObject *wqobj;
+    WSIObject *wsiobj;
+    bool ambiguous;
+    MaruTunerDecoderState *s = DO_UPCAST(MaruTunerDecoderState, dev, dev);
+    uint8_t *pci_conf = s->dev.config;
+    static int device_id = 0;
+
+    LOG_INFO("device initialization.\n");
+    LOG_INFO("still image directory : %s\n", g_stillimg_dir);
+
+    wqobj = workqueueobject_create(&ambiguous);
+
+    if (ambiguous) {
+        LOG_SEVERE("ambiguous work queue, set 'render_queue' property");
+        return -1;
+    }
+
+    if (!wqobj) {
+        LOG_SEVERE("unable to create work queue");
+        return -1;
+    }
+
+    wsiobj = wsiobject_find(g_wsi_name);
+
+    if (!wsiobj) {
+        LOG_SEVERE("winsys interface '%s' not found", g_wsi_name);
+        return -1;
+    }
+
+    g_render_queue = wqobj->wq;
+    g_wsi = wsiobj->wsi;
+
+    s->device_id = device_id;
+    device_id++;
+
+    maru_tuner_init_queue(s);
+
+    qemu_mutex_init(&s->state_mutex);
+    qemu_mutex_init(&s->av_state.av_mutex);
+    qemu_mutex_init(&s->video_event.ve_mutex);
+
+    /* Initialize A/V sync information */
+    s->av_state.video_time = 0;
+    s->av_state.video_last_pts = AV_NOPTS_VALUE;
+    s->av_state.video_start_pts = AV_NOPTS_VALUE;
+    s->av_state.audio_start_pts = AV_NOPTS_VALUE;
+    s->av_state.av_diff_pts = AV_NOPTS_VALUE;
+
+    s->video_event.vlock = 0;
+    s->video_event.vinfo = 0;
+    s->video_event.unmute = 0;
+    s->video_event.first_avsync = 0;
+
+    //LOG_WARNING("## WARNING ## decoder sets stillimage_on = true even if it is not tuned-up.\n");
+    //s->stillimage_on = true;
+
+    maru_tuner_transfer_threads_create(s);
+
+    /* load still image file */
+    if(stillimage == NULL) {
+        load_still_image();
+    }
+
+    /* register a function to qemu bottom-halves to switch context. */
+    s->bh = qemu_bh_new(maru_tuner_decoder_bh_callback, s);
+
+    pci_config_set_interrupt_pin(pci_conf, 1);
+
+    memory_region_init_ram(&s->vram, OBJECT(s), "maru_tuner_decoder.vram", TUNER_DECODER_MEM_SIZE, &error_abort);
+    s->vaddr = (uint8_t *)memory_region_get_ram_ptr(&s->vram);
+
+    memory_region_init_io(&s->mmio, OBJECT(s), &maru_tuner_decoder_mmio_ops, s,
+                        "maru_tuner_decoder.mmio", TUNER_DECODER_REG_SIZE);
+
+    pci_register_bar(&s->dev, 0, PCI_BASE_ADDRESS_MEM_PREFETCH, &s->vram);
+    pci_register_bar(&s->dev, 1, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->mmio);
+
+    if (s->device_id == TUNER_MAIN_DECODER_ID) {
+        g_main_decoder_state = s;
+
+#if TUNER_COMPOSITE_TO_SDLFB
+        qemu_mutex_init(&tuner_dec_video_frame_mutex);
+#endif
+        // register plugins
+        if ((s->hwaccel_plugin = probe_plugin())) {
+            LOG_INFO("%s extension is enabled.\n", s->hwaccel_plugin->name);
+        }
+    }
+
+    return 0;
+}
+
+static void maru_tuner_decoder_exitfn(PCIDevice *dev)
+{
+    MaruTunerDecoderState *s = DO_UPCAST(MaruTunerDecoderState, dev, dev);
+    LOG_INFO("device exit\n");
+
+    qemu_mutex_destroy(&s->state_mutex);
+
+    qemu_mutex_destroy(&s->dec_q[MARUDEC_TYPE_VIDEO].queue_mutex);
+    qemu_mutex_destroy(&s->dec_q[MARUDEC_TYPE_AUDIO].queue_mutex);
+    qemu_mutex_destroy(&s->av_state.av_mutex);
+    qemu_mutex_destroy(&s->video_event.ve_mutex);
+#if TUNER_CAPTION_CONFIG
+    qemu_mutex_destroy(&s->cc_q.queue_mutex);
+#endif
+
+#if TUNER_COMPOSITE_TO_SDLFB
+    if (s->device_id == TUNER_MAIN_DECODER_ID) {
+        qemu_mutex_destroy(&tuner_dec_video_frame_mutex);
+    }
+#endif
+
+    qemu_sem_destroy(&s->dec_q[MARUDEC_TYPE_VIDEO].queue_sem_p);
+
+    qemu_bh_delete(s->bh);
+}
+
+#ifdef QTEST_TIZEN
+/**
+ * @test  UTC_TUNERDECODER_TEST02
+ * @sut   TUNERDECODER
+ * @brief Check invalid parameter for maru_tuner_dec_init_ctx
+ * @flow  #02-01 call maru_tuner_dec_init_ctx with MaruTunerDecoderState=NULL paramerter
+ *        #02-02 call maru_tuner_dec_init_ctx with AVStream=NULL paramerter
+ * @type  Error Condition
+ * @input #02-01 MaruTunerDecoderState=NULL
+ *        #02-02 AVStream=NULL
+ */
+static void qtest_tunerdecoder_02(MaruTunerDecoderState *s, AVStream *stream)
+{
+    int ret = 0;
+
+    /* flow #02-01 */
+    ret = maru_tuner_dec_init_ctx(NULL, stream);
+    if (ret < 0) {
+        LOG_INFO("[QTEST][TUNERDECODER][UTC_TUNERDECODER_TEST02] #02-01 : SUCCESS\n");
+    } else {
+        LOG_INFO("[QTEST][TUNERDECODER][UTC_TUNERDECODER_TEST02] #02-01 : FAIL\n");
+    }
+
+    /* flow #02-02 */
+    ret = maru_tuner_dec_init_ctx(s, NULL);
+    if (ret < 0) {
+        LOG_INFO("[QTEST][TUNERDECODER][UTC_TUNERDECODER_TEST02] #02-02 : SUCCESS\n");
+    } else {
+        LOG_INFO("[QTEST][TUNERDECODER][UTC_TUNERDECODER_TEST02] #02-02 : FAIL\n");
+    }
+}
+
+/**
+ * @test  UTC_TUNERDECODER_TEST03
+ * @sut   TUNERDECODER
+ * @brief Initialize the decoder context.
+ * @flow  #03-01 call maru_tuner_dec_init_ctx to initialize audio context.
+ *        #03-02 call maru_tuner_dec_init_ctx to initialize video context.
+ * @type  Right
+ * @input #03-01 AVStream structure for audio
+ *        #03-02 AVStream structure for video
+ */
+static void qtest_tunerdecoder_03(MaruTunerDecoderState *s, AVStream *as, AVStream *vs)
+{
+    int ret = 0;
+
+    /* flow #03-01 */
+    ret = maru_tuner_dec_init_ctx(s, as);
+    if (ret < 0) {
+        LOG_INFO("[QTEST][TUNERDECODER][UTC_TUNERDECODER_TEST03] #03-01 : FAIL\n");
+    } else {
+        LOG_INFO("[QTEST][TUNERDECODER][UTC_TUNERDECODER_TEST03] #03-01 : SUCCESS\n");
+    }
+
+    /* flow #03-02 */
+    ret = maru_tuner_dec_init_ctx(s, vs);
+    if (ret < 0) {
+        LOG_INFO("[QTEST][TUNERDECODER][UTC_TUNERDECODER_TEST03] #03-02 : FAIL\n");
+    } else {
+        LOG_INFO("[QTEST][TUNERDECODER][UTC_TUNERDECODER_TEST03] #03-02 : SUCCESS\n");
+    }
+}
+
+/**
+ * @test  UTC_TUNERDECODER_TEST04
+ * @sut   TUNERDECODER
+ * @brief Check invalid parameter for maru_tuner_dec_decode_pkt
+ * @flow  #04-01 call maru_tuner_dec_decode_pkt with MaruTunerDecoderState=NULL paramerter
+ *        #04-02 call maru_tuner_dec_decode_pkt with AVStream=NULL paramerter
+ *        #04-03 call maru_tuner_dec_decode_pkt with AVPacket=NULL paramerter
+ * @type  Error Condition
+ * @input #04-01 MaruTunerDecoderState=NULL
+ *        #04-02 AVStream=NULL
+ *        #04-03 AVPacket=NULL
+ */
+static void qtest_tunerdecoder_04(MaruTunerDecoderState *s, AVStream *stream, AVFormatContext *fmt_ctx)
+{
+    int ret = 0;
+    AVPacket pkt;
+
+    ret = av_read_frame(fmt_ctx, &pkt);
+    if (ret < 0) {
+        LOG_SEVERE("incorrect qtest environment for tuner decoder\n");
+        return;
+    }
+
+    /* flow #04-01 */
+    ret = maru_tuner_dec_decode_pkt(NULL, stream, &pkt);
+    if (ret < 0) {
+        LOG_INFO("[QTEST][TUNERDECODER][UTC_TUNERDECODER_TEST04] #04-01 : SUCCESS\n");
+    } else {
+        LOG_INFO("[QTEST][TUNERDECODER][UTC_TUNERDECODER_TEST04] #04-01 : FAIL\n");
+    }
+
+    /* flow #04-02 */
+    ret = maru_tuner_dec_decode_pkt(s, NULL, &pkt);
+    if (ret < 0) {
+        LOG_INFO("[QTEST][TUNERDECODER][UTC_TUNERDECODER_TEST04] #04-02 : SUCCESS\n");
+    } else {
+        LOG_INFO("[QTEST][TUNERDECODER][UTC_TUNERDECODER_TEST04] #04-02 : FAIL\n");
+    }
+
+    /* flow #04-03 */
+    ret = maru_tuner_dec_decode_pkt(s, stream, NULL);
+    if (ret < 0) {
+        LOG_INFO("[QTEST][TUNERDECODER][UTC_TUNERDECODER_TEST04] #04-03 : SUCCESS\n");
+    } else {
+        LOG_INFO("[QTEST][TUNERDECODER][UTC_TUNERDECODER_TEST04] #04-03 : FAIL\n");
+    }
+}
+
+/**
+ * @test  UTC_TUNERDECODER_TEST05
+ * @sut   TUNERDECODER
+ * @brief Decode audio/video packet.
+ * @flow  #05-01 call maru_tuner_dec_init_ctx to decode audio packet.
+ *        #05-02 call maru_tuner_dec_init_ctx to decode video packet.
+ * @type  Right
+ * @input #05-01 AVStream structure for audio, AVPacket including audio data.
+ *        #05-02 AVStream structure for video, AVPacket including video data.
+ */
+static void qtest_tunerdecoder_05(MaruTunerDecoderState *s, AVStream *as, AVStream *vs,
+                            AVFormatContext *fmt_ctx, int as_idx, int vs_idx)
+{
+    int ret = 0;
+    int try_count = 0;
+    AVPacket pkt;
+
+    /* flow #05-01 */
+    while (1) {
+        ret = av_read_frame(fmt_ctx, &pkt);
+        if (ret < 0) {
+            LOG_SEVERE("incorrect qtest environment for tuner decoder\n");
+            return;
+        }
+        if (pkt.stream_index == as_idx) {
+            break;
+       }
+    }
+    ret = maru_tuner_dec_decode_pkt(s, as, &pkt);
+    if (ret < 0) {
+        LOG_INFO("[QTEST][TUNERDECODER][UTC_TUNERDECODER_TEST05] #05-01 : FAIL\n");
+    } else {
+        LOG_INFO("[QTEST][TUNERDECODER][UTC_TUNERDECODER_TEST05] #05-01 : SUCCESS\n");
+    }
+
+    /* flow #05-02 */
+    while (1) {
+        ret = av_read_frame(fmt_ctx, &pkt);
+        if (ret < 0) {
+            LOG_SEVERE("incorrect qtest environment for tuner decoder\n");
+            return;
+        }
+        if (pkt.stream_index == vs_idx) {
+            try_count++;
+            ret = maru_tuner_dec_decode_pkt(s, vs, &pkt);
+            if (ret == 0 || try_count > 10) {
+                break;
+            }
+       }
+    }
+    if (ret < 0) {
+        LOG_INFO("[QTEST][TUNERDECODER][UTC_TUNERDECODER_TEST05] #05-02 : FAIL\n");
+    } else {
+        LOG_INFO("[QTEST][TUNERDECODER][UTC_TUNERDECODER_TEST05] #05-02 : SUCCESS\n");
+    }
+}
+
+/**
+ * @test  UTC_TUNERDECODER_TEST06
+ * @sut   TUNERDECODER
+ * @brief Check invalid parameter for maru_tuner_dec_deinit_ctx
+ * @flow  #06-01 call maru_tuner_dec_deinit_ctx with MaruTunerDecoderState=NULL paramerter
+ *        #06-02 call maru_tuner_dec_deinit_ctx with AVStream=NULL paramerter
+ * @type  Error Condition
+ * @input #06-01 MaruTunerDecoderState=NULL
+ *        #06-02 AVStream=NULL
+ */
+static void qtest_tunerdecoder_06(MaruTunerDecoderState *s, AVStream *stream)
+{
+    int ret = 0;
+
+    /* flow #06-01 */
+    ret = maru_tuner_dec_deinit_ctx(NULL, stream);
+    if (ret < 0) {
+        LOG_INFO("[QTEST][TUNERDECODER][UTC_TUNERDECODER_TEST06] #06-01 : SUCCESS\n");
+    } else {
+        LOG_INFO("[QTEST][TUNERDECODER][UTC_TUNERDECODER_TEST06] #06-01 : FAIL\n");
+    }
+
+    /* flow #06-02 */
+    ret = maru_tuner_dec_deinit_ctx(s, NULL);
+    if (ret < 0) {
+        LOG_INFO("[QTEST][TUNERDECODER][UTC_TUNERDECODER_TEST06] #06-02 : SUCCESS\n");
+    } else {
+        LOG_INFO("[QTEST][TUNERDECODER][UTC_TUNERDECODER_TEST06] #06-02 : FAIL\n");
+    }
+}
+
+/**
+ * @test  UTC_TUNERDECODER_TEST07
+ * @sut   TUNERDECODER
+ * @brief Deinitialize the decoder context.
+ * @flow  #07-01 call maru_tuner_dec_deinit_ctx to deinitialize audio context.
+ *        #07-02 call maru_tuner_dec_deinit_ctx to deinitialize video context.
+ * @type  Right
+ * @input #07-01 AVStream structure for audio
+ *        #07-02 AVStream structure for video
+ */
+static void qtest_tunerdecoder_07(MaruTunerDecoderState *s, AVStream *as, AVStream *vs)
+{
+    int ret = 0;
+
+    /* flow #07-01 */
+    ret = maru_tuner_dec_deinit_ctx(s, as);
+    if (ret < 0) {
+        LOG_INFO("[QTEST][TUNERDECODER][UTC_TUNERDECODER_TEST07] #07-01 : FAIL\n");
+    } else {
+        LOG_INFO("[QTEST][TUNERDECODER][UTC_TUNERDECODER_TEST07] #07-01 : SUCCESS\n");
+    }
+
+    /* flow #07-02 */
+    ret = maru_tuner_dec_deinit_ctx(s, vs);
+    if (ret < 0) {
+        LOG_INFO("[QTEST][TUNERDECODER][UTC_TUNERDECODER_TEST07] #07-02 : FAIL\n");
+    } else {
+        LOG_INFO("[QTEST][TUNERDECODER][UTC_TUNERDECODER_TEST07] #07-02 : SUCCESS\n");
+    }
+}
+
+static int prepare_qtest_tunerdecoder(AVFormatContext **p_fmt_ctx, int *p_as_idx, int *p_vs_idx)
+{
+    char *home_path = NULL;
+    char ts_file_path[PATH_MAX] = {0,};
+    int ret = 0;
+
+    home_path = getenv("HOME");
+    sprintf(ts_file_path, "%s/tuner_test.trp", home_path);
+    LOG_TRACE("ts_file_path: %s\n", ts_file_path);
+
+    /* register all formats and codecs */
+    av_register_all();
+    LOG_TRACE("av_register_all() success!\n");
+
+    /* open input file, and allocate format context */
+    ret = avformat_open_input(p_fmt_ctx, ts_file_path, NULL, NULL);
+    if (ret < 0) {
+        LOG_SEVERE("[QTEST] Could not open TS file(%s), errno=%d\n", ts_file_path, ret);
+        return ret;
+    }
+    LOG_TRACE("avformat_open_input() success!\n");
+
+    /* retrieve stream information */
+    ret = avformat_find_stream_info(*p_fmt_ctx, NULL);
+    if (ret < 0) {
+        LOG_SEVERE("Could not find stream information\n");
+        return ret;
+    }
+    LOG_TRACE("av_find_stream_info() success!, nb_streams=%d, %p, %p\n", (*p_fmt_ctx)->nb_streams, p_fmt_ctx, *p_fmt_ctx);
+
+    /* find best audio/video stream */
+    ret = av_find_best_stream(*p_fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
+    if (ret < 0) {
+        LOG_SEVERE("Could not find audio stream in TS file(%s)\n", ts_file_path);
+        return ret;
+    }
+    *p_as_idx = ret;
+
+    ret = av_find_best_stream(*p_fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
+    if (ret < 0) {
+        LOG_SEVERE("Could not find video stream in TS file(%s)\n", ts_file_path);
+        return ret;
+    }
+    *p_vs_idx = ret;
+
+    return 0;
+}
+
+static int maru_tuner_decoder_qtest_initfn(PCIDevice *dev)
+{
+    MaruTunerDecoderState *s = NULL;
+    AVFormatContext *fmt_ctx = NULL;
+    AVStream *as = NULL;
+    AVStream *vs = NULL;
+    int ret = 0;
+    int as_idx = -1;
+    int vs_idx = -1;
+
+    LOG_INFO("initialize tuner decoder device for qtest \n");
+    ret = maru_tuner_decoder_initfn(dev);
+    if (ret < 0) {
+        LOG_SEVERE("failed to initialize tuner decoder device.\n");
+        return ret;
+    }
+    s = DO_UPCAST(MaruTunerDecoderState, dev, dev);
+
+    ret = prepare_qtest_tunerdecoder(&fmt_ctx, &as_idx, &vs_idx);
+    if (ret < 0) {
+        LOG_SEVERE("incorrect qtest environment for tuner decoder\n");
+        return ret;
+    }
+
+    LOG_TRACE("fmt_ctx=%p, as_idx=%d, vs_idx=%d\n", fmt_ctx, as_idx, vs_idx);
+
+    as = fmt_ctx->streams[as_idx];
+    LOG_TRACE("audio stream idx=%d, pid=%d\n", as_idx, as->id);
+    vs = fmt_ctx->streams[vs_idx];
+    LOG_TRACE("video stream idx=%d, pid=%d\n", vs_idx, vs->id);
+
+    /* maru_tuner_dec_init_ctx() */
+    qtest_tunerdecoder_02(s, as);
+    qtest_tunerdecoder_03(s, as, vs);
+
+    /* maru_tuner_dec_decode_pkt() */
+    qtest_tunerdecoder_04(s, as, fmt_ctx);
+    qtest_tunerdecoder_05(s, as, vs, fmt_ctx, as_idx, vs_idx);
+
+    /* maru_tuner_dec_deinit_ctx() */
+    qtest_tunerdecoder_06(s, as);
+    qtest_tunerdecoder_07(s, as, vs);
+
+    return ret;
+}
+#endif
+
+static void maru_tuner_decoder_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+#ifdef QTEST_TIZEN
+    k->init = maru_tuner_decoder_qtest_initfn;
+#else
+    k->init = maru_tuner_decoder_initfn;
+#endif
+    k->exit = maru_tuner_decoder_exitfn;
+    k->vendor_id = PCI_VENDOR_ID_TIZEN;
+    k->device_id = PCI_DEVICE_ID_VIRTUAL_TUNER_DECODER;
+    k->class_id = PCI_CLASS_OTHERS;
+    dc->desc = "Virtual Tuner Decoder device for Tizen emulator";
+}
+
+static TypeInfo tuner_decoder_device_info = {
+    .name          = TUNER_DECODER_DEVICE_NAME,
+    .parent        = TYPE_PCI_DEVICE,
+    .instance_size = sizeof(MaruTunerDecoderState),
+    .class_init    = maru_tuner_decoder_class_init,
+};
+
+static void tuner_decoder_register_types(void)
+{
+    type_register_static(&tuner_decoder_device_info);
+}
+
+type_init(tuner_decoder_register_types)
diff --git a/tizen/src/hw/pci/maru_tuner_decoder.h b/tizen/src/hw/pci/maru_tuner_decoder.h
new file mode 100644 (file)
index 0000000..344f5ff
--- /dev/null
@@ -0,0 +1,275 @@
+/*
+ * Virtual Tuner Decoder device
+ *
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Contact:
+ *  GunSoo Kim <gunsoo83.kim@samsung.com>
+ *  SangHo Park <sangho1206.park@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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ *
+ * Contributors:
+ * - S-Core Co., Ltd
+ *
+ */
+#ifndef _MARU_TUNER_DECODER_H_
+#define _MARU_TUNER_DECODER_H_
+/**
+ @file maru_tuner_decoder.h
+ @brief Virtual Tuner Decoder Device file.
+*/
+
+/// @cond
+#include <stdio.h>
+#include <sys/types.h>
+
+#include "hw/hw.h"
+#include "sysemu/kvm.h"
+#include "hw/pci/pci.h"
+#include "hw/pci/pci_ids.h"
+#include "qemu/thread.h"
+
+#include "hw/pci/maru_brillcodec_plugin.h"
+#include "hw/pci/maru_brillcodec.h"
+#include "util/new_debug_ch.h"
+#include "hw/maru_device_ids.h"
+#include "libavformat/avformat.h"
+#include "libavutil/mathematics.h"
+#include "libavutil/opt.h"
+#include "libavutil/pixdesc.h"
+#include "libswscale/swscale.h"
+#include "libavresample/avresample.h"
+
+#include <pixman.h>
+
+/* Tuner Image */
+extern uint8_t tuner_video_power;
+extern uint8_t tuner_video_image_valid;
+extern uint16_t tuner_video_left;
+extern uint16_t tuner_video_top;
+extern uint16_t tuner_video_width;
+extern uint16_t tuner_video_height;
+extern uint16_t tuner_video_width_back;
+extern uint16_t tuner_video_height_back;
+extern pixman_image_t *tuner_video_image;
+extern pixman_image_t *tuner_video_image_back;
+extern QemuMutex tuner_dec_video_frame_mutex;
+extern char *g_stillimg_dir;
+extern char *g_wsi_name;
+
+#define TUNER_DECODER_DEVICE_NAME   "maru-tuner-decoder"
+
+#define TUNER_CAPTION_CONFIG    0
+
+#define TUNER_MAIN_DECODER_ID   0
+#define TUNER_SUB_DECODER_ID    1
+
+enum MaruTunerDecType {
+    MARUDEC_TYPE_AUDIO,
+    MARUDEC_TYPE_VIDEO,
+};
+
+typedef struct AudioVideoState {
+    /* audio state */
+    int sample_rate;
+    int channels;
+    int sample_fmt;
+    int64_t pop_size;
+    int64_t audio_start_pts;
+
+    /* video state */
+    int64_t video_time;
+    int64_t video_last_pts;
+    int64_t video_start_pts;
+    int is_first;
+
+    int64_t av_diff_pts;
+
+    QemuMutex av_mutex;
+} AudioVideoState;
+
+typedef struct VideoEvent {
+    int vlock;
+    int vinfo;
+    int unmute;
+    int first_avsync;
+
+    QemuMutex ve_mutex;
+} VideoEvent;
+
+
+typedef struct VideoFrameEntry {
+    QTAILQ_ENTRY(VideoFrameEntry) node;
+
+    AVFrame *frame;
+    int time_base_num;
+    int time_base_den;
+    int width;
+    int height;
+    bool is_stillimage;
+} VideoFrameEntry;
+
+typedef struct AudioFrameEntry {
+    QTAILQ_ENTRY(AudioFrameEntry) node;
+
+    uint8_t *samples;
+    int size;
+} AudioFrameEntry;
+
+typedef union TunerDecQueue {
+    QTAILQ_HEAD(TunerDecAudioQueue, AudioFrameEntry) aq;
+    QTAILQ_HEAD(TunerDecVideoQueue, VideoFrameEntry) vq;
+} TunerDecQueue;
+
+typedef struct TunerDecQueueInfo {
+    TunerDecQueue queue_head;
+
+    int elem_cnt;
+    QemuMutex queue_mutex;
+    QemuSemaphore queue_sem_p;
+} TunerDecQueueInfo;
+
+typedef struct TunerDecCCQueueInfo {
+    int front_idx;
+    int rear_idx;
+    int elem_cnt;
+    QemuMutex queue_mutex;
+} TunerDecCCQueueInfo;
+
+enum dtv_decoder_isr_reason {
+    TUNER_DECODER_ISR_EVENT     = 0x01,
+    TUNER_DECODER_ISR_CCDATA    = 0x02,
+};
+
+enum dtv_decoder_event_type {
+    TUNER_DECODER_VLOCK_EVENT   = 0x01,
+    TUNER_DECODER_VINFO_EVENT   = 0x02,
+    TUNER_DECODER_UNMUTE_EVENT  = 0x04,
+};
+
+/*
+ *  Tuner Decoder Device Structures
+ */
+typedef struct MaruTunerDecoderState {
+    PCIDevice           dev;
+
+    uint8_t             *vaddr;
+    MemoryRegion        vram;
+    MemoryRegion        mmio;
+
+    QemuMutex           state_mutex;
+    QEMUBH              *bh;
+    uint32_t            isr;
+    uint32_t            irq_raised;
+    uint32_t            event_count;
+    uint32_t            device_id;
+
+    TunerDecQueueInfo   dec_q[2]; /* audio,video queue */
+#if TUNER_CAPTION_CONFIG
+    TunerDecCCQueueInfo cc_q; /* cc queue */
+#endif
+
+    AudioVideoState     av_state;
+    VideoEvent          video_event;
+
+    QemuSemaphore       stillimage_sem;
+    bool                stillimage_on;
+    char                *stillimg;
+
+    CodecPlugin         *hwaccel_plugin;
+
+    CodecContext        context;
+} MaruTunerDecoderState;
+
+/*
+ *  Maru Tuner Device Functions
+ */
+int pci_marutuner_decoder_init(PCIBus *bus);
+/// @endcond
+
+/**
+  @defgroup Dtv-Dec-Dev_mmio IDtvDecMmio0
+  @ingroup DTV
+  @brief  DTV decoder device MMIO interface to communicate with DTV decoder driver.
+  @{
+*/
+/**
+ @brief DTV decoder device MMIO command list to communicate with DTV decoder driver.
+ */
+enum dtv_decoder_io_cmd {
+    TUNER_DECODER_CMD_GET_VERSION         = 0x00, ///< Get DTV decoder device version.
+    TUNER_DECODER_CMD_SET_AUDIO_CODEC     = 0x04, ///< Set Audio codec type used to initialize the audio decoder context.
+    TUNER_DECODER_CMD_SET_VIDEO_CODEC     = 0x08, ///< Set Video codec type used to initialize the video decoder context.
+    TUNER_DECODER_CMD_AUDIO_PLAY          = 0x0C, ///< Asks the decoder device to start playing the audio stream.
+    TUNER_DECODER_CMD_AUDIO_STOP          = 0x10, ///< Asks the decoder device to stop playing the audio stream.
+    TUNER_DECODER_CMD_VIDEO_PLAY          = 0x14, ///< Asks the decoder device to start playing the video stream.
+    TUNER_DECODER_CMD_VIDEO_STOP          = 0x18, ///< Asks the decoder device to stop playing the video stream.
+    TUNER_DECODER_CMD_GET_VIDMEM_ADDR     = 0x1C, ///< Get Video memory address that contain the raw video frame.
+    TUNER_DECODER_CMD_GET_AUDIO_CLOCK     = 0x20, ///< Get Audio clock to AV-sync.
+    TUNER_DECODER_CMD_ISR                 = 0x24, ///< Get the interrupt state of DTV decoder device.
+    TUNER_DECODER_CMD_VIDEO_SET_OUTPUT_Y  = 0x28, ///< Set Video output surface.
+    TUNER_DECODER_CMD_VIDEO_SET_OUTPUT_U  = 0x2C, ///< Set Video output surface.
+    TUNER_DECODER_CMD_VIDEO_SET_OUTPUT_V  = 0x30, ///< Set Video output surface.
+    TUNER_DECODER_CMD_CC_INDEX            = 0x34, ///< Get Closed Caption data index.
+    TUNER_DECODER_CMD_CC_COMPLETE         = 0x38, ///< Complete to read Closed Caption data.
+    TUNER_DECODER_CMD_GET_DEVICE_ID       = 0x3C, ///< Get DTV decoder device ID.
+    TUNER_DECODER_CMD_GET_VIDEO_EVENT_STATE = 0x40, ///< Get Video event state.
+};
+/** @} */
+
+/**
+  @defgroup Dtv-dec-dev_stream IDtvDecDev1
+  @ingroup DTV
+  @brief  DTV decoder device interface to decode TS stream.
+  @{
+*/
+/**
+@brief Initialize decoder context.
+@param stream contains the audio/video stream infomation.
+@return 0 on success, and a different value on error.
+@remark stream(AVStream structure) must be obtained through the libavformat.
+*/
+int maru_tuner_dec_init_ctx(MaruTunerDecoderState* state, AVStream* stream);
+
+/**
+@brief Decode the packet(AVPacket) and Save the decoded data to play the stream.
+@param stream contain the audio/video stream infomation.
+@param pkt contain the audio/video stream that is encoded data.
+@return 0 on success, and a different value on error.
+@remark stream(AVStream structure), pkt(AVPacket structure) must be obtained through the libavformat.
+*/
+int maru_tuner_dec_decode_pkt(MaruTunerDecoderState* state, AVStream *stream, AVPacket *pkt);
+
+/**
+@brief Deinitialize decoder context.
+@param stream contains the audio/video stream infomation.
+@return 0 on success, and a different value on error.
+@remark stream(AVStream structure) must be obtained through the libavformat.
+*/
+int maru_tuner_dec_deinit_ctx(MaruTunerDecoderState* state, AVStream* stream);
+
+/**
+@brief Check the deocder queue.
+@return true on decoder is full.
+*/
+bool maru_tuner_dec_queue_is_full(MaruTunerDecoderState* state);
+
+void* maru_tuner_decoder_pop_queue(MaruTunerDecoderState* state, enum MaruTunerDecType type);
+
+int maru_tuner_dec_set_stillimage(MaruTunerDecoderState* s, bool on);
+/** @} */
+#endif /* _MARU_TUNER_DECODER_H_ */