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