// // Copyright (C) 2015-2023 Markus Hiienkari // // This file is part of Open Source Scan Converter project. // // 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 3 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, see . // #include #include #include "system.h" #include "string.h" #include "altera_avalon_pio_regs.h" #include "i2c_opencores.h" #include "av_controller.h" #include "tvp7002.h" #include "ths7353.h" #include "pcm1862.h" #include "video_modes.h" #include "lcd.h" #include "flash.h" #include "sdcard.h" #include "menu.h" #include "avconfig.h" #include "firmware.h" #include "userdata.h" #include "it6613.h" #include "it6613_sys.h" #include "HDMI_TX.h" #include "hdmitx.h" #include "sd_io.h" #include "sys/alt_timestamp.h" #define MIN_LINES_PROGRESSIVE 200 #define MIN_LINES_INTERLACED 400 #define STATUS_TIMEOUT_US 25000 #define MAINLOOP_INTERVAL_US 10000 #define PCNT_TOLERANCE 50 #define HSYNC_WIDTH_TOLERANCE 8 alt_u32 sys_ctrl; // Current mode avmode_t cm; extern mode_data_t video_modes_plm[]; extern ypbpr_to_rgb_csc_t csc_coeffs[]; extern alt_u16 rc_keymap[REMOTE_MAX_KEYS]; extern alt_u16 rc_keymap_default[REMOTE_MAX_KEYS]; extern alt_u32 remote_code; extern alt_u32 btn_code, btn_code_prev; extern alt_u8 remote_rpt, remote_rpt_prev; extern avconfig_t tc, tc_default; extern alt_u8 vm_sel; extern char target_profile_name[PROFILE_NAME_LEN+1]; tvp_input_t target_tvp; tvp_sync_input_t target_tvp_sync; alt_u8 target_type; alt_u8 update_cur_vm; alt_u8 profile_sel, profile_sel_menu, input_profiles[AV_LAST], lt_sel, def_input, profile_link, lcd_bl_timeout; alt_u8 osd_enable=1, osd_status_timeout=1; alt_u8 auto_input, auto_av1_ypbpr, auto_av2_ypbpr = 1, auto_av3_ypbpr; char row1[LCD_ROW_LEN+1], row2[LCD_ROW_LEN+1], menu_row1[LCD_ROW_LEN+1], menu_row2[LCD_ROW_LEN+1]; extern alt_u8 menu_active; avinput_t target_input; alt_u8 pcm1862_active; uint8_t sl_def_iv_x, sl_def_iv_y; alt_u32 read_it2(alt_u32 regaddr); mode_data_t vmode_in, vmode_out; vm_proc_config_t vm_conf; // Manually (see cyiv-51005.pdf) or automatically (MIF/HEX from PLL megafunction) generated config may not // provide fully correct scan chain data (e.g. mismatches in C3) and lead to incorrect PLL configuration. // To get correct scan chain data, do the following: // 1. Create a ALTPLL_RECONFIG instance with initial value read from your MIF/HEX file // 2. Connect ALTPLL_RECONFIG to your PLL and set its reconfig input to something you can control easily (e.g. button) // 3. Create a signaltap file and add all PLL signals to capture. Set sample depth to 256 and clock to scanclk // 4. Compile the design and program the FPGA // 5. Open signaltap and set trigger to scanclkena rising edge // 6. Run signaltap and trigger PLL reconfiguration // 7. Export VCD file for analysis // 8. Compare your MIF/HEX to the captured scan chain and update it accordingly // 9. Dump the updated scan chain data to an array like below (last 16 bits are 0) // 10. PLL can be then reconfigured with custom pll_reconfig as shown in program_mode() const pll_config_t pll_configs[] = { {{0x0d806000, 0x00402010, 0x08800020, 0x00080002, 0x00000000}}, // 1x (default) {{0x0d806000, 0x00402008, 0x04800020, 0x00080002, 0x00000000}}, // 2x (~20-40MHz) {{0x0d806000, 0x00441c07, 0x02800020, 0x00080002, 0x00000000}}, // 3x (~20-40MHz) {{0x0d806000, 0x00402004, 0x02800020, 0x00080002, 0x00000000}}, // 4x (~20-40MHz) {{0x0d806000, 0x00441c05, 0x01800020, 0x00080002, 0x00000000}}, // 5x (~20-40MHz) {{0x0d806000, 0x00301802, 0x01800020, 0x00080002, 0x00000000}}, // 6x (~20-40MHz) {{0x0e406000, 0x00281407, 0x02800020, 0x00080002, 0x00000000}} }; // 2x (~75MHz) volatile sc_regs *sc = (volatile sc_regs*)SC_CONFIG_0_BASE; volatile osd_regs *osd = (volatile osd_regs*)OSD_GENERATOR_0_BASE; volatile pll_reconfig_regs *pll_reconfig = (volatile pll_reconfig_regs*)PLL_RECONFIG_0_BASE; void ui_disp_menu(alt_u8 osd_mode) { alt_u8 menu_page; if ((osd_mode == 1) || (osd_enable == 2)) { strncpy((char*)osd->osd_array.data[0][0], menu_row1, OSD_CHAR_COLS); strncpy((char*)osd->osd_array.data[1][0], menu_row2, OSD_CHAR_COLS); osd->osd_row_color.mask = 0; osd->osd_sec_enable[0].mask = 3; osd->osd_sec_enable[1].mask = 0; } else if (osd_mode == 2) { menu_page = get_current_menunavi()->mp; strncpy((char*)osd->osd_array.data[menu_page][1], menu_row2, OSD_CHAR_COLS); osd->osd_sec_enable[1].mask |= (1<osd_config.status_refresh = 1; strncpy((char*)osd->osd_array.data[0][0], row1, OSD_CHAR_COLS); strncpy((char*)osd->osd_array.data[1][0], row2, OSD_CHAR_COLS); osd->osd_row_color.mask = 0; osd->osd_sec_enable[0].mask = 3; osd->osd_sec_enable[1].mask = 0; lcd_write((char*)&row1, (char*)&row2); } } #ifdef ENABLE_AUDIO inline void SetupAudio(tx_mode_t mode) { // shut down audio-tx before setting new config (recommended for changing audio-tx config) DisableAudioOutput(); EnableAudioInfoFrame(FALSE, NULL); if (mode != TX_DVI) { EnableAudioOutput4OSSC(cm.pclk_o_hz, tc.audio_dw_sampl, tc.audio_swap_lr); HDMITX_SetAudioInfoFrame((BYTE)tc.audio_dw_sampl); #ifdef DEBUG Switch_HDMITX_Bank(1); usleep(1000); alt_u32 cts = 0; cts |= read_it2(0x35) >> 4; cts |= read_it2(0x36) << 4; cts |= read_it2(0x37) << 12; printf("CTS: %lu\n", cts); #endif } } #endif inline void TX_enable(tx_mode_t mode) { // shut down TX before setting new config SetAVMute(TRUE); DisableVideoOutput(); EnableAVIInfoFrame(FALSE, NULL); //Setup TX configuration //TODO: set pclk target and VIC dynamically EnableVideoOutput(cm.hdmitx_pclk_level ? PCLK_HIGH : PCLK_MEDIUM, COLOR_RGB444, (mode == TX_HDMI_YCBCR444) ? COLOR_YUV444 : COLOR_RGB444, (mode != TX_DVI)); if (mode != TX_DVI) { HDMITX_SetAVIInfoFrame(vmode_out.vic, (mode == TX_HDMI_RGB) ? F_MODE_RGB444 : F_MODE_YUV444, 0, 0, tc.hdmi_itc, vm_conf.hdmitx_pixr_ifr); HDMITX_SetHDRInfoFrame(tc.hdmi_hdr ? 3 : 0); cm.cc.hdmi_itc = tc.hdmi_itc; cm.cc.hdmi_hdr = tc.hdmi_hdr; } #ifdef ENABLE_AUDIO SetupAudio(mode); #endif // start TX SetAVMute(FALSE); } void pll_reconfigure(alt_u8 id) { if ((id < sizeof(pll_configs)/sizeof(pll_config_t)) && (id != pll_reconfig->pll_config_status.c_config_id)) { memcpy((void*)pll_reconfig->pll_config_data.data, pll_configs[id].data, sizeof(pll_config_t)); pll_reconfig->pll_config_status.t_config_id = id; printf("Reconfiguring PLL to config %u\n", id); // Try switching to fixed reference clock as otherwise reconfig may hang or corrupt configuration if (cm.avinput != AV_TESTPAT) { sys_ctrl &= ~VIDGEN_OFF; IOWR_ALTERA_AVALON_PIO_DATA(PIO_0_BASE, sys_ctrl); usleep(10); } // Do not reconfigure if clock switch failed if ((IORD_ALTERA_AVALON_PIO_DATA(PIO_1_BASE) & PLL_ACTIVECLK_MASK) == 0) { // reset state machine if previous reconfigure hanged (should not occur with stable refclk) if (pll_reconfig->pll_config_status.busy) { pll_reconfig->pll_config_status.reset = 1; usleep(1); } pll_reconfig->pll_config_status.reset = 0; pll_reconfig->pll_config_status.update = 1; usleep(10); } if (cm.avinput != AV_TESTPAT) { sys_ctrl |= VIDGEN_OFF; IOWR_ALTERA_AVALON_PIO_DATA(PIO_0_BASE, sys_ctrl); } } } void set_lpf(alt_u8 lpf) { alt_u32 pclk; pclk = estimate_dotclk(&vmode_in, (TVP_EXTCLK_HZ/cm.clkcnt)); //Auto if (lpf == 0) { if (target_tvp == TVP_INPUT3) { tvp_set_lpf((pclk < 30000000) ? 0x0F : 0); ths_set_lpf(THS_LPF_BYPASS); } else { tvp_set_lpf(0); switch (target_type) { case VIDEO_PC: case VIDEO_HDTV: ths_set_lpf((pclk < 80000000) ? THS_LPF_35MHZ : THS_LPF_BYPASS); break; case VIDEO_EDTV: ths_set_lpf(THS_LPF_16MHZ); break; case VIDEO_SDTV: default: ths_set_lpf(THS_LPF_9MHZ); break; } } } else { if (target_tvp == TVP_INPUT3) { tvp_set_lpf((lpf == 2) ? 0x0F : 0); ths_set_lpf(THS_LPF_BYPASS); } else { tvp_set_lpf(0); ths_set_lpf((lpf > 2) ? (VIDEO_LPF_MAX-lpf) : THS_LPF_BYPASS); } } } void set_csc(alt_u8 csc) { if (csc > 1) { if (target_type == VIDEO_HDTV) csc = 1; else csc = 0; } tvp_sel_csc(&csc_coeffs[csc]); } inline int check_linecnt(alt_u8 progressive, alt_u32 totlines) { if (progressive) return (totlines >= MIN_LINES_PROGRESSIVE); else return (totlines >= MIN_LINES_INTERLACED); } // Check if input video status / target configuration has changed status_t get_status(tvp_sync_input_t syncinput) { alt_u32 totlines, clkcnt, pcnt_frame; alt_u8 progressive, sync_active, valid_linecnt, hsync_width; status_t status = NO_CHANGE; alt_timestamp_type start_ts = alt_timestamp(); // Wait until vsync active (avoid noise coupled to I2C bus on earlier prototypes) while (alt_timestamp() < start_ts + STATUS_TIMEOUT_US*(TIMER_0_FREQ/1000000)) { if (IORD_ALTERA_AVALON_PIO_DATA(PIO_1_BASE) & VSYNC_FLAG_MASK) break; } //sync_active = tvp_check_sync(syncinput); sync_active = sc->fe_status.sync_active; // Read sync information from TVP7002 frontend totlines = sc->fe_status.vtotal; progressive = !sc->fe_status.interlace_flag; pcnt_frame = (unsigned long)sc->fe_status2.pcnt_frame; hsync_width = (unsigned long)sc->fe_status2.hsync_width; clkcnt = pcnt_frame/(totlines>>!progressive); valid_linecnt = check_linecnt(progressive, totlines); // Check sync activity if (!cm.sync_active && sync_active && valid_linecnt) { cm.sync_active = 1; status = ACTIVITY_CHANGE; } else if (cm.sync_active && (!sync_active || !valid_linecnt)) { cm.sync_active = 0; status = ACTIVITY_CHANGE; } if (valid_linecnt) { if ((totlines != cm.totlines) || (progressive != cm.progressive) || (pcnt_frame < (cm.pcnt_frame - PCNT_TOLERANCE)) || (pcnt_frame > (cm.pcnt_frame + PCNT_TOLERANCE)) || (abs(((int)hsync_width - (int)cm.hsync_width)) > HSYNC_WIDTH_TOLERANCE)) { printf("totlines: %lu (cur) / %lu (prev), pcnt_frame: %lu (cur) / %lu (prev), hsync_width: %lu (cur) / %lu (prev)\n", totlines, cm.totlines, pcnt_frame, cm.pcnt_frame, hsync_width, cm.hsync_width); status = (status < MODE_CHANGE) ? MODE_CHANGE : status; } if (memcmp(&tc, &cm.cc, offsetof(avconfig_t, sl_mode)) || (update_cur_vm == 1)) status = (status < MODE_CHANGE) ? MODE_CHANGE : status; if ((vm_conf.si_pclk_mult > 1) && (pll_reconfig->pll_config_status.c_config_id != 6) && (vm_conf.si_pclk_mult-1 != pll_reconfig->pll_config_status.c_config_id)) status = (status < MODE_CHANGE) ? MODE_CHANGE : status; cm.totlines = totlines; cm.clkcnt = clkcnt; cm.pcnt_frame = pcnt_frame; cm.hsync_width = hsync_width; cm.progressive = progressive; } if (memcmp(&tc.sl_mode, &cm.cc.sl_mode, offsetof(avconfig_t, sync_vth) - offsetof(avconfig_t, sl_mode))) status = (status < SC_CONFIG_CHANGE) ? SC_CONFIG_CHANGE : status; if (tc.sync_vth != cm.cc.sync_vth) tvp_set_sog_thold(tc.sync_vth); if (tc.linelen_tol != cm.cc.linelen_tol) tvp_set_linelen_tol(tc.linelen_tol); if (tc.vsync_thold != cm.cc.vsync_thold) tvp_set_ssthold(tc.vsync_thold); if ((tc.pre_coast != cm.cc.pre_coast) || (tc.post_coast != cm.cc.post_coast)) tvp_set_hpllcoast(tc.pre_coast, tc.post_coast); if (tc.ypbpr_cs != cm.cc.ypbpr_cs) set_csc(tc.ypbpr_cs); if (tc.video_lpf != cm.cc.video_lpf) set_lpf(tc.video_lpf); if (tc.sync_lpf != cm.cc.sync_lpf) tvp_set_sync_lpf(tc.sync_lpf); if (tc.stc_lpf != cm.cc.stc_lpf) tvp_set_clp_lpf(tc.stc_lpf); if ((tc.alc_h_filter != cm.cc.alc_h_filter) || (tc.alc_v_filter != cm.cc.alc_v_filter)) tvp_set_alcfilt(tc.alc_v_filter, tc.alc_h_filter); if (memcmp(&tc.col, &cm.cc.col, sizeof(color_setup_t))) tvp_set_gain_offset(&tc.col); #ifdef ENABLE_AUDIO if ((tc.audio_dw_sampl != cm.cc.audio_dw_sampl) || #ifdef MANUAL_CTS update_cur_vm || #endif (tc.audio_swap_lr != cm.cc.audio_swap_lr)) SetupAudio(tc.tx_mode); if (pcm1862_active && (tc.audio_gain != cm.cc.audio_gain)) pcm_set_gain(tc.audio_gain-AUDIO_GAIN_0DB); if (pcm1862_active && (tc.audio_mono != cm.cc.audio_mono)) { DisableAudioOutput(); pcm_set_stereo_mode(tc.audio_mono); SetupAudio(cm.cc.tx_mode); } #endif cm.cc = tc; update_cur_vm = 0; return status; } void update_sc_config(mode_data_t *vm_in, mode_data_t *vm_out, vm_proc_config_t *vm_conf, avconfig_t *avconfig) { int i; hv_config_reg hv_in_config = {.data=0x00000000}; hv_config2_reg hv_in_config2 = {.data=0x00000000}; hv_config3_reg hv_in_config3 = {.data=0x00000000}; hv_config_reg hv_out_config = {.data=0x00000000}; hv_config2_reg hv_out_config2 = {.data=0x00000000}; hv_config3_reg hv_out_config3 = {.data=0x00000000}; xy_config_reg xy_out_config = {.data=0x00000000}; xy_config2_reg xy_out_config2 = {.data=0x00000000}; misc_config_reg misc_config = {.data=0x00000000}; sl_config_reg sl_config = {.data=0x00000000}; sl_config2_reg sl_config2 = {.data=0x00000000}; sl_config3_reg sl_config3 = {.data=0x00000000}; // Set input params hv_in_config.h_total = vm_in->timings.h_total; hv_in_config.h_active = vm_in->timings.h_active; hv_in_config.h_synclen = vm_in->timings.h_synclen; hv_in_config2.h_backporch = vm_in->timings.h_backporch; hv_in_config2.v_active = vm_in->timings.v_active; hv_in_config3.v_synclen = vm_in->timings.v_synclen; hv_in_config3.v_backporch = vm_in->timings.v_backporch; hv_in_config2.interlaced = vm_in->timings.interlaced; hv_in_config3.v_startline = vm_in->timings.v_synclen+vm_in->timings.v_backporch+12; hv_in_config3.h_skip = vm_conf->h_skip; hv_in_config3.h_sample_sel = vm_conf->h_sample_sel; // Set output params hv_out_config.h_total = vm_out->timings.h_total; hv_out_config.h_active = vm_out->timings.h_active; hv_out_config.h_synclen = vm_out->timings.h_synclen; hv_out_config2.h_backporch = vm_out->timings.h_backporch; hv_out_config2.v_total = vm_out->timings.v_total; hv_out_config2.v_active = vm_out->timings.v_active; hv_out_config3.v_synclen = vm_out->timings.v_synclen; hv_out_config3.v_backporch = vm_out->timings.v_backporch; hv_out_config2.interlaced = vm_out->timings.interlaced; hv_out_config3.v_startline = vm_conf->framesync_line; xy_out_config.x_size = vm_conf->x_size; xy_out_config.y_size = vm_conf->y_size; xy_out_config.y_offset = vm_conf->y_offset; xy_out_config2.x_offset = vm_conf->x_offset; xy_out_config2.x_start_lb = vm_conf->x_start_lb; xy_out_config2.y_start_lb = vm_conf->y_start_lb; xy_out_config2.x_rpt = vm_conf->x_rpt; xy_out_config2.y_rpt = vm_conf->y_rpt; misc_config.mask_br = avconfig->mask_br; misc_config.mask_color = avconfig->mask_color; misc_config.reverse_lpf = avconfig->reverse_lpf; misc_config.shmask_mode = avconfig->shmask_mode; /*misc_config.lm_deint_mode = 0; misc_config.nir_even_offset = 0; misc_config.ypbpr_cs = (avconfig->ypbpr_cs == 0) ? ((vm_in->type & VIDEO_HDTV) ? 1 : 0) : avconfig->ypbpr_cs-1; misc_config.vip_enable = 0; misc_config.bfi_enable = 0; misc_config.bfi_str = 0;*/ // set default/custom scanline interval sl_def_iv_y = (vm_conf->y_rpt > 0) ? vm_conf->y_rpt : 1; sl_def_iv_x = (vm_conf->x_rpt > 0) ? vm_conf->x_rpt : sl_def_iv_y; sl_config3.sl_iv_x = ((avconfig->sl_type == 3) && (avconfig->sl_cust_iv_x)) ? avconfig->sl_cust_iv_x : sl_def_iv_x; sl_config3.sl_iv_y = ((avconfig->sl_type == 3) && (avconfig->sl_cust_iv_y)) ? avconfig->sl_cust_iv_y : sl_def_iv_y; // construct custom/default scanline overlay for (i=0; i<6; i++) { if (avconfig->sl_type == 3) { sl_config.sl_l_str_arr |= ((avconfig->sl_cust_l_str[i]-1)&0xf)<<(4*i); sl_config.sl_l_overlay |= (avconfig->sl_cust_l_str[i]!=0)<sl_str<<(4*i); if ((i==5) && ((avconfig->sl_type == 0) || (avconfig->sl_type == 2))) { sl_config.sl_l_overlay = (1<<((sl_config3.sl_iv_y+1)/2))-1; if (avconfig->sl_id) sl_config.sl_l_overlay <<= (sl_config3.sl_iv_y+2)/2; } } } for (i=0; i<10; i++) { if (avconfig->sl_type == 3) { if (i<8) sl_config2.sl_c_str_arr_l |= ((avconfig->sl_cust_c_str[i]-1)&0xf)<<(4*i); else sl_config3.sl_c_str_arr_h |= ((avconfig->sl_cust_c_str[i]-1)&0xf)<<(4*(i-8)); sl_config3.sl_c_overlay |= (avconfig->sl_cust_c_str[i]!=0)<sl_str<<(4*i); else sl_config3.sl_c_str_arr_h |= avconfig->sl_str<<(4*(i-8)); if ((i==9) && ((avconfig->sl_type == 1) || (avconfig->sl_type == 2))) sl_config3.sl_c_overlay = (1<<((sl_config3.sl_iv_x+1)/2))-1; } } sl_config.sl_method = avconfig->sl_method; sl_config.sl_altern = avconfig->sl_altern; sl_config3.sl_hybr_str = avconfig->sl_hybr_str; // disable scanlines if configured so if (((avconfig->sl_mode == 1) && (!vm_conf->y_rpt)) || (avconfig->sl_mode == 0)) { sl_config.sl_l_overlay = 0; sl_config3.sl_c_overlay = 0; } sc->hv_in_config = hv_in_config; sc->hv_in_config2 = hv_in_config2; sc->hv_in_config3 = hv_in_config3; sc->hv_out_config = hv_out_config; sc->hv_out_config2 = hv_out_config2; sc->hv_out_config3 = hv_out_config3; sc->xy_out_config = xy_out_config; sc->xy_out_config2 = xy_out_config2; sc->misc_config = misc_config; sc->sl_config = sl_config; sc->sl_config2 = sl_config2; sc->sl_config3 = sl_config3; } // Configure TVP7002 and scan converter logic based on the video mode void program_mode() { int retval; alt_u8 h_syncinlen, v_syncinlen, macrovis, hdmitx_pclk_level, osd_x_size, osd_y_size; alt_u32 h_hz, h_synclen_px, pclk_i_hz, dotclk_hz, pll_h_total; memset(&vmode_in, 0, sizeof(mode_data_t)); vmode_in.timings.v_hz_x100 = (100*27000000UL)/cm.pcnt_frame; h_hz = (100*27000000UL)/((100*cm.pcnt_frame*(1+!cm.progressive))/cm.totlines); printf("\nLines: %u %c\n", (unsigned)cm.totlines, cm.progressive ? 'p' : 'i'); printf("Clocks per line: %u\n", (unsigned)cm.clkcnt); //h_syncinlen = tvp_readreg(TVP_HSINWIDTH); h_syncinlen = cm.hsync_width; #ifdef DEBUG v_syncinlen = tvp_readreg(TVP_VSINWIDTH); macrovis = !!(tvp_readreg(TVP_LINECNT2) & (1<<6)); printf("Hswidth: %u Vswidth: %u Macrovision: %u\n", (unsigned)h_syncinlen, (unsigned)(v_syncinlen & 0x1F), (unsigned)macrovis); #endif vmode_in.timings.h_synclen = h_syncinlen; vmode_in.timings.v_total = cm.totlines; vmode_in.timings.interlaced = !cm.progressive; sniprintf(row1, LCD_ROW_LEN+1, "%s %u-%c", avinput_str[cm.avinput], (unsigned)cm.totlines, cm.progressive ? 'p' : 'i'); sniprintf(row2, LCD_ROW_LEN+1, "%u.%.2ukHz %u.%.2uHz", (unsigned)(h_hz/1000), (unsigned)((h_hz%1000)/10), (unsigned)(vmode_in.timings.v_hz_x100/100), (unsigned)(vmode_in.timings.v_hz_x100%100)); ui_disp_status(1); retval = get_pure_lm_mode(&cm.cc, &vmode_in, &vmode_out, &vm_conf); if (retval == -1) { printf ("ERROR: no suitable mode preset found\n"); vm_conf.si_pclk_mult = 0; return; } cm.id = retval; vm_sel = cm.id; pll_h_total = (vm_conf.h_skip+1) * vmode_in.timings.h_total + (((vm_conf.h_skip+1) * vmode_in.timings.h_total_adj * 5 + 50) / 100); // Double TVP7002 PLL sampling rate when possible to minimize jitter while (1) { pclk_i_hz = h_hz * pll_h_total; if ((pclk_i_hz < 25000000UL) && ((vm_conf.si_pclk_mult % 2) == 0)) { pll_h_total *= 2; vm_conf.h_skip = 2*(vm_conf.h_skip+1)-1; vm_conf.si_pclk_mult /= 2; } else { break; } } // Tweak infoframe pixel repetition indicator if passing thru horizontally multiplied mode if ((vm_conf.y_rpt == 0) && (vm_conf.h_skip > 0)) vm_conf.hdmitx_pixr_ifr = vm_conf.h_skip; dotclk_hz = estimate_dotclk(&vmode_in, h_hz); cm.pclk_o_hz = calculate_pclk(pclk_i_hz, &vmode_out, &vm_conf); printf("H: %lu.%.2lukHz V: %u.%.2uHz\n", (h_hz+5)/1000, ((h_hz+5)%1000)/10, (vmode_in.timings.v_hz_x100/100), (vmode_in.timings.v_hz_x100%100)); printf("Estimated source dot clock: %lu.%.2luMHz\n", (dotclk_hz+5000)/1000000, ((dotclk_hz+5000)%1000000)/10000); printf("PCLK_IN: %luHz PCLK_OUT: %luHz\n", pclk_i_hz, cm.pclk_o_hz); // Trilevel sync is used with HDTV modes using composite sync // CEA-770.3 HDTV modes use tri-level syncs which have twice the width of bi-level syncs of corresponding CEA-861 modes if (video_modes_plm[cm.id].type & VIDEO_HDTV) { target_type = (target_tvp_sync <= TVP_SOG3) ? VIDEO_HDTV : VIDEO_PC; if (target_type == VIDEO_HDTV) vmode_in.timings.h_synclen *= 2; } else { target_type = video_modes_plm[cm.id].type; } h_synclen_px = ((alt_u32)h_syncinlen * pll_h_total) / cm.clkcnt; printf("Mode %s selected - hsync width: %upx\n", video_modes_plm[cm.id].name, (unsigned)h_synclen_px); tvp_source_setup(target_type, pll_h_total, cm.clkcnt, 0, (alt_u8)h_synclen_px, (alt_8)(cm.cc.clamp_offset-SIGNED_NUMVAL_ZERO)); set_lpf(cm.cc.video_lpf); set_csc(cm.cc.ypbpr_cs); set_sampler_phase(video_modes_plm[cm.id].sampler_phase, 0); pll_reconfig->pll_config_status.reset = (vm_conf.si_pclk_mult <= 1); if (vm_conf.si_pclk_mult > 1) { if ((vm_conf.si_pclk_mult == 2) && (pclk_i_hz > 50000000UL)) pll_reconfigure(6); else pll_reconfigure(vm_conf.si_pclk_mult-1); sys_ctrl &= ~PLL_BYPASS; } else { sys_ctrl |= PLL_BYPASS; } IOWR_ALTERA_AVALON_PIO_DATA(PIO_0_BASE, sys_ctrl); update_osd_size(&vmode_out); update_sc_config(&vmode_in, &vmode_out, &vm_conf, &cm.cc); TX_SetPixelRepetition(vm_conf.tx_pixelrep, ((cm.cc.tx_mode!=TX_DVI) && (vm_conf.tx_pixelrep == vm_conf.hdmitx_pixr_ifr)) ? 1 : 0); if (cm.pclk_o_hz > 85000000) hdmitx_pclk_level = 1; else if (cm.pclk_o_hz < 75000000) hdmitx_pclk_level = 0; else hdmitx_pclk_level = cm.hdmitx_pclk_level; printf("PCLK level: %u, PR: %u, IPR: %u, ITC: %u\n", hdmitx_pclk_level, vm_conf.tx_pixelrep, vm_conf.hdmitx_pixr_ifr, cm.cc.hdmi_itc); // Full TX initialization increases mode switch delay, use only when necessary if (cm.cc.full_tx_setup || (cm.hdmitx_pclk_level != hdmitx_pclk_level)) { cm.hdmitx_pclk_level = hdmitx_pclk_level; TX_enable(cm.cc.tx_mode); } else if (cm.cc.tx_mode!=TX_DVI) { HDMITX_SetAVIInfoFrame(vmode_out.vic, (cm.cc.tx_mode == TX_HDMI_RGB) ? F_MODE_RGB444 : F_MODE_YUV444, 0, 0, cm.cc.hdmi_itc, vm_conf.hdmitx_pixr_ifr); #ifdef ENABLE_AUDIO #ifdef MANUAL_CTS SetupAudio(cm.cc.tx_mode); #endif #endif } } void set_sampler_phase(uint8_t sampler_phase, uint8_t update_sc) { uint32_t sample_rng_x1000; uint8_t tvp_phase; vmode_in.sampler_phase = sampler_phase; if (vm_conf.h_skip == 0) { vm_conf.h_sample_sel = 0; tvp_phase = sampler_phase; } else { sample_rng_x1000 = 360000 / (vm_conf.h_skip+1); vm_conf.h_sample_sel = (sampler_phase*11250)/sample_rng_x1000; tvp_phase = ((((sampler_phase*11250) % sample_rng_x1000)*32)/sample_rng_x1000); } if (vm_conf.h_skip > 0) printf("Sample sel: %u/%u\n", (vm_conf.h_sample_sel+1), (vm_conf.h_skip+1)); tvp_set_hpll_phase(tvp_phase); if (update_sc) update_sc_config(&vmode_in, &vmode_out, &vm_conf, &cm.cc); } int load_profile() { int retval; retval = read_userdata(profile_sel_menu, 0); if (retval == 0) { profile_sel = profile_sel_menu; // Change the input if the new profile demands it. if (tc.link_av != AV_LAST) target_input = tc.link_av; // Update profile link (also prevents the change of input from inducing a profile load). input_profiles[profile_link ? target_input : AV_TESTPAT] = profile_sel; write_userdata(INIT_CONFIG_SLOT); } return retval; } int save_profile() { int retval; retval = write_userdata(profile_sel_menu); if (retval == 0) { profile_sel = profile_sel_menu; input_profiles[profile_link ? cm.avinput : AV_TESTPAT] = profile_sel; write_userdata(INIT_CONFIG_SLOT); } return retval; } // Initialize hardware int init_hw() { alt_u32 chiprev; // Reset hardware IOWR_ALTERA_AVALON_PIO_DATA(PIO_0_BASE, AV_RESET_N|LCD_BL); IOWR_ALTERA_AVALON_PIO_DATA(PIO_0_BASE, 0x0000); sc->hv_in_config.data = 0x00000000; sc->hv_in_config2.data = 0x00000000; sc->hv_in_config3.data = 0x00000000; usleep(10000); // unreset hw sys_ctrl = AV_RESET_N|LCD_BL|SD_SPI_SS_N|LCD_CS_N|REMOTE_EVENT; IOWR_ALTERA_AVALON_PIO_DATA(PIO_0_BASE, sys_ctrl); // Reload initial PLL config (needed after jtagm_reset_req if config has changed). // Note that test pattern gets restored only if pclk was active before jtagm_reset_req assertion. pll_reconfigure(PLL_CONFIG_VG); //wait >500ms for SD card interface to be stable //over 200ms and LCD may be buggy? usleep(200000); // IT6613 officially supports only 100kHz, but 400kHz seems to work I2C_init(I2CA_BASE,ALT_CPU_FREQ,400000); //I2C_init(I2C_OPENCORES_1_BASE,ALT_CPU_FREQ,400000); /* Initialize the character display */ lcd_init(); if (!ths_init()) { printf("Error: could not read from THS7353\n"); return -2; } /* check if TVP is found */ chiprev = tvp_readreg(TVP_CHIPREV); //printf("chiprev %d\n", chiprev); if (chiprev == 0xff) { printf("Error: could not read from TVP7002\n"); return -3; } tvp_init(); chiprev = HDMITX_ReadI2C_Byte(IT_DEVICEID); if (chiprev != 0x13) { printf("Error: could not read from IT6613\n"); return -4; } InitIT6613(); #ifdef ENABLE_AUDIO if (pcm1862_init()) { printf("PCM1862 found\n"); pcm1862_active = 1; } #endif if (init_flash() != 0) { printf("Error: could not find flash\n"); return -1; } // Set defaults set_default_avconfig(); memcpy(&cm.cc, &tc_default, sizeof(avconfig_t)); memcpy(rc_keymap, rc_keymap_default, sizeof(rc_keymap)); // Init menu init_menu(); // Load initconfig and profile read_userdata(INIT_CONFIG_SLOT, 0); read_userdata(profile_sel, 0); // Setup test pattern get_vmode(VMODE_480p, &vmode_in, &vmode_out, &vm_conf); update_sc_config(&vmode_in, &vmode_out, &vm_conf, &cm.cc); // init always in HDMI mode (fixes yellow screen bug) TX_enable(TX_HDMI_RGB); // Setup remote keymap if (!(IORD_ALTERA_AVALON_PIO_DATA(PIO_1_BASE) & PB1_BIT)) setup_rc(); return 0; } void print_vm_stats() { int row = 0; if (!menu_active) { memset((void*)osd->osd_array.data, 0, sizeof(osd_char_array)); read_userdata(profile_sel, 1); sniprintf((char*)osd->osd_array.data[row][0], OSD_CHAR_COLS, "Mode preset:"); sniprintf((char*)osd->osd_array.data[row][1], OSD_CHAR_COLS, "%s", vmode_out.name); sniprintf((char*)osd->osd_array.data[++row][0], OSD_CHAR_COLS, "Refresh rate:"); sniprintf((char*)osd->osd_array.data[row][1], OSD_CHAR_COLS, "%u.%.2uHz", vmode_out.timings.v_hz_x100/100, vmode_out.timings.v_hz_x100%100); sniprintf((char*)osd->osd_array.data[++row][0], OSD_CHAR_COLS, "H/V synclen:"); sniprintf((char*)osd->osd_array.data[row][1], OSD_CHAR_COLS, "%-5u %-5u", vmode_out.timings.h_synclen, vmode_out.timings.v_synclen); sniprintf((char*)osd->osd_array.data[++row][0], OSD_CHAR_COLS, "H/V backporch:"); sniprintf((char*)osd->osd_array.data[row][1], OSD_CHAR_COLS, "%-5u %-5u", vmode_out.timings.h_backporch, vmode_out.timings.v_backporch); sniprintf((char*)osd->osd_array.data[++row][0], OSD_CHAR_COLS, "H/V active:"); sniprintf((char*)osd->osd_array.data[row][1], OSD_CHAR_COLS, "%-5u %-5u", vmode_out.timings.h_active, vmode_out.timings.v_active); sniprintf((char*)osd->osd_array.data[++row][0], OSD_CHAR_COLS, "H/V total:"); sniprintf((char*)osd->osd_array.data[row][1], OSD_CHAR_COLS, "%-5u %-5u", vmode_out.timings.h_total, vmode_out.timings.v_total); row++; sniprintf((char*)osd->osd_array.data[++row][0], OSD_CHAR_COLS, "Profile:"); sniprintf((char*)osd->osd_array.data[row][1], OSD_CHAR_COLS, "%u: %s", profile_sel, (target_profile_name[0] == 0) ? "" : target_profile_name); sniprintf((char*)osd->osd_array.data[++row][0], OSD_CHAR_COLS, "FW:"); sniprintf((char*)osd->osd_array.data[row][1], OSD_CHAR_COLS, "%u.%.2u" FW_SUFFIX1 FW_SUFFIX2 " @ " __DATE__, FW_VER_MAJOR, FW_VER_MINOR); osd->osd_config.status_refresh = 1; osd->osd_row_color.mask = 0; osd->osd_sec_enable[0].mask = (1<<(row+1))-1; osd->osd_sec_enable[1].mask = (1<<(row+1))-1; } } int latency_test() { lt_status_reg lt_status; alt_u16 latency_ms_x100, stb_ms_x100; alt_u32 btn_vec, btn_vec_prev=1; alt_u8 position = lt_sel+1; sys_ctrl |= LT_ACTIVE|(position<lt_status.lt_finished) {} //Hangs if sync is lost SPI_Timer_On(1000); while ((SPI_Timer_Status()==TRUE)) { lt_status = sc->lt_status; if (lt_status.lt_finished) break; } SPI_Timer_Off(); latency_ms_x100 = lt_status.lt_lat_result; stb_ms_x100 = lt_status.lt_stb_result; if (latency_ms_x100 == 0) sniprintf(menu_row2, LCD_ROW_LEN+1, "False trigger"); else if (latency_ms_x100 == 0xffff) sniprintf(menu_row2, LCD_ROW_LEN+1, "Timeout"); else if (stb_ms_x100 == 0xfff) sniprintf(menu_row2, LCD_ROW_LEN+1, "%u.%.2ums", latency_ms_x100/100, latency_ms_x100%100); else sniprintf(menu_row2, LCD_ROW_LEN+1, "%u.%.2ums/%u.%.2ums", latency_ms_x100/100, latency_ms_x100%100, stb_ms_x100/100, stb_ms_x100%100); ui_disp_menu(0); } else if (btn_vec == rc_keymap[RC_BACK]) { break; } sys_ctrl &= ~LT_ARMED; IOWR_ALTERA_AVALON_PIO_DATA(PIO_0_BASE, sys_ctrl); } btn_vec_prev = btn_vec; usleep(WAITLOOP_SLEEP_US); } sys_ctrl &= ~LT_CTRL_MASK; IOWR_ALTERA_AVALON_PIO_DATA(PIO_0_BASE, sys_ctrl); return 0; } int main() { ths_input_t target_ths = 0; pcm_input_t target_pcm = 0; video_format target_format = 0; status_t status; alt_u32 input_vec; alt_timestamp_type start_ts; alt_timestamp_type auto_input_timestamp = 0; alt_u8 auto_input_changed = 0; alt_u8 auto_input_ctr = 0; alt_u8 auto_input_current_ctr = AUTO_CURRENT_MAX_COUNT; alt_u8 auto_input_keep_current = 0; int init_stat, man_input_change; // Start system timer alt_timestamp_start(); init_stat = init_hw(); if (init_stat >= 0) { printf("### DIY VIDEO DIGITIZER / SCANCONVERTER INIT OK ###\n\n"); sniprintf(row1, LCD_ROW_LEN+1, "OSSC fw. %u.%.2u" FW_SUFFIX1 FW_SUFFIX2, FW_VER_MAJOR, FW_VER_MINOR); #ifndef DEBUG strncpy(row2, "2014-2023 marqs", LCD_ROW_LEN+1); #else strncpy(row2, "** DEBUG BUILD *", LCD_ROW_LEN+1); #endif ui_disp_status(1); usleep(500000); } else { sniprintf(row1, LCD_ROW_LEN+1, "Init error %d", init_stat); strncpy(row2, "", LCD_ROW_LEN+1); ui_disp_status(1); while (1) {} } // Mainloop while(1) { start_ts = alt_timestamp(); // Read remote control and PCB button status input_vec = IORD_ALTERA_AVALON_PIO_DATA(PIO_1_BASE); remote_code = input_vec & RC_MASK; btn_code = ~input_vec & PB_MASK; remote_rpt = input_vec >> 24; if ((remote_rpt == 0) || ((remote_rpt > 1) && (remote_rpt < 6)) || (remote_rpt == remote_rpt_prev)) remote_code = 0; remote_rpt_prev = remote_rpt; if (btn_code_prev == 0) { btn_code_prev = btn_code; } else { btn_code_prev = btn_code; btn_code = 0; } // Auto input switching if ((auto_input != AUTO_OFF) && (cm.avinput != AV_TESTPAT) && !cm.sync_active && !menu_active && (alt_timestamp() >= auto_input_timestamp + 300 * (alt_timestamp_freq() >> 10)) && (auto_input_ctr < AUTO_MAX_COUNT)) { // Keep switching on the same physical input when set to Current input or a short time after losing sync. auto_input_keep_current = (auto_input == AUTO_CURRENT_INPUT || auto_input_current_ctr < AUTO_CURRENT_MAX_COUNT); switch(cm.avinput) { case AV1_RGBs: target_input = auto_av1_ypbpr ? AV1_YPBPR : AV1_RGsB; break; case AV1_RGsB: case AV1_YPBPR: target_input = auto_input_keep_current ? AV1_RGBs : (auto_av2_ypbpr ? AV2_YPBPR : AV2_RGsB); break; case AV2_YPBPR: case AV2_RGsB: target_input = auto_input_keep_current ? target_input : AV3_RGBHV; break; case AV3_RGBHV: target_input = AV3_RGBs; break; case AV3_RGBs: target_input = auto_av3_ypbpr ? AV3_YPBPR : AV3_RGsB; break; case AV3_RGsB: case AV3_YPBPR: target_input = auto_input_keep_current ? AV3_RGBHV : AV1_RGBs; break; default: break; } auto_input_ctr++; if (auto_input_current_ctr < AUTO_CURRENT_MAX_COUNT) auto_input_current_ctr++; // For input linked profile loading below auto_input_changed = 1; // set auto_input_timestamp auto_input_timestamp = alt_timestamp(); } man_input_change = parse_control(); if (menu_active) display_menu(0); // Only auto load profile when input is manually changed or when sync is active after automatic switch. if ((target_input != cm.avinput && man_input_change) || (auto_input_changed && cm.sync_active)) { // The input changed, so load the appropriate profile if // input->profile link is enabled if (profile_link && (profile_sel != input_profiles[target_input])) { profile_sel = input_profiles[target_input]; read_userdata(profile_sel, 0); } auto_input_changed = 0; } if ((target_input != cm.avinput) || ((target_tvp_sync >= TVP_HV_A) && ((tc.av3_alt_rgb != cm.cc.av3_alt_rgb)))) { target_tvp = TVP_INPUT1; target_tvp_sync = TVP_SOG1; if ((target_input <= AV1_YPBPR) || (tc.av3_alt_rgb==1 && ((target_input == AV3_RGBHV) || (target_input == AV3_RGBs)))) { target_ths = THS_INPUT_B; target_pcm = PCM_INPUT4; } else if ((target_input <= AV2_RGsB) || (tc.av3_alt_rgb==2 && ((target_input == AV3_RGBHV) || (target_input == AV3_RGBs)))) { target_ths = THS_INPUT_A; target_pcm = PCM_INPUT3; } else { // if (target_input <= AV3_YPBPR) { target_tvp = TVP_INPUT3; target_ths = THS_STANDBY; target_pcm = PCM_INPUT2; } switch (target_input) { case AV1_RGBs: case AV3_RGBs: target_format = FORMAT_RGBS; break; case AV1_RGsB: case AV2_RGsB: case AV3_RGsB: target_format = FORMAT_RGsB; break; case AV1_YPBPR: case AV2_YPBPR: case AV3_YPBPR: target_format = FORMAT_YPbPr; break; case AV3_RGBHV: target_format = FORMAT_RGBHV; break; default: break; } switch (target_input) { case AV1_RGBs: target_tvp_sync = TVP_SOG2; break; case AV3_RGBHV: target_tvp_sync = TVP_HV_A; break; case AV3_RGBs: target_tvp_sync = TVP_CS_A; break; case AV3_RGsB: case AV3_YPBPR: target_tvp_sync = TVP_SOG3; break; default: break; } printf("### SWITCH MODE TO %s ###\n", avinput_str[target_input]); cm.avinput = target_input; cm.sync_active = 0; ths_source_sel(target_ths, (cm.cc.video_lpf > 1) ? (VIDEO_LPF_MAX-cm.cc.video_lpf) : THS_LPF_BYPASS); tvp_powerdown(); #ifdef ENABLE_AUDIO DisableAudioOutput(); if (pcm1862_active) pcm_source_sel(target_pcm); #endif tvp_source_sel(target_tvp, target_tvp_sync, target_format); cm.clkcnt = 0; //TODO: proper invalidate sys_ctrl &= ~VSYNC_I_TYPE; if (target_format == FORMAT_RGBHV) sys_ctrl |= VSYNC_I_TYPE; IOWR_ALTERA_AVALON_PIO_DATA(PIO_0_BASE, sys_ctrl); strncpy(row1, avinput_str[cm.avinput], LCD_ROW_LEN+1); strncpy(row2, " NO SYNC", LCD_ROW_LEN+1); ui_disp_status(1); if (man_input_change) { // record last input if it was selected manually if (def_input == AV_LAST) write_userdata(INIT_CONFIG_SLOT); // Set auto_input_timestamp when input is manually changed auto_input_ctr = 0; auto_input_timestamp = alt_timestamp(); } } // Check here to enable regardless of input if (tc.tx_mode != cm.cc.tx_mode) { HDMITX_SetAVIInfoFrame(vmode_out.vic, F_MODE_RGB444, 0, 0, 0, 0); TX_enable(tc.tx_mode); cm.cc.tx_mode = tc.tx_mode; cm.clkcnt = 0; //TODO: proper invalidate } if (tc.tx_mode != TX_DVI) { if (tc.hdmi_itc != cm.cc.hdmi_itc) { //EnableAVIInfoFrame(FALSE, NULL); printf("setting ITC to %d\n", tc.hdmi_itc); HDMITX_SetAVIInfoFrame(vmode_out.vic, (tc.tx_mode == TX_HDMI_RGB) ? F_MODE_RGB444 : F_MODE_YUV444, 0, 0, tc.hdmi_itc, vm_conf.hdmitx_pixr_ifr); cm.cc.hdmi_itc = tc.hdmi_itc; } if (tc.hdmi_hdr != cm.cc.hdmi_hdr) { printf("setting HDR flag to %d\n", tc.hdmi_hdr); HDMITX_SetHDRInfoFrame(tc.hdmi_hdr ? 3 : 0); cm.cc.hdmi_hdr = tc.hdmi_hdr; } } if (tc.av3_alt_rgb != cm.cc.av3_alt_rgb) { printf("Changing AV3 RGB source\n"); cm.cc.av3_alt_rgb = tc.av3_alt_rgb; } if ((!!osd_enable != osd->osd_config.enable) || (osd_status_timeout != osd->osd_config.status_timeout)) { osd->osd_config.enable = !!osd_enable; osd->osd_config.status_timeout = osd_status_timeout; if (menu_active) { remote_code = 0; render_osd_page(); display_menu(1); } } if (cm.avinput != AV_TESTPAT) { status = get_status(target_tvp_sync); switch (status) { case ACTIVITY_CHANGE: if (cm.sync_active) { printf("Sync up\n"); sys_ctrl |= VIDGEN_OFF; IOWR_ALTERA_AVALON_PIO_DATA(PIO_0_BASE, sys_ctrl); tvp_powerup(); program_mode(); #ifdef ENABLE_AUDIO SetupAudio(cm.cc.tx_mode); #endif } else { printf("Sync lost\n"); cm.clkcnt = 0; //TODO: proper invalidate tvp_powerdown(); //ths_source_sel(THS_STANDBY, 0); strncpy(row1, avinput_str[cm.avinput], LCD_ROW_LEN+1); strncpy(row2, " NO SYNC", LCD_ROW_LEN+1); ui_disp_status(1); // Set auto_input_timestamp auto_input_timestamp = alt_timestamp(); auto_input_ctr = 0; auto_input_current_ctr = 0; } break; case MODE_CHANGE: if (cm.sync_active) { printf("Mode change\n"); program_mode(); } break; case SC_CONFIG_CHANGE: if (cm.sync_active) { printf("Scanconverter config change\n"); update_sc_config(&vmode_in, &vmode_out, &vm_conf, &cm.cc); } break; default: break; } } while (alt_timestamp() < start_ts + MAINLOOP_INTERVAL_US*(TIMER_0_FREQ/1000000)) {} // restart timer if past half-range if (start_ts > 0x7fffffff) { alt_timestamp_start(); if (auto_input_timestamp > start_ts) auto_input_timestamp -= start_ts; else auto_input_timestamp = 0; //printf("Timer restart\n"); } } return 0; }