2021-10-27 18:46:41 +05:00
// SPDX-License-Identifier: GPL-2.0-only
/*
*
* Copyright ( C ) 2005 Mike Isely < isely @ pobox . com >
* Copyright ( C ) 2004 Aurelien Alleaume < slts @ free . fr >
*/
# include <linux/device.h> // for linux/firmware.h
# include <linux/firmware.h>
# include "pvrusb2-util.h"
# include "pvrusb2-encoder.h"
# include "pvrusb2-hdw-internal.h"
# include "pvrusb2-debug.h"
# include "pvrusb2-fx2-cmd.h"
/* 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
# define MBOX_BASE 0x44
static int pvr2_encoder_write_words ( struct pvr2_hdw * hdw ,
unsigned int offs ,
const u32 * data , unsigned int dlen )
{
unsigned int idx , addr ;
unsigned int bAddr ;
int ret ;
unsigned int chunkCnt ;
/*
Format : First byte must be 0x01 . Remaining 32 bit words are
spread out into chunks of 7 bytes each , with the first 4 bytes
being the data word ( little endian ) , and the next 3 bytes
being the address where that data word is to be written ( big
endian ) . 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 ) ) ;
bAddr = 0 ;
hdw - > cmd_buffer [ bAddr + + ] = FX2CMD_MEM_WRITE_DWORD ;
for ( idx = 0 ; idx < chunkCnt ; idx + + ) {
addr = idx + offs ;
hdw - > cmd_buffer [ bAddr + 6 ] = ( addr & 0xffu ) ;
hdw - > cmd_buffer [ bAddr + 5 ] = ( ( addr > > 8 ) & 0xffu ) ;
hdw - > cmd_buffer [ bAddr + 4 ] = ( ( addr > > 16 ) & 0xffu ) ;
PVR2_DECOMPOSE_LE ( hdw - > cmd_buffer , bAddr , data [ idx ] ) ;
bAddr + = 7 ;
}
ret = pvr2_send_request ( hdw ,
hdw - > cmd_buffer , 1 + ( chunkCnt * 7 ) ,
NULL , 0 ) ;
if ( ret ) return ret ;
data + = chunkCnt ;
dlen - = chunkCnt ;
offs + = chunkCnt ;
}
return 0 ;
}
static int pvr2_encoder_read_words ( struct pvr2_hdw * hdw ,
unsigned int offs ,
u32 * data , unsigned int dlen )
{
unsigned int idx ;
int ret ;
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 MBOX_BASE + 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 ;
if ( chunkCnt < 16 ) chunkCnt = 1 ;
hdw - > cmd_buffer [ 0 ] =
( ( chunkCnt = = 1 ) ?
FX2CMD_MEM_READ_DWORD : FX2CMD_MEM_READ_64BYTES ) ;
hdw - > cmd_buffer [ 1 ] = 0 ;
hdw - > cmd_buffer [ 2 ] = 0 ;
hdw - > cmd_buffer [ 3 ] = 0 ;
hdw - > cmd_buffer [ 4 ] = 0 ;
hdw - > cmd_buffer [ 5 ] = ( ( offs > > 16 ) & 0xffu ) ;
hdw - > cmd_buffer [ 6 ] = ( ( offs > > 8 ) & 0xffu ) ;
hdw - > cmd_buffer [ 7 ] = ( offs & 0xffu ) ;
ret = pvr2_send_request ( hdw ,
hdw - > cmd_buffer , 8 ,
hdw - > cmd_buffer ,
( chunkCnt = = 1 ? 4 : 16 * 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 ;
}
/* This prototype is set up to be compatible with the
cx2341x_mbox_func prototype in cx2341x . h , which should be in
kernels 2.6 .18 or later . We do this so that we can enable
cx2341x . ko to write to our encoder ( by handing it a pointer to this
function ) . For earlier kernels this doesn ' t really matter . */
static int pvr2_encoder_cmd ( void * ctxt ,
u32 cmd ,
int arg_cnt_send ,
int arg_cnt_recv ,
u32 * argp )
{
unsigned int poll_count ;
unsigned int try_count = 0 ;
int retry_flag ;
int ret = 0 ;
unsigned int idx ;
/* These sizes look to be limited by the FX2 firmware implementation */
u32 wrData [ 16 ] ;
u32 rdData [ 16 ] ;
struct pvr2_hdw * hdw = ( struct pvr2_hdw * ) ctxt ;
/*
The encoder seems to speak entirely using blocks 32 bit words .
In ivtv driver terms , this is a mailbox at MBOX_BASE 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 the return count words . 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 until it is set .
Finally , write out just the first word again , but set it to
0x0 this time ( which probably means " idle " ) .
*/
if ( arg_cnt_send > ( ARRAY_SIZE ( wrData ) - 4 ) ) {
pvr2_trace (
PVR2_TRACE_ERROR_LEGS ,
" Failed to write cx23416 command - too many input arguments (was given %u limit %lu) " ,
arg_cnt_send , ( long unsigned ) ARRAY_SIZE ( wrData ) - 4 ) ;
return - EINVAL ;
}
if ( arg_cnt_recv > ( ARRAY_SIZE ( rdData ) - 4 ) ) {
pvr2_trace (
PVR2_TRACE_ERROR_LEGS ,
" Failed to write cx23416 command - too many return arguments (was given %u limit %lu) " ,
arg_cnt_recv , ( long unsigned ) ARRAY_SIZE ( rdData ) - 4 ) ;
return - EINVAL ;
}
2022-03-05 21:17:59 +05:00
LOCK_TAKE ( hdw - > ctl_lock ) ;
while ( 1 ) {
2021-10-27 18:46:41 +05:00
if ( ! hdw - > state_encoder_ok ) {
ret = - EIO ;
break ;
}
retry_flag = 0 ;
try_count + + ;
ret = 0 ;
wrData [ 0 ] = 0 ;
wrData [ 1 ] = cmd ;
wrData [ 2 ] = 0 ;
wrData [ 3 ] = 0x00060000 ;
for ( idx = 0 ; idx < arg_cnt_send ; idx + + ) {
wrData [ idx + 4 ] = argp [ idx ] ;
}
for ( ; idx < ARRAY_SIZE ( wrData ) - 4 ; idx + + ) {
wrData [ idx + 4 ] = 0 ;
}
ret = pvr2_encoder_write_words ( hdw , MBOX_BASE , wrData , idx ) ;
if ( ret ) break ;
wrData [ 0 ] = IVTV_MBOX_DRIVER_DONE | IVTV_MBOX_DRIVER_BUSY ;
ret = pvr2_encoder_write_words ( hdw , MBOX_BASE , wrData , 1 ) ;
if ( ret ) break ;
poll_count = 0 ;
while ( 1 ) {
poll_count + + ;
ret = pvr2_encoder_read_words ( hdw , MBOX_BASE , rdData ,
arg_cnt_recv + 4 ) ;
if ( ret ) {
break ;
}
if ( rdData [ 0 ] & IVTV_MBOX_FIRMWARE_DONE ) {
break ;
}
if ( rdData [ 0 ] & & ( poll_count < 1000 ) ) continue ;
if ( ! rdData [ 0 ] ) {
retry_flag = ! 0 ;
pvr2_trace (
PVR2_TRACE_ERROR_LEGS ,
" Encoder timed out waiting for us; arranging to retry " ) ;
} else {
pvr2_trace (
PVR2_TRACE_ERROR_LEGS ,
" ***WARNING*** device's encoder appears to be stuck (status=0x%08x) " ,
rdData [ 0 ] ) ;
}
pvr2_trace (
PVR2_TRACE_ERROR_LEGS ,
" Encoder command: 0x%02x " , cmd ) ;
for ( idx = 4 ; idx < arg_cnt_send ; idx + + ) {
pvr2_trace (
PVR2_TRACE_ERROR_LEGS ,
" Encoder arg%d: 0x%08x " ,
idx - 3 , wrData [ idx ] ) ;
}
ret = - EBUSY ;
break ;
}
if ( retry_flag ) {
if ( try_count < 20 ) continue ;
pvr2_trace (
PVR2_TRACE_ERROR_LEGS ,
" Too many retries... " ) ;
ret = - EBUSY ;
}
if ( ret ) {
del_timer_sync ( & hdw - > encoder_run_timer ) ;
hdw - > state_encoder_ok = 0 ;
pvr2_trace ( PVR2_TRACE_STBITS ,
" State bit %s <-- %s " ,
" state_encoder_ok " ,
( hdw - > state_encoder_ok ? " true " : " false " ) ) ;
if ( hdw - > state_encoder_runok ) {
hdw - > state_encoder_runok = 0 ;
pvr2_trace ( PVR2_TRACE_STBITS ,
" State bit %s <-- %s " ,
" state_encoder_runok " ,
( hdw - > state_encoder_runok ?
" true " : " false " ) ) ;
}
pvr2_trace (
PVR2_TRACE_ERROR_LEGS ,
" Giving up on command. This is normally recovered via a firmware reload and re-initialization; concern is only warranted if this happens repeatedly and rapidly. " ) ;
break ;
}
wrData [ 0 ] = 0x7 ;
for ( idx = 0 ; idx < arg_cnt_recv ; idx + + ) {
argp [ idx ] = rdData [ idx + 4 ] ;
}
wrData [ 0 ] = 0x0 ;
ret = pvr2_encoder_write_words ( hdw , MBOX_BASE , wrData , 1 ) ;
break ;
}
LOCK_GIVE ( hdw - > ctl_lock ) ;
return ret ;
}
static int pvr2_encoder_vcmd ( struct pvr2_hdw * hdw , int cmd ,
int args , . . . )
{
va_list vl ;
unsigned int idx ;
u32 data [ 12 ] ;
if ( args > ARRAY_SIZE ( data ) ) {
pvr2_trace (
PVR2_TRACE_ERROR_LEGS ,
" Failed to write cx23416 command - too many arguments (was given %u limit %lu) " ,
args , ( long unsigned ) ARRAY_SIZE ( data ) ) ;
return - EINVAL ;
}
va_start ( vl , args ) ;
for ( idx = 0 ; idx < args ; idx + + ) {
data [ idx ] = va_arg ( vl , u32 ) ;
}
va_end ( vl ) ;
return pvr2_encoder_cmd ( hdw , cmd , args , 0 , data ) ;
}
/* This implements some extra setup for the encoder that seems to be
specific to the PVR USB2 hardware . */
static int pvr2_encoder_prep_config ( struct pvr2_hdw * hdw )
{
int ret = 0 ;
int encMisc3Arg = 0 ;
#if 0
/* This inexplicable bit happens in the Hauppauge windows
driver ( for both 24 xxx and 29 xxx devices ) . However I
currently see no difference in behavior with or without
this stuff . Leave this here as a note of its existence ,
but don ' t use it . */
LOCK_TAKE ( hdw - > ctl_lock ) ; do {
u32 dat [ 1 ] ;
dat [ 0 ] = 0x80000640 ;
pvr2_encoder_write_words ( hdw , 0x01fe , dat , 1 ) ;
pvr2_encoder_write_words ( hdw , 0x023e , dat , 1 ) ;
} while ( 0 ) ; LOCK_GIVE ( hdw - > ctl_lock ) ;
# endif
/* Mike Isely <isely@pobox.com> 26-Jan-2006 The windows driver
sends the following list of ENC_MISC commands ( for both
24 xxx and 29 xxx devices ) . Meanings are not entirely clear ,
however without the ENC_MISC ( 3 , 1 ) command then we risk
random perpetual video corruption whenever the video input
breaks up for a moment ( like when switching channels ) . */
#if 0
/* This ENC_MISC(5,0) command seems to hurt 29xxx sync
performance on channel changes , but is not a problem on
24 xxx devices . */
ret | = pvr2_encoder_vcmd ( hdw , CX2341X_ENC_MISC , 4 , 5 , 0 , 0 , 0 ) ;
# endif
/* This ENC_MISC(3,encMisc3Arg) command is critical - without
it there will eventually be video corruption . Also , the
saa7115 case is strange - the Windows driver is passing 1
regardless of device type but if we have 1 for saa7115
devices the video turns sluggish . */
if ( hdw - > hdw_desc - > flag_has_cx25840 ) {
encMisc3Arg = 1 ;
} else {
encMisc3Arg = 0 ;
}
ret | = pvr2_encoder_vcmd ( hdw , CX2341X_ENC_MISC , 4 , 3 ,
encMisc3Arg , 0 , 0 ) ;
ret | = pvr2_encoder_vcmd ( hdw , CX2341X_ENC_MISC , 4 , 8 , 0 , 0 , 0 ) ;
#if 0
/* This ENC_MISC(4,1) command is poisonous, so it is commented
out . But I ' m leaving it here anyway to document its
existence in the Windows driver . The effect of this
command is that apps displaying the stream become sluggish
with stuttering video . */
ret | = pvr2_encoder_vcmd ( hdw , CX2341X_ENC_MISC , 4 , 4 , 1 , 0 , 0 ) ;
# endif
ret | = pvr2_encoder_vcmd ( hdw , CX2341X_ENC_MISC , 4 , 0 , 3 , 0 , 0 ) ;
ret | = pvr2_encoder_vcmd ( hdw , CX2341X_ENC_MISC , 4 , 15 , 0 , 0 , 0 ) ;
/* prevent the PTSs from slowly drifting away in the generated
MPEG stream */
ret | = pvr2_encoder_vcmd ( hdw , CX2341X_ENC_MISC , 2 , 4 , 1 ) ;
return ret ;
}
int pvr2_encoder_adjust ( struct pvr2_hdw * hdw )
{
int ret ;
ret = cx2341x_update ( hdw , pvr2_encoder_cmd ,
( hdw - > enc_cur_valid ? & hdw - > enc_cur_state : NULL ) ,
& hdw - > enc_ctl_state ) ;
if ( ret ) {
pvr2_trace ( PVR2_TRACE_ERROR_LEGS ,
" Error from cx2341x module code=%d " , ret ) ;
} else {
hdw - > enc_cur_state = hdw - > enc_ctl_state ;
hdw - > enc_cur_valid = ! 0 ;
}
return ret ;
}
int pvr2_encoder_configure ( struct pvr2_hdw * hdw )
{
int ret ;
int val ;
pvr2_trace ( PVR2_TRACE_ENCODER , " pvr2_encoder_configure (cx2341x module) " ) ;
hdw - > enc_ctl_state . port = CX2341X_PORT_STREAMING ;
hdw - > enc_ctl_state . width = hdw - > res_hor_val ;
hdw - > enc_ctl_state . height = hdw - > res_ver_val ;
hdw - > enc_ctl_state . is_50hz = ( ( hdw - > std_mask_cur & V4L2_STD_525_60 ) ?
0 : 1 ) ;
ret = 0 ;
ret | = pvr2_encoder_prep_config ( hdw ) ;
/* saa7115: 0xf0 */
val = 0xf0 ;
if ( hdw - > hdw_desc - > flag_has_cx25840 ) {
/* ivtv cx25840: 0x140 */
val = 0x140 ;
}
if ( ! ret ) ret = pvr2_encoder_vcmd (
hdw , CX2341X_ENC_SET_NUM_VSYNC_LINES , 2 ,
val , val ) ;
/* setup firmware to notify us about some events (don't know why...) */
if ( ! ret ) ret = pvr2_encoder_vcmd (
hdw , CX2341X_ENC_SET_EVENT_NOTIFICATION , 4 ,
0 , 0 , 0x10000000 , 0xffffffff ) ;
if ( ! ret ) ret = pvr2_encoder_vcmd (
hdw , CX2341X_ENC_SET_VBI_LINE , 5 ,
0xffffffff , 0 , 0 , 0 , 0 ) ;
if ( ret ) {
pvr2_trace ( PVR2_TRACE_ERROR_LEGS ,
" Failed to configure cx23416 " ) ;
return ret ;
}
ret = pvr2_encoder_adjust ( hdw ) ;
if ( ret ) return ret ;
ret = pvr2_encoder_vcmd (
hdw , CX2341X_ENC_INITIALIZE_INPUT , 0 ) ;
if ( ret ) {
pvr2_trace ( PVR2_TRACE_ERROR_LEGS ,
" Failed to initialize cx23416 video input " ) ;
return ret ;
}
return 0 ;
}
int pvr2_encoder_start ( struct pvr2_hdw * hdw )
{
int status ;
/* unmask some interrupts */
pvr2_write_register ( hdw , 0x0048 , 0xbfffffff ) ;
pvr2_encoder_vcmd ( hdw , CX2341X_ENC_MUTE_VIDEO , 1 ,
hdw - > input_val = = PVR2_CVAL_INPUT_RADIO ? 1 : 0 ) ;
switch ( hdw - > active_stream_type ) {
case pvr2_config_vbi :
status = pvr2_encoder_vcmd ( hdw , CX2341X_ENC_START_CAPTURE , 2 ,
0x01 , 0x14 ) ;
break ;
case pvr2_config_mpeg :
status = pvr2_encoder_vcmd ( hdw , CX2341X_ENC_START_CAPTURE , 2 ,
0 , 0x13 ) ;
break ;
default : /* Unhandled cases for now */
status = pvr2_encoder_vcmd ( hdw , CX2341X_ENC_START_CAPTURE , 2 ,
0 , 0x13 ) ;
break ;
}
return status ;
}
int pvr2_encoder_stop ( struct pvr2_hdw * hdw )
{
int status ;
/* mask all interrupts */
pvr2_write_register ( hdw , 0x0048 , 0xffffffff ) ;
switch ( hdw - > active_stream_type ) {
case pvr2_config_vbi :
status = pvr2_encoder_vcmd ( hdw , CX2341X_ENC_STOP_CAPTURE , 3 ,
0x01 , 0x01 , 0x14 ) ;
break ;
case pvr2_config_mpeg :
status = pvr2_encoder_vcmd ( hdw , CX2341X_ENC_STOP_CAPTURE , 3 ,
0x01 , 0 , 0x13 ) ;
break ;
default : /* Unhandled cases for now */
status = pvr2_encoder_vcmd ( hdw , CX2341X_ENC_STOP_CAPTURE , 3 ,
0x01 , 0 , 0x13 ) ;
break ;
}
return status ;
}