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
#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)
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)
{
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 {
--- /dev/null
+/*
+ * 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);
+
/* 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 */
/*
+----------------------+--------------------+---------------+
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
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
--- /dev/null
+/*
+ * 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)
--- /dev/null
+/*
+ * 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_ */
--- /dev/null
+/*
+ * 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)
--- /dev/null
+/*
+ * 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
--- /dev/null
+/* * 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);
+}
+
--- /dev/null
+/* * 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_ */
--- /dev/null
+/*
+ * 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)
--- /dev/null
+/*
+ * 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_ */