/* * * $Id: pvrusb2-encoder.c,v 1.4 2006/01/01 08:26:03 mcisely Exp $ * * Copyright (C) 2005 Mike Isely * Copyright (C) 2004 Aurelien Alleaume * * 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 * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include // for linux/firmware.h #include #include "pvrusb2-util.h" #include "pvrusb2-encoder.h" #include "pvrusb2-hdw-internal.h" #include "pvrusb2-debug.h" static u32 pvr_tbl_emphasis [] = { [PVR2_CVAL_AUDIOEMPHASIS_NONE] = 0x0 << 12, [PVR2_CVAL_AUDIOEMPHASIS_50_15] = 0x1 << 12, [PVR2_CVAL_AUDIOEMPHASIS_CCITT] = 0x3 << 12, }; static u32 pvr_tbl_srate[] = { [PVR2_CVAL_SRATE_48] = 0x01, [PVR2_CVAL_SRATE_44_1] = 0x00, }; static u32 pvr_tbl_audiobitrate[] = { [PVR2_CVAL_AUDIOBITRATE_384] = 0xe << 4, [PVR2_CVAL_AUDIOBITRATE_320] = 0xd << 4, [PVR2_CVAL_AUDIOBITRATE_256] = 0xc << 4, [PVR2_CVAL_AUDIOBITRATE_224] = 0xb << 4, [PVR2_CVAL_AUDIOBITRATE_192] = 0xa << 4, [PVR2_CVAL_AUDIOBITRATE_160] = 0x9 << 4, [PVR2_CVAL_AUDIOBITRATE_128] = 0x8 << 4, [PVR2_CVAL_AUDIOBITRATE_112] = 0x7 << 4, [PVR2_CVAL_AUDIOBITRATE_96] = 0x6 << 4, [PVR2_CVAL_AUDIOBITRATE_80] = 0x5 << 4, [PVR2_CVAL_AUDIOBITRATE_64] = 0x4 << 4, [PVR2_CVAL_AUDIOBITRATE_56] = 0x3 << 4, [PVR2_CVAL_AUDIOBITRATE_48] = 0x2 << 4, [PVR2_CVAL_AUDIOBITRATE_32] = 0x1 << 4, [PVR2_CVAL_AUDIOBITRATE_VBR] = 0x0 << 4, }; /* Firmware API commands - definitions found from ivtv */ #ifdef notdef #define IVTV_API_DEC_PING_FW 0x00 #define IVTV_API_DEC_START_PLAYBACK 0x01 #define IVTV_API_DEC_STOP_PLAYBACK 0x02 #define IVTV_API_DEC_PLAYBACK_SPEED 0x03 #define IVTV_API_DEC_STEP_VIDEO 0x05 #define IVTV_API_DEC_DMA_BLOCKSIZE 0x08 #define IVTV_API_DEC_XFER_INFO 0x09 #define IVTV_API_DEC_DMA_STATUS 0x0a #define IVTV_API_DEC_DMA_FROM_HOST 0x0b #define IVTV_API_DEC_PAUSE_PLAYBACK 0x0d #define IVTV_API_DEC_HALT_FW 0x0f #define IVTV_API_DEC_DISP_STANDARD 0x10 #define IVTV_API_DEC_GETVER 0x11 #define IVTV_API_DEC_STREAM_INPUT 0x14 #define IVTV_API_DEC_TIMING_INFO 0x15 #define IVTV_API_DEC_SELECT_AUDIO 0x16 #define IVTV_API_DEC_EVENT_NOTIFICATION 0x17 #define IVTV_API_DEC_DISPLAY_BUFFERS 0x18 #define IVTV_API_DEC_EXTRACT_VBI 0x19 #define IVTV_API_DEC_DECODE_SOURCE 0x1a #define IVTV_API_DEC_AUDIO_OUTPUT 0x1b #define IVTV_API_DEC_SET_AV_DELAY 0x1c #define IVTV_API_DEC_BUFFER 0x1e #define IVTV_API_FB_GET_FRAMEBUFFER 0x41 #define IVTV_API_FB_GET_PIXEL_FORMAT 0x42 #define IVTV_API_FB_SET_PIXEL_FORMAT 0x43 #define IVTV_API_FB_GET_STATE 0x44 #define IVTV_API_FB_SET_STATE 0x45 #define IVTV_API_FB_GET_OSD_COORDS 0x46 #define IVTV_API_FB_SET_OSD_COORDS 0x47 #define IVTV_API_FB_GET_SCREEN_COORDS 0x48 #define IVTV_API_FB_SET_SCREEN_COORDS 0x49 #define IVTV_API_FB_GET_GLOBAL_ALPHA 0x4a #define IVTV_API_FB_SET_GLOBAL_ALPHA 0x4b #define IVTV_API_FB_SET_BLEND_COORDS 0x4c #define IVTV_API_FB_GET_FLICKER_STATE 0x4f #define IVTV_API_FB_SET_FLICKER_STATE 0x50 #define IVTV_API_FB_BLT_COPY 0x52 #define IVTV_API_FB_BLT_FILL 0x53 #define IVTV_API_FB_BLT_TEXT 0x54 #define IVTV_API_FB_SET_FRAMEBUFFER_WINDOW 0x56 #define IVTV_API_FB_SET_CHROMA_KEY 0x60 #define IVTV_API_FB_GET_ALPHA_CONTENT_INDEX 0x61 #define IVTV_API_FB_SET_ALPHA_CONTENT_INDEX 0x62 #endif #define IVTV_API_ENC_PING_FW 0x80 #define IVTV_API_BEGIN_CAPTURE 0x81 #define IVTV_API_END_CAPTURE 0x82 #define IVTV_API_ASSIGN_FRAMERATE 0x8f #define IVTV_API_ASSIGN_FRAME_SIZE 0x91 #define IVTV_API_ASSIGN_BITRATES 0x95 #define IVTV_API_ASSIGN_GOP_PROPERTIES 0x97 #define IVTV_API_ASSIGN_ASPECT_RATIO 0x99 #define IVTV_API_ASSIGN_DNR_FILTER_MODE 0x9b #define IVTV_API_ASSIGN_DNR_FILTER_PROPS 0x9d #define IVTV_API_ASSIGN_CORING_LEVELS 0x9f #define IVTV_API_ASSIGN_SPATIAL_FILTER_TYPE 0xa1 #define IVTV_API_ASSIGN_3_2_PULLDOWN 0xb1 #define IVTV_API_SELECT_VBI_LINE 0xb7 #define IVTV_API_ASSIGN_STREAM_TYPE 0xb9 #define IVTV_API_ASSIGN_OUTPUT_PORT 0xbb #define IVTV_API_ASSIGN_AUDIO_PROPERTIES 0xbd #define IVTV_API_ENC_HALT_FW 0xc3 #define IVTV_API_ENC_GETVER 0xc4 #define IVTV_API_ASSIGN_GOP_CLOSURE 0xc5 #define IVTV_API_ASSIGN_PGM_INDEX_INFO 0xc7 #define IVTV_API_CONFIG_VBI 0xc8 #define IVTV_API_ASSIGN_DMA_BLOCKLEN 0xc9 #define IVTV_API_SCHED_DMA_TO_HOST 0xcc #define IVTV_API_INITIALIZE_INPUT 0xcd #define IVTV_API_ASSIGN_FRAME_DROP_RATE 0xd0 #define IVTV_API_PAUSE_ENCODER 0xd2 #define IVTV_API_REFRESH_INPUT 0xd3 #define IVTV_API_EVENT_NOTIFICATION 0xd5 #define IVTV_API_ASSIGN_NUM_VSYNC_LINES 0xd6 #define IVTV_API_ASSIGN_PLACEHOLDER 0xd8 #define IVTV_API_MUTE_VIDEO 0xd9 #define IVTV_API_MUTE_AUDIO 0xda #define IVTV_API_OSD_DMA_FROM_HOST 0xff /* Firmware mailbox flags - definitions found from ivtv */ #define IVTV_MBOX_FIRMWARE_DONE 0x00000004 #define IVTV_MBOX_DRIVER_DONE 0x00000002 #define IVTV_MBOX_DRIVER_BUSY 0x00000001 static int pvr2_write_encoder_words(struct pvr2_hdw *hdw, const u32 *data, unsigned int dlen) { unsigned int idx; int ret; unsigned int offs = 0; unsigned int chunkCnt; /* Format: First byte must be 0x01. Remaining 32 bit words are spread out into chunks of 7 bytes each, little-endian ordered, offset at zero within each 2 blank bytes following and a single byte that is 0x44 plus the offset of the word. Repeat request for additional words, with offset adjusted accordingly. */ while (dlen) { chunkCnt = 8; if (chunkCnt > dlen) chunkCnt = dlen; memset(hdw->cmd_buffer,0,sizeof(hdw->cmd_buffer)); hdw->cmd_buffer[0] = 0x01; for (idx = 0; idx < chunkCnt; idx++) { hdw->cmd_buffer[1+(idx*7)+6] = 0x44 + idx + offs; PVR2_DECOMPOSE_LE(hdw->cmd_buffer, 1+(idx*7), data[idx]); } ret = pvr2_send_request(hdw, hdw->cmd_buffer,1+(chunkCnt*7), 0,0); if (ret) return ret; data += chunkCnt; dlen -= chunkCnt; offs += chunkCnt; } return 0; } static int pvr2_read_encoder_words(struct pvr2_hdw *hdw,int statusFl, u32 *data, unsigned int dlen) { unsigned int idx; int ret; unsigned int offs = 0; unsigned int chunkCnt; /* Format: First byte must be 0x02 (status check) or 0x28 (read back block of 32 bit words). Next 6 bytes must be zero, followed by a single byte of 0x44+offset for portion to be read. Returned data is packed set of 32 bits words that were read. */ while (dlen) { chunkCnt = 16; if (chunkCnt > dlen) chunkCnt = dlen; memset(hdw->cmd_buffer,0,sizeof(hdw->cmd_buffer)); hdw->cmd_buffer[0] = statusFl ? 0x02 : 0x28; hdw->cmd_buffer[7] = 0x44 + offs; ret = pvr2_send_request(hdw, hdw->cmd_buffer,8, hdw->cmd_buffer,chunkCnt * 4); if (ret) return ret; for (idx = 0; idx < chunkCnt; idx++) { data[idx] = PVR2_COMPOSE_LE(hdw->cmd_buffer,idx*4); } data += chunkCnt; dlen -= chunkCnt; offs += chunkCnt; } return 0; } static int pvr2_write_encoder_vcmd (struct pvr2_hdw *hdw, u8 cmd, int args, ...) { unsigned int poll_count; int ret = 0; va_list vl; unsigned int idx; u32 wrData[16]; u32 rdData[32]; /* The encoder seems to speak entirely using blocks 32 bit words. In ivtv driver terms, this is a mailbox which we populate with data and watch what the hardware does with it. The first word is a set of flags used to control the transaction, the second word is the command to execute, the third byte is zero (ivtv driver suggests that this is some kind of return value), and the fourth byte is a specified timeout (windows driver always uses 0x00060000 except for one case when it is zero). All successive words are the argument words for the command. First, write out the entire set of words, with the first word being zero. Next, write out just the first word again, but set it to IVTV_MBOX_DRIVER_DONE | IVTV_DRIVER_BUSY this time (which probably means "go"). Next, read back 16 words as status. Check the first word, which should have IVTV_MBOX_FIRMWARE_DONE set. If however that bit is not set, then the command isn't done so repeat the read. Next, read back 32 words and compare with the original arugments. Hopefully they will match. Finally, write out just the first word again, but set it to 0x0 this time (which probably means "idle"). */ LOCK_TAKE(hdw->ctl_lock); do { wrData[0] = 0; wrData[1] = cmd; wrData[2] = 0; wrData[3] = 0x00060000; va_start(vl, args); for (idx = 0; idx < args; idx++) { wrData[idx+4] = va_arg(vl, u32); } va_end(vl); args += 4; while (args < sizeof(wrData)/sizeof(wrData[0])) { wrData[args++] = 0; } ret = pvr2_write_encoder_words(hdw,wrData,args); if (ret) break; wrData[0] = IVTV_MBOX_DRIVER_DONE|IVTV_MBOX_DRIVER_BUSY; ret = pvr2_write_encoder_words(hdw,wrData,1); if (ret) break; poll_count = 0; while (1) { if (poll_count < 10000000) poll_count++; ret = pvr2_read_encoder_words(hdw,!0,rdData,1); if (ret) break; if (rdData[0] & IVTV_MBOX_FIRMWARE_DONE) { break; } if (poll_count == 100) { pvr2_trace( PVR2_TRACE_ERROR_LEGS, "***WARNING*** device's encoder" " appears to be stuck" " (status=0%08x)",rdData[0]); pvr2_trace( PVR2_TRACE_ERROR_LEGS, "Encoder command: 0x%02x",cmd); for (idx = 4; idx < args; idx++) { pvr2_trace( PVR2_TRACE_ERROR_LEGS, "Encoder arg%d: 0x%08x", idx-3,wrData[idx]); } pvr2_trace( PVR2_TRACE_ERROR_LEGS, "Giving up waiting." " It is likely that" " this is a bad idea..."); ret = -EBUSY; break; } } if (ret) break; wrData[0] = 0x7; ret = pvr2_read_encoder_words(hdw,0,rdData,16); if (ret) break; for (idx = 0; idx < args; idx++) { if (rdData[idx] != wrData[idx]) { pvr2_trace( PVR2_TRACE_DEBUG, "pvr2_encoder idx %02x mismatch exp:" " %08x got: %08x", idx,wrData[idx],rdData[idx]); } } wrData[0] = 0x0; ret = pvr2_write_encoder_words(hdw,wrData,1); if (ret) break; } while(0); LOCK_GIVE(hdw->ctl_lock); return ret; } int pvr2_encoder_configure(struct pvr2_hdw *hdw) { int ret = 0, audio, i; int vd_std = hdw->controls[PVR2_CID_VIDEOSTANDARD].value; int height = hdw->controls[PVR2_CID_VRES].value; int width = hdw->controls[PVR2_CID_HRES].value; int height_full = !hdw->controls[PVR2_CID_INTERLACE].value; int is_30fps, is_ntsc; switch (vd_std) { case PVR2_CVAL_VIDEOSTANDARD_NTSC_M: is_ntsc=1; is_30fps=1; break; case PVR2_CVAL_VIDEOSTANDARD_PAL_M: is_ntsc=0; is_30fps=1; break; default: is_ntsc=0; is_30fps=0; break; } pvr2_trace(PVR2_TRACE_ENCODER,"pvr2_encoder_configure"); /* set stream output port. */ ret |= pvr2_write_encoder_vcmd(hdw, IVTV_API_ASSIGN_OUTPUT_PORT, 2, 0x01, 0x01); /* set the Program Index Information. We want I,P,B frames (max 400) */ ret |= pvr2_write_encoder_vcmd(hdw, IVTV_API_ASSIGN_PGM_INDEX_INFO, 2, 0x07, 0x0190); /* NOTE : windows driver sends some 0xdc */ /* Mike Isely 19-Jun-2005 I've confirmed that the Windows driver seems to issue these commands, but right now I have no idea what these do (and neither does the ivtv driver). But, if I leave them in, then mplayer goes nuts with xrun errors. So for now we don't do this. It sure would be nice to know what these are for. */ #ifdef notdef ret |= pvr2_write_encoder_vcmd(hdw, 0xdc, 1, 5); ret |= pvr2_write_encoder_vcmd(hdw, 0xdc, 2, 3, 1); ret |= pvr2_write_encoder_vcmd(hdw, 0xdc, 1, 8); #endif /* Strange compared to ivtv data. */ #ifdef notdef ret |= pvr2_write_encoder_vcmd(hdw, IVTV_API_ASSIGN_NUM_VSYNC_LINES, 2, 0x0120, 0x0120); ret |= pvr2_write_encoder_vcmd(hdw, IVTV_API_ASSIGN_NUM_VSYNC_LINES, 2, 0x0131, 0x0131); #endif ret |= pvr2_write_encoder_vcmd(hdw, IVTV_API_ASSIGN_NUM_VSYNC_LINES, 2, 0xf0, 0xf0); /* setup firmware to notify us about some events (don't know why...) */ ret |= pvr2_write_encoder_vcmd(hdw, IVTV_API_EVENT_NOTIFICATION, 4, 0, 0, 0x10000000, 0xffffffff); /* set fps to 25 or 30 (1 or 0)*/ ret |= pvr2_write_encoder_vcmd(hdw, IVTV_API_ASSIGN_FRAMERATE, 1, is_30fps ? 0 : 1); /* set encoding resolution */ ret |= pvr2_write_encoder_vcmd(hdw, IVTV_API_ASSIGN_FRAME_SIZE, 2, (height_full ? height : (height / 2)), width); /* set encoding aspect ratio to 4:3 */ ret |= pvr2_write_encoder_vcmd(hdw, IVTV_API_ASSIGN_ASPECT_RATIO, 1, 0x02); /* VBI */ if (hdw->config == pvr2_config_vbi) { int lines = 2 * (is_30fps ? 12 : 18); int size = (4*((lines*1443+3)/4)) / lines; ret |= pvr2_write_encoder_vcmd(hdw, IVTV_API_CONFIG_VBI, 7, 0xbd05, 1, 4, 0x25256262, 0x387f7f7f, lines , size); // 0x25256262, 0x13135454, lines , size); /* select vbi lines */ #define line_used(l) (is_30fps ? (l >= 10 && l <= 21) : (l >= 6 && l <= 23)) for (i = 2 ; i <= 24 ; i++){ ret |= pvr2_write_encoder_vcmd( hdw,IVTV_API_SELECT_VBI_LINE, 5, i-1,line_used(i), 0, 0, 0); ret |= pvr2_write_encoder_vcmd( hdw,IVTV_API_SELECT_VBI_LINE, 5, (i-1) | (1 << 31), line_used(i), 0, 0, 0); } } else { ret |= pvr2_write_encoder_vcmd( hdw,IVTV_API_SELECT_VBI_LINE, 5, 0xffffffff,0,0,0,0); } /* set stream type, depending on resolution. */ ret |= pvr2_write_encoder_vcmd(hdw, IVTV_API_ASSIGN_STREAM_TYPE, 1, height_full ? 0x0a : 0x0b); /* set video bitrate */ ret |= pvr2_write_encoder_vcmd( hdw, IVTV_API_ASSIGN_BITRATES, 3, (hdw->controls[PVR2_CID_VBR].value ? 1 : 0), hdw->controls[PVR2_CID_AVERAGEVIDEOBITRATE].value, (u32) (hdw->controls[PVR2_CID_PEAKVIDEOBITRATE].value) / 400); /* setup GOP structure (GOP size = 0f or 0c, 3-1 = 2 B-frames) */ ret |= pvr2_write_encoder_vcmd(hdw, IVTV_API_ASSIGN_GOP_PROPERTIES, 2, is_30fps ? 0x0f : 0x0c, 0x03); /* enable 3:2 pulldown */ ret |= pvr2_write_encoder_vcmd(hdw,IVTV_API_ASSIGN_3_2_PULLDOWN,1,0); /* set GOP open/close property (open) */ ret |= pvr2_write_encoder_vcmd(hdw,IVTV_API_ASSIGN_GOP_CLOSURE,1,0); /* set audio stream properties 0x40b9? 0100 0000 1011 1001 */ audio = (pvr_tbl_audiobitrate[hdw->controls[ PVR2_CID_AUDIOBITRATE].value] | pvr_tbl_srate[hdw->controls[PVR2_CID_SRATE].value] | hdw->controls[PVR2_CID_AUDIOLAYER].value << 2 | (hdw->controls[PVR2_CID_AUDIOCRC].value ? 1 << 14 : 0) | pvr_tbl_emphasis[hdw->controls[ PVR2_CID_AUDIOEMPHASIS].value]); ret |= pvr2_write_encoder_vcmd(hdw,IVTV_API_ASSIGN_AUDIO_PROPERTIES,1, audio); /* set dynamic noise reduction filter to manual, Horiz/Vert */ ret |= pvr2_write_encoder_vcmd(hdw, IVTV_API_ASSIGN_DNR_FILTER_MODE, 2, 0, 0x03); /* dynamic noise reduction filter param */ ret |= pvr2_write_encoder_vcmd(hdw, IVTV_API_ASSIGN_DNR_FILTER_PROPS, 2 , 0, 0); /* dynamic noise reduction median filter */ ret |= pvr2_write_encoder_vcmd(hdw, IVTV_API_ASSIGN_CORING_LEVELS, 4, 0, 0xff, 0, 0xff); /* spacial prefiler parameter */ ret |= pvr2_write_encoder_vcmd(hdw, IVTV_API_ASSIGN_SPATIAL_FILTER_TYPE, 2, 0x01, 0x01); /* initialize video input */ ret |= pvr2_write_encoder_vcmd(hdw, IVTV_API_INITIALIZE_INPUT, 0); if (!ret) { hdw->subsys_enabled_mask |= PVR2_SUBSYS_ENC_CFG; } return ret; } int pvr2_encoder_start(struct pvr2_hdw *hdw) { int status; /* unmask some interrupts */ pvr2_write_register(hdw, 0x0048, 0xbfffffff); /* change some GPIO data */ pvr2_hdw_gpio_chg_dir(hdw,0xffffffff,0x00000481); pvr2_hdw_gpio_chg_out(hdw,0xffffffff,0x00000000); if (hdw->config == pvr2_config_vbi) { status = pvr2_write_encoder_vcmd(hdw,IVTV_API_BEGIN_CAPTURE,2, 0x01,0x14); } else if (hdw->config == pvr2_config_mpeg) { status = pvr2_write_encoder_vcmd(hdw,IVTV_API_BEGIN_CAPTURE,2, 0,0x13); } else { status = pvr2_write_encoder_vcmd(hdw,IVTV_API_BEGIN_CAPTURE,2, 0,0x13); } if (!status) { hdw->subsys_enabled_mask |= PVR2_SUBSYS_ENC_RUN; } return status; } int pvr2_encoder_stop(struct pvr2_hdw *hdw) { int status; /* mask all interrupts */ pvr2_write_register(hdw, 0x0048, 0xffffffff); if (hdw->config == pvr2_config_vbi) { status = pvr2_write_encoder_vcmd(hdw,IVTV_API_END_CAPTURE,3, 0x01,0x01,0x14); } else if (hdw->config == pvr2_config_mpeg) { status = pvr2_write_encoder_vcmd(hdw,IVTV_API_END_CAPTURE,3, 0x01,0,0x13); } else { status = pvr2_write_encoder_vcmd(hdw,IVTV_API_END_CAPTURE,3, 0x01,0,0x13); } /* change some GPIO data */ /* Note: Bit d7 of dir appears to control the LED. So we shut it off here. */ pvr2_hdw_gpio_chg_dir(hdw,0xffffffff,0x00000401); pvr2_hdw_gpio_chg_out(hdw,0xffffffff,0x00000000); if (!status) { hdw->subsys_enabled_mask &= ~PVR2_SUBSYS_ENC_RUN; } return status; } /* Stuff for Emacs to see, in order to encourage consistent editing style: *** Local Variables: *** *** mode: c *** *** fill-column: 70 *** *** tab-width: 8 *** *** c-basic-offset: 8 *** *** End: *** */